# typed: strict
# frozen_string_literal: true

require "formulary"
require "utils"
require "utils/output"

module Homebrew
  # Helper module for checking if there is a reason a formula is missing.
  module MissingFormula
    extend Utils::Output::Mixin

    class << self
      sig { params(name: String, silent: T::Boolean, show_info: T::Boolean).returns(T.nilable(String)) }
      def reason(name, silent: false, show_info: false)
        cask_reason(name, silent:, show_info:) || disallowed_reason(name) ||
          tap_migration_reason(name) || deleted_reason(name, silent:)
      end

      sig { params(name: String).returns(T.nilable(String)) }
      def disallowed_reason(name)
        case name.downcase
        when "gem", /^rubygems?$/ then <<~EOS
          macOS provides gem as part of Ruby. To install a newer version:
            brew install ruby
        EOS
        when "pip" then <<~EOS
          pip is part of the python formula:
            brew install python
        EOS
        when "pil" then <<~EOS
          Instead of PIL, consider pillow:
            brew install pillow
        EOS
        when "macruby" then <<~EOS
          MacRuby has been discontinued. Consider RubyMotion:
            brew install --cask rubymotion
        EOS
        when /(lib)?lzma/ then <<~EOS
          lzma is now part of the xz formula:
            brew install xz
        EOS
        when "gsutil" then <<~EOS
          gsutil is available through pip:
            pip3 install gsutil
        EOS
        when "gfortran" then <<~EOS
          GNU Fortran is part of the GCC formula:
            brew install gcc
        EOS
        when "play" then <<~EOS
          Play 2.3 replaces the play command with activator:
            brew install typesafe-activator

          You can read more about this change at:
            #{Formatter.url("https://www.playframework.com/documentation/2.3.x/Migration23")}
            #{Formatter.url("https://www.playframework.com/documentation/2.3.x/Highlights23")}
        EOS
        when "haskell-platform" then <<~EOS
          The components of the Haskell Platform are available separately.

          Glasgow Haskell Compiler:
            brew install ghc

          Cabal build system:
            brew install cabal-install

          Haskell Stack tool:
            brew install haskell-stack
        EOS
        when "mysqldump-secure" then <<~EOS
          The creator of mysqldump-secure tried to game our popularity metrics.
        EOS
        when "ngrok" then <<~EOS
          Upstream sunsetted 1.x in March 2016 and 2.x is not open-source.

          If you wish to use the 2.x release you can install it with:
            brew install --cask ngrok
        EOS
        when "cargo" then <<~EOS
          cargo is part of the rust formula:
            brew install rust
        EOS
        when "cargo-completion" then <<~EOS
          cargo-completion is part of the rust formula:
            brew install rust
        EOS
        when "uconv" then <<~EOS
          uconv is part of the icu4c formula:
            brew install icu4c
        EOS
        when "postgresql", "postgres" then <<~EOS
          postgresql breaks existing databases on upgrade without human intervention.

          See a more specific version to install with:
            brew formulae | grep postgresql@
        EOS
        end
      end

      sig { params(name: String).returns(T.nilable(String)) }
      def tap_migration_reason(name)
        message = T.let(nil, T.nilable(String))

        Tap.each do |old_tap|
          new_tap = old_tap.tap_migrations[name]
          next unless new_tap

          same_tap = new_tap == name
          migrated_tap_name = Utils.tap_from_full_name(new_tap)
          same_tap_new_name = !same_tap && migrated_tap_name.nil? && new_tap.exclude?("/")
          new_tap_name = if migrated_tap_name
            migrated_tap_name
          elsif new_tap.include?("/")
            new_tap
          elsif same_tap_new_name
            old_tap.name
          end
          new_tap_new_name = if migrated_tap_name
            Utils.name_from_full_name(new_tap)
          elsif same_tap_new_name
            new_tap
          end

          message = if same_tap
            "It was migrated from a formula to a cask.\n"
          else
            "It was migrated from #{old_tap} to #{new_tap}.\n"
          end

          install_cmd = if new_tap_name&.start_with?("homebrew/cask") || same_tap
            "install --cask"
          else
            "install"
          end
          new_tap_new_name ||= name

          message += if same_tap || same_tap_new_name || new_tap_name == CoreTap.instance.name
            "You can install it by running:\n"
          else
            <<~EOS
              You can access it again by running:
                brew tap #{T.must(new_tap_name)}
              And then you can install it by running:
            EOS
          end
          message += "  brew #{install_cmd} #{new_tap_new_name}\n"
          break
        end

        message
      end

      sig { params(name: String, silent: T::Boolean).returns(T.nilable(String)) }
      def deleted_reason(name, silent: false)
        path = Formulary.path name
        return if File.exist? path

        tap = Tap.from_path(path)
        return if tap.nil? || !File.exist?(tap.path)

        relative_path = path.relative_path_from tap.path

        tap.path.cd do
          unless silent
            ohai "Searching for a previously deleted formula (in the last month)..."
            if (tap.path/".git/shallow").exist?
              opoo <<~EOS
                #{tap} is shallow clone. To get its complete history, run:
                  git -C "$(brew --repo #{tap})" fetch --unshallow

              EOS
            end
          end

          # Optimization for the core tap which has many monthly commits
          if tap.core_tap?
            # Check if the formula has been deleted in the last month.
            diff_command = ["git", "diff", "--diff-filter=D", "--name-only",
                            "@{'1 month ago'}", "--", relative_path]
            deleted_formula = Utils.popen_read(*diff_command)

            if deleted_formula.blank?
              ofail "No previously deleted formula found." unless silent
              return
            end
          end

          # Find commit where formula was deleted in the last month.
          log_command = "git log --since='1 month ago' --diff-filter=D " \
                        "--name-only --max-count=1 " \
                        "--format=%H\\\\n%h\\\\n%B -- #{relative_path}"
          hash, short_hash, *commit_message, relative_path_string =
            Utils.popen_read(log_command).gsub("\\n", "\n").lines.map(&:chomp)

          if hash.blank? || short_hash.blank? || relative_path_string.blank?
            ofail "No previously deleted formula found." unless silent
            return
          end

          commit_message = commit_message.reject(&:empty?).join("\n  ")

          commit_message.sub!(/ \(#(\d+)\)$/, " (#{tap.issues_url}/\\1)")
          commit_message.gsub!(/(Closes|Fixes) #(\d+)/, "\\1 #{tap.issues_url}/\\2")

          <<~EOS
            #{name} was deleted from #{tap.name} in commit #{short_hash}:
              #{commit_message}

            To show the formula before removal, run:
              git -C "$(brew --repo #{tap})" show #{short_hash}^:#{relative_path_string}

            If you still use this formula, consider creating your own tap:
              #{Formatter.url("https://docs.brew.sh/How-to-Create-and-Maintain-a-Tap")}
          EOS
        end
      end

      sig { params(name: String, silent: T::Boolean, show_info: T::Boolean).returns(T.nilable(String)) }
      def cask_reason(name, silent: false, show_info: false); end

      sig { params(name: String, command: String).returns(T.nilable(String)) }
      def suggest_command(name, command); end

      require "extend/os/missing_formula"
    end
  end
end
