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

  import Phoenix.Component, only: [assign: 3]

  alias BDS.{Media, Posts}
  alias BDS.MapUtils
  alias BDS.Media.Media, as: MediaRecord
  alias BDS.Posts.Post
  alias BDS.UI.Workbench

  @doc """
  Apply a CLI entity change payload to the shell socket. `reload_fun` is
  called with `(socket, workbench)` to refresh derived data.
  """
  @spec apply_entity_change(Phoenix.LiveView.Socket.t(), map(), (Phoenix.LiveView.Socket.t(),
                                                                 map() ->
                                                                   Phoenix.LiveView.Socket.t())) ::
          Phoenix.LiveView.Socket.t()
  def apply_entity_change(socket, payload, reload_fun) do
    entity = MapUtils.attr(payload, :entity) || MapUtils.attr(payload, :entity_type)

    entity_id =
      MapUtils.attr(payload, :entity_id) || Map.get(payload, :entityId) ||
        Map.get(payload, "entityId")

    action = normalize_action(MapUtils.attr(payload, :action))

    if is_binary(entity) and entity != "" and is_binary(entity_id) and entity_id != "" and
         action in [:created, :updated, :deleted] do
      {socket, workbench} = maybe_close_deleted_tab(socket, entity, entity_id, action)

      socket
      |> maybe_refresh_tab_meta(entity, entity_id, action)
      |> reload_fun.(workbench)
    else
      socket
    end
  end

  defp maybe_close_deleted_tab(socket, "post", post_id, :deleted) do
    workbench = Workbench.close_tab(socket.assigns.workbench, :post, post_id)

    socket =
      socket
      |> assign(:workbench, workbench)
      |> assign(:shell_overlay, nil)
      |> assign(:tab_meta, Map.delete(socket.assigns.tab_meta, {:post, post_id}))

    {socket, workbench}
  end

  defp maybe_close_deleted_tab(socket, "media", media_id, :deleted) do
    workbench = Workbench.close_tab(socket.assigns.workbench, :media, media_id)

    socket =
      socket
      |> assign(:workbench, workbench)
      |> assign(:shell_overlay, nil)
      |> assign(:tab_meta, Map.delete(socket.assigns.tab_meta, {:media, media_id}))

    {socket, workbench}
  end

  defp maybe_close_deleted_tab(socket, _entity, _entity_id, _action),
    do: {socket, socket.assigns.workbench}

  defp maybe_refresh_tab_meta(socket, "post", post_id, action)
       when action in [:created, :updated] do
    maybe_put_tab_meta(socket, :post, post_id, fn ->
      case Posts.get_post(post_id) do
        %Post{} = post ->
          %{title: post.title || post.slug || post.id, subtitle: Atom.to_string(post.status)}

        _other ->
          nil
      end
    end)
  end

  defp maybe_refresh_tab_meta(socket, "media", media_id, action)
       when action in [:created, :updated] do
    maybe_put_tab_meta(socket, :media, media_id, fn ->
      case Media.get_media(media_id) do
        %MediaRecord{} = media ->
          %{
            title: media.title || media.filename || media.id,
            subtitle: media.filename || media.mime_type || "media"
          }

        _other ->
          nil
      end
    end)
  end

  defp maybe_refresh_tab_meta(socket, _entity, _entity_id, _action), do: socket

  defp maybe_put_tab_meta(socket, route, entity_id, meta_fun) do
    key = {route, entity_id}

    if tab_present?(socket.assigns.workbench, key) or Map.has_key?(socket.assigns.tab_meta, key) do
      case meta_fun.() do
        %{} = fresh_meta ->
          updated_meta =
            Map.update(socket.assigns.tab_meta, key, fresh_meta, &Map.merge(&1, fresh_meta))

          assign(socket, :tab_meta, updated_meta)

        _other ->
          socket
      end
    else
      socket
    end
  end

  defp tab_present?(%{tabs: tabs}, {route, entity_id}) do
    Enum.any?(tabs, &(&1.type == route and &1.id == entity_id))
  end

  defp normalize_action(action) when action in [:created, :updated, :deleted], do: action

  defp normalize_action(action) do
    action
    |> to_string()
    |> String.downcase()
    |> case do
      "created" -> :created
      "updated" -> :updated
      "deleted" -> :deleted
      _other -> :unknown
    end
  end
end
