
// 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 }
  | {
      type: 'pty_exited';
      session_id: string;
      pane_id: string;
      exit_code: number;
      last_lines: string[];
      session_title?: string | null;
      session_description?: string | null;
      parent_agent?: string | null;
      timed_out: boolean;
    };

// 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 Record<string, unknown>;
    if (parsed.type === 'init') return { type: 'init' };
    if (parsed.type === 'exit') return { type: 'exit', code: (parsed.code as number) ?? 0 };
    if (parsed.type === 'pty_exited') {
      return {
        type: 'pty_exited',
        session_id: parsed.session_id as string,
        pane_id: parsed.pane_id as string,
        exit_code: parsed.exit_code as number,
        last_lines: parsed.last_lines as string[],
        session_title: (parsed.session_title as string | null) ?? null,
        session_description: (parsed.session_description as string | null) ?? null,
        parent_agent: (parsed.parent_agent as string | null) ?? null,
        timed_out: (parsed.timed_out as boolean) ?? false,
      };
    }
  } catch {
    /* not JSON -- caller writes as text */
  }
  return null;
}
