defmodule Mv.Membership.CustomField do
  @moduledoc """
  Ash resource defining the schema for custom member fields.

  ## Overview
  CustomFields define the "schema" for custom fields in the membership system.
  Each CustomField specifies the name, data type, and behavior of a custom field
  that can be attached to members via CustomFieldValue resources.

  ## Attributes
  - `name` - Unique identifier for the custom field (e.g., "phone_mobile", "birthday")
  - `slug` - URL-friendly, immutable identifier automatically generated from name (e.g., "phone-mobile")
  - `value_type` - Data type constraint (`:string`, `:integer`, `:boolean`, `:date`, `:email`). Immutable after creation.
  - `description` - Optional human-readable description
  - `required` - If true, all members must have this custom field (future feature)
  - `show_in_overview` - If true, this custom field will be displayed in the member overview table and can be sorted

  ## Supported Value Types
  - `:string` - Text data (max 10,000 characters)
  - `:integer` - Numeric data (64-bit integers)
  - `:boolean` - True/false flags
  - `:date` - Date values (no time component)
  - `:email` - Validated email addresses (max 254 characters)

  ## Relationships
  - `has_many :custom_field_values` - All custom field values of this type

  ## Constraints
  - Name must be unique across all custom fields
  - Name maximum length: 100 characters
  - `value_type` cannot be changed after creation (immutable)
  - Deleting a custom field will cascade delete all associated custom field values

  ## Calculations
  - `assigned_members_count` - Returns the number of distinct members with values for this custom field

  ## Examples
      # Create a new custom field
      CustomField.create!(%{
        name: "phone_mobile",
        value_type: :string,
        description: "Mobile phone number"
      })

      # Create a required custom field
      CustomField.create!(%{
        name: "emergency_contact",
        value_type: :string,
        required: true
      })
  """
  use Ash.Resource,
    domain: Mv.Membership,
    data_layer: AshPostgres.DataLayer,
    authorizers: [Ash.Policy.Authorizer]

  postgres do
    table "custom_fields"
    repo Mv.Repo
  end

  actions do
    defaults [:read]
    default_accept [:name, :value_type, :description, :required, :show_in_overview]

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

    update :update do
      accept [:name, :description, :required, :show_in_overview]
      require_atomic? false

      validate fn changeset, _context ->
        if Ash.Changeset.changing_attribute?(changeset, :value_type) do
          {:error, field: :value_type, message: "cannot be changed after creation"}
        else
          :ok
        end
      end
    end

    destroy :destroy_with_values do
      primary? true
    end

    read :prepare_deletion do
      argument :id, :uuid, allow_nil?: false

      filter expr(id == ^arg(:id))
      prepare build(load: [:assigned_members_count])
    end
  end

  policies do
    policy action_type([:read, :create, :update, :destroy]) do
      description "Check permissions from user's role"
      authorize_if Mv.Authorization.Checks.HasPermission
    end
  end

  attributes do
    uuid_primary_key :id

    attribute :name, :string,
      allow_nil?: false,
      public?: true,
      constraints: [
        max_length: 100,
        trim?: true
      ]

    attribute :slug, :string,
      allow_nil?: false,
      public?: true,
      writable?: false,
      constraints: [
        max_length: 100,
        trim?: true
      ]

    attribute :value_type, :atom,
      constraints: [one_of: [:string, :integer, :boolean, :date, :email]],
      allow_nil?: false,
      description: "Defines the datatype `CustomFieldValue.value` is interpreted as"

    attribute :description, :string,
      allow_nil?: true,
      public?: true,
      constraints: [
        max_length: 500,
        trim?: true
      ]

    attribute :required, :boolean,
      default: false,
      allow_nil?: false

    attribute :show_in_overview, :boolean,
      default: true,
      allow_nil?: false,
      public?: true,
      description: "If true, this custom field will be displayed in the member overview table"
  end

  relationships do
    has_many :custom_field_values, Mv.Membership.CustomFieldValue
  end

  calculations do
    calculate :assigned_members_count,
              :integer,
              expr(
                fragment(
                  "(SELECT COUNT(DISTINCT member_id) FROM custom_field_values WHERE custom_field_id = ?)",
                  id
                )
              )
  end

  identities do
    identity :unique_name, [:name]
    identity :unique_slug, [:slug]
  end
end
