defmodule MvWeb.Components.FieldVisibilityDropdownComponent do
  @moduledoc """
  LiveComponent for managing field visibility in the member overview.

  Provides an accessible dropdown menu where users can select/deselect
  which member fields and custom fields are visible in the table.

  ## Props
    - `:all_fields` - List of all available fields
    - `:custom_fields` - List of CustomField resources
    - `:selected_fields` - Map field_name → boolean
    - `:id` - Component ID

  ## Events sent to parent:
    - `{:field_toggled, field, value}`
    - `{:fields_selected, map}`
  """

  use MvWeb, :live_component

  alias MvWeb.Translations.MemberFields

  # ---------------------------------------------------------------------------
  # UPDATE
  # ---------------------------------------------------------------------------

  @impl true
  def update(assigns, socket) do
    socket =
      socket
      |> assign(assigns)
      |> assign_new(:open, fn -> false end)
      |> assign_new(:all_fields, fn -> [] end)
      |> assign_new(:custom_fields, fn -> [] end)
      |> assign_new(:selected_fields, fn -> %{} end)

    {:ok, socket}
  end

  # ---------------------------------------------------------------------------
  # RENDER
  # ---------------------------------------------------------------------------

  @impl true
  def render(assigns) do
    all_fields = assigns.all_fields || []
    custom_fields = assigns.custom_fields || []

    all_items =
      (Enum.map(extract_member_field_keys(all_fields), fn field ->
         %{
           value: field_to_string(field),
           label: format_field_label(field)
         }
       end) ++
         Enum.map(extract_custom_field_keys(all_fields), fn field ->
           %{
             value: field,
             label: format_custom_field_label(field, custom_fields)
           }
         end))
      |> Enum.uniq_by(fn item -> item.value end)

    assigns = assign(assigns, :all_items, all_items)

    # LiveComponents require a static HTML element as root, not a function component
    ~H"""
    <div>
      <.dropdown_menu
        id="field-visibility-menu"
        icon="hero-adjustments-horizontal"
        button_label={gettext("Show/Hide Columns")}
        items={@all_items}
        checkboxes={true}
        selected={@selected_fields}
        open={@open}
        show_select_buttons={true}
        phx_target={@myself}
      />
    </div>
    """
  end

  # ---------------------------------------------------------------------------
  # EVENTS (matching the Core Component API)
  # ---------------------------------------------------------------------------

  @impl true
  def handle_event("toggle_dropdown", _params, socket) do
    {:noreply, assign(socket, :open, !socket.assigns.open)}
  end

  def handle_event("close_dropdown", _params, socket) do
    {:noreply, assign(socket, :open, false)}
  end

  # toggle single item
  def handle_event("select_item", %{"item" => item}, socket) do
    current = Map.get(socket.assigns.selected_fields, item, true)
    updated = Map.put(socket.assigns.selected_fields, item, !current)

    send(self(), {:field_toggled, item, !current})
    {:noreply, assign(socket, :selected_fields, updated)}
  end

  # select all
  def handle_event("select_all", _params, socket) do
    all =
      socket.assigns.all_fields
      |> Enum.map(&field_to_string/1)
      |> Enum.map(&{&1, true})
      |> Enum.into(%{})

    send(self(), {:fields_selected, all})
    {:noreply, assign(socket, :selected_fields, all)}
  end

  # select none
  def handle_event("select_none", _params, socket) do
    none =
      socket.assigns.all_fields
      |> Enum.map(&field_to_string/1)
      |> Enum.map(&{&1, false})
      |> Enum.into(%{})

    send(self(), {:fields_selected, none})
    {:noreply, assign(socket, :selected_fields, none)}
  end

  # ---------------------------------------------------------------------------
  # HELPERS (with defensive nil guards)
  # ---------------------------------------------------------------------------

  defp extract_member_field_keys(nil), do: []

  defp extract_member_field_keys(fields) do
    prefix = Mv.Constants.custom_field_prefix()

    Enum.filter(fields, fn field ->
      is_atom(field) ||
        (is_binary(field) && not String.starts_with?(field, prefix))
    end)
  end

  defp extract_custom_field_keys(nil), do: []

  defp extract_custom_field_keys(fields) do
    prefix = Mv.Constants.custom_field_prefix()

    Enum.filter(fields, fn field ->
      is_binary(field) && String.starts_with?(field, prefix)
    end)
  end

  defp field_to_string(field) when is_atom(field), do: Atom.to_string(field)
  defp field_to_string(field) when is_binary(field), do: field

  defp format_field_label(field) when is_atom(field) do
    MemberFields.label(field)
  end

  defp format_field_label(field) when is_binary(field) do
    case safe_to_existing_atom(field) do
      {:ok, atom} -> MemberFields.label(atom)
      :error -> fallback_label(field)
    end
  end

  defp safe_to_existing_atom(string) do
    {:ok, String.to_existing_atom(string)}
  rescue
    ArgumentError -> :error
  end

  defp fallback_label(field) do
    field
    |> String.replace("_", " ")
    |> String.split()
    |> Enum.map_join(" ", &String.capitalize/1)
  end

  defp format_custom_field_label(field_string, custom_fields) do
    id = String.trim_leading(field_string, Mv.Constants.custom_field_prefix())
    find_custom_field_name(id, field_string, custom_fields)
  end

  defp find_custom_field_name("", field_string, _custom_fields), do: field_string

  defp find_custom_field_name(id, _field_string, custom_fields) do
    case Enum.find(custom_fields, fn cf -> to_string(cf.id) == id end) do
      nil -> gettext("Custom Field %{id}", id: id)
      custom_field -> custom_field.name
    end
  end
end
