lib/phoenix_llm_chat/workspace.ex

defmodule PhoenixLLMChat.Workspace do
  @moduledoc """
  Multi-tab session management for chat workspace.

  Handles:
  - Creating new sessions
  - Switching between tabs
  - Renaming sessions
  - Deleting sessions
  - Loading/saving session state to storage backend
  """

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

  def hydrate_workspace(socket, session_store_impl) do
    case apply(session_store_impl, :list, []) do
      {:ok, session_ids} ->
        sessions_by_id =
          Enum.reduce(session_ids, %{}, fn sid, acc ->
            case apply(session_store_impl, :load, [sid]) do
              {:ok, data} -> Map.put(acc, sid, data)
              {:error, _} -> acc
            end
          end)

        socket
        |> assign(:sessions_by_id, sessions_by_id)
        |> assign(:open_session_ids, session_ids)

      {:error, reason} ->
        Logger.error("Failed to hydrate workspace: #{inspect(reason)}")
        socket
        |> assign(:sessions_by_id, %{})
        |> assign(:open_session_ids, [])
    end
  end

  def create_session(socket, session_id, initial_data \\ %{}) do
    sessions_by_id = socket.assigns[:sessions_by_id] || %{}
    open_session_ids = socket.assigns[:open_session_ids] || []

    if Map.has_key?(sessions_by_id, session_id) do
      socket
    else
      new_session_data = %{"messages" => [], "metadata" => %{}} |> Map.merge(initial_data)

      socket
      |> assign(:sessions_by_id, Map.put(sessions_by_id, session_id, new_session_data))
      |> assign(:open_session_ids, [session_id | open_session_ids])
    end
  end

  def open_session(socket, session_id) do
    sessions_by_id = socket.assigns[:sessions_by_id] || %{}

    case Map.fetch(sessions_by_id, session_id) do
      {:ok, session_data} ->
        socket
        |> assign(:active_session_id, session_id)
        |> assign(:messages, session_data["messages"] || [])

      :error ->
        Logger.warning("Session not found: #{session_id}")
        socket
    end
  end

  def switch_session(socket, session_id) do
    # Save current session first, then switch
    socket
    |> maybe_save_active_session()
    |> open_session(session_id)
    |> push_workspace_state()
  end

  def rename_session(socket, session_id, new_name) do
    sessions_by_id = socket.assigns[:sessions_by_id] || %{}

    updated_session = sessions_by_id
      |> Map.get(session_id, %{})
      |> Map.put("name", new_name)

    socket
    |> assign(:sessions_by_id, Map.put(sessions_by_id, session_id, updated_session))
    |> push_workspace_state()
  end

  def delete_session(socket, session_id, session_store_impl) do
    sessions_by_id = socket.assigns[:sessions_by_id] || %{}
    open_session_ids = socket.assigns[:open_session_ids] || []
    active_session_id = socket.assigns[:active_session_id]

    apply(session_store_impl, :delete, [session_id])

    updated_socket = socket
      |> assign(:sessions_by_id, Map.delete(sessions_by_id, session_id))
      |> assign(:open_session_ids, List.delete(open_session_ids, session_id))

    # Switch to another session if we deleted the active one
    if active_session_id == session_id do
      case List.first(updated_socket.assigns[:open_session_ids]) do
        nil -> updated_socket
        new_active_id -> open_session(updated_socket, new_active_id)
      end
    else
      updated_socket
    end
    |> push_workspace_state()
  end

  def push_workspace_state(socket) do
    sessions_list = build_sessions_list(socket.assigns[:sessions_by_id] || %{})

    Phoenix.LiveView.push_event(socket, "workspace_state", %{
      sessions: sessions_list,
      active_session_id: socket.assigns[:active_session_id]
    })
  end

  defp maybe_save_active_session(socket) do
    case socket.assigns[:active_session_id] do
      nil ->
        socket

      session_id ->
        sessions_by_id = socket.assigns[:sessions_by_id] || %{}
        current_session = Map.get(sessions_by_id, session_id, %{})

        updated_session = current_session
          |> Map.put("messages", socket.assigns[:messages] || [])
          |> Map.put("updated_at", DateTime.utc_now())

        assign(socket, :sessions_by_id, Map.put(sessions_by_id, session_id, updated_session))
    end
  end

  defp build_sessions_list(sessions_by_id) do
    Enum.map(sessions_by_id, fn {id, data} ->
      %{
        id: id,
        name: data["name"] || "Session",
        message_count: length(data["messages"] || []),
        updated_at: data["updated_at"]
      }
    end)
  end
end