defmodule Forge.Ast.Analysis.Alias do
  alias Forge.Ast
  alias Forge.Document
  alias Forge.Document.Position
  alias Forge.Document.Range

  defstruct [:module, :as, :range, explicit?: true]

  @type t :: %__MODULE__{
          module: [atom],
          as: [module()],
          range: Range.t() | nil
        }

  def explicit(%Document{} = document, ast, module, as) when is_list(module) do
    as = List.wrap(as)
    range = range_for_ast(document, ast, module, as)
    %__MODULE__{module: module, as: as, range: range}
  end

  def implicit(%Document{} = document, ast, module, as) when is_list(module) do
    range = implicit_range(document, ast)
    %__MODULE__{module: module, as: List.wrap(as), range: range, explicit?: false}
  end

  def to_module(%__MODULE__{} = alias) do
    Module.concat(alias.module)
  end

  @implicit_aliases [:__MODULE__, :"@for", :"@protocol"]
  defp range_for_ast(document, ast, _alias, [as]) when as in @implicit_aliases do
    implicit_range(document, ast)
  end

  defp range_for_ast(document, ast, _alias, _as) do
    # All other kinds of aliases defined with the `alias` special form
    Ast.Range.get(ast, document)
  end

  defp implicit_range(%Document{} = document, ast) do
    # There are kinds of aliases that are automatically generated by elixir
    # such as __MODULE__, these don't really have any code that defines them,
    with [line: line, column: _] <- Sourceror.get_start_position(ast),
         {:ok, line_text} <- Document.fetch_text_at(document, line) do
      line_length = String.length(line_text)
      alias_start = Position.new(document, line, line_length)
      Range.new(alias_start, alias_start)
    else
      _ ->
        nil
    end
  end
end
