# typed: strict
# frozen_string_literal: true

require "abstract_command"
require "caveats"
require "unlink"

module Homebrew
  module Cmd
    class Link < AbstractCommand
      cmd_args do
        description <<~EOS
          Symlink all of <formula>'s installed files into Homebrew's prefix.
          This is done automatically when you install formulae but can be useful
          for manual installations.
        EOS
        switch "--overwrite",
               description: "Delete files that already exist in the prefix while linking."
        switch "-n", "--dry-run",
               description: "List files which would be linked or deleted by " \
                            "`brew link --overwrite` without actually linking or deleting any files."
        switch "-f", "--force",
               description: "Allow keg-only formulae to be linked."
        switch "--HEAD",
               description: "Link the HEAD version of the formula if it is installed."

        named_args :installed_formula, min: 1
      end

      sig { override.void }
      def run
        options = {
          overwrite: args.overwrite?,
          dry_run:   args.dry_run?,
          verbose:   args.verbose?,
        }

        kegs = if args.HEAD?
          args.named.to_kegs.group_by(&:name).filter_map do |name, resolved_kegs|
            head_keg = resolved_kegs.find { |keg| keg.version.head? }
            next head_keg if head_keg.present?

            opoo <<~EOS
              No HEAD keg installed for #{name}
              To install, run:
                brew install --HEAD #{name}
            EOS

            nil
          end
        else
          args.named.to_latest_kegs
        end

        kegs.freeze.each do |keg|
          keg_only = Formulary.keg_only?(keg.rack)

          if keg.linked?
            opoo "Already linked: #{keg}"
            name_and_flag = "#{"--HEAD " if args.HEAD?}#{"--force " if keg_only}#{keg.name}"
            puts <<~EOS
              To relink, run:
                brew unlink #{keg.name} && brew link #{name_and_flag}
            EOS
            next
          end

          if args.dry_run?
            if args.overwrite?
              puts "Would remove:"
            else
              puts "Would link:"
            end
            keg.link(**options)
            puts_keg_only_path_message(keg) if keg_only
            next
          end

          formula = begin
            keg.to_formula
          rescue FormulaUnavailableError
            # Not all kegs may belong to formulae
            nil
          end

          if keg_only
            if HOMEBREW_PREFIX.to_s == HOMEBREW_DEFAULT_PREFIX && formula.present? &&
               formula.keg_only_reason.by_macos?
              caveats = Caveats.new(formula)
              opoo <<~EOS
                Refusing to link macOS provided/shadowed software: #{keg.name}
                #{T.must(caveats.keg_only_text(skip_reason: true)).strip}
              EOS
              next
            end

            if !args.force? && (formula.blank? || !formula.keg_only_reason.versioned_formula?)
              opoo "#{keg.name} is keg-only and must be linked with `--force`."
              puts_keg_only_path_message(keg)
              next
            end
          end

          Unlink.unlink_versioned_formulae(formula, verbose: args.verbose?) if formula

          keg.lock do
            print "Linking #{keg}... "
            puts if args.verbose?

            begin
              n = keg.link(**options)
            rescue Keg::LinkError
              puts
              raise
            else
              puts "#{n} symlinks created."
            end

            puts_keg_only_path_message(keg) if keg_only && !Homebrew::EnvConfig.developer?
          end
        end
      end

      private

      sig { params(keg: Keg).void }
      def puts_keg_only_path_message(keg)
        bin = keg/"bin"
        sbin = keg/"sbin"
        return if !bin.directory? && !sbin.directory?

        opt = HOMEBREW_PREFIX/"opt/#{keg.name}"
        puts "\nIf you need to have this software first in your PATH instead consider running:"
        puts "  #{Utils::Shell.prepend_path_in_profile(opt/"bin")}"  if bin.directory?
        puts "  #{Utils::Shell.prepend_path_in_profile(opt/"sbin")}" if sbin.directory?
      end
    end
  end
end
