defmodule MusicLibraryWeb.CollectionLive.Index do
  use MusicLibraryWeb, :live_view

  import MusicLibraryWeb.Components.BarcodeScanner, only: [barcode_icon: 1]
  import MusicLibraryWeb.Components.Pagination
  import MusicLibraryWeb.LiveHelpers.Params
  import MusicLibraryWeb.RecordComponents

  alias MusicLibrary.Collection
  alias MusicLibrary.Records

  alias MusicLibraryWeb.ErrorMessages

  @default_records_list_params %{
    query: "",
    page: 1,
    page_size: 72,
    order: :purchase
  }

  @impl true
  def render(assigns) do
    ~H"""
    <Layouts.app
      flash={@flash}
      current_section={@current_section}
      socket={@socket}
      toasts_sync={assigns[:toasts_sync]}
    >
      <header class="mb-2 gap-6">
        <div class="my-2 flex items-center justify-between gap-6">
          <.search_form query={@record_list_params.query} />
          <.button_group>
            <.button
              variant="solid"
              size="sm"
              patch={~p"/collection/import"}
            >
              <.icon name="hero-plus" class="icon" aria-hidden="true" data-slot="icon" />
              {gettext("Add")}
            </.button>
            <.button
              variant="solid"
              size="sm"
              patch={~p"/collection/scan"}
            >
              <.barcode_icon class="icon fill-current" />
              {gettext("Scan")}
            </.button>
          </.button_group>
        </div>
      </header>

      <div class="mt-6 flex items-end justify-between gap-6">
        <.button_group>
          <.button
            patch={order_path(@record_list_params, :purchase)}
            size="sm"
            class={[
              @record_list_params.order == :purchase && "bg-zinc-100! dark:bg-zinc-700!"
            ]}
          >
            <.icon
              name="hero-banknotes-solid"
              class="icon"
              aria-hidden="true"
              data-slot="icon"
            />
            <span class="sr-only sm:not-sr-only">{gettext("Purchase")}</span>
          </.button>
          <.button
            patch={order_path(@record_list_params, :alphabetical)}
            size="sm"
            class={[
              @record_list_params.order == :alphabetical && "bg-zinc-100! dark:bg-zinc-700!"
            ]}
          >
            <.icon name="hero-user-solid" class="icon" aria-hidden="true" data-slot="icon" />
            <span class="sr-only sm:not-sr-only">{gettext("A->Z")}</span>
          </.button>
          <.button
            patch={order_path(@record_list_params, :release)}
            size="sm"
            class={[
              @record_list_params.order == :release && "bg-zinc-100! dark:bg-zinc-700!"
            ]}
          >
            <.icon name="hero-calendar-days" class="icon" aria-hidden="true" data-slot="icon" />
            <span class="sr-only sm:not-sr-only">{gettext("Release")}</span>
          </.button>
        </.button_group>

        <.button_group>
          <.button
            phx-click="set_display"
            phx-value-mode="grid"
            size="sm"
            class={[
              @display == :grid && "bg-zinc-100! dark:bg-zinc-700!"
            ]}
          >
            <.icon
              name="hero-squares-2x2"
              class="icon"
              aria-hidden="true"
              data-slot="icon"
            />
            <span class="sr-only sm:not-sr-only">
              {gettext("Grid")}
            </span>
          </.button>
          <.button
            phx-click="set_display"
            phx-value-mode="list"
            size="sm"
            class={[
              @display == :list && "bg-zinc-100! dark:bg-zinc-700!"
            ]}
          >
            <.icon name="hero-list-bullet" class="icon" aria-hidden="true" data-slot="icon" />
            <span class="sr-only sm:not-sr-only">
              {gettext("List")}
            </span>
          </.button>
        </.button_group>
      </div>

      <.record_grid
        :if={@display == :grid}
        id="collection"
        records={@streams.records}
        record_show_path={fn record -> ~p"/collection/#{record}" end}
        record_edit_path={fn record -> ~p"/collection/#{record}/edit" end}
        display_artist_names
        density={:high}
      />

      <.record_list
        :if={@display == :list}
        records={@streams.records}
        record_show_path={fn record -> ~p"/collection/#{record}" end}
        record_edit_path={fn record -> ~p"/collection/#{record}/edit" end}
      />

      <.structured_modal
        :if={@live_action == :edit}
        id="record-modal"
        on_close={JS.patch(back_path(@record_list_params))}
      >
        <.live_component
          module={MusicLibraryWeb.Components.RecordForm}
          id={@record.id}
          action={@live_action}
          show_purchased_at={true}
          record={@record}
          patch={back_path(@record_list_params)}
        />
      </.structured_modal>

      <.structured_modal
        :if={@live_action == :import}
        id="record-modal"
        on_close={JS.patch(back_path(@record_list_params))}
      >
        <.live_component
          module={MusicLibraryWeb.Components.AddRecord}
          id={:search}
          title={@page_title}
          action={@live_action}
          record={@record}
          patch={back_path(@record_list_params)}
          initial_query={@import_query}
          icon_name="hero-plus"
        />
      </.structured_modal>

      <.structured_modal
        :if={@live_action == :barcode_scan}
        id="barcode-scanner-modal"
        on_close={JS.patch(back_path(@record_list_params))}
      >
        <.live_component
          module={MusicLibraryWeb.Components.BarcodeScanner}
          id={:barcode_scanner}
          title={@page_title}
          action={@live_action}
          patch={back_path(@record_list_params)}
        />
      </.structured_modal>

      <.pagination id={:bottom_pagination} pagination_params={@record_list_params} />
    </Layouts.app>
    """
  end

  @impl true
  def mount(_params, _session, socket) do
    {:ok,
     socket
     |> assign(:current_section, :collection)
     |> assign(:import_query, "")
     |> assign(:display, :grid)}
  end

  @impl true
  def handle_params(params, _url, socket) do
    {:noreply, apply_action(socket, socket.assigns.live_action, params)}
  end

  defp apply_action(socket, :import, params) do
    import_query = params["import_query"] || ""

    socket
    |> apply_fallback_index(params, :records, &apply_action/3)
    |> assign(:page_title, gettext("Add new Record · Collection"))
    |> assign(:import_query, import_query)
    |> assign(:record, nil)
  end

  defp apply_action(socket, :barcode_scan, params) do
    socket
    |> apply_fallback_index(params, :records, &apply_action/3)
    |> assign(:page_title, gettext("Scan barcodes · Collection"))
    |> assign(:record, nil)
  end

  defp apply_action(socket, :edit, %{"id" => id} = params) do
    record = Records.get_record!(id)

    socket
    |> apply_fallback_index(params, :records, &apply_action/3)
    |> assign(:page_title, page_title(:edit, record))
    |> assign(:record, record)
  end

  defp apply_action(socket, :index, params) do
    query = params["query"] || ""
    order = parse_order(params["order"] || "purchase", [:purchase, :alphabetical, :release])
    total_records = Collection.search_records_count(query)

    record_list_params =
      @default_records_list_params
      |> merge_query(query)
      |> merge_order(order)
      |> merge_pagination(params, total_records)

    load_and_assign_records(socket, record_list_params)
  end

  @impl true
  def handle_info({MusicLibraryWeb.Components.RecordForm, {:saved, _record}}, socket) do
    {:noreply, load_and_assign_records(socket, socket.assigns.record_list_params)}
  end

  @impl true
  def handle_event("delete", %{"id" => id}, socket) do
    record = Records.get_record!(id)
    {:ok, _} = Records.delete_record(record)

    {:noreply, stream_delete(socket, :records, record)}
  end

  def handle_event("search", %{"query" => query}, socket) do
    qs =
      @default_records_list_params
      |> Map.put(:query, query)
      |> Map.take([:query, :page, :page_size])

    {:noreply, push_patch(socket, to: ~p"/collection?#{qs}")}
  end

  def handle_event("import", %{"id" => musicbrainz_id, "format" => format}, socket) do
    current_time = DateTime.utc_now()

    case Records.import_from_musicbrainz_release_group(musicbrainz_id,
           format: format,
           purchased_at: current_time
         ) do
      {:ok, record} ->
        {:noreply,
         socket
         |> put_toast(:info, gettext("Record imported successfully"))
         |> push_navigate(to: ~p"/collection/#{record.id}")}

      {:error, reason} ->
        {:noreply,
         socket
         |> put_toast(
           :error,
           gettext("Error importing record") <> ": " <> ErrorMessages.friendly_message(reason)
         )
         |> push_patch(to: ~p"/collection")}
    end
  end

  def handle_event("set_display", %{"mode" => mode}, socket) do
    mode = parse_mode(mode)

    {:noreply,
     socket
     |> assign(:display, mode)
     |> load_and_assign_records(socket.assigns.record_list_params)}
  end

  defp load_and_assign_records(socket, record_list_params) do
    offset = page_to_offset(record_list_params.page, record_list_params.page_size)

    opts = [
      limit: record_list_params.page_size,
      offset: offset,
      order: record_list_params.order
    ]

    records =
      Collection.search_records(record_list_params.query, opts)

    socket
    |> assign(:page_title, gettext("Collection"))
    |> assign(:record, nil)
    |> assign(:record_list_params, record_list_params)
    |> stream(:records, records, reset: true)
  end

  defp order_path(record_list_params, order) do
    qs =
      record_list_params
      |> Map.take([:query])
      |> Map.put(:order, order)
      |> Enum.filter(fn {_, v} -> v not in ["", nil] end)

    ~p"/collection?#{qs}"
  end

  defp back_path(record_list_params) do
    qs =
      record_list_params
      |> Map.take([:query, :page, :page_size, :order])
      |> Enum.filter(fn {_, v} -> v not in ["", nil] end)

    ~p"/collection?#{qs}"
  end

  defp page_title(action, record) do
    Enum.join(
      [
        Records.Record.artist_names(record),
        "-",
        record.title,
        "·",
        title_segment(action),
        "·",
        gettext("Collection")
      ],
      " "
    )
  end

  defp title_segment(:edit), do: gettext("Edit")
end
