defmodule Mobilizon.Service.FrontEndAnalytics do
  @moduledoc """
  Behaviour for any analytics service
  """

  @callback id() :: String.t()

  @doc """
  Whether the service is enabled
  """
  @callback enabled?() :: boolean()

  @doc """
  The configuration for the service
  """
  @callback configuration() :: keyword()

  @doc """
  The CSP configuration to add for the service to work
  """
  @callback csp() :: keyword()

  @spec providers :: list(module())
  def providers do
    :mobilizon
    |> Application.get_env(:analytics, [])
    |> Keyword.get(:providers, [])
  end

  @spec config :: map()
  def config do
    Enum.reduce(providers(), [], &load_config/2)
  end

  @spec csp :: keyword()
  def csp do
    providers()
    |> Enum.map(& &1.csp())
    |> Enum.reduce([], &merge_csp_config/2)
  end

  @spec load_config(module(), list(map())) :: list(map())
  defp load_config(provider, acc) do
    acc ++
      [
        %{
          id: provider.id(),
          enabled: provider.enabled?(),
          configuration: convert_config(provider.configuration())
        }
      ]
  end

  @spec convert_config(Keyword.t()) :: list(map())
  defp convert_config(config) do
    Enum.reduce(config, [], fn {key, val}, acc ->
      acc ++ [%{key: key, value: val, type: type(val)}]
    end)
  end

  defp type(val) when is_integer(val), do: :integer
  defp type(val) when is_float(val), do: :float
  defp type(val) when is_boolean(val), do: :boolean
  defp type(val) when is_binary(val), do: :string

  defp merge_csp_config(config, global_config) do
    Keyword.merge(global_config, config, fn _key, global, config ->
      global ++ config
    end)
  end
end