module Ameba::Formatter
  # A formatter that creates a todo config.
  # Basically, it takes all issues reported and disables corresponding rules
  # or excludes failed sources from these rules.
  class TODOFormatter < DotFormatter
    @config_path : Path

    def initialize(@output = STDOUT, @config_path = Config::DEFAULT_PATH)
    end

    def finished(sources) : Nil
      super

      issues = sources.flat_map(&.issues)
      unless issues.any? { |issue| !issue.disabled? }
        @output.puts "No issues found. File is not generated."
        return
      end

      if issues.any?(&.syntax?)
        @output.puts "Unable to generate TODO file. Please fix syntax issues."
        return
      end

      generate_todo_config(issues)

      @output.puts "Created #{@config_path}"
    end

    private def generate_todo_config(issues) : Nil
      File.open(@config_path, mode: "w") do |file|
        file << header

        rule_issues_map(issues).each do |rule, rule_issues|
          rule_todo = rule_todo(rule, rule_issues)
          rule_todo =
            {rule_todo.name => rule_todo}
              .to_yaml.gsub("---", "")

          file << "\n# Problems found: #{rule_issues.size}"
          file << "\n# Run `ameba --only #{rule.name}` for details"
          file << rule_todo
        end
      end
    end

    private def rule_issues_map(issues)
      Hash(Rule::Base, Array(Issue)).new.tap do |hash|
        issues.each do |issue|
          next if issue.disabled? || issue.rule.is_a?(Rule::Lint::Syntax)
          next if issue.correctable? && config[:autocorrect]?

          (hash[issue.rule] ||= Array(Issue).new) << issue
        end
      end
    end

    private def header
      <<-HEADER
        # This configuration file was generated by `ameba --gen-config`
        # on #{Time.utc} using Ameba version #{VERSION}.
        # The point is for the user to remove these configuration records
        # one by one as the reported problems are removed from the code base.

        HEADER
    end

    private def rule_todo(rule, issues)
      rule.dup.tap do |rule_todo|
        rule_todo.excluded = issues
          .compact_map(&.location.try &.filename.try &.to_s)
          .uniq!
      end
    end
  end
end
