/**
 * v2.6 — AgentBackend abstraction (Phase 0 scaffold; types only, zero runtime logic).
 *
 * The core abstraction for persistent agent sessions. Two implementations land
 * later: `OpenCodeServerBackend` (Phase 1, opencode HTTP server) and
 * `WarmAcpBackend` (Phase 2, long-lived ACP process). Backends emit
 * transport-agnostic `AgentEvent`s; the dispatcher maps them to WS frames.
 *
 * Nothing imports this file yet — it must compile standalone.
 * Spec: openspec/changes/v2-6-persistent-agent-sessions/design.md §2.
 */
import type { AcpToolSnapshot } from './acp-tool-snapshot.js';
import type { AgentCommand } from './provider-types.js';

/** Backend transport kind. Mirrors `agent_sessions.backend` CHECK in schema.sql. */
export type AgentBackendKind = 'opencode_server' | 'acp_warm' | 'claude_sdk' | 'paseo';

/**
 * Normalized, transport-agnostic events a backend emits during a turn (§2).
 * Derived from acp-dispatch's session-update handling, but WITHOUT the WS
 * envelope (message_id/chat_id) — the dispatcher owns frame mapping.
 *
 * `tool_call` vs `tool_update` are kept distinct on purpose: acp-dispatch
 * currently merges both into one snapshot frame, but opencode's SSE
 * distinguishes tool-start from tool-result, so the contract carries both.
 * `commands` mirrors the ACP `available_commands_update` path (v2.5.10).
 */
export type AgentEvent =
  | { type: 'text'; text: string }
  | { type: 'reasoning'; text: string }
  | { type: 'tool_call'; toolCall: AcpToolSnapshot }
  | { type: 'tool_update'; toolCall: AcpToolSnapshot }
  | { type: 'commands'; commands: AgentCommand[] };

/** Params to establish (or look up) a backend session (§2). */
export interface EnsureSessionOpts {
  agent: string;
  /** Resolved model id. */
  model: string;
  /** P1.5-b: the chat (tab) this turn belongs to. agent_sessions is keyed
   *  (chat_id, agent) — the tab/chat is the context unit. Always non-null:
   *  the dispatcher creates a chat for session-less tasks before calling. */
  chatId: string;
  /** Shared per-session worktree (one per `sessions.id`, not per pane). */
  worktreePath: string;
  /** P1.5-b: the `worktrees.id` for this session's worktree — stored on the
   *  agent_sessions row informationally (NOT the key). */
  worktreeId: string;
  projectId: string;
}

/** Opaque handle to a live backend session, persisted to `agent_sessions` (§2). */
export interface AgentSessionHandle {
  sessionId: string;
  agent: string;
  backend: AgentBackendKind;
  /** P1.5-b: the chat (tab) this session is keyed on (with agent). */
  chatId: string;
  /** P1.5-b: the worktree this session's chat runs in (informational link). */
  worktreeId: string;
  /** Provider's own session id (resume token); null until the backend assigns one. */
  agentSessionId: string | null;
  /** opencode HTTP server port; null for ACP backends. */
  serverPort: number | null;
}

/** Per-turn context passed to `prompt` (§2). */
export interface PromptCtx {
  worktreePath: string;
  model: string;
  signal: AbortSignal;
  onEvent: (e: AgentEvent) => void;
  /** Phase 2: per-turn task id, so a warm ACP backend can route permission /
   *  elicitation prompts back to the UI via the permission-waiter. Optional —
   *  the opencode-server backend (autonomous) ignores it. */
  taskId?: string;
  /** Phase 2: per-turn mode id (gates autonomous mode in the permission-waiter). */
  modeId?: string;
}

/** Result of a completed turn (§2). Diff/persist happen outside the backend. */
export interface TurnResult {
  ok: boolean;
  error?: string;
  // Optional context-window telemetry (claude SDK): the model's reported window
  // (ctxMax, 1M-aware) and the peak request input ≈ current fill (ctxUsed). The
  // dispatcher writes these onto the assistant message so the ContextBar renders a
  // real fill for the turn. Omitted by backends that don't report a window.
  ctxUsed?: number;
  ctxMax?: number;
}

/**
 * The core backend abstraction (§2). Implementations: OpenCodeServerBackend
 * (Phase 1), WarmAcpBackend (Phase 2).
 */
export interface AgentBackend {
  /** Lazy: spawn server / warm process if not already up for this (session, agent). §2 */
  ensureSession(sessionId: string, opts: EnsureSessionOpts): Promise<AgentSessionHandle>;
  /** Send a prompt; stream events via ctx.onEvent; resolves when the turn completes. §2 */
  prompt(handle: AgentSessionHandle, input: string, ctx: PromptCtx): Promise<TurnResult>;
  /** Graceful teardown of one session (session close or idle timeout). §2 */
  closeSession(handle: AgentSessionHandle): Promise<void>;
  /** Full teardown — kills all spawned servers/processes. §2 */
  dispose(): Promise<void>;
  /** Liveness for health endpoint + dispatcher fallback decision. §2 */
  health(): 'up' | 'down';
  /**
   * v2.6 Phase 3: true iff a turn is in flight on this backend. The pool's idle
   * eviction + LRU cap NEVER evict a busy backend (design §6 busy rule); the
   * health-monitor defers a restart while busy (stale-grace). Optional so the
   * Phase-0 scaffold and any test double stay compatible — absent ⇒ treated as
   * not busy. opencode-server (multi-session) is busy iff ANY session has an
   * active turn; warm-acp (single session) iff its one slot is active.
   */
  isBusy?(): boolean;
  /**
   * v2.6 Phase 3: optional proactive health probe + busy-aware self-restart, run
   * by the pool's periodic sweep. The opencode-server backend implements it
   * (detects a hung-but-not-exited server and restarts when non-busy). Backends
   * with no long-lived shared process (warm-ACP recovers lazily on its own child
   * exit) can omit it. Must never throw — the sweep ignores rejections.
   */
  tickHealth?(now?: number): Promise<void>;
}
