defmodule Berrypod.Pages.Page do
  use Ecto.Schema
  import Ecto.Changeset

  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id

  @system_slugs ~w(
    home about delivery privacy terms contact
    collection pdp cart search
    checkout_success orders order_detail error
  )

  @reserved_paths ~w(
    about delivery privacy terms contact
    collections products cart search checkout orders
    coming-soon admin health image_cache images favicon
    sitemap.xml robots.txt setup dev
  )

  schema "pages" do
    field :slug, :string
    field :title, :string
    field :blocks, {:array, :map}, default: []
    field :type, :string, default: "system"
    field :published, :boolean, default: true
    field :meta_description, :string
    field :show_in_nav, :boolean, default: false
    field :nav_label, :string
    field :nav_position, :integer
    field :url_slug, :string

    timestamps(type: :utc_datetime)
  end

  @doc """
  Returns the effective URL path for this page.
  Uses url_slug if set, otherwise falls back to slug.
  Works with both Page structs and maps (from cache).
  """
  def effective_url(%__MODULE__{url_slug: url_slug, slug: slug}) do
    url_slug || slug
  end

  def effective_url(%{url_slug: url_slug, slug: slug}) do
    url_slug || slug
  end

  def effective_url(%{slug: slug}), do: slug

  def effective_url(_), do: nil

  def system_changeset(page, attrs) do
    page
    |> cast(attrs, [:slug, :title, :blocks, :url_slug])
    |> validate_required([:slug, :title, :blocks])
    |> validate_inclusion(:slug, @system_slugs)
    |> validate_url_slug()
    |> unique_constraint(:slug)
    |> unique_constraint(:url_slug)
  end

  def custom_changeset(page, attrs) do
    page
    |> cast(attrs, [
      :slug,
      :title,
      :blocks,
      :type,
      :published,
      :meta_description,
      :show_in_nav,
      :nav_label,
      :nav_position,
      :url_slug
    ])
    |> validate_required([:slug, :title])
    |> validate_format(:slug, ~r/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/,
      message: "must be lowercase letters, numbers, and hyphens only"
    )
    |> validate_exclusion(:slug, @system_slugs ++ @reserved_paths, message: "is reserved")
    |> validate_length(:slug, max: 100)
    |> validate_length(:title, max: 200)
    |> validate_length(:meta_description, max: 300)
    |> validate_url_slug()
    |> put_defaults()
    |> unique_constraint(:slug)
    |> unique_constraint(:url_slug)
  end

  defp put_defaults(changeset) do
    changeset
    |> put_change(:type, "custom")
    |> maybe_put(:blocks, [])
  end

  # Only puts the change if no explicit value was provided in the changeset
  defp maybe_put(changeset, field, default) do
    if get_change(changeset, field), do: changeset, else: put_change(changeset, field, default)
  end

  # Validates the url_slug format and ensures it doesn't conflict with reserved paths
  defp validate_url_slug(changeset) do
    case get_change(changeset, :url_slug) do
      nil ->
        changeset

      "" ->
        # Empty string means clear the custom URL
        put_change(changeset, :url_slug, nil)

      url_slug ->
        changeset
        |> validate_format(:url_slug, ~r/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/,
          message: "must be lowercase letters, numbers, and hyphens only"
        )
        |> validate_exclusion(:url_slug, @reserved_paths, message: "is reserved")
        |> validate_length(:url_slug, max: 100)
        |> validate_url_slug_not_system_slug(url_slug)
    end
  end

  # Ensure url_slug doesn't match another page's canonical slug
  defp validate_url_slug_not_system_slug(changeset, url_slug) do
    if url_slug in @system_slugs do
      add_error(changeset, :url_slug, "cannot match a system page slug")
    else
      changeset
    end
  end

  def system_slugs, do: @system_slugs
  def reserved_paths, do: @reserved_paths
  def system_slug?(slug), do: slug in @system_slugs
  def reserved_path?(slug), do: slug in @reserved_paths
end
