defmodule Credo.Check.Consistency.ExceptionNames do
  use Credo.Check,
    id: "EX1001",
    run_on_all: true,
    base_priority: :high,
    explanations: [
      check: """
      Exception names should end with a common suffix like "Error".

      Try to name your exception modules consistently:

          defmodule BadCodeError do
            defexception [:message]
          end

          defmodule ParserError do
            defexception [:message]
          end

      Inconsistent use should be avoided:

          defmodule BadHTTPResponse do
            defexception [:message]
          end

          defmodule HTTPHeaderException do
            defexception [:message]
          end

      While this is not necessarily a concern for the correctness of your code,
      you should use a consistent style throughout your codebase.
      """
    ]

  @collector Credo.Check.Consistency.ExceptionNames.Collector

  @doc false
  @impl true
  def run_on_all_source_files(exec, source_files, params) do
    @collector.find_and_append_issues(source_files, exec, params, &issues_for/3, true)
  end

  defp issues_for(expected, source_file, params) do
    issue_meta = IssueMeta.for(source_file, params)

    issue_locations = @collector.find_locations_not_matching(expected, source_file)

    Enum.map(issue_locations, fn location ->
      format_issue(issue_meta, [
        {:message, message_for(expected, location[:trigger])} | location
      ])
    end)
  end

  defp message_for({:prefix, expected}, trigger) do
    message = """
    Exception modules should be named consistently.
    It seems your strategy is to prefix them with `#{expected}`,
    but `#{trigger}` does not follow that convention.
    """

    to_one_line(message)
  end

  defp message_for({:suffix, expected}, trigger) do
    message = """
    Exception modules should be named consistently.
    It seems your strategy is to have `#{expected}` as a suffix,
    but `#{trigger}` does not follow that convention.
    """

    to_one_line(message)
  end

  defp to_one_line(str) do
    str
    |> String.split()
    |> Enum.join(" ")
  end
end
