require Logger

defmodule ExSync.Config do
  def reload_timeout do
    Application.get_env(application(), :reload_timeout, 150)
  end

  def logging_enabled do
    Application.get_env(application(), :logging_enabled, true)
  end

  def reload_callback do
    Application.get_env(application(), :reload_callback)
  end

  def beam_dirs do
    if Mix.Project.umbrella?() do
      for %Mix.Dep{app: app, opts: opts} <- Mix.Dep.Umbrella.loaded() do
        config = [
          umbrella?: true,
          app_path: opts[:build]
        ]

        Mix.Project.in_project(app, opts[:path], config, fn _ -> beam_dirs() end)
      end
    else
      dep_paths =
        Mix.Dep.cached()
        |> Enum.filter(fn dep -> dep.opts[:path] != nil end)
        |> Enum.map(fn %Mix.Dep{app: app, opts: opts} = dep ->
          config = [
            umbrella?: opts[:in_umbrella],
            app_path: opts[:build]
          ]

          path = resolve_dep_path(dep)

          Mix.Project.in_project(app, path, config, fn _ -> beam_dirs() end)
        end)

      # Elixir 1.15/OTP 26 compiles into the project build_path, but
      # we append dep_paths to support earlier versions
      [Mix.Project.build_path() | dep_paths]
    end
    |> List.flatten()
    |> Enum.uniq()
  end

  def src_monitor_enabled do
    case Application.fetch_env(application(), :src_monitor) do
      :error ->
        Logger.debug([
          "Defaulting to enable source monitor, set config :exsync, src_monitor: false",
          " to disable\n"
        ])

        true

      {:ok, value} when value in [true, false] ->
        value

      {:ok, invalid} ->
        Logger.error([
          "Value #{inspect(invalid)} not valid for setting :src_monitor, expected",
          " true or false.  Enabling source monitor."
        ])

        true
    end
  end

  def src_dirs do
    src_default_dirs() ++ src_addition_dirs()
  end

  defp src_default_dirs do
    if Mix.Project.umbrella?() do
      for %Mix.Dep{app: app, opts: opts} <- Mix.Dep.Umbrella.loaded() do
        Mix.Project.in_project(app, opts[:path], fn _ -> src_default_dirs() end)
      end
    else
      dep_paths =
        Mix.Dep.cached()
        |> Enum.filter(fn dep -> dep.opts[:path] != nil end)
        |> Enum.map(fn %Mix.Dep{app: app} = dep ->
          path = resolve_dep_path(dep)

          Mix.Project.in_project(app, path, fn _ ->
            src_default_dirs()
          end)
        end)

      self_paths =
        Mix.Project.config()
        |> Keyword.take([:elixirc_paths, :erlc_paths, :erlc_include_path])
        |> Keyword.values()
        |> List.flatten()
        |> Enum.map(&Path.join(app_source_dir(), &1))
        |> Enum.filter(&File.exists?/1)

      [self_paths | dep_paths]
    end
    |> List.flatten()
    |> Enum.uniq()
  end

  defp src_addition_dirs do
    Application.get_env(:exsync, :addition_dirs, [])
    |> Enum.map(&Path.join(app_source_dir(), &1))
    |> Enum.filter(&File.exists?/1)
  end

  # Resolve dep path (which may be a relative path)
  defp resolve_dep_path(%Mix.Dep{} = dep) do
    %Mix.Dep{from: from, opts: opts} = dep
    dep_path = opts[:path]
    dep_dir = Path.dirname(from)
    Path.expand(dep_path, dep_dir)
  end

  def src_extensions do
    Application.get_env(
      :exsync,
      :extensions,
      [".erl", ".hrl", ".ex", ".eex"] ++ Application.get_env(:exsync, :extra_extensions, [])
    )
  end

  def src_exclusions do
    Application.get_env(
      :exsync,
      :exclusions,
      Application.get_env(:exsync, :exclusions, [])
    )
  end

  def application do
    :exsync
  end

  def app_source_dir do
    Path.dirname(Mix.ProjectStack.peek().file)
  end
end
