defmodule Mv.Membership.Changes.GenerateSlug do
  @moduledoc """
  Ash Change that automatically generates a URL-friendly slug from the `name` attribute.

  ## Behavior

  - **On Create**: Generates a slug from the name attribute using slugify
  - **On Update**: Slug remains unchanged (immutable after creation)
  - **Slug Generation**: Uses the `slugify` library to convert name to slug
    - Converts to lowercase
    - Replaces spaces with hyphens
    - Removes special characters
    - Handles UTF-8 characters (e.g., ä → a, ß → ss)
    - Trims leading/trailing hyphens
    - Truncates to max 100 characters

  ## Usage

  Works for any resource with `name` and `slug` attributes.
  Used by CustomField and Group resources.

      create :create do
        accept [:name, :description]
        change Mv.Membership.Changes.GenerateSlug
        validate string_length(:slug, min: 1)
      end

  ## Examples

      # Create with automatic slug generation
      CustomField.create!(%{name: "Mobile Phone"})
      # => %CustomField{name: "Mobile Phone", slug: "mobile-phone"}

      Group.create!(%{name: "Test Group"})
      # => %Group{name: "Test Group", slug: "test-group"}

      # German umlauts are converted
      CustomField.create!(%{name: "Café Müller"})
      # => %CustomField{name: "Café Müller", slug: "cafe-muller"}

      # Slug is immutable on update
      custom_field = CustomField.create!(%{name: "Original"})
      CustomField.update!(custom_field, %{name: "New Name"})
      # => %CustomField{name: "New Name", slug: "original"}  # slug unchanged!

  ## Implementation Note

  This change only runs on `:create` actions. The slug is immutable by design,
  as changing slugs would break external references (e.g., CSV imports/exports, URL routes).
  """
  use Ash.Resource.Change

  @doc """
  Generates a slug from the changeset's `name` attribute.

  Only runs on create actions. Returns the changeset unchanged if:
  - The action is not :create
  - The name is not being changed
  - The name is nil or empty

  ## Parameters

  - `changeset` - The Ash changeset
  - `_opts` - Options passed to the change (unused)
  - `_context` - Ash context map (unused)

  ## Returns

  The changeset with the `:slug` attribute set to the generated slug.
  """
  @impl true
  def change(changeset, _opts, _context) do
    # Only generate slug on create, not on update (immutability)
    if changeset.action_type == :create do
      case Ash.Changeset.get_attribute(changeset, :name) do
        nil ->
          changeset

        name when is_binary(name) ->
          slug = generate_slug(name)
          Ash.Changeset.force_change_attribute(changeset, :slug, slug)

        _ ->
          changeset
      end
    else
      # On update, don't touch the slug (immutable)
      changeset
    end
  end

  @doc """
  Generates a URL-friendly slug from a given string.

  Uses the `slugify` library to create a clean, lowercase slug with:
  - Spaces replaced by hyphens
  - Special characters removed
  - UTF-8 characters transliterated (ä → a, ß → ss, etc.)
  - Multiple consecutive hyphens reduced to single hyphen
  - Leading/trailing hyphens removed
  - Maximum length of 100 characters

  ## Parameters

  - `name` - The string to convert to a slug

  ## Returns

  A URL-friendly slug string, or empty string if input is invalid.

  ## Examples

      iex> generate_slug("Mobile Phone")
      "mobile-phone"

      iex> generate_slug("Café Müller")
      "cafe-muller"

      iex> generate_slug("TEST NAME")
      "test-name"

      iex> generate_slug("E-Mail & Address!")
      "e-mail-address"

      iex> generate_slug("Multiple   Spaces")
      "multiple-spaces"

      iex> generate_slug("-Test-")
      "test"

      iex> generate_slug("Straße")
      "strasse"

  """
  @spec generate_slug(String.t()) :: String.t()
  def generate_slug(name) when is_binary(name) do
    slug = Slug.slugify(name)

    case slug do
      nil -> ""
      "" -> ""
      slug when is_binary(slug) -> String.slice(slug, 0, 100)
    end
  end

  def generate_slug(_), do: ""
end
