defmodule BDS.Desktop.ShellLive.SidebarState do
  @moduledoc false

  def merge_ui_state(socket, view_id, sidebar_data) do
    filters = Map.get(sidebar_data, :filters)

    if is_map(filters) and Map.get(filters, :enabled) do
      panel_state = filter_panel_state(socket, view_id)

      Map.put(
        sidebar_data,
        :filters,
        Map.merge(filters, %{
          filter_panel_visible: panel_state.visible,
          archive_collapsed: panel_state.archive_collapsed,
          tags_collapsed: panel_state.tags_collapsed,
          categories_collapsed: panel_state.categories_collapsed,
          expanded_year: panel_state.expanded_year
        })
      )
    else
      sidebar_data
    end
  end

  def put_filter_panel_state(socket, updater) do
    view_id = Atom.to_string(socket.assigns.workbench.active_view)
    state = socket |> filter_panel_state(view_id) |> updater.()

    Phoenix.Component.assign(
      socket,
      :sidebar_filter_panels,
      Map.put(socket.assigns.sidebar_filter_panels, view_id, state)
    )
  end

  def current_filters(socket, view_id) do
    socket.assigns.sidebar_filters_by_view
    |> Map.get(view_id, %{})
    |> normalize_filters(socket.assigns[:sidebar_data])
  end

  def put_filters(socket, updater) do
    view_id = Atom.to_string(socket.assigns.workbench.active_view)

    filters =
      current_filters(socket, view_id)
      |> updater.()
      |> normalize_filters(socket.assigns.sidebar_data)

    Phoenix.Component.assign(
      socket,
      :sidebar_filters_by_view,
      Map.put(socket.assigns.sidebar_filters_by_view, view_id, filters)
    )
  end

  def toggle_filter_value(filters, key, value) do
    values = Map.get(filters, key, [])

    next_values =
      if value in values do
        List.delete(values, value)
      else
        values ++ [value]
      end

    Map.put(filters, key, next_values)
  end

  def normalize_filter_string(nil), do: nil

  def normalize_filter_string(value) do
    value
    |> to_string()
    |> String.trim()
    |> case do
      "" -> nil
      trimmed -> trimmed
    end
  end

  def parse_optional_integer(nil), do: nil
  def parse_optional_integer(value) when is_integer(value), do: value

  def parse_optional_integer(value) when is_binary(value) do
    case Integer.parse(value) do
      {parsed, _rest} -> parsed
      :error -> nil
    end
  end

  def sidebar_page_size(nil), do: 500

  def sidebar_page_size(sidebar_data) do
    sidebar_data
    |> Map.get(:filters, %{})
    |> Map.get(:max_items, 500)
  end

  defp filter_panel_state(socket, view_id) do
    default_state = default_filter_panel_state()

    case Map.get(socket.assigns.sidebar_filter_panels, view_id) do
      state when is_map(state) -> Map.merge(default_state, state)
      visible when is_boolean(visible) -> Map.put(default_state, :visible, visible)
      _other -> default_state
    end
  end

  defp default_filter_panel_state do
    %{
      visible: false,
      archive_collapsed: true,
      tags_collapsed: true,
      categories_collapsed: true,
      expanded_year: nil
    }
  end

  defp normalize_filters(filters, sidebar_data) do
    max_items = sidebar_page_size(sidebar_data)

    %{
      search: normalize_filter_string(Map.get(filters, :search)),
      year: Map.get(filters, :year),
      month: Map.get(filters, :month),
      tags: Map.get(filters, :tags, []),
      categories: Map.get(filters, :categories, []),
      display_limit: max(Map.get(filters, :display_limit, max_items) || max_items, max_items)
    }
  end
end
