/**
 * WebSocket frame contract for the web (client/server) transport — ticket #13,
 * lot F1. Mirrors the first-draft frames in
 * `docs/ticket13-b0-backend-transport-inventory.md` (§ "WebSocket first draft").
 *
 * F1 delivers the *types + skeleton* only. The full PTY wiring runs on top of
 * these in F3/B5 once a server exists (B3/B4). Keeping the frame shapes here — in
 * the adapters layer — lets the terminal/agent/chat gateways be structurally
 * correct against the contract today without a live backend.
 *
 * Envelope (B0): every frame carries an `id` (client→server) or optional
 * `replyTo` (server→client, present for acknowledgements, absent for unsolicited
 * output/events). PTY bytes travel base64-encoded (`bytesBase64`) so binary
 * survives JSON.
 */

// ---------------------------------------------------------------------------
// Base64 helpers (binary PTY bytes over JSON frames)
// ---------------------------------------------------------------------------

/** Encodes raw bytes to a base64 string for a `bytesBase64` frame field. */
export function bytesToBase64(bytes: Uint8Array): string {
  let binary = "";
  for (let i = 0; i < bytes.length; i += 1) {
    binary += String.fromCharCode(bytes[i]);
  }
  // `btoa` exists in browsers and jsdom.
  return btoa(binary);
}

/** Decodes a base64 `bytesBase64` frame field back to raw bytes. */
export function base64ToBytes(base64: string): Uint8Array {
  const binary = atob(base64);
  const out = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i += 1) {
    out[i] = binary.charCodeAt(i);
  }
  return out;
}

// ---------------------------------------------------------------------------
// Client → server frames
// ---------------------------------------------------------------------------

/** Client→server frame kinds (B0 § PTY + structured chat + ping). */
export type ClientFrameKind =
  | "terminal.attach"
  | "terminal.open"
  | "agent.launch"
  | "terminal.input"
  | "terminal.resize"
  | "terminal.detach"
  | "terminal.close"
  | "chat.attach"
  | "chat.send"
  | "chat.detach"
  | "ping";

/** A client→server frame. `id` correlates the eventual `replyTo`. */
export interface ClientFrame {
  id: string;
  kind: ClientFrameKind;
  payload: Record<string, unknown>;
}

// ---------------------------------------------------------------------------
// Server → client frames
// ---------------------------------------------------------------------------

/** Server→client frame kinds. */
export type ServerFrameKind =
  | "terminal.attached"
  | "terminal.output"
  | "terminal.status"
  | "chat.attached"
  | "chat.output"
  | "event.domain"
  | "error"
  | "pong";

/** Suggested lifecycle statuses (B0). */
export type TerminalStatus =
  | "starting"
  | "running"
  | "exited"
  | "closed"
  | "detached"
  | "reattached";

/** A server→client frame. `replyTo` is present only for acknowledgements. */
export interface ServerFrame {
  kind: ServerFrameKind;
  replyTo?: string;
  payload: Record<string, unknown>;
}

/** Payload of a `terminal.attached` acknowledgement (B5, `server.rs`). */
export interface AttachedPayload {
  session: {
    sessionId: string;
    projectId?: string;
    nodeId?: string | null;
    rows: number;
    cols: number;
  };
  nextSeq: number;
  /**
   * Bounded scrollback replayed at (re)attach. B5 sends a single entry (`seq:0`)
   * carrying all retained bytes, or an empty array when there is nothing to
   * replay.
   */
  scrollback: { seq: number; bytesBase64: string }[];
  gap: boolean;
  /** Lifecycle status carried on the ack (B5 sends `"running"`). */
  status?: string;
  /** Conversation id minted by an `agent.launch` (mirrors `assignedConversationId`). */
  assignedConversationId?: string;
}

/** Payload of a `terminal.output` frame. */
export interface OutputPayload {
  sessionId: string;
  seq: number;
  bytesBase64: string;
}

/**
 * Payload of a `terminal.status` frame (B5). `status` is a lifecycle transition
 * (`"exited"` on close); `exitCode` is present for `"exited"` (may be `null`).
 */
export interface StatusPayload {
  sessionId: string;
  status: string;
  exitCode?: number | null;
}

/** Payload of a `chat.output` frame (structured assistant stream). */
export interface ChatOutputPayload {
  sessionId: string;
  chunk: unknown; // ReplyChunk-shaped (kind-tagged); normalized by the caller.
}
