import type { V4RuntimeEvent } from '@/types/workbench';

const KNOWN_V4_EVENTS = new Set([
  'v4_runtime_start',
  'provider_switched',
  'sub_agent_spawn',
  'sub_agent_output',
  'sub_agent_finish',
  'sub_agent_error',
  'sub_agent_cancelled',
  'sub_agent_timeout',
  'sandbox_stdout',
  'sandbox_stderr',
]);

function looksLikeJsonPrefix(line: string): boolean {
  const trimmed = line.trimStart();
  return trimmed.startsWith('{');
}

function parseV4Event(line: string): V4RuntimeEvent | null {
  const trimmed = line.trim();
  if (!trimmed || !trimmed.startsWith('{')) return null;
  let parsed: unknown;
  try {
    parsed = JSON.parse(trimmed);
  } catch {
    return null;
  }
  if (!parsed || typeof parsed !== 'object') return null;
  const obj = parsed as Record<string, unknown>;
  const event = typeof obj.event === 'string' ? obj.event : '';
  if (!KNOWN_V4_EVENTS.has(event)) return null;

  const payload = obj.payload && typeof obj.payload === 'object'
    ? (obj.payload as Record<string, unknown>)
    : {};

  return {
    event,
    agent_id: typeof obj.agent_id === 'string' ? obj.agent_id : undefined,
    task: typeof obj.task === 'string' ? obj.task : undefined,
    payload,
    ts: typeof obj.ts === 'number' ? obj.ts : undefined,
  };
}

export function extractV4RuntimeEvents(
  chunk: string,
  carry: string,
): { visible: string; carry: string; events: V4RuntimeEvent[] } {
  const combined = `${carry}${chunk}`;
  const lines = combined.split('\n');
  const tail = lines.pop() ?? '';

  const passthrough: string[] = [];
  const events: V4RuntimeEvent[] = [];

  for (const line of lines) {
    const event = parseV4Event(line);
    if (event) {
      events.push(event);
      continue;
    }
    passthrough.push(line);
  }

  // NOTE: the trailing `tail` (text after the last '\n', i.e. an
  // in-progress/incomplete line -- often just the single character the user
  // just typed, echoed back with no newline at all) must NOT be folded into
  // `passthrough` before the join. Doing that unconditionally appended a
  // trailing '\n' after it (since the join step always adds one when the
  // array is non-empty), turning "no newline in this chunk" into "add one
  // anyway" -- so every single keystroke echoed by the mainAgent pane got a
  // spurious linefeed after it, moving the cursor down a row on every
  // character typed (the terminal "staircase" bug). Complete lines are
  // joined/newline-terminated here; the incomplete tail is appended
  // afterwards, verbatim, exactly like deck-events.ts/goa-events.ts do.
  let visible = passthrough.length > 0 ? `${passthrough.join('\n')}\n` : '';

  let nextCarry = tail;
  if (tail && !looksLikeJsonPrefix(tail)) {
    visible += tail;
    nextCarry = '';
  }

  return { visible, carry: nextCarry, events };
}
