import type { MessageCost, PromptImage, TurnMessage } from '../api/chat/types';

// ── Envelope ──

export type SidecarMessage = SidecarCommand | SidecarEvent;

// ── Commands (API server → sidecar) ──

export type SidecarCommand =
  | { type: 'ping'; id: string }
  | { type: 'state:sync'; id: string }
  // Proxy
  | { type: 'proxy:secret'; id: string }
  // Claude Code
  | { type: 'claude:spawn'; id: string; params: ClaudeSpawnParams }
  | { type: 'claude:spawn-streaming'; id: string; params: ClaudeSpawnStreamingParams }
  | { type: 'claude:kill'; id: string; sessionKey: string }
  | { type: 'claude:interrupt'; id: string; sessionKey: string }
  | { type: 'claude:clear-session'; id: string; sessionKey: string }
  // Is a turn still running for this session? Only the process that owns the session can say, which is
  // exactly why it is asked over the wire — see `isClaudeGenerating` in sidecar-registry.
  | { type: 'claude:is-generating'; id: string; sessionKey: string }
  // Which session key owns this transcript? The map lives on the agent's disk, so only it can answer —
  // see `findClaudeSessionKey` in sidecar-registry, and `attach` in the chat socket for why it is asked.
  | { type: 'claude:find-session'; id: string; claudeSessionId: string }
  // OpenCode — drive a turn via `opencode run … --format json` (tools re-anchored to cwd via --dir)
  | { type: 'opencode:run-streaming'; id: string; params: OpenCodeRunParams }
  | { type: 'opencode:kill'; id: string; sessionKey: string }
  // VNC
  | { type: 'vnc:start'; id: string; params: VncStartParams }
  // Provision the VNC password without starting a server — the UI needs it before it can connect
  | { type: 'vnc:ensure-password'; id: string; email: string }
  | { type: 'vnc:stop'; id: string; email: string };

// ── Responses/Events (sidecar → API server) ──

export type SidecarEvent =
  | { type: 'pong'; id: string }
  | { type: 'state:sync'; id: string; state: ClaudeState }
  | { type: 'proxy:secret'; id: string; secret: string }
  // Claude Code
  | { type: 'claude:spawned'; id: string; sessionKey: string }
  // A finished, browser-facing turn message. The agent has already committed it to chat_session_events
  // and `seq` is its cursor id there; officer relays it verbatim. No `seq` means it is not durable —
  // an `assistant:delta` (superseded by the text that follows) or a message whose write failed.
  | { type: 'claude:message'; sessionKey: string; msg: TurnMessage; seq?: number }
  | { type: 'claude:result'; id: string; result: ClaudeCodeResult }
  | { type: 'claude:error'; id: string; error: string }
  | { type: 'claude:killed'; id: string }
  | { type: 'claude:interrupted'; id: string }
  | { type: 'claude:session-cleared'; id: string }
  | { type: 'claude:generating'; id: string; generating: boolean }
  | { type: 'claude:session-key'; id: string; sessionKey: string | null }
  // VNC
  | { type: 'vnc:started'; id: string; port: number; display: number }
  | { type: 'vnc:password'; id: string; password: string }
  | { type: 'vnc:stopped'; id: string }
  | { type: 'vnc:error'; id: string; error: string }
  // Email — new mail no longer crosses this socket; the sidecar owns the SSE stream and pushes directly
  // OpenCode — the sidecar reports where its `opencode serve` is listening (random port) on connect
  | { type: 'opencode:server'; port: number }
  // OpenCode turn streaming (analog of claude:*): spawned ack, per-message stream, session id report
  | { type: 'opencode:spawned'; id: string; sessionKey: string }
  // Same contract as `claude:message`: a finished turn message the sidecar has already committed to
  // chat_session_events, plus the cursor id it landed on. Officer relays it and folds it into its
  // in-memory transcript; it does not translate or persist.
  | { type: 'opencode:message'; sessionKey: string; msg: TurnMessage; seq?: number }
  | { type: 'opencode:session'; sessionKey: string; sessionId: string }
  | { type: 'opencode:error'; id: string; error: string }
  // Music — the sidecar reports where its audio-streaming HTTP server is listening (random port) on connect
  | { type: 'music:server'; port: number }
  // Vault — the sidecar reports where its Vaultwarden reverse-proxy HTTP/WS server is listening on connect
  | { type: 'vault:server'; port: number }
  // slskd — the sidecar reports where its slskd reverse-proxy HTTP server is listening (random port) on connect
  | { type: 'slskd:server'; port: number }
  // Headscale — the sidecar reports where its HTTP server is listening (random port) on connect
  | { type: 'headscale:server'; port: number }
  // Transmission — the sidecar reports where its HTTP server is listening (random port) on connect
  | { type: 'transmission:server'; port: number }
  // InvoiceShelf — the sidecar reports where its HTTP server is listening (random port) on connect
  | { type: 'invoiceshelf:server'; port: number }
  // Jellyfin — the sidecar reports where its HTTP server is listening (random port) on connect
  | { type: 'jellyfin:server'; port: number }
  // Photos (Immich) — the sidecar reports where its HTTP server is listening (random port) on connect
  | { type: 'photos:server'; port: number }
  // Memos — the sidecar reports where its HTTP server is listening (random port) on connect
  | { type: 'memos:server'; port: number }
  // Gitea — the sidecar reports where its HTTP server is listening (random port) on connect
  | { type: 'gitea:server'; port: number }
  // CalDAV/CardDAV — the sidecar reports where its HTTP server is listening (random port) on connect.
  // One port serves both doors: /dav (forwarded to Radicale) and /_officer (JSON for Officer's UI).
  | { type: 'caldav:server'; port: number }
  // Wallet — the sidecar reports where its HTTP server is listening (random port) on connect
  | { type: 'wallet:server'; port: number }
  // PTY — the sidecar reports where its terminal HTTP/WS server is listening (random port) on connect
  | { type: 'pty:server'; port: number }
  // Email — the sidecar reports where its mail HTTP server is listening (random port) on connect
  | { type: 'email:server'; port: number }
  // Notify — the sidecar reports where its notification HTTP server is listening (random port) on connect
  | { type: 'notify:server'; port: number }
  // Generic
  | { type: 'error'; id?: string; error: string };

