import { AbracadabraClient, AbracadabraProvider, DocumentMeta, ServerInfo } from "@abraca/dabra";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import * as Y from "yjs";

//#region packages/mcp/src/server.d.ts
/**
 * Controls when the agent reacts to incoming chat:
 * - `all`          — respond to every message in every visible channel (legacy)
 * - `mention`      — group chats require `@<alias>`; DMs always respond
 * - `task`         — ignore chat entirely; only respond to ai:task awareness events
 * - `mention+task` — group chats require mention OR ai:task; DMs always respond (default)
 */
type TriggerMode = "all" | "mention" | "task" | "mention+task";
interface MCPServerConfig {
  url: string;
  agentName?: string;
  agentColor?: string;
  inviteCode?: string;
  keyFile?: string;
  triggerMode?: TriggerMode;
  /** Aliases matched in `@<alias>` tokens. Defaults to `[agentName]`. */
  mentionAliases?: string[];
}
declare class AbracadabraMCPServer {
  readonly config: MCPServerConfig;
  readonly client: AbracadabraClient;
  private _serverInfo;
  private _rootDocId;
  private _spaces;
  private _activeConnection;
  private _spaceConnections;
  private childCache;
  private evictionTimer;
  private heartbeatTimer;
  private _mcpServerRef;
  private _serverRef;
  private _handledTaskIds;
  private _userId;
  private _statusClearTimer;
  private _typingInterval;
  private _lastChatChannel;
  private _activeTurnChannel;
  private _signFn;
  /** Rolling buffer of the last N tool calls in the current turn, surfaced via awareness. */
  private _toolHistory;
  private static readonly TOOL_HISTORY_MAX;
  /** De-dupes concurrent connection heals across parallel tool calls. */
  private _reconnecting;
  private _inboxStarted;
  private _inboxDocId;
  private _inboxDoc;
  private _inboxProvider;
  private _inboxDisposers;
  private _inboxInitialized;
  private _seenInboxIds;
  /** Shared message-id dedupe so the inbox path and `_handleStatelessChat`
   *  (subscribed-doc broadcast) never dispatch the same message twice. */
  private _dispatchedMessageIds;
  private static readonly DEDUPE_MAX;
  constructor(config: MCPServerConfig);
  get agentName(): string;
  get agentColor(): string;
  get triggerMode(): TriggerMode;
  get mentionAliases(): string[];
  get serverInfo(): ServerInfo | null;
  get rootDocId(): string | null;
  /**
   * Spaces visible to the caller — direct children of the server root with
   * `kind === "space"`. Populated by {@link connect}.
   */
  get spaces(): DocumentMeta[];
  get rootDocument(): Y.Doc | null;
  get rootYProvider(): AbracadabraProvider | null;
  get userId(): string | null;
  /** Start the server: authenticate with Ed25519 key, discover spaces/root doc, connect provider. */
  connect(): Promise<void>;
  /** Connect to a space's root doc and cache it. Sets it as the active connection. */
  private _connectToSpace;
  /**
   * Switch the active space to the given doc ID.
   * Clears the child provider cache (children belong to the previous space).
   */
  switchSpace(docId: string): Promise<void>;
  /**
   * Make the space that OWNS `targetId` the active connection before a tree
   * read/write, so the op targets the right space's `doc-tree` map.
   *
   * Without this, every tree tool used `getTreeMap()` (the single active
   * space) and treated `rootId`/`parentId` as a mere filter — so reading a
   * non-active space returned stale/empty trees, and *writing* to one
   * silently orphaned the entry into the active space's map (a real
   * data-loss path: docs built "in Ideas" while Development was active
   * landed in Development under a non-node parent and never reached Ideas).
   *
   * Resolution order:
   *  - falsy or already the active root → no-op.
   *  - a known Space id → connect/activate it (cheap; `_spaceConnections`
   *    caches the provider so re-activation is free).
   *  - present in the active space's tree → no-op (a normal child doc).
   *  - present in an already-connected *other* space's tree → activate that.
   *  - otherwise unresolved → returns `false`; callers decide (reads report
   *    not-found/empty, writes refuse rather than orphan).
   *
   * Switching uses {@link _connectToSpace} (not {@link switchSpace}) so the
   * child-content provider cache survives — content docs are keyed by global
   * guid, independent of which space is active.
   */
  /**
   * Heal the active connection before a tool op so a dropped/idle WebSocket or
   * an expired JWT doesn't surface as a 15s sync timeout ("MCP not responding").
   * Refreshes the token, lets the SDK's own auto-reconnect finish if it's mid-
   * flight, and rebuilds the space connection from scratch if the socket is
   * truly dead. De-duped so parallel tool calls heal once. Best-effort: never
   * throws — a failed heal falls through to the normal (possibly erroring) op.
   */
  /** Whether a provider's shared WebSocket is currently connected. Isolated in
   *  its own scope so repeated checks don't trip TS control-flow narrowing on
   *  the live `connectionStatus` getter. */
  private _wsConnected;
  ensureConnected(): Promise<void>;
  /**
   * Bring one provider's socket back to a synced state WITHOUT swapping its
   * Y.Doc. First let the SDK's own auto-reconnect finish if it's mid-flight;
   * if the socket is still dead, force a reconnect on the same doc. Because the
   * document is never replaced, every observer attached to it (root awareness,
   * tree reads, the inbox `entries` array, the stateless chat listener) keeps
   * firing — no re-attach needed. Best-effort: logs but never throws.
   */
  private _healProvider;
  ensureSpaceActive(targetId: string | null | undefined): Promise<boolean>;
  /** Get the root doc-tree Y.Map of the active space. */
  getTreeMap(): Y.Map<any> | null;
  /**
   * Resolve a doc's `{label, type}` from the active space's tree map. Used to
   * enrich chat-dispatch notifications so Claude knows what kind of doc the
   * chat is attached to (e.g. checklist vs. kanban) without burning tool
   * calls to discover it. Returns null when the doc isn't in this tree.
   */
  getDocSummary(docId: string): {
    label?: string;
    type?: string;
  } | null;
  /** Get the root doc-trash Y.Map of the active space. */
  getTrashMap(): Y.Map<any> | null;
  /** Get plugin names enabled in the active space via space-plugins Y.Map. */
  getEnabledPluginNames(): string[];
  /**
   * Get or create a child provider for a given document ID.
   * Caches providers and waits for sync before returning.
   */
  getChildProvider(docId: string): Promise<AbracadabraProvider>;
  /** Update root awareness to reflect the currently focused document. */
  setFocusedDoc(docId: string): void;
  /**
   * Set a TipTap-compatible collapsed cursor (anchor === head) on a child doc's awareness.
   * Only call after getChildProvider has cached the provider.
   * @param index Character index in xmlFragment ('default'). Clamped to [0, length].
   */
  setDocCursor(docId: string, index: number): void;
  /** Evict child providers that have been idle for too long. */
  private evictIdle;
  /** Wire up real-time channel notifications via awareness observation and stateless chat. */
  startChannelNotifications(mcpServer: McpServer): void;
  /**
   * Bootstrap inbox observation. Sends `messages:inbox_fetch` over the active
   * provider; the server's `messages:inbox_history` reply carries the
   * `inbox_doc_id`. We then open a dedicated provider on that doc and observe
   * its `entries` Y.Array for live DM/mention dispatch. Mirrors the
   * dashboard's `useNotifications` pattern.
   */
  private _startInboxNotifications;
  /**
   * Open a dedicated provider on the agent's inbox doc and observe its
   * `entries` Y.Array. The server is the only writer; we only read.
   */
  private _ensureInboxObserver;
  /**
   * Diff the inbox `entries` array against what we've already seen. On the
   * first pump we only record a baseline (don't replay history). New entries
   * are classified by their authoritative `kind` and dispatched.
   */
  private _pumpInbox;
  /** Classify + dispatch one inbox entry as a channel notification. */
  private _dispatchInboxEntry;
  /**
   * Resolve full message content. The inbox `preview` is truncated to ≤200
   * bytes (and null for E2E), so for fidelity we read the actual record from
   * the channel/DM doc's active period — same shape the dashboard reads.
   * Falls back to the preview, then a short notice.
   */
  private _resolveInboxContent;
  /** Check-and-mark: true if already dispatched, else records it and returns false. */
  private _rememberDispatched;
  /** Check-only: true if this id was already dispatched (does NOT record it). */
  private _wasDispatched;
  private _trimSet;
  /** Attach awareness observer to detect `ai:task` fields from human users. */
  private _observeRootAwareness;
  /** Dispatch an ai:task as a channel notification. */
  private _dispatchAiTask;
  /**
   * Clear an ai:task from awareness to signal completion.
   * Scans all awareness states for the given task ID and removes it.
   */
  clearAiTask(taskId: string): void;
  /** Handle incoming `messages:new_message` broadcasts and dispatch as channel notifications. */
  private _handleStatelessChat;
  /**
   * Set the agent's status in root awareness with auto-clear after idle.
   * @param statusContext — scopes the status to a specific channel/context so the
   *   dashboard only shows it in the relevant chat. Defaults to `_lastChatChannel`.
   */
  setAutoStatus(status: string | null, docId?: string, statusContext?: string | null): void;
  /**
   * Start a new agent turn. Mints a fresh UUID and writes it to awareness so
   * the dashboard can gate the incantation on "there is an active turn",
   * decoupled from the (racier) status field. Called from chat arrival and
   * ai:task dispatch right before `setAutoStatus('thinking')`.
   *
   * Also starts a heartbeat typing indicator so the dashboard renders typing
   * dots whenever no tool pill is active during the turn — this replaces the
   * "dead air" gap users see between thinking and the final reply.
   */
  private _beginTurn;
  /**
   * Enter the "writing" phase: the agent has finished reasoning and is about
   * to send a chat message. The dashboard maps `status === 'writing'` to
   * typing dots (not the incantation phrase). Keeps the turn alive (turnId
   * stays set) and emits a typing frame immediately so the dots appear at
   * once; the heartbeat keeps them alive until `setAutoStatus(null)`.
   */
  setWritingStatus(channel: string): void;
  /** Re-send typing indicator every 2s so dashboard keeps showing it (expires at 3s). */
  private _startTypingInterval;
  private _stopTypingInterval;
  /**
   * Broadcast which tool the agent is currently executing.
   *
   * Renders as a ChatTool pill on the dashboard. On non-null calls, the tool
   * is also appended to `toolHistory` (capped at TOOL_HISTORY_MAX) and written
   * to awareness so the dashboard's inline trace can show the turn's recent
   * activity. Tools do NOT clear (`setActiveToolCall(null)`) on completion —
   * the pill stays until the next tool replaces it or `setAutoStatus(null)`
   * flushes the turn. This keeps pills visible long enough to see.
   */
  setActiveToolCall(toolCall: {
    name: string;
    target?: string;
    detail?: unknown;
  } | null): void;
  /**
   * Attach expandable `detail` (arguments + result summary) to the most recent
   * tool-history entry for `toolName`, then re-broadcast `toolHistory`.
   *
   * Called by the central tool wrapper in index.ts *after* a tool resolves, so
   * the dashboard's tool cards can expand to show what the tool was called with
   * and what it returned — the same affordance built-in Claude tools (bash /
   * read / edit) already get via the hook-bridge. Tools that never emitted a
   * pill (no `setActiveToolCall`) have no entry to attach to, so this no-ops.
   */
  recordToolDetail(toolName: string, detail: unknown): void;
  /**
   * Send a typing indicator to a chat channel. Pass the channel doc id
   * (or for legacy callers, a `group:<docId>` string — we strip the prefix).
   */
  sendTypingIndicator(channel: string): void;
  /** Graceful shutdown. */
  destroy(): Promise<void>;
}
//#endregion
export { AbracadabraMCPServer, type MCPServerConfig };