defmodule Raxol.MCP.ContextTree do
  @moduledoc """
  Assembles a structured context tree from multiple sources.

  The context tree provides a unified view of session state for AI agents.
  Different sources contribute subtrees (model projections, widget tree,
  tools, agents, notifications). Built on demand -- no cached state.

  ## Sources

  - `:model` -- TEA model projections from registered resources
  - `:widgets` -- current widget tree (types, IDs, bounds)
  - `:tools` -- list of available MCP tools
  - `:session` -- session metadata (id, uptime)

  ## Roles

  `filter_for_role/2` restricts the tree based on a role atom:

  - `:full` -- everything (default)
  - `:observer` -- model + widgets + session (no tools)
  - `:operator` -- model + widgets + tools + session
  """

  alias Raxol.MCP.Registry

  @type source :: :model | :widgets | :tools | :session
  @type role :: :full | :observer | :operator
  @type context :: %{
          registry: GenServer.server(),
          session_id: term(),
          view_tree: map() | list() | nil,
          model: map() | nil
        }

  @doc """
  Build a context tree from the given sources.

  The `context` map must include `:registry` and `:session_id`.
  Optional keys: `:view_tree`, `:model`.
  """
  @spec build([source()], context()) :: map()
  def build(sources, context) do
    sources
    |> Enum.reduce(%{}, fn source, acc ->
      Map.put(acc, source, build_source(source, context))
    end)
  end

  @doc """
  Build a full context tree with all sources.
  """
  @spec build_all(context()) :: map()
  def build_all(context) do
    build([:model, :widgets, :tools, :session], context)
  end

  @doc """
  Filter a context tree for a given role.
  """
  @spec filter_for_role(map(), role()) :: map()
  def filter_for_role(tree, :full), do: tree
  def filter_for_role(tree, :observer), do: Map.take(tree, [:model, :widgets, :session])
  def filter_for_role(tree, :operator), do: Map.take(tree, [:model, :widgets, :tools, :session])

  # -- Source builders ---------------------------------------------------------

  defp build_source(:model, context) do
    prefix = "raxol://session/#{context.session_id}/model/"

    context.registry
    |> Registry.list_resources()
    |> Enum.filter(fn r -> String.starts_with?(r.uri, prefix) end)
    |> Enum.reduce(%{}, fn r, acc ->
      key = String.replace_prefix(r.uri, prefix, "")

      case Registry.read_resource(context.registry, r.uri) do
        {:ok, value} -> Map.put(acc, key, value)
        _ -> acc
      end
    end)
  end

  defp build_source(:widgets, %{view_tree: nil}), do: []

  defp build_source(:widgets, %{view_tree: tree}) do
    sanitize_tree(tree)
  end

  defp build_source(:tools, context) do
    Registry.list_tools(context.registry)
  end

  defp build_source(:session, context) do
    %{
      id: context.session_id,
      timestamp: DateTime.utc_now() |> DateTime.to_iso8601()
    }
  end

  # Strip callbacks and non-serializable values from a view tree
  defp sanitize_tree(node) when is_map(node) do
    node
    |> Map.drop([:callback, :on_click, :on_change, :on_submit])
    |> Map.new(fn {k, v} -> {k, sanitize_tree(v)} end)
  end

  defp sanitize_tree(nodes) when is_list(nodes) do
    Enum.map(nodes, &sanitize_tree/1)
  end

  defp sanitize_tree(other), do: other
end
