defmodule MvWeb.MemberLive.Form do
  @moduledoc """
  LiveView form for creating and editing members.

  ## Features
  - Create new members with personal information
  - Edit existing member details
  - Grouped sections for better organization
  - Manage custom properties (dynamic fields, displayed sorted by name)
  - Real-time validation with visual feedback

  ## Form Sections
  - Personal Data: Name, address, contact information, membership dates, notes
  - Custom Fields: Dynamic fields in uniform grid layout (displayed sorted by name)
  - Membership Fee: Selection of membership fee type with interval validation

  ## Events
  - `validate` - Real-time form validation
  - `save` - Submit form (create or update member)
  """
  use MvWeb, :live_view

  require Logger
  import MvWeb.LiveHelpers, only: [current_actor: 1, submit_form: 3]

  alias Mv.Membership
  alias Mv.Membership.Helpers.VisibilityConfig
  alias Mv.MembershipFees
  alias Mv.MembershipFees.MembershipFeeType
  alias MvWeb.Helpers.MembershipFeeHelpers

  @impl true
  def render(assigns) do
    # Sort custom fields by name for display only
    sorted_custom_fields = Enum.sort_by(assigns.custom_fields, & &1.name)
    assigns = assign(assigns, :sorted_custom_fields, sorted_custom_fields)

    ~H"""
    <Layouts.app flash={@flash} current_user={@current_user}>
      <.form for={@form} id="member-form" phx-change="validate" phx-submit="save">
        <.header>
          <:leading>
            <.button navigate={return_path(@return_to, @member)} variant="neutral">
              <.icon name="hero-arrow-left" class="size-4" />
              {gettext("Back")}
            </.button>
          </:leading>
          <%= if @member do %>
            {MvWeb.Helpers.MemberHelpers.display_name(@member)}
          <% else %>
            {gettext("New Member")}
          <% end %>
          <:actions>
            <.button phx-disable-with={gettext("Saving...")} variant="primary" type="submit">
              {gettext("Save")}
            </.button>
          </:actions>
        </.header>

        <div class="mt-6 space-y-6">
          <%!-- Tab navigation: Payments tab not shown on new/edit (only on member show) --%>
          <div role="tablist" class="tabs tabs-bordered">
            <button type="button" role="tab" class="tab tab-active" aria-selected="true">
              <.icon name="hero-identification" class="size-4 mr-2" />
              {gettext("Contact Data")}
            </button>
          </div>

          <%!-- Personal Data and Custom Fields Row --%>
          <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
            <%!-- Personal Data Section --%>
            <div>
              <.form_section title={gettext("Personal Data")}>
                <div class="space-y-4">
                  <%!-- Name Row --%>
                  <div class="flex gap-4">
                    <div class="w-48">
                      <.input
                        field={@form[:first_name]}
                        label={gettext("First Name")}
                        required={@member_field_required_map[:first_name]}
                      />
                    </div>
                    <div class="w-48">
                      <.input
                        field={@form[:last_name]}
                        label={gettext("Last Name")}
                        required={@member_field_required_map[:last_name]}
                      />
                    </div>
                  </div>

                  <%!-- Address: Country, Postal Code, City in one row --%>
                  <div class="flex gap-4">
                    <div class="w-48">
                      <.input field={@form[:country]} label={gettext("Country")} />
                    </div>
                    <div class="w-24">
                      <.input
                        field={@form[:postal_code]}
                        label={gettext("Postal Code")}
                        required={@member_field_required_map[:postal_code]}
                      />
                    </div>
                    <div class="w-48">
                      <.input field={@form[:city]} label={gettext("City")} />
                    </div>
                  </div>

                  <%!-- Street and Nr. below --%>
                  <div class="flex gap-4">
                    <div class="w-64">
                      <.input field={@form[:street]} label={gettext("Street")} />
                    </div>
                    <div class="w-24">
                      <.input field={@form[:house_number]} label={gettext("Nr.")} />
                    </div>
                  </div>

                  <%!-- Email --%>
                  <div class="w-64">
                    <.input field={@form[:email]} label={gettext("Email")} required type="email" />
                  </div>

                  <%!-- Membership Dates Row --%>
                  <div class="flex gap-4">
                    <div class="w-36">
                      <.input
                        field={@form[:join_date]}
                        label={gettext("Join Date")}
                        type="date"
                        required={@member_field_required_map[:join_date]}
                      />
                    </div>
                    <div class="w-36">
                      <.input
                        field={@form[:exit_date]}
                        label={gettext("Exit Date")}
                        type="date"
                        required={@member_field_required_map[:exit_date]}
                      />
                    </div>
                  </div>

                  <%!-- Notes --%>
                  <div>
                    <.input
                      field={@form[:notes]}
                      label={gettext("Notes")}
                      type="textarea"
                      required={@member_field_required_map[:notes]}
                    />
                  </div>
                </div>
              </.form_section>
            </div>

            <%!-- Custom Fields Section --%>
            <%= if Enum.any?(@custom_fields) do %>
              <div>
                <.form_section title={gettext("Custom Fields")}>
                  <div class="grid grid-cols-2 gap-4">
                    <%!-- Render in sorted order by finding the form for each sorted custom field --%>
                    <%= for cf <- @sorted_custom_fields do %>
                      <.inputs_for :let={f_cfv} field={@form[:custom_field_values]}>
                        <%= if f_cfv[:custom_field_id].value == cf.id do %>
                          <div class={if cf.value_type == :boolean, do: "flex items-end", else: ""}>
                            <.inputs_for :let={value_form} field={f_cfv[:value]}>
                              <.input
                                field={value_form[:value]}
                                label={cf.name}
                                type={custom_field_input_type(cf.value_type)}
                                required={cf.required}
                              />
                            </.inputs_for>
                            <input
                              type="hidden"
                              name={f_cfv[:custom_field_id].name}
                              value={f_cfv[:custom_field_id].value}
                            />
                          </div>
                        <% end %>
                      </.inputs_for>
                    <% end %>
                  </div>
                </.form_section>
              </div>
            <% end %>
          </div>

          <%!-- Membership Fee Section --%>
          <div class="max-w-xl">
            <.form_section title={gettext("Membership Fee")}>
              <div class="space-y-4">
                <div>
                  <label class="label">
                    <span class="label-text font-semibold">{gettext("Membership Fee Type")}</span>
                  </label>
                  <select
                    class="select select-bordered w-full"
                    name={@form[:membership_fee_type_id].name}
                    phx-change="validate"
                    value={@form[:membership_fee_type_id].value || ""}
                  >
                    <%!-- No "None" option: a membership fee type is required (validated in Member resource). --%>
                    <option value="">{gettext("Select a membership fee type")}</option>
                    <%= for fee_type <- @available_fee_types do %>
                      <option
                        value={fee_type.id}
                        selected={fee_type.id == @form[:membership_fee_type_id].value}
                      >
                        {fee_type.name} ({MembershipFeeHelpers.format_currency(fee_type.amount)}, {MembershipFeeHelpers.format_interval(
                          fee_type.interval
                        )})
                      </option>
                    <% end %>
                  </select>
                  <%= for error <- List.wrap(@form.errors[:membership_fee_type_id] || []) do %>
                    <% {msg, _opts} = if is_tuple(error), do: error, else: {error, []} %>
                    <p class="text-error text-sm mt-1">{msg}</p>
                  <% end %>
                  <%= if @interval_warning do %>
                    <div class="alert alert-warning mt-2">
                      <.icon name="hero-exclamation-triangle" class="size-5" />
                      <span>{@interval_warning}</span>
                    </div>
                  <% end %>
                  <p class="text-sm text-base-content/60 mt-2">
                    {gettext(
                      "Select a membership fee type for this member. Members can only switch between types with the same interval."
                    )}
                  </p>
                </div>
              </div>
            </.form_section>
          </div>

          <%!-- Bottom Action Buttons --%>
          <div class="flex justify-end gap-4 mt-6">
            <.button navigate={return_path(@return_to, @member)} variant="neutral" type="button">
              {gettext("Cancel")}
            </.button>
            <.button phx-disable-with={gettext("Saving...")} variant="primary" type="submit">
              {gettext("Save Member")}
            </.button>
          </div>

          <%!-- Danger zone: same section pattern as MemberLive.Show (canonical) --%>
          <%= if @member && can?(@current_user, :destroy, @member) do %>
            <section class="mt-8 mb-6" aria-labelledby="danger-zone-heading">
              <h2 id="danger-zone-heading" class="text-lg font-semibold mb-3 text-error">
                {gettext("Danger zone")}
              </h2>
              <div class="border border-base-300 rounded-lg p-4 bg-base-100">
                <p class="text-base-content/70 mb-4">
                  {gettext(
                    "Deleting this member cannot be undone. All related data (e.g. membership fee cycles) will be removed."
                  )}
                </p>
                <.button
                  variant="danger"
                  type="button"
                  phx-click="delete"
                  phx-value-id={@member.id}
                  data-confirm={
                    gettext("Are you sure you want to delete %{name}? This action cannot be undone.",
                      name: MvWeb.Helpers.MemberHelpers.display_name(@member)
                    )
                  }
                  data-testid="member-delete"
                  aria-label={
                    gettext("Delete member %{name}",
                      name: MvWeb.Helpers.MemberHelpers.display_name(@member)
                    )
                  }
                >
                  <.icon name="hero-trash" class="size-4" />
                  {gettext("Delete member")}
                </.button>
              </div>
            </section>
          <% end %>
        </div>
      </.form>
    </Layouts.app>
    """
  end

  @impl true
  def mount(params, _session, socket) do
    # current_user should be set by on_mount hooks (LiveUserAuth + LiveHelpers)
    actor = current_actor(socket)
    {:ok, custom_fields} = Mv.Membership.list_custom_fields(actor: actor)

    initial_custom_field_values =
      Enum.map(custom_fields, fn cf ->
        %{
          "custom_field_id" => cf.id,
          "value" => %{
            "type" => cf.value_type,
            "value" => nil,
            "_union_type" => Atom.to_string(cf.value_type)
          }
        }
      end)

    member =
      case params["id"] do
        nil -> nil
        id -> Ash.get!(Mv.Membership.Member, id, load: [:membership_fee_type], actor: actor)
      end

    page_title =
      if is_nil(member), do: gettext("Create Member"), else: gettext("Edit Member")

    # Load available membership fee types
    available_fee_types = load_available_fee_types(member, actor)

    # Load settings to know which member fields are required (for asterisk/tooltip)
    member_field_required_map = get_member_field_required_map()

    {:ok,
     socket
     |> assign(:return_to, return_to(params["return_to"]))
     |> assign(:custom_fields, custom_fields)
     |> assign(:initial_custom_field_values, initial_custom_field_values)
     |> assign(member: member)
     |> assign(:page_title, page_title)
     |> assign(:available_fee_types, available_fee_types)
     |> assign(:interval_warning, nil)
     |> assign(:member_field_required_map, member_field_required_map)
     |> assign_form()}
  end

  defp get_member_field_required_map do
    vereinfacht_required? = Mv.Config.vereinfacht_configured?()

    case Membership.get_settings() do
      {:ok, settings} ->
        required_config = settings.member_field_required || %{}
        normalized = VisibilityConfig.normalize(required_config)

        Mv.Constants.member_fields()
        |> Enum.map(fn field ->
          required =
            field == :email ||
              (vereinfacht_required? && Mv.Constants.vereinfacht_required_field?(field)) ||
              Map.get(normalized, field, false)

          {field, required}
        end)
        |> Map.new()

      {:error, _} ->
        # Email always required; Vereinfacht fields when integration active
        Map.new(Mv.Constants.member_fields(), fn f ->
          {f,
           f == :email || (vereinfacht_required? && Mv.Constants.vereinfacht_required_field?(f))}
        end)
    end
  end

  defp return_to("show"), do: "show"
  defp return_to(_), do: "index"

  @impl true
  def handle_event("validate", %{"member" => member_params}, socket) do
    # Merge with existing form values to preserve unchanged fields (especially custom_field_values)
    # Extract values directly from form fields to get current state
    existing_values = get_existing_form_values(socket.assigns.form)

    # Merge existing values with new params (new params take precedence)
    merged_params = Map.merge(existing_values, member_params)

    validated_form = AshPhoenix.Form.validate(socket.assigns.form, merged_params)

    # Check for interval mismatch if membership_fee_type_id changed
    socket = check_interval_change(socket, merged_params)

    {:noreply, assign(socket, form: validated_form)}
  end

  def handle_event("save", %{"member" => member_params}, socket) do
    try do
      actor = current_actor(socket)

      case submit_form(socket.assigns.form, member_params, actor) do
        {:ok, member} ->
          handle_save_success(socket, member)

        {:error, form} ->
          handle_save_error(socket, form)
      end
    rescue
      _e in [Ash.Error.Forbidden, Ash.Error.Forbidden.Policy] ->
        handle_save_forbidden(socket)

      e ->
        handle_save_exception(socket, e)
    end
  end

  @impl true
  def handle_event("delete", %{"id" => id}, socket) do
    member = socket.assigns.member
    actor = current_actor(socket)

    cond do
      is_nil(member) ->
        {:noreply, put_flash(socket, :error, gettext("Member not found"))}

      to_string(id) != to_string(member.id) ->
        {:noreply, put_flash(socket, :error, gettext("Member not found"))}

      true ->
        handle_member_delete_destroy(socket, member, actor)
    end
  end

  defp handle_member_delete_destroy(socket, member, actor) do
    case Ash.destroy(member, actor: actor) do
      :ok ->
        {:noreply,
         socket
         |> put_flash(:success, gettext("Member deleted successfully"))
         |> push_navigate(to: ~p"/members")}

      {:error, %Ash.Error.Forbidden{}} ->
        {:noreply,
         put_flash(socket, :error, gettext("You do not have permission to delete this member"))}

      {:error, error} ->
        Logger.warning("Member delete failed: member_id=#{member.id} error=#{inspect(error)}")
        {:noreply, put_flash(socket, :error, format_destroy_error(error))}
    end
  end

  defp handle_save_success(socket, member) do
    notify_parent({:saved, member})

    flash_message =
      case socket.assigns.form.source.type do
        :create -> gettext("Member created successfully")
        :update -> gettext("Member updated successfully")
        other -> gettext("Member %{action} successfully", action: get_action_name(other))
      end

    socket =
      socket
      |> put_flash(:success, flash_message)
      |> maybe_put_vereinfacht_sync_flash(member.id)
      |> push_navigate(to: return_path(socket.assigns.return_to, member))

    {:noreply, socket}
  end

  defp maybe_put_vereinfacht_sync_flash(socket, member_id) do
    case Mv.Vereinfacht.SyncFlash.take(to_string(member_id)) do
      {:warning, message} ->
        put_flash(socket, :warning, translate_vereinfacht_flash(message))

      {:ok, _message} ->
        # Optionally show sync success; for now we keep only the main success message
        socket

      nil ->
        socket
    end
  end

  defp translate_vereinfacht_flash(message) when is_binary(message) do
    prefix = "Vereinfacht: "

    if String.starts_with?(message, prefix) do
      detail = message |> String.trim_leading(prefix) |> String.trim()

      Gettext.dgettext(MvWeb.Gettext, "default", "Vereinfacht: %{detail}",
        detail: Gettext.dgettext(MvWeb.Gettext, "default", detail)
      )
    else
      Gettext.dgettext(MvWeb.Gettext, "default", message)
    end
  end

  defp format_destroy_error(%Ash.Error.Invalid{errors: errors}) do
    error_messages =
      Enum.map(errors, fn
        %{field: field, message: message} -> "#{field}: #{message}"
        %{message: message} -> message
        _ -> inspect(errors)
      end)

    Enum.join(error_messages, ", ")
  end

  defp format_destroy_error(error), do: inspect(error)

  defp handle_save_error(socket, form) do
    # Always show a flash message when save fails
    # Field-level validation errors are displayed in form fields, but flash provides additional feedback
    error_message = extract_error_message(form)
    {:noreply, socket |> assign(form: form) |> put_flash(:error, error_message)}
  end

  defp handle_save_forbidden(socket) do
    # Handle policy violations that aren't properly displayed in forms
    # AshPhoenix.Form doesn't implement FormData.Error protocol for Forbidden errors
    action = get_action_name(socket.assigns.form.source.type)

    error_message =
      gettext("You do not have permission to %{action} members.", action: action)

    {:noreply, put_flash(socket, :error, error_message)}
  end

  defp handle_save_exception(socket, exception) do
    # Handle unexpected exceptions (database errors, network issues, etc.)
    require Logger
    Logger.error("Unexpected error saving member: #{inspect(exception)}")

    action = get_action_name(socket.assigns.form.source.type)
    error_message = gettext("Failed to %{action} member.", action: action)

    {:noreply, put_flash(socket, :error, error_message)}
  end

  # Extracts a user-friendly error message from form errors
  defp extract_error_message(form) do
    source_errors = get_source_errors(form)

    cond do
      has_invalid_error?(source_errors) ->
        extract_invalid_error_message(source_errors)

      has_other_error?(source_errors) ->
        extract_other_error_message(source_errors)

      has_form_errors?(form) ->
        gettext("Please correct the errors in the form and try again.")

      true ->
        gettext("Failed to save member. Please try again.")
    end
  end

  # Checks if source errors contain an Ash.Error.Invalid
  defp has_invalid_error?([%Ash.Error.Invalid{errors: errors} | _]) when is_list(errors), do: true
  defp has_invalid_error?(_), do: false

  # Extracts message from Ash.Error.Invalid
  defp extract_invalid_error_message([%Ash.Error.Invalid{errors: errors} | _]) do
    case List.first(errors) do
      %{message: message} when is_binary(message) ->
        gettext("Validation failed: %{message}", message: message)

      %{field: field, message: message} when is_binary(message) ->
        gettext("Validation failed: %{field} %{message}", field: field, message: message)

      _ ->
        gettext("Validation failed. Please check your input.")
    end
  end

  # Checks if source errors contain other error types
  defp has_other_error?([_ | _]), do: true
  defp has_other_error?(_), do: false

  # Extracts message from other error types
  defp extract_other_error_message([error | _]) do
    cond do
      Map.has_key?(error, :message) and is_binary(error.message) ->
        error.message

      is_struct(error) ->
        extract_struct_error_message(error)

      true ->
        gettext("Failed to save member. Please try again.")
    end
  end

  # Extracts message from struct error using Ash.ErrorKind protocol
  defp extract_struct_error_message(error) do
    try do
      Ash.ErrorKind.message(error)
    rescue
      Protocol.UndefinedError -> gettext("Failed to save member. Please try again.")
    end
  end

  # Checks if form has any errors
  defp has_form_errors?(form) do
    case Map.get(form, :errors) do
      errors when is_list(errors) and errors != [] -> true
      _ -> false
    end
  end

  # Extracts source-level errors from form (Ash errors, etc.)
  defp get_source_errors(form) do
    case form.source do
      %{errors: errors} when is_list(errors) -> errors
      %Ash.Changeset{errors: errors} when is_list(errors) -> errors
      _ -> []
    end
  end

  defp get_action_name(:create), do: gettext("create")
  defp get_action_name(:update), do: gettext("update")
  defp get_action_name(other), do: to_string(other)

  defp notify_parent(msg), do: send(self(), {__MODULE__, msg})

  defp assign_form(%{assigns: assigns} = socket) do
    member = assigns.member
    actor = assigns[:current_user] || assigns.current_user

    form =
      if member do
        {:ok, member} = Ash.load(member, [custom_field_values: [:custom_field]], actor: actor)

        existing_custom_field_values =
          member.custom_field_values
          |> Enum.map(& &1.custom_field_id)

        is_missing_custom_field_value = fn i ->
          not Enum.member?(existing_custom_field_values, Map.get(i, "custom_field_id"))
        end

        params = %{
          "custom_field_values" =>
            Enum.map(member.custom_field_values, fn cfv ->
              %{
                "custom_field_id" => cfv.custom_field_id,
                "value" => %{
                  "_union_type" => Atom.to_string(cfv.value.type),
                  "type" => cfv.value.type,
                  "value" => cfv.value.value
                }
              }
            end)
        }

        form =
          AshPhoenix.Form.for_update(
            member,
            :update_member,
            api: Mv.Membership,
            as: "member",
            params: params,
            forms: [auto?: true],
            actor: actor
          )

        missing_custom_field_values =
          Enum.filter(socket.assigns[:initial_custom_field_values], is_missing_custom_field_value)

        Enum.reduce(
          missing_custom_field_values,
          form,
          &AshPhoenix.Form.add_form(&2, [:custom_field_values], params: &1)
        )
      else
        AshPhoenix.Form.for_create(
          Mv.Membership.Member,
          :create_member,
          api: Mv.Membership,
          as: "member",
          params: %{"custom_field_values" => socket.assigns[:initial_custom_field_values]},
          forms: [auto?: true],
          actor: actor
        )
      end

    assign(socket, form: to_form(form))
  end

  defp return_path("index", _member), do: ~p"/members"
  defp return_path("show", nil), do: ~p"/members"
  defp return_path("show", member), do: ~p"/members/#{member.id}"

  # -----------------------------------------------------------------
  # Helper Functions
  # -----------------------------------------------------------------

  defp load_available_fee_types(member, actor) do
    all_types =
      MembershipFeeType
      |> Ash.Query.sort(name: :asc)
      |> Ash.read!(domain: MembershipFees, actor: actor)

    # If member has a fee type, filter to same interval
    if member && member.membership_fee_type do
      Enum.filter(all_types, fn type ->
        type.interval == member.membership_fee_type.interval
      end)
    else
      all_types
    end
  end

  # Checks if membership fee type interval changed and updates socket assigns
  defp check_interval_change(socket, member_params) do
    if Map.has_key?(member_params, "membership_fee_type_id") &&
         socket.assigns.member &&
         socket.assigns.member.membership_fee_type do
      handle_interval_change(socket, member_params["membership_fee_type_id"])
    else
      socket
    end
  end

  # Handles interval change validation
  defp handle_interval_change(socket, new_fee_type_id) do
    if new_fee_type_id != "" &&
         new_fee_type_id != socket.assigns.member.membership_fee_type_id do
      validate_interval_match(socket, new_fee_type_id)
    else
      assign(socket, :interval_warning, nil)
    end
  end

  # Validates that new fee type has same interval as current
  defp validate_interval_match(socket, new_fee_type_id) do
    new_fee_type = find_fee_type(socket.assigns.available_fee_types, new_fee_type_id)

    if new_fee_type &&
         new_fee_type.interval != socket.assigns.member.membership_fee_type.interval do
      show_interval_warning(socket, new_fee_type)
    else
      assign(socket, :interval_warning, nil)
    end
  end

  # Shows interval mismatch warning
  defp show_interval_warning(socket, new_fee_type) do
    assign(
      socket,
      :interval_warning,
      gettext(
        "Warning: Changing from %{old_interval} to %{new_interval} is not allowed. Please select a membership fee type with the same interval.",
        old_interval:
          MembershipFeeHelpers.format_interval(socket.assigns.member.membership_fee_type.interval),
        new_interval: MembershipFeeHelpers.format_interval(new_fee_type.interval)
      )
    )
  end

  defp find_fee_type(fee_types, fee_type_id) do
    Enum.find(fee_types, &(&1.id == fee_type_id))
  end

  # -----------------------------------------------------------------
  # Helper Functions for Custom Fields
  # -----------------------------------------------------------------

  # Returns input type for custom field based on value type
  defp custom_field_input_type(:string), do: "text"
  defp custom_field_input_type(:integer), do: "number"
  defp custom_field_input_type(:boolean), do: "checkbox"
  defp custom_field_input_type(:date), do: "date"
  defp custom_field_input_type(:email), do: "email"
  defp custom_field_input_type(_), do: "text"

  # -----------------------------------------------------------------
  # Helper Functions for Form Value Preservation
  # -----------------------------------------------------------------

  # Helper to extract existing form values to preserve them when only one field changes
  # This ensures custom_field_values and other fields are preserved when only the dropdown changes
  defp get_existing_form_values(form) do
    %{}
    |> extract_form_value(form, :first_name, &to_string/1)
    |> extract_form_value(form, :last_name, &to_string/1)
    |> extract_form_value(form, :email, &to_string/1)
    |> extract_form_value(form, :street, &to_string/1)
    |> extract_form_value(form, :house_number, &to_string/1)
    |> extract_form_value(form, :postal_code, &to_string/1)
    |> extract_form_value(form, :city, &to_string/1)
    |> extract_form_value(form, :country, &to_string/1)
    |> extract_form_value(form, :join_date, &format_date_value/1)
    |> extract_form_value(form, :exit_date, &format_date_value/1)
    |> extract_form_value(form, :notes, &to_string/1)
    |> extract_form_value(form, :membership_fee_type_id, &to_string/1)
    |> extract_form_value(form, :membership_fee_start_date, &format_date_value/1)
    |> extract_custom_field_values(form)
  end

  # Helper to extract a single form field value
  defp extract_form_value(acc, form, field, formatter) do
    if form[field] && form[field].value do
      Map.put(acc, to_string(field), formatter.(form[field].value))
    else
      acc
    end
  end

  # Extracts custom field values from the form structure
  # The form is a Phoenix.HTML.Form with source being AshPhoenix.Form
  # Custom field values are in form.source.params["custom_field_values"] as a map
  defp extract_custom_field_values(acc, form) do
    cfv_params = get_custom_field_values_params(form)

    if map_size(cfv_params) > 0 do
      custom_field_values = convert_cfv_params_to_list(cfv_params)
      Map.put(acc, "custom_field_values", custom_field_values)
    else
      acc
    end
  end

  # Gets custom_field_values from form params
  defp get_custom_field_values_params(form) do
    ash_form = form.source

    if ash_form && Map.has_key?(ash_form, :params) && ash_form.params["custom_field_values"] do
      ash_form.params["custom_field_values"]
    else
      %{}
    end
  end

  # Converts custom field values map to sorted list
  defp convert_cfv_params_to_list(cfv_params) do
    cfv_params
    |> Map.to_list()
    |> Enum.sort_by(&parse_numeric_key/1)
    |> Enum.map(&build_custom_field_value/1)
  end

  # Parses numeric key for sorting
  defp parse_numeric_key({key, _}) do
    case Integer.parse(key) do
      {num, _} -> num
      :error -> 999_999
    end
  end

  # Builds a custom field value map from params
  defp build_custom_field_value({_key, cfv_map}) do
    %{
      "custom_field_id" => Map.get(cfv_map, "custom_field_id", ""),
      "value" => extract_custom_field_value_from_map(Map.get(cfv_map, "value", %{}))
    }
  end

  # Extracts the value map structure from a custom field value
  # Handles both map format and Ash.Union struct format
  defp extract_custom_field_value_from_map(%Ash.Union{} = union) do
    union_type = Atom.to_string(union.type)

    %{
      "_union_type" => union_type,
      "type" => union_type,
      "value" => format_custom_field_value(union.value)
    }
  end

  defp extract_custom_field_value_from_map(value_map) when is_map(value_map) do
    union_type = extract_union_type_from_map(value_map)
    value = Map.get(value_map, "value") || Map.get(value_map, :value)

    %{
      "_union_type" => union_type,
      "type" => union_type,
      "value" => format_custom_field_value(value)
    }
  end

  defp extract_custom_field_value_from_map(_),
    do: %{"_union_type" => "", "type" => "", "value" => ""}

  # Extracts union type from map, checking various possible locations
  defp extract_union_type_from_map(value_map) do
    cond do
      has_non_empty_string(value_map, "_union_type") ->
        Map.get(value_map, "_union_type")

      has_non_empty_atom(value_map, :_union_type) ->
        to_string(Map.get(value_map, :_union_type))

      has_atom_type(value_map) ->
        Atom.to_string(Map.get(value_map, :type))

      has_string_type(value_map) ->
        Map.get(value_map, "type")

      true ->
        ""
    end
  end

  # Helper to check if map has non-empty string value
  defp has_non_empty_string(map, key) do
    value = Map.get(map, key)
    value && value != ""
  end

  # Helper to check if map has non-empty atom value
  defp has_non_empty_atom(map, key) do
    value = Map.get(map, key)
    value && value != ""
  end

  # Helper to check if map has atom type
  defp has_atom_type(map) do
    value = Map.get(map, :type)
    value && is_atom(value)
  end

  # Helper to check if map has string type
  defp has_string_type(map) do
    value = Map.get(map, "type")
    value && is_binary(value)
  end

  # Formats custom field value based on its type
  defp format_custom_field_value(%Date{} = date), do: Date.to_iso8601(date)
  defp format_custom_field_value(%Decimal{} = decimal), do: Decimal.to_string(decimal, :normal)
  defp format_custom_field_value(value) when is_boolean(value), do: to_string(value)
  defp format_custom_field_value(value) when is_binary(value), do: value
  defp format_custom_field_value(value), do: to_string(value)

  # Formats date value (Date or string) to string
  defp format_date_value(%Date{} = date), do: Date.to_iso8601(date)
  defp format_date_value(value) when is_binary(value), do: value
  defp format_date_value(_), do: ""
end
