defmodule Berrypod.Theme.CSSCache do
  @moduledoc """
  GenServer that maintains an ETS table for caching generated theme CSS.

  This provides fast lookups for theme CSS without regenerating it on every request.
  The cache is invalidated when theme settings are updated.
  """

  use GenServer

  @table_name :theme_css_cache

  ## Client API

  @doc """
  Starts the CSS cache GenServer.
  """
  def start_link(opts) do
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end

  @doc """
  Gets cached CSS for the site theme.

  Returns `{:ok, css}` if found in cache, or `:miss` if not cached.

  ## Examples

      iex> CSSCache.get()
      {:ok, "/* Theme CSS ... */"}

      iex> CSSCache.get()
      :miss

  """
  def get do
    case :ets.lookup(@table_name, :site_theme) do
      [{:site_theme, css}] -> {:ok, css}
      [] -> :miss
    end
  end

  @doc """
  Caches CSS for the site theme.

  ## Examples

      iex> CSSCache.put(css_string)
      :ok

  """
  def put(css) when is_binary(css) do
    :ets.insert(@table_name, {:site_theme, css})
    :ok
  end

  @doc """
  Invalidates the cached CSS, forcing regeneration on next request.

  ## Examples

      iex> CSSCache.invalidate()
      :ok

  """
  def invalidate do
    :ets.delete(@table_name, :site_theme)
    :ok
  rescue
    ArgumentError -> :ok
  end

  @doc """
  Warms the cache by generating and storing CSS from current theme settings.

  ## Examples

      iex> CSSCache.warm()
      :ok

  """
  def warm do
    alias Berrypod.Settings
    alias Berrypod.Theme.CSSGenerator

    settings = Settings.get_theme_settings()

    # Use endpoint's static_path for digested URLs in production
    path_resolver = &BerrypodWeb.Endpoint.static_path/1

    css = CSSGenerator.generate(settings, path_resolver)
    put(css)
    :ok
  end

  ## Server Callbacks

  @impl true
  def init(_opts) do
    :ets.new(@table_name, [
      :set,
      :public,
      :named_table,
      read_concurrency: true,
      write_concurrency: false
    ])

    {:ok, %{}, {:continue, :warm}}
  end

  @impl true
  def handle_continue(:warm, state) do
    try do
      warm()
    rescue
      _ -> :ok
    end

    {:noreply, state}
  end
end
