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
    getter config_path : Path

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

    def finished(sources) : Nil
      super

      issues = sources.flat_map(&.issues)

      if issues.none?(&.enabled?)
        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

      issues.sort_by! do |issue|
        location = issue.location
        {
          issue.rule.name,
          location.try(&.filename).to_s,
          location.try(&.line_number) || 0,
          location.try(&.column_number) || 0,
        }
      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.puts header

        YAML::Builder.build(file) do |builder|
          builder.stream do
            builder.document(implicit_start_indicator: true) do
              build_yaml(file, builder, issues)
            end
          end
        end
      end
    end

    private def build_yaml(io, builder, issues)
      builder.mapping do
        rule_issues_map(issues).each do |rule, rule_issues|
          builder.flush

          io.puts

          rule_to_yaml(builder, rule, rule_issues)
        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.syntax?
          next if issue.correctable? && config[:autocorrect]?

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

    private def header
      version = Ameba.version

      <<-YAML
        # 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.
        #
        # For more details on any individual rule, run
        # `ameba --ignore-config --only RuleName`.

        Version: "#{version.simple.copy_with(prerelease: nil)}"

        YAML
    end

    private def exclude_paths(issues)
      issues
        .compact_map(&.location.try(&.filename.try(&.to_s)))
        .to_set
        .map { |path| Path[path].to_posix.to_s }
    end

    private def rule_to_yaml(yaml, rule, issues)
      yaml.scalar(rule.name)
      yaml.mapping do
        yaml.scalar("Excluded")
        yaml.sequence do
          exclude_paths(issues).each do |path|
            yaml.scalar(path)
          end
        end
      end
    end
  end
end
