defmodule CleanMixer.ArchMap.Component do
  alias CleanMixer.CodeMap.SourceFile
  alias CleanMixer.CodeMap.FileDependency
  alias CleanMixer.CodeMap.CodeModule

  @type name :: String.t() | atom

  defstruct [
    :name,
    :files,
    :file_dependencies,
    meta: %{}
  ]

  @type t :: %__MODULE__{
          name: name,
          files: list(SourceFile.t()),
          file_dependencies: list(FileDependency.t()),
          meta: map
        }

  @subcomponent_delimiter "/"

  @spec new(name, list(SourceFile.t()), list(FileDependency.t())) :: t
  def new(name, files \\ [], file_dependencies \\ [], meta \\ %{}) do
    %__MODULE__{
      name: name,
      files: files,
      file_dependencies: file_dependencies,
      meta: meta
    }
  end

  @spec hex_pack?(t) :: boolean()
  def hex_pack?(component) do
    !!get_in(component.meta, [:tags, :hex_pack])
  end

  @spec file_dependencies(t, t) :: list(FileDependency.t())
  def file_dependencies(component, other_component) do
    component.file_dependencies
    |> Enum.filter(&(&1.target in other_component.files))
  end

  @spec child?(t, t) :: boolean
  def child?(child_component, parent_component) do
    child_component.name
    |> to_string()
    |> String.starts_with?([parent_component.name, @subcomponent_delimiter] |> Enum.join())
  end

  @spec has_file?(t, Path.t()) :: boolean
  def has_file?(component, path) do
    path in Enum.map(component.files, & &1.path)
  end

  @spec depth(t) :: pos_integer
  def depth(%__MODULE__{name: name}) do
    name
    |> to_string()
    |> trim(@subcomponent_delimiter)
    |> count_occurences(@subcomponent_delimiter)
  end

  @spec modules(t) :: list(CodeModule.t())
  def modules(%__MODULE__{files: files}) do
    Enum.flat_map(files, & &1.modules)
  end

  defp count_occurences(string, symbol) do
    subparts_count =
      string
      |> String.split(symbol, trim: true)
      |> Enum.count()

    subparts_count - 1
  end

  defp trim(string, symbol) do
    string
    |> String.trim_leading(symbol)
    |> String.trim_trailing(symbol)
  end
end
