defmodule CanneryWeb.Components.ContainerTableComponent do
  @moduledoc """
  A component that displays a list of containers
  """
  use CanneryWeb, :live_component
  alias Cannery.{Accounts.User, Ammo, Containers.Container}
  alias CanneryWeb.Components.TableComponent
  alias Ecto.UUID
  alias Phoenix.LiveView.{Rendered, Socket}

  @impl true
  @spec update(
          %{
            required(:id) => UUID.t(),
            required(:current_user) => User.t(),
            optional(:containers) => [Container.t()],
            optional(:range) => Rendered.t(),
            optional(:tag_actions) => Rendered.t(),
            optional(:actions) => Rendered.t(),
            optional(any()) => any()
          },
          Socket.t()
        ) :: {:ok, Socket.t()}
  def update(%{id: _id, containers: _containers, current_user: _current_user} = assigns, socket) do
    socket =
      socket
      |> assign(assigns)
      |> assign_new(:range, fn -> [] end)
      |> assign_new(:tag_actions, fn -> [] end)
      |> assign_new(:actions, fn -> [] end)
      |> display_containers()

    {:ok, socket}
  end

  defp display_containers(
         %{
           assigns: %{
             containers: containers,
             current_user: current_user,
             range: range,
             tag_actions: tag_actions,
             actions: actions
           }
         } = socket
       ) do
    columns =
      [
        %{label: gettext("Name"), key: :name, type: :string},
        %{label: gettext("Description"), key: :desc, type: :string},
        %{label: gettext("Location"), key: :location, type: :string},
        %{label: gettext("Type"), key: :type, type: :string}
      ]
      |> Enum.filter(fn %{key: key, type: type} ->
        # remove columns if all values match defaults
        default_value =
          case type do
            :boolean -> false
            _other_type -> nil
          end

        containers
        |> Enum.any?(fn container ->
          type in [:tags, :actions] or not (container |> Map.get(key) == default_value)
        end)
      end)
      |> Enum.concat([
        %{label: gettext("Packs"), key: :packs, type: :integer},
        %{label: gettext("Rounds"), key: :rounds, type: :integer}
      ])
      |> Enum.concat(
        [
          %{label: gettext("Tags"), key: :tags, type: :tags},
          %{label: gettext("Actions"), key: :actions, sortable: false, type: :actions}
        ]
        |> TableComponent.maybe_compose_columns(
          %{label: gettext("Range"), key: :range},
          range != []
        )
      )

    extra_data = %{
      current_user: current_user,
      range: range,
      tag_actions: tag_actions,
      actions: actions,
      pack_count:
        Ammo.get_grouped_packs_count(current_user,
          containers: containers,
          group_by: :container_id
        ),
      round_count:
        Ammo.get_grouped_round_count(current_user,
          containers: containers,
          group_by: :container_id
        )
    }

    rows =
      containers
      |> Enum.map(fn container ->
        container |> get_row_data_for_container(columns, extra_data)
      end)

    socket
    |> assign(
      columns: columns,
      rows: rows
    )
  end

  @impl true
  def render(assigns) do
    ~H"""
    <div id={@id} class="w-full">
      <.live_component
        module={CanneryWeb.Components.TableComponent}
        id={"table-#{@id}"}
        columns={@columns}
        rows={@rows}
      />
    </div>
    """
  end

  @spec get_row_data_for_container(Container.t(), columns :: [map()], extra_data :: map) :: map()
  defp get_row_data_for_container(container, columns, extra_data) do
    columns
    |> Map.new(fn %{key: key} -> {key, get_value_for_key(key, container, extra_data)} end)
  end

  @spec get_value_for_key(atom(), Container.t(), extra_data :: map) :: any()
  defp get_value_for_key(:name, %{name: container_name} = assigns, _extra_data) do
    {container_name,
     ~H"""
     <div class="flex flex-wrap justify-center items-center">
       <.link navigate={~p"/container/#{@id}"} class="link">
         {@name}
       </.link>
     </div>
     """}
  end

  defp get_value_for_key(:packs, %{id: container_id}, %{pack_count: pack_count}) do
    pack_count |> Map.get(container_id, 0)
  end

  defp get_value_for_key(:rounds, %{id: container_id}, %{round_count: round_count}) do
    round_count |> Map.get(container_id, 0)
  end

  defp get_value_for_key(:range, %{staged: staged} = container, %{range: range}) do
    assigns = %{range: range, container: container}

    {staged,
     ~H"""
     {render_slot(@range, @container)}
     """}
  end

  defp get_value_for_key(:tags, container, %{tag_actions: tag_actions}) do
    assigns = %{tag_actions: tag_actions, container: container}

    tag_names =
      container.tags
      |> Enum.map(fn %{name: name} -> name end)
      |> Enum.sort()
      |> Enum.join(" ")

    {tag_names,
     ~H"""
     <div class="flex flex-wrap justify-center items-center">
       <.simple_tag_card :for={tag <- @container.tags} :if={@container.tags} tag={tag} />

       {render_slot(@tag_actions, @container)}
     </div>
     """}
  end

  defp get_value_for_key(:actions, container, %{actions: actions}) do
    assigns = %{actions: actions, container: container}

    ~H"""
    {render_slot(@actions, @container)}
    """
  end

  defp get_value_for_key(key, container, _extra_data), do: container |> Map.get(key)
end
