// Terminal WebSocket wire protocol (centralized; v2 Phase 9 extraction).
//
// The booterm WS multiplexes two directions on one socket with a binary/text
// discriminator (mirrored server-side in apps/booterm):
//   - PTY input (keystrokes, paste, hotkey bytes) is sent as a BINARY frame.
//   - Control frames are JSON text: outbound {type:'resize',cols,rows};
//     inbound {type:'init'} and {type:'exit',code}.
// This module is the single source of that encoding so a server-side protocol
// change is mirrored in one place. Behavior is byte-identical to the prior
// inline encoding scattered across TerminalPane.

// TextEncoder is stateless; a single shared instance is equivalent to the
// per-call `new TextEncoder()` the inline sites used.
const textEncoder = new TextEncoder();

// Keystrokes / paste / hotkey bytes go out as a BINARY frame so the server can
// disambiguate them from JSON control frames. TextEncoder is in every modern
// browser.
export function encodeInput(text: string): Uint8Array {
  return textEncoder.encode(text);
}

// Resize is in-band on the WebSocket as a JSON text frame. The HTTP /resize
// endpoint had a race with PTY-map registration; WS frames don't.
export function encodeResize(cols: number, rows: number): string {
  return JSON.stringify({ type: 'resize', cols, rows });
}

export type ServerControlFrame =
  | { type: 'init' }
  | { type: 'exit'; code: number };

// Parse an inbound text frame. Returns a recognized control frame, or `null`
// when the text is not JSON or not a known control type — in which case the
// caller writes it to the terminal as raw text. Preserves the original
// try/catch fall-through: a parse error or an unknown `type` both yield null.
export function parseServerFrame(data: string): ServerControlFrame | null {
  try {
    const parsed = JSON.parse(data) as { type?: string; code?: number };
    if (parsed.type === 'init') return { type: 'init' };
    if (parsed.type === 'exit') return { type: 'exit', code: parsed.code ?? 0 };
  } catch {
    /* not JSON — caller writes as text */
  }
  return null;
}