// ── Claude sidecar state ──

export type ClaudeState = {
  proxySecret: string;
  claudeSessions: Record<string, string>; // sessionKey → Claude Code session_id
};

// ── Param types ──

export type ClaudeSpawnParams = {
  userId: number;
  email: string;
  username: string;
  prompt: string;
  sessionKey: string;
  model?: string;
  cwd?: string;
};

export type ClaudeSpawnStreamingParams = {
  userId: number;
  email: string;
  username: string;
  prompt: string;
  images?: PromptImage[]; // sent as native image content blocks alongside the prompt text
  sessionKey: string;
  cwd?: string;
  model?: string;
  resumeSessionId?: string; // resume this Claude session uuid (from the /chat session list)
  // Whether turn output should be committed to chat_session_events (default true). A chat session wants
  // it — that is what survives an officer restart. A pipeline step does not: its sessionKey is a throwaway
  // uuid no browser will ever replay, and the job's own event log is its record.
  durable?: boolean;
};

export type ClaudeCodeResult = {
  text: string;
  sessionId: string;
  model: string;
  cost: MessageCost;
};

// ── OpenCode turn params ──

export type OpenCodeRunParams = {
  sessionKey: string;
  prompt: string;
  cwd?: string; // passed to `opencode run --dir` — hard-re-anchors tools to this directory
  model?: string; // `providerID/modelID` (e.g. opencode/claude-haiku-4-5); passed to --model verbatim
  resumeSessionId?: string; // OpenCode `ses_…` id to continue (`--session`)
  durable?: boolean; // commit turn output to chat_session_events (default true) — see ClaudeSpawnStreamingParams
};

// ── VNC types ──

export type VncStartParams = {
  email: string;
  username: string;
  resolution?: string;
};

export type VncSessionInfo = {
  email: string;
  display: number;
  port: number;
  pid: number;
  alive: boolean;
};

// ── PTY ──
//
// Nothing but a port crosses this socket now. The pty sidecar serves its own HTTP + WebSocket listener and
// the browser reaches it through a byte relay, so there is no command vocabulary left: pty:init, :input,
// :resize, :close and :list all lived here until the sidecar owned its own transport, and officer filtered
// one global output stream per session to feed them. The port arrives as the shared `pty:server` event
// that createSidecarProxy already listens for.
