# typed: true
# frozen_string_literal: true

require "digest"
require "erb"

module Homebrew
  # Class for generating a formula from a template.
  #
  # @api private
  class FormulaCreator
    extend T::Sig

    attr_reader :args, :url, :sha256, :desc, :homepage
    attr_accessor :name, :version, :tap, :path, :mode, :license

    def initialize(args)
      @args = args
    end

    def url=(url)
      @url = url
      path = Pathname.new(url)
      if @name.nil?
        case url
        when %r{github\.com/(\S+)/(\S+)\.git}
          @user = Regexp.last_match(1)
          @name = Regexp.last_match(2)
          @head = true
          @github = true
        when %r{github\.com/(\S+)/(\S+)/(archive|releases)/}
          @user = Regexp.last_match(1)
          @name = Regexp.last_match(2)
          @github = true
        else
          @name = path.basename.to_s[/(.*?)[-_.]?#{Regexp.escape(path.version.to_s)}/, 1]
        end
      end
      update_path
      @version = if @version
        Version.create(@version)
      else
        Version.detect(url)
      end
    end

    def update_path
      return if @name.nil? || @tap.nil?

      @path = Formulary.path "#{@tap}/#{@name}"
    end

    def fetch?
      !args.no_fetch?
    end

    def head?
      @head || args.HEAD?
    end

    def generate!
      raise "#{path} already exists" if path.exist?

      if version.nil? || version.null?
        opoo "Version cannot be determined from URL."
        puts "You'll need to add an explicit 'version' to the formula."
      elsif fetch?
        unless head?
          r = Resource.new
          r.url(url)
          r.version(version)
          r.owner = self
          @sha256 = r.fetch.sha256 if r.download_strategy == CurlDownloadStrategy
        end

        if @user && @name
          begin
            metadata = GitHub.repository(@user, @name)
            @desc = metadata["description"]
            @homepage = metadata["homepage"]
            @license = metadata["license"]["spdx_id"] if metadata["license"]
          rescue GitHub::API::HTTPNotFoundError
            # If there was no repository found assume the network connection is at
            # fault rather than the input URL.
            nil
          end
        end
      end

      path.dirname.mkpath
      path.write ERB.new(template, trim_mode: ">").result(binding)
    end

    sig { returns(String) }
    def template
      <<~ERB
        # Documentation: https://docs.brew.sh/Formula-Cookbook
        #                https://rubydoc.brew.sh/Formula
        # PLEASE REMOVE ALL GENERATED COMMENTS BEFORE SUBMITTING YOUR PULL REQUEST!
        <% if mode == :node %>
        require "language/node"

        <% end %>
        class #{Formulary.class_s(name)} < Formula
        <% if mode == :python %>
          include Language::Python::Virtualenv

        <% end %>
          desc "#{desc}"
          homepage "#{homepage}"
        <% unless head? %>
          url "#{url}"
        <% unless version.nil? or version.detected_from_url? %>
          version "#{version}"
        <% end %>
          sha256 "#{sha256}"
        <% end %>
          license "#{license}"
        <% if head? %>
          head "#{url}"
        <% end %>

        <% if mode == :cmake %>
          depends_on "cmake" => :build
        <% elsif mode == :crystal %>
          depends_on "crystal" => :build
        <% elsif mode == :go %>
          depends_on "go" => :build
        <% elsif mode == :meson %>
          depends_on "meson" => :build
          depends_on "ninja" => :build
        <% elsif mode == :node %>
          depends_on "node"
        <% elsif mode == :perl %>
          uses_from_macos "perl"
        <% elsif mode == :python %>
          depends_on "python"
        <% elsif mode == :ruby %>
          uses_from_macos "ruby"
        <% elsif mode == :rust %>
          depends_on "rust" => :build
        <% elsif mode.nil? %>
          # depends_on "cmake" => :build
        <% end %>

        <% if mode == :perl %>
          # Additional dependency
          # resource "" do
          #   url ""
          #   sha256 ""
          # end

        <% end %>
          def install
            # ENV.deparallelize  # if your formula fails when building in parallel
        <% if mode == :cmake %>
            system "cmake", "-S", ".", "-B", "build", *std_cmake_args
            system "cmake", "--build", "build"
            system "cmake", "--install", "build"
        <% elsif mode == :autotools %>
            # Remove unrecognized options if warned by configure
            # https://rubydoc.brew.sh/Formula.html#std_configure_args-instance_method
            system "./configure", *std_configure_args, "--disable-silent-rules"
            system "make", "install" # if this fails, try separate make/make install steps
        <% elsif mode == :crystal %>
            system "shards", "build", "--release"
            bin.install "bin/#{name}"
        <% elsif mode == :go %>
            system "go", "build", *std_go_args(ldflags: "-s -w")
        <% elsif mode == :meson %>
            system "meson", "setup", "build", *std_meson_args
            system "meson", "compile", "-C", "build", "--verbose"
            system "meson", "install", "-C", "build"
        <% elsif mode == :node %>
            system "npm", "install", *Language::Node.std_npm_install_args(libexec)
            bin.install_symlink Dir["\#{libexec}/bin/*"]
        <% elsif mode == :perl %>
            ENV.prepend_create_path "PERL5LIB", libexec/"lib/perl5"
            ENV.prepend_path "PERL5LIB", libexec/"lib"

            # Stage additional dependency (Makefile.PL style)
            # resource("").stage do
            #   system "perl", "Makefile.PL", "INSTALL_BASE=\#{libexec}"
            #   system "make"
            #   system "make", "install"
            # end

            # Stage additional dependency (Build.PL style)
            # resource("").stage do
            #   system "perl", "Build.PL", "--install_base", libexec
            #   system "./Build"
            #   system "./Build", "install"
            # end

            bin.install name
            bin.env_script_all_files(libexec/"bin", PERL5LIB: ENV["PERL5LIB"])
        <% elsif mode == :python %>
            virtualenv_install_with_resources
        <% elsif mode == :ruby %>
            ENV["GEM_HOME"] = libexec
            system "gem", "build", "\#{name}.gemspec"
            system "gem", "install", "\#{name}-\#{version}.gem"
            bin.install libexec/"bin/\#{name}"
            bin.env_script_all_files(libexec/"bin", GEM_HOME: ENV["GEM_HOME"])
        <% elsif mode == :rust %>
            system "cargo", "install", *std_cargo_args
        <% else %>
            # Remove unrecognized options if warned by configure
            # https://rubydoc.brew.sh/Formula.html#std_configure_args-instance_method
            system "./configure", *std_configure_args, "--disable-silent-rules"
            # system "cmake", "-S", ".", "-B", "build", *std_cmake_args
        <% end %>
          end

          test do
            # `test do` will create, run in and delete a temporary directory.
            #
            # This test will fail and we won't accept that! For Homebrew/homebrew-core
            # this will need to be a test that verifies the functionality of the
            # software. Run the test with `brew test #{name}`. Options passed
            # to `brew install` such as `--HEAD` also need to be provided to `brew test`.
            #
            # The installed folder is not in the path, so use the entire path to any
            # executables being tested: `system "\#{bin}/program", "do", "something"`.
            system "false"
          end
        end
      ERB
    end
  end
end
