defmodule BerrypodWeb.Plugs.DynamicRoutes do
  @moduledoc """
  Plug that enables dynamic route prefixes for products, collections, and orders.

  When a shop owner customises their URL prefixes (e.g., /products → /p), this plug:

  1. **Rewrites incoming paths** - If the URL uses a custom prefix (/p/123), rewrites
     path_info to the canonical form (/products/123) so the router matches.

  2. **Redirects old prefixes** - If the URL uses the old default prefix when a custom
     one is set (/products/123 when prefix is "p"), issues a 301 redirect to /p/123.

  This plug must run AFTER the Redirects plug (which handles trailing slashes and
  explicit redirects) but BEFORE the router.

  ## How it works

  The R module maintains an ETS cache of custom prefixes loaded from Settings.
  `R.prefix/1` returns the current prefix for a type, and `R.prefix_type_from_segment/1`
  performs reverse lookups.

  ## Example

      # Settings: url_prefixes = %{"products" => "p", "collections" => "c"}

      GET /p/123           → Rewrite to /products/123, router matches :product
      GET /products/123    → Redirect 301 to /p/123
      GET /c/art           → Rewrite to /collections/art, router matches :collection
      GET /collections/art → Redirect 301 to /c/art

  When no custom prefix is set, paths pass through unchanged.
  """

  import Plug.Conn
  alias BerrypodWeb.R

  @default_prefixes %{
    products: "products",
    collections: "collections",
    orders: "orders"
  }

  def init(opts), do: opts

  def call(conn, _opts) do
    case conn.path_info do
      [first_segment | rest] when rest != [] ->
        handle_prefixed_path(conn, first_segment, rest)

      _ ->
        conn
    end
  end

  defp handle_prefixed_path(conn, segment, rest) do
    prefix_type = R.prefix_type_from_segment(segment)
    current_prefix = prefix_type && R.prefix(prefix_type)
    default_prefix = prefix_type && @default_prefixes[prefix_type]

    cond do
      # No prefix type matched - not a dynamic route
      is_nil(prefix_type) ->
        conn

      # Using the current (possibly custom) prefix - rewrite to default for router
      segment == current_prefix and current_prefix != default_prefix ->
        rewrite_path(conn, default_prefix, rest, segment)

      # Using default prefix but custom is set - redirect to custom prefix
      segment == default_prefix and current_prefix != default_prefix ->
        redirect_to_custom(conn, current_prefix, rest)

      # Using the default prefix and no custom set, or already canonical
      true ->
        conn
    end
  end

  # Rewrite path_info so the router's static routes match
  # Store original path in conn.private for LiveView to use
  defp rewrite_path(conn, canonical_prefix, rest, original_prefix) do
    conn
    |> put_private(:original_prefix, original_prefix)
    |> put_private(:original_request_path, conn.request_path)
    |> Map.put(:path_info, [canonical_prefix | rest])
  end

  # 301 redirect to the canonical custom prefix
  defp redirect_to_custom(conn, custom_prefix, rest) do
    path = "/" <> Enum.join([custom_prefix | rest], "/")
    location = append_query(path, conn.query_string)

    conn
    |> put_resp_header("location", location)
    |> send_resp(301, "")
    |> halt()
  end

  defp append_query(path, ""), do: path
  defp append_query(path, qs), do: "#{path}?#{qs}"
end
