defmodule Credo.Execution.ExecutionIssues do
  @moduledoc false

  use GenServer

  alias Credo.Execution
  alias Credo.Issue
  alias Credo.SourceFile

  def start_server(exec) do
    {:ok, pid} = GenServer.start_link(__MODULE__, [])

    %{exec | issues_pid: pid}
  end

  @doc "Appends an `issue` for the specified `filename`."
  def append(_, [] = _issues), do: :ok

  def append(%Execution{issues_pid: pid}, issues) when is_list(issues) do
    issues
    |> Enum.group_by(& &1.filename)
    |> Enum.each(fn {filename, issues} ->
      GenServer.call(pid, {:append, filename, issues})
    end)
  end

  def append(%Execution{issues_pid: pid}, %Issue{} = issue) do
    GenServer.call(pid, {:append, issue.filename, issue})
  end

  @doc "Appends an `issue` for the specified `filename`."
  def append(%Execution{issues_pid: pid}, %SourceFile{filename: filename}, issue) do
    GenServer.call(pid, {:append, filename, issue})
  end

  @doc "Returns the issues for the specified `filename`."
  def get(%Execution{issues_pid: pid}, %SourceFile{filename: filename}) do
    GenServer.call(pid, {:get, filename})
  end

  @doc "Sets/overwrites all `issues` for the given Execution struct."
  def set(%Execution{issues_pid: pid}, issues) do
    GenServer.call(pid, {:set, issues})
  end

  @doc "Returns all `issues` for the given Execution struct."
  def to_map(%Execution{issues_pid: pid}) do
    GenServer.call(pid, :to_map)
  end

  # callbacks

  def init(_) do
    {:ok, %{}}
  end

  def handle_call({:append, filename, issue_or_issue_list}, _from, current_state) do
    existing_issues = List.wrap(current_state[filename])
    new_issue_list = List.wrap(issue_or_issue_list) ++ existing_issues
    new_current_state = Map.put(current_state, filename, new_issue_list)

    {:reply, new_issue_list, new_current_state}
  end

  def handle_call({:get, filename}, _from, current_state) do
    {:reply, List.wrap(current_state[filename]), current_state}
  end

  def handle_call({:set, issues}, _from, _current_state) do
    new_current_state = Enum.group_by(issues, fn issue -> issue.filename end)

    {:reply, new_current_state, new_current_state}
  end

  def handle_call(:to_map, _from, current_state) do
    {:reply, current_state, current_state}
  end
end
