"""Server state container for biff MCP tools.

All shared state lives in a frozen dataclass. Tool closures capture it
directly during registration; it is also available as the lifespan context.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from pathlib import Path

from biff.models import BiffConfig
from biff.nats_relay import NatsRelay
from biff.relay import DormantRelay, LocalRelay, Relay
from biff.server.activity import ActivityTracker
from biff.server.display_queue import DisplayQueue
from biff.tty import build_session_key, generate_tty, get_hostname, get_pwd


@dataclass(frozen=True)
class ServerState:
    """Immutable container for all server-wide shared state."""

    config: BiffConfig
    relay: Relay
    activity: ActivityTracker = field(default_factory=ActivityTracker)
    display_queue: DisplayQueue = field(default_factory=DisplayQueue)
    tty: str = ""
    hostname: str = ""
    pwd: str = ""
    unread_path: Path | None = None
    owns_relay: bool = True
    dormant: bool = False
    repo_root: Path | None = None
    org_repos: frozenset[str] = field(default_factory=lambda: frozenset[str]())

    @property
    def session_key(self) -> str:
        """Composite key ``{user}:{tty}`` for this server instance."""
        return build_session_key(self.config.user, self.tty)

    @property
    def visible_repos(self) -> frozenset[str]:
        """All repos visible to this session: self + explicit peers + org-discovered.

        Merges ``config.visible_repos`` (repo_name + explicit peers) with
        org-discovered repos from ``NatsRelay.discover_repos_for_org()``
        (DES-034).
        """
        if not self.org_repos:
            return self.config.visible_repos
        return self.config.visible_repos | self.org_repos


def create_state(
    config: BiffConfig,
    data_dir: Path,
    *,
    relay: Relay | None = None,
    unread_path: Path | None = None,
    tty: str | None = None,
    hostname: str | None = None,
    pwd: str | None = None,
    dormant: bool = False,
    repo_root: Path | None = None,
    org_repos: frozenset[str] | None = None,
) -> ServerState:
    """Create a ``ServerState`` from config and data directory.

    Relay selection (in priority order):

    1. An explicit *relay* always wins (used by tests).
    2. When *dormant* is ``True``, uses
       :class:`~biff.relay.DormantRelay`.
    3. ``config.relay_url`` selects
       :class:`~biff.nats_relay.NatsRelay`.
    4. Otherwise :class:`~biff.relay.LocalRelay`.

    Runtime identity (tty, hostname, pwd) is auto-generated when not
    provided — each server instance gets a unique session key.
    """
    owns_relay = relay is None
    if relay is None:
        if dormant:
            relay = DormantRelay()
        elif config.relay_url:
            relay = NatsRelay(
                url=config.relay_url,
                auth=config.relay_auth,
                name=f"biff-{config.repo_name}-{config.user}",
                repo_name=config.repo_name,
            )
        else:
            relay = LocalRelay(data_dir=data_dir)
    return ServerState(
        config=config,
        relay=relay,
        tty=tty or generate_tty(),
        hostname=hostname or get_hostname(),
        pwd=pwd or get_pwd(),
        unread_path=unread_path,
        owns_relay=owns_relay,
        dormant=dormant,
        repo_root=repo_root,
        org_repos=org_repos or frozenset(),
    )
