defmodule MvWeb.UserLive.Form do
  @moduledoc """
  LiveView form for creating and editing users.

  ## Features
  - Create new users with email
  - Edit existing user details
  - Optional password setting (checkbox to toggle)
  - Link/unlink member accounts
  - Email synchronization with linked members

  ## Form Fields
  **Required:**
  - email

  **Optional:**
  - password (for password authentication strategy)
  - linked member (select from existing members)

  ## Password Management
  - New users: Can optionally set password with confirmation
  - Existing users: Can change password (no confirmation required, admin action)
  - Checkbox toggles password section visibility

  ## Member Linking
  Users can be linked to existing member accounts. When linked, emails are
  synchronized bidirectionally with User.email as the source of truth.

  ## Events
  - `validate` - Real-time form validation
  - `save` - Submit form (create or update user)
  - `toggle_password_section` - Show/hide password fields
  """
  use MvWeb, :live_view

  require Jason

  alias Mv.Authorization

  import MvWeb.LiveHelpers, only: [current_actor: 1, submit_form: 3]
  import MvWeb.Authorization, only: [can?: 3]
  import MvWeb.ErrorHelpers, only: [format_ash_error: 1]

  @impl true
  def render(assigns) do
    ~H"""
    <Layouts.app flash={@flash} current_user={@current_user}>
      <.header>
        <:leading>
          <.button navigate={return_path(@return_to, @user)} variant="neutral">
            <.icon name="hero-arrow-left" class="size-4" />
            {gettext("Back")}
          </.button>
        </:leading>
        {@page_title}
        <:subtitle>{gettext("Use this form to manage user records in your database.")}</:subtitle>
        <:actions>
          <.button
            form="user-form"
            phx-disable-with={gettext("Saving...")}
            variant="primary"
            type="submit"
          >
            {gettext("Save User")}
          </.button>
        </:actions>
      </.header>

      <.form class="max-w-xl" for={@form} id="user-form" phx-change="validate" phx-submit="save">
        <.input field={@form[:email]} label={gettext("Email")} required type="email" />

        <%= if @user && @can_assign_role do %>
          <div class="mt-4">
            <.input
              field={@form[:role_id]}
              type="select"
              label={gettext("Role")}
              options={Enum.map(@roles, &{&1.name, &1.id})}
              prompt={gettext("Select role...")}
            />
          </div>
        <% end %>
        
    <!-- Password Section -->
        <div class="mt-6">
          <label class="flex items-center space-x-2">
            <input
              type="checkbox"
              name="set_password"
              phx-click="toggle_password_section"
              checked={@show_password_fields}
              class="checkbox checkbox-sm"
            />
            <span class="text-sm font-medium">
              {if @user, do: gettext("Change Password"), else: gettext("Set Password")}
            </span>
          </label>

          <%= if @show_password_fields do %>
            <div class="p-4 mt-4 space-y-4 rounded-lg bg-gray-50">
              <%= if @user && MvWeb.Helpers.UserHelpers.has_oidc?(@user) do %>
                <div class="p-3 mb-4 border border-red-300 rounded-lg bg-red-50" role="alert">
                  <p class="text-sm font-semibold text-red-800">
                    {gettext("SSO / OIDC user")}
                  </p>
                  <p class="mt-1 text-sm text-red-700">
                    {gettext(
                      "This user is linked via SSO (Single Sign-On). A password set or changed here only affects login with email and password in this application. It does not change the password in your identity provider (e.g. Authentik). To change the SSO password, use the identity provider or your organization's IT."
                    )}
                  </p>
                </div>
              <% end %>
              <.input
                field={@form[:password]}
                label={gettext("Password")}
                type="password"
                required
                autocomplete="new-password"
              />
              
    <!-- Only show password confirmation for new users (register_with_password) -->
              <%= if !@user do %>
                <.input
                  field={@form[:password_confirmation]}
                  label={gettext("Confirm Password")}
                  type="password"
                  required
                  autocomplete="new-password"
                />
              <% end %>

              <div class="text-sm text-gray-600">
                <p><strong>{gettext("Password requirements")}:</strong></p>
                <ul class="mt-1 space-y-1 text-xs list-disc list-inside">
                  <li>{gettext("At least 8 characters")}</li>
                  <li>{gettext("Include both letters and numbers")}</li>
                  <li>{gettext("Consider using special characters")}</li>
                </ul>
              </div>

              <%= if @user && @can_manage_member_linking do %>
                <div class="p-3 mt-3 border border-orange-200 rounded bg-orange-50">
                  <p class="text-sm text-orange-800">
                    <strong>{gettext("Admin Note")}:</strong> {gettext(
                      "As an administrator, you can directly set a new password for this user using the same secure Ash Authentication system."
                    )}
                  </p>
                </div>
              <% end %>
            </div>
          <% else %>
            <%= if @user do %>
              <div class="p-4 mt-4 rounded-lg bg-blue-50">
                <p class="text-sm text-blue-800">
                  <strong>{gettext("Note")}:</strong> {gettext(
                    "Check 'Change Password' above to set a new password for this user."
                  )}
                </p>
              </div>
            <% else %>
              <div class="p-4 mt-4 rounded-lg bg-yellow-50">
                <p class="text-sm text-yellow-800">
                  <strong>{gettext("Note")}:</strong> {gettext(
                    "User will be created without a password. Check 'Set Password' to add one."
                  )}
                </p>
              </div>
            <% end %>
          <% end %>
        </div>
        
    <!-- Member Linking Section (admin only: only admins can link/unlink users to members) -->
        <%= if @can_manage_member_linking do %>
          <div class="mt-6">
            <h2 class="mb-3 text-base font-semibold">{gettext("Linked Member")}</h2>

            <%= if @user && @user.member && !@unlink_member do %>
              <!-- Show linked member with unlink button -->
              <div class="p-4 border border-green-200 rounded-lg bg-green-50">
                <div class="flex items-center justify-between">
                  <div>
                    <p class="font-medium text-green-900">
                      {MvWeb.Helpers.MemberHelpers.display_name(@user.member)}
                    </p>
                    <p class="text-sm text-green-700">{@user.member.email}</p>
                  </div>
                  <.button
                    type="button"
                    variant="danger"
                    size="sm"
                    phx-click="unlink_member"
                  >
                    {gettext("Unlink Member")}
                  </.button>
                </div>
              </div>
            <% else %>
              <%= if @unlink_member do %>
                <!-- Show unlink pending message -->
                <div class="p-4 border border-yellow-200 rounded-lg bg-yellow-50">
                  <p class="text-sm text-yellow-800">
                    <strong>{gettext("Unlinking scheduled")}:</strong> {gettext(
                      "Member will be unlinked when you save. Cannot select new member until saved."
                    )}
                  </p>
                </div>
              <% end %>
              <!-- Show member search/selection for unlinked users -->
              <div class="space-y-3">
                <div class="relative">
                  <input
                    type="text"
                    id="member-search-input"
                    role="combobox"
                    phx-hook="ComboBox"
                    phx-focus="show_member_dropdown"
                    phx-change="search_members"
                    phx-debounce="300"
                    phx-window-keydown="member_dropdown_keydown"
                    value={@member_search_query}
                    placeholder={gettext("Search for a member to link...")}
                    class="w-full input"
                    name="member_search"
                    disabled={@unlink_member}
                    aria-label={gettext("Search for member to link")}
                    aria-describedby={if @selected_member_name, do: "member-selected", else: nil}
                    aria-autocomplete="list"
                    aria-controls="member-dropdown"
                    aria-expanded={to_string(@show_member_dropdown)}
                    aria-activedescendant={
                      if @focused_member_index,
                        do: "member-option-#{@focused_member_index}",
                        else: nil
                    }
                    autocomplete="off"
                  />

                  <%= if length(@available_members) > 0 do %>
                    <div
                      id="member-dropdown"
                      role="listbox"
                      aria-label={gettext("Available members")}
                      class={"absolute z-10 w-full mt-1 bg-base-100 border border-base-300 rounded-lg shadow-lg max-h-60 overflow-auto #{if !@show_member_dropdown, do: "hidden"}"}
                      phx-click-away="hide_member_dropdown"
                    >
                      <%= for {member, index} <- Enum.with_index(@available_members) do %>
                        <div
                          id={"member-option-#{index}"}
                          role="option"
                          tabindex="0"
                          aria-selected={to_string(@focused_member_index == index)}
                          phx-click="select_member"
                          phx-value-id={member.id}
                          data-member-id={member.id}
                          class={[
                            "px-4 py-3 cursor-pointer border-b border-base-300 last:border-b-0",
                            if(@focused_member_index == index,
                              do: "bg-base-300",
                              else: "hover:bg-base-200"
                            )
                          ]}
                        >
                          <p class="font-medium">
                            {MvWeb.Helpers.MemberHelpers.display_name(member)}
                          </p>
                          <p class="text-sm text-base-content/70">{member.email}</p>
                        </div>
                      <% end %>
                    </div>
                  <% end %>
                </div>

                <%= if @user && @user.email && @available_members != [] && Enum.all?(@available_members, &(&1.email == to_string(@user.email))) do %>
                  <div class="p-3 border border-yellow-200 rounded bg-yellow-50">
                    <p class="text-sm text-yellow-800">
                      <strong>{gettext("Note")}:</strong> {gettext(
                        "A member with this email already exists. To link with a different member, please change one of the email addresses first."
                      )}
                    </p>
                  </div>
                <% end %>

                <%= if @selected_member_id && @selected_member_name do %>
                  <div
                    id="member-selected"
                    class="p-3 mt-2 border border-blue-200 rounded-lg bg-blue-50"
                  >
                    <p class="text-sm text-blue-800">
                      <strong>{gettext("Selected")}:</strong> {@selected_member_name}
                    </p>
                    <p class="mt-1 text-xs text-blue-600">
                      {gettext("Save to confirm linking.")}
                    </p>
                  </div>
                <% end %>
              </div>
            <% end %>
          </div>
        <% end %>

        <%!-- Danger zone: canonical pattern (same as member form) --%>
        <%= if @user && can?(@current_user, :destroy, @user) && !Mv.Helpers.SystemActor.system_user?(@user) 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 user cannot be undone. The user account and any linked member association will be affected."
                )}
              </p>
              <.button
                type="button"
                variant="danger"
                phx-click="delete"
                phx-value-id={@user.id}
                data-confirm={
                  gettext(
                    "Are you sure you want to delete the user %{email}? This action cannot be undone.",
                    email: @user.email
                  )
                }
                data-testid="user-delete"
                aria-label={gettext("Delete user %{email}", email: @user.email)}
              >
                <.icon name="hero-trash" class="size-4" />
                {gettext("Delete user")}
              </.button>
            </div>
          </section>
        <% end %>

        <div class="mt-4">
          <.button navigate={return_path(@return_to, @user)} variant="neutral">
            {gettext("Cancel")}
          </.button>
          <.button phx-disable-with={gettext("Saving...")} variant="primary">
            {gettext("Save User")}
          </.button>
        </div>
      </.form>
    </Layouts.app>
    """
  end

  @impl true
  def mount(params, _session, socket) do
    actor = current_actor(socket)

    case load_user_or_redirect(params["id"], actor, socket) do
      {:redirect, socket} ->
        {:ok, socket}

      {:ok, user} ->
        mount_continue(user, params, socket)
    end
  end

  defp load_user_or_redirect(nil, _actor, _socket), do: {:ok, nil}

  defp load_user_or_redirect(id, actor, socket) do
    user = Ash.get!(Mv.Accounts.User, id, domain: Mv.Accounts, load: [:member], actor: actor)

    if Mv.Helpers.SystemActor.system_user?(user) do
      {:redirect,
       socket
       |> put_flash(:error, gettext("This user cannot be edited."))
       |> push_navigate(to: ~p"/users")}
    else
      {:ok, user}
    end
  end

  defp mount_continue(user, params, socket) do
    actor = current_actor(socket)
    action = if is_nil(user), do: gettext("New"), else: gettext("Edit")
    page_title = action <> " " <> gettext("User")

    # Only admins can link/unlink users to members (permission docs; prevents privilege escalation).
    can_manage_member_linking = can?(actor, :destroy, Mv.Accounts.User)
    # Only admins can assign user roles (Role update permission).
    can_assign_role = can?(actor, :update, Mv.Authorization.Role)
    roles = if can_assign_role, do: load_roles(actor), else: []

    {:ok,
     socket
     |> assign(:return_to, return_to(params["return_to"]))
     |> assign(user: user)
     |> assign(:page_title, page_title)
     |> assign(:can_manage_member_linking, can_manage_member_linking)
     |> assign(:can_assign_role, can_assign_role)
     |> assign(:roles, roles)
     |> assign(:show_password_fields, false)
     |> assign(:member_search_query, "")
     |> assign(:available_members, [])
     |> assign(:show_member_dropdown, false)
     |> assign(:selected_member_id, nil)
     |> assign(:selected_member_name, nil)
     |> assign(:unlink_member, false)
     |> assign(:focused_member_index, nil)
     |> load_initial_members()
     |> assign_form()}
  end

  @spec return_to(String.t() | nil) :: String.t()
  defp return_to("show"), do: "show"
  defp return_to(_), do: "index"

  @impl true
  def handle_event("toggle_password_section", _params, socket) do
    show_password_fields = !socket.assigns.show_password_fields

    socket =
      socket
      |> assign(:show_password_fields, show_password_fields)
      |> assign_form()

    {:noreply, socket}
  end

  @impl true
  def handle_event("validate", %{"user" => user_params}, socket) do
    validated_form = AshPhoenix.Form.validate(socket.assigns.form, user_params)

    # Reload members if email changed (for email-match priority; only when member linking UI is shown)
    socket =
      if Map.has_key?(user_params, "email") and socket.assigns[:can_manage_member_linking] do
        user_email = user_params["email"]
        members = load_members_for_linking(user_email, socket.assigns.member_search_query, socket)

        assign(socket, form: validated_form, available_members: members)
      else
        assign(socket, form: validated_form)
      end

    {:noreply, socket}
  end

  @impl true
  def handle_event("save", %{"user" => user_params}, socket) do
    actor = current_actor(socket)

    # Include current member in params when not linking/unlinking so update_user's
    # manage_relationship(on_missing: :unrelate) does not accidentally unlink.
    user_params = params_with_member_if_unchanged(socket, user_params)

    case submit_form(socket.assigns.form, user_params, actor) do
      {:ok, user} ->
        handle_member_linking(socket, user, actor)

      {:error, form} ->
        {:noreply, assign(socket, form: form)}
    end
  end

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

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

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

      Mv.Helpers.SystemActor.system_user?(user) ->
        {:noreply, put_flash(socket, :error, gettext("System user cannot be deleted."))}

      true ->
        handle_user_delete_destroy(socket, user, actor)
    end
  end

  @impl true
  def handle_event("show_member_dropdown", _params, socket) do
    {:noreply, assign(socket, show_member_dropdown: true)}
  end

  @impl true
  def handle_event("hide_member_dropdown", _params, socket) do
    {:noreply, assign(socket, show_member_dropdown: false, focused_member_index: nil)}
  end

  @impl true
  def handle_event("member_dropdown_keydown", %{"key" => "ArrowDown"}, socket) do
    return_if_dropdown_closed(socket, fn ->
      max_index = length(socket.assigns.available_members) - 1
      current = socket.assigns.focused_member_index

      new_index =
        case current do
          nil -> 0
          index when index < max_index -> index + 1
          _ -> current
        end

      {:noreply, assign(socket, focused_member_index: new_index)}
    end)
  end

  @impl true
  def handle_event("member_dropdown_keydown", %{"key" => "ArrowUp"}, socket) do
    return_if_dropdown_closed(socket, fn ->
      current = socket.assigns.focused_member_index

      new_index =
        case current do
          nil -> 0
          0 -> 0
          index -> index - 1
        end

      {:noreply, assign(socket, focused_member_index: new_index)}
    end)
  end

  @impl true
  def handle_event("member_dropdown_keydown", %{"key" => "Enter"}, socket) do
    return_if_dropdown_closed(socket, fn ->
      select_focused_member(socket)
    end)
  end

  @impl true
  def handle_event("member_dropdown_keydown", %{"key" => "Escape"}, socket) do
    return_if_dropdown_closed(socket, fn ->
      {:noreply, assign(socket, show_member_dropdown: false, focused_member_index: nil)}
    end)
  end

  @impl true
  def handle_event("member_dropdown_keydown", _params, socket) do
    # Ignore other keys
    {:noreply, socket}
  end

  @impl true
  def handle_event("search_members", %{"member_search" => query}, socket) do
    socket =
      socket
      |> assign(:member_search_query, query)
      |> load_available_members(query)
      |> assign(:show_member_dropdown, true)
      |> assign(:focused_member_index, nil)

    {:noreply, socket}
  end

  @impl true
  def handle_event("select_member", %{"id" => member_id}, socket) do
    # Find the selected member to get their name
    selected_member = Enum.find(socket.assigns.available_members, &(&1.id == member_id))

    member_name =
      if selected_member,
        do: MvWeb.Helpers.MemberHelpers.display_name(selected_member),
        else: ""

    # Store the selected member ID and name in socket state and clear unlink flag
    socket =
      socket
      |> assign(:selected_member_id, member_id)
      |> assign(:selected_member_name, member_name)
      |> assign(:unlink_member, false)
      |> assign(:show_member_dropdown, false)
      |> assign(:focused_member_index, nil)

    {:noreply, socket}
  end

  @impl true
  def handle_event("unlink_member", _params, socket) do
    socket =
      socket
      |> assign(:selected_member_id, nil)
      |> assign(:selected_member_name, nil)
      |> assign(:unlink_member, true)
      |> assign(:show_member_dropdown, false)
      |> assign(:focused_member_index, nil)

    {:noreply, socket}
  end

  defp handle_user_delete_destroy(socket, user, actor) do
    case Ash.destroy(user, domain: Mv.Accounts, actor: actor) do
      :ok ->
        {:noreply,
         socket
         |> put_flash(:success, gettext("User deleted successfully"))
         |> push_navigate(to: ~p"/users")}

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

      {:error, error} ->
        {:noreply, put_flash(socket, :error, format_ash_error(error))}
    end
  end

  defp handle_member_linking(socket, user, actor) do
    result = perform_member_link_action(socket, user, actor)

    case result do
      {:ok, updated_user} ->
        handle_save_success(socket, updated_user)

      {:error, error} ->
        handle_member_link_error(socket, error)
    end
  end

  defp perform_member_link_action(socket, user, actor) do
    # Only admins may link/unlink (backend policy also restricts update_user; UI must not call it).
    if can?(actor, :destroy, Mv.Accounts.User) do
      cond do
        # Selected member ID takes precedence (new link)
        socket.assigns.selected_member_id ->
          Mv.Accounts.update_user(user, %{member: %{id: socket.assigns.selected_member_id}},
            actor: actor
          )

        # Unlink flag is set
        socket.assigns[:unlink_member] ->
          Mv.Accounts.update_user(user, %{member: nil}, actor: actor)

        # No changes to member relationship
        true ->
          {:ok, user}
      end
    else
      {:ok, user}
    end
  end

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

    action = get_action_name(socket.assigns.form.source.type)

    socket =
      socket
      |> put_flash(:success, gettext("User %{action} successfully", action: action))
      |> push_navigate(to: return_path(socket.assigns.return_to, updated_user))

    {:noreply, socket}
  end

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

  # When user has a linked member and we are not linking/unlinking, include current member in params
  # so update_user's manage_relationship(on_missing: :unrelate) does not unlink the member.
  defp params_with_member_if_unchanged(socket, params) do
    user = socket.assigns.user
    linking = socket.assigns.selected_member_id
    unlinking = socket.assigns[:unlink_member]

    if user && user.member_id && !linking && !unlinking do
      Map.put(params, "member", %{"id" => user.member_id})
    else
      params
    end
  end

  defp handle_member_link_error(socket, error) do
    error_message = extract_error_message(error)

    {:noreply,
     put_flash(
       socket,
       :error,
       gettext("Failed to link member: %{error}", error: error_message)
     )}
  end

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

  # Helper to ignore keyboard events when dropdown is closed
  @spec return_if_dropdown_closed(Phoenix.LiveView.Socket.t(), function()) ::
          {:noreply, Phoenix.LiveView.Socket.t()}
  defp return_if_dropdown_closed(socket, func) do
    if socket.assigns.show_member_dropdown do
      func.()
    else
      {:noreply, socket}
    end
  end

  # Select the currently focused member from the dropdown
  @spec select_focused_member(Phoenix.LiveView.Socket.t()) ::
          {:noreply, Phoenix.LiveView.Socket.t()}
  defp select_focused_member(socket) do
    with index when not is_nil(index) <- socket.assigns.focused_member_index,
         member when not is_nil(member) <- Enum.at(socket.assigns.available_members, index) do
      handle_event("select_member", %{"id" => member.id}, socket)
    else
      _ -> {:noreply, socket}
    end
  end

  @spec assign_form(Phoenix.LiveView.Socket.t()) :: Phoenix.LiveView.Socket.t()
  defp assign_form(
         %{
           assigns: %{
             user: user,
             show_password_fields: show_password_fields,
             can_manage_member_linking: can_manage_member_linking,
             can_assign_role: can_assign_role
           }
         } = socket
       ) do
    actor = current_actor(socket)

    form =
      if user do
        # For existing users: admin uses update_user (email + member + role_id); non-admin uses update (email only).
        # Password change uses admin_set_password for both.
        action =
          cond do
            show_password_fields -> :admin_set_password
            can_manage_member_linking or can_assign_role -> :update_user
            true -> :update
          end

        form =
          AshPhoenix.Form.for_update(user, action, domain: Mv.Accounts, as: "user", actor: actor)

        # Ensure role_id is always included on submit when role dropdown is shown (AshPhoenix.Form
        # only submits keys in touched_forms; marking as touched avoids role change being dropped).
        if can_assign_role and action == :update_user do
          AshPhoenix.Form.touch(form, [:role_id])
        else
          form
        end
      else
        # For new users, use password registration if password fields are shown
        action = if show_password_fields, do: :register_with_password, else: :create_user

        AshPhoenix.Form.for_create(Mv.Accounts.User, action,
          domain: Mv.Accounts,
          as: "user",
          actor: actor
        )
      end

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

  @spec return_path(String.t(), Mv.Accounts.User.t() | nil) :: String.t()
  defp return_path("index", _user), do: ~p"/users"
  defp return_path("show", user), do: ~p"/users/#{user.id}"

  @spec load_initial_members(Phoenix.LiveView.Socket.t()) :: Phoenix.LiveView.Socket.t()
  defp load_initial_members(socket) do
    user = socket.assigns.user
    user_email = if user, do: user.email, else: nil

    members = load_members_for_linking(user_email, "", socket)

    # Dropdown should ALWAYS be hidden initially
    # It will only show when user focuses the input field (show_member_dropdown event)
    socket
    |> assign(available_members: members)
    |> assign(show_member_dropdown: false)
  end

  @spec load_available_members(Phoenix.LiveView.Socket.t(), String.t()) ::
          Phoenix.LiveView.Socket.t()
  defp load_available_members(socket, query) do
    user = socket.assigns.user
    user_email = if user, do: user.email, else: nil

    members = load_members_for_linking(user_email, query, socket)
    assign(socket, available_members: members)
  end

  @spec load_members_for_linking(String.t() | nil, String.t() | nil, Phoenix.LiveView.Socket.t()) ::
          [
            Mv.Membership.Member.t()
          ]
  defp load_members_for_linking(user_email, search_query, socket) do
    user_email_str = if user_email, do: to_string(user_email), else: nil
    search_query_str = if search_query && search_query != "", do: search_query, else: nil

    query =
      Mv.Membership.Member
      |> Ash.Query.for_read(:available_for_linking, %{
        user_email: user_email_str,
        search_query: search_query_str
      })

    actor = current_actor(socket)

    # Early return if no actor (prevents exceptions in unauthenticated tests)
    if is_nil(actor) do
      []
    else
      case Ash.read(query, domain: Mv.Membership, actor: actor) do
        {:ok, members} -> apply_email_filter(members, user_email_str)
        {:error, _} -> []
      end
    end
  end

  @spec apply_email_filter([Mv.Membership.Member.t()], String.t() | nil) ::
          [Mv.Membership.Member.t()]
  defp apply_email_filter(members, nil), do: members

  defp apply_email_filter(members, user_email_str) when is_binary(user_email_str) do
    Mv.Membership.Member.filter_by_email_match(members, user_email_str)
  end

  @spec load_roles(any()) :: [Mv.Authorization.Role.t()]
  defp load_roles(actor) do
    case Authorization.list_roles(actor: actor) do
      {:ok, roles} -> roles
      {:error, _} -> []
    end
  end

  # Extract user-friendly error message from Ash.Error
  @spec extract_error_message(any()) :: String.t()
  defp extract_error_message(%Ash.Error.Invalid{errors: errors}) when is_list(errors) do
    # Take first error and extract message
    case List.first(errors) do
      %{message: message} when is_binary(message) -> message
      %{field: field, message: message} -> "#{field}: #{message}"
      _ -> gettext("Unknown error")
    end
  end

  defp extract_error_message(error) when is_binary(error), do: error
  defp extract_error_message(_), do: gettext("Unknown error")
end
