#!/usr/bin/env node
// Spike S3 (2e passe) — D2..D5 avec commandes hors allowlist, scénarios isolés,
// détection registre + fallback écran. D1 (trust) déjà validé en 1re passe.
import { execSync } from 'node:child_process';
import { mkdirSync, writeFileSync, rmSync, existsSync } from 'node:fs';
import { join } from 'node:path';
import { ClaudeSession, sleep, setLogFile, log } from '../s1-resume/harness.mjs';

const CAPTURES = new URL('./captures/', import.meta.url).pathname;
mkdirSync(CAPTURES, { recursive: true });
setLogFile(join(CAPTURES, 'steps2.jsonl'));

const results = {};
const save = () => writeFileSync(join(CAPTURES, 'results2.json'), JSON.stringify(results, null, 1));

function freshRepo(name) {
  const d = `/tmp/spike-s3-${name}`;
  rmSync(d, { recursive: true, force: true });
  mkdirSync(d, { recursive: true });
  execSync('git init -q -b main && echo hello > witness.txt && git add -A && git -c user.email=s@s -c user.name=spike commit -qm init', { cwd: d, shell: '/bin/bash' });
  return d;
}

function extractOptions(screenTail) {
  const opts = [];
  for (const m of screenTail.matchAll(/^\s*(❯\s*)?(\d+)\.\s*(.+)$/gm)) {
    opts.push({ selected: !!m[1], n: m[2], label: m[3].trim().slice(0, 90) });
  }
  return opts;
}

// Attente d'un dialogue : registre 'waiting' OU motif de dialogue à l'écran (texte aplati sans espaces)
async function waitDialog(S, timeoutMs = 120000) {
  return S.until('dialogue détecté', () => {
    const r = S.registry();
    const flat = S.tail(2500).replace(/\s+/g, '');
    const screenDialog = /❯1\./.test(flat) && /(Doyouwant|permission|Allow|autoris)/i.test(flat);
    if (r?.status === 'waiting' || screenDialog) {
      return { registryStatus: r?.status ?? null, waitingFor: r?.waitingFor ?? null, screenDialog };
    }
    return false;
  }, timeoutMs, 400);
}

async function respond(S, digit) {
  const stillWaiting = () => {
    const r = S.registry();
    const flat = S.tail(2000).replace(/\s+/g, '');
    return (r?.status === 'waiting') || /❯\d+\./.test(flat) && /(Doyouwant|permission|Allow)/i.test(flat);
  };
  const tries = [];
  S.write(digit);
  await sleep(2500);
  tries.push({ method: `digit ${digit}`, dialogGone: !stillWaiting() });
  if (tries.at(-1).dialogGone) return { ok: true, tries };
  S.write('\r');
  await sleep(2500);
  tries.push({ method: 'enter', dialogGone: !stillWaiting() });
  if (tries.at(-1).dialogGone) return { ok: true, tries };
  const downs = Math.max(0, parseInt(digit, 10) - 1);
  for (let i = 0; i < downs; i++) { S.write('\x1b[B'); await sleep(250); }
  S.write('\r');
  await sleep(2500);
  tries.push({ method: `arrows x${downs} + enter`, dialogGone: !stillWaiting() });
  return { ok: tries.at(-1).dialogGone, tries };
}

async function scenario(name, fn) {
  log(`=== ${name} ===`);
  try { results[name] = await fn(); }
  catch (err) { results[name] = { error: String(err) }; log(`${name}: ÉCHEC`, { err: String(err) }); }
  save();
}

// ---------- D2 : permission Bash (node -e, hors allowlist) → approbation "1" ----------
await scenario('d2_permBash', async () => {
  const d = freshRepo('bash2');
  const S = new ClaudeSession([], d, 'perm-bash2', CAPTURES);
  try {
    await S.waitReady();
    await S.type(`Run this exact bash command and show me its output: node -e "console.log('spike-s3-marker-xyz')" — do not do anything else.`);
    const det = await waitDialog(S);
    await sleep(1200);
    const screen = S.tail(2500);
    const resp = await respond(S, '1');
    let completed = false;
    try { await S.waitTurnDone(120000); completed = true; } catch {}
    return {
      detection: det, options: extractOptions(screen), screenExcerpt: screen.slice(-1100),
      respond: resp, turnCompleted: completed,
      markerInOutput: S.plain().includes('spike-s3-marker-xyz'),
    };
  } finally { S.kill('SIGTERM'); }
});

// ---------- D3 : permission Write (création de fichier) → approbation ----------
await scenario('d3_permWrite', async () => {
  const d = freshRepo('write2');
  const S = new ClaudeSession([], d, 'perm-write2', CAPTURES);
  try {
    await S.waitReady();
    await S.type('Create a file named s3-edit.txt containing exactly the word hello. Use the Write tool. Do not do anything else.');
    const det = await waitDialog(S);
    await sleep(1200);
    const screen = S.tail(2500);
    const resp = await respond(S, '1');
    let completed = false;
    try { await S.waitTurnDone(120000); completed = true; } catch {}
    return {
      detection: det, options: extractOptions(screen), screenExcerpt: screen.slice(-1100),
      respond: resp, turnCompleted: completed, fileCreated: existsSync(join(d, 's3-edit.txt')),
    };
  } finally { S.kill('SIGTERM'); }
});

// ---------- D4 : refus par Esc ----------
await scenario('d4_denyEsc', async () => {
  const d = freshRepo('deny2');
  const S = new ClaudeSession([], d, 'deny-esc2', CAPTURES);
  try {
    await S.waitReady();
    await S.type(`Run this exact bash command and show me its output: node -e "console.log('should-be-denied')" — do not do anything else.`);
    await waitDialog(S);
    await sleep(1200);
    const screenBefore = S.tail(1200);
    S.write('\x1b');
    await sleep(4000);
    const reg = S.registry();
    return {
      screenBeforeEsc: screenBefore.slice(-800),
      statusAfterEsc: reg?.status ?? null,
      screenAfterEsc: S.tail(900),
      commandExecuted: /should-be-denied\s*$/m.test(S.plain().replace(/\s+/g, ' ')),
    };
  } finally { S.kill('SIGTERM'); }
});

// ---------- D5 : AskUserQuestion ----------
await scenario('d5_askUserQuestion', async () => {
  const d = freshRepo('ask2');
  const S = new ClaudeSession([], d, 'ask2', CAPTURES);
  try {
    await S.waitReady();
    await S.type('Use the AskUserQuestion tool to ask me whether I prefer red or blue. Offer exactly those two options.');
    const det = await waitDialog(S);
    await sleep(1200);
    const screen = S.tail(2500);
    // 2e option : flèche bas + Entrée d'abord (hypothèse sélecteur), chiffre en fallback
    S.write('\x1b[B'); await sleep(400); S.write('\r');
    await sleep(3000);
    let reg = S.registry();
    const arrowsWorked = reg?.status !== 'waiting';
    let digitFallback = null;
    if (!arrowsWorked) { S.write('2'); await sleep(2500); reg = S.registry(); digitFallback = reg?.status; }
    let completed = false;
    try { await S.waitTurnDone(90000); completed = true; } catch {}
    return {
      detection: det, options: extractOptions(screen), screenExcerpt: screen.slice(-1100),
      arrowsEnterWorked: arrowsWorked, digitFallbackStatus: digitFallback, turnCompleted: completed,
      finalScreen: S.tail(700),
    };
  } finally { S.kill('SIGTERM'); }
});

log('=== S3 passe 2 terminée ===');
process.exit(0);
