defmodule MvWeb.UserLive.Index do
  @moduledoc """
  LiveView for displaying and managing the user list.

  ## Features
  - List all users with email and linked member
  - Sort users by email (default)
  - Delete users
  - Navigate to user details and edit forms
  - Bulk selection for future batch operations

  ## Relationships
  Displays linked member information when a user is connected to a member account.

  ## Events
  - `delete` - Remove a user from the database
  - `select_user` - Toggle individual user selection
  - `select_all` - Toggle selection of all visible users

  ## Security
  User deletion requires admin permissions (enforced by Ash policies).
  """
  use MvWeb, :live_view
  import MvWeb.TableComponents

  import MvWeb.LiveHelpers, only: [current_actor: 1]

  require Ash.Query
  import MvWeb.ErrorHelpers, only: [format_ash_error: 1]

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

    users =
      Mv.Accounts.User
      |> Ash.Query.filter(email != ^Mv.Helpers.SystemActor.system_user_email())
      |> Ash.read!(domain: Mv.Accounts, load: [:member, :role], actor: actor)

    sorted = Enum.sort_by(users, & &1.email)

    {:ok,
     socket
     |> assign(:page_title, gettext("Listing Users"))
     |> assign(:sort_field, :email)
     |> assign(:sort_order, :asc)
     |> assign(:users, sorted)
     |> assign(:selected_users, [])}
  end

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

    case Ash.get(Mv.Accounts.User, id, domain: Mv.Accounts, actor: actor) do
      {:ok, user} ->
        case Ash.destroy(user, domain: Mv.Accounts, actor: actor) do
          :ok ->
            updated_users = Enum.reject(socket.assigns.users, &(&1.id == id))

            {:noreply,
             socket
             |> assign(:users, updated_users)
             |> put_flash(:info, gettext("User deleted successfully"))}

          {: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

      {:error, %Ash.Error.Query.NotFound{}} ->
        {:noreply, put_flash(socket, :error, gettext("User not found"))}

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

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

  # Selects one user in the list of users
  @impl true
  def handle_event("select_user", %{"id" => id}, socket) do
    # Normalize ID to string for consistent comparison
    id_str = to_string(id)

    selected =
      if id_str in socket.assigns.selected_users do
        List.delete(socket.assigns.selected_users, id_str)
      else
        [id_str | socket.assigns.selected_users]
      end

    {:noreply, assign(socket, :selected_users, selected)}
  end

  # Sorts the list of users according to a field, when you click on the column header
  @impl true
  def handle_event("sort", %{"field" => field_str}, socket) do
    users = socket.assigns.users
    field = String.to_existing_atom(field_str)

    new_order =
      if socket.assigns.sort_field == field do
        toggle_order(socket.assigns.sort_order)
      else
        :asc
      end

    sorted_users =
      users
      |> Enum.sort_by(&Map.get(&1, field), sort_fun(new_order))

    {:noreply,
     socket
     |> assign(:sort_field, field)
     |> assign(:sort_order, new_order)
     |> assign(:users, sorted_users)}
  end

  # Selects all users in the list of users
  @impl true
  def handle_event("select_all", _params, socket) do
    users = socket.assigns.users

    # Normalize IDs to strings for consistent comparison
    all_ids = Enum.map(users, &to_string(&1.id))

    selected =
      if Enum.sort(socket.assigns.selected_users) == Enum.sort(all_ids) do
        []
      else
        all_ids
      end

    {:noreply, assign(socket, :selected_users, selected)}
  end

  defp toggle_order(:asc), do: :desc
  defp toggle_order(:desc), do: :asc
  defp sort_fun(:asc), do: &<=/2
  defp sort_fun(:desc), do: &>=/2
end
