#!/usr/bin/env node
/**
 * Rule #4 (companion) — Record verification artifact.
 *
 * PostToolUse on Bash. If the command was a full project test run AND it
 * passed (exit 0 + recognisable PASS marker in stdout), write a sentinel
 * `~/.claude/runtime/verify-pass-<session>.json` consumed by the
 * enforce-verify-before-push gate.
 *
 * Failed runs ALSO record a sentinel with result=fail — so the gate can
 * distinguish "never ran" from "ran and failed".
 *
 * Spec: docs/superpowers/specs/2026-05-25-enforce-hard-rules-design.md
 */

import {
  readStdin,
  parseEventJson,
  writeSentinel,
  exitDecision,
  detectFullTestRun,
} from './enforce-hook-helpers.mjs';

export function extractTestMetrics(stdout) {
  const out = { tests_total: null, tests_passed: null, tests_failed: null };
  if (typeof stdout !== 'string') return out;
  // vitest summary lines:
  //   "Tests  3708 passed (3708)"
  //   "Tests  924 passed | 3 skipped (927)"      ← was missed pre-2026-05-26
  //   "Tests  1 failed | 631 passed (632)"
  //   "Tests  1 failed | 920 passed | 3 skipped (924)"  ← was missed pre-2026-05-26
  let m = stdout.match(/Tests\s+(\d+)\s+passed\s*\((\d+)\)/);
  if (m) { out.tests_passed = +m[1]; out.tests_total = +m[2]; out.tests_failed = 0; return out; }
  m = stdout.match(/Tests\s+(\d+)\s+passed\s*\|\s*(\d+)\s+skipped\s*\((\d+)\)/);
  if (m) { out.tests_passed = +m[1]; out.tests_failed = 0; out.tests_total = +m[3]; return out; }
  m = stdout.match(/Tests\s+(\d+)\s+failed\s*\|\s*(\d+)\s+passed\s*\|\s*\d+\s+skipped\s*\((\d+)\)/);
  if (m) { out.tests_failed = +m[1]; out.tests_passed = +m[2]; out.tests_total = +m[3]; return out; }
  m = stdout.match(/Tests\s+(\d+)\s+failed\s*\|\s*(\d+)\s+passed\s*\((\d+)\)/);
  if (m) { out.tests_failed = +m[1]; out.tests_passed = +m[2]; out.tests_total = +m[3]; return out; }
  // Pest: "Tests:  742 passed (1908 assertions)"
  m = stdout.match(/Tests:\s+(\d+)\s+passed/);
  if (m) { out.tests_passed = +m[1]; out.tests_total = +m[1]; out.tests_failed = 0; return out; }
  // Pest JSON reporter: {"tool":"pest","result":"passed","tests":14,"passed":14,"assertions":22}
  //                     {"tool":"pest","result":"failed","tests":15,"passed":14,"errors":1,...}
  // Mirrors the JSON recognition added to enforce-tdd-gate.mjs in commit 1d2d43a6.
  const jres = stdout.match(/"result"\s*:\s*"(passed|failed)"/);
  if (jres) {
    const pM = stdout.match(/"passed"\s*:\s*(\d+)/);
    const fM = stdout.match(/"failed"\s*:\s*(\d+)/);
    const eM = stdout.match(/"errors"\s*:\s*(\d+)/);
    const tM = stdout.match(/"tests"\s*:\s*(\d+)/);
    out.tests_passed = pM ? +pM[1] : null;
    let failed = (fM ? +fM[1] : 0) + (eM ? +eM[1] : 0);
    if (jres[1] === 'failed' && failed === 0) failed = 1; // result=failed but no count → force >=1
    out.tests_failed = failed;
    out.tests_total = tM ? +tM[1] : (out.tests_passed ?? null);
    return out;
  }
  return out;
}

export function decideRecord({ toolName, command, exitCode, stdout }) {
  if (toolName !== 'Bash') return null;
  const kind = detectFullTestRun(command);
  if (!kind) return null;
  const metrics = extractTestMetrics(stdout || '');
  // PASS criteria — actual test outcomes drive verdict, not exit code:
  //   - tests_failed parseable AND zero (e.g., "Tests 8091 passed (8091)"
  //     or "Tests 0 failed | 8091 passed"). Exit code may still be 1 if
  //     test FILES failed to load (infra failures like worktree CRLF or
  //     ruflo dormant copies) — those don't count.
  //   - tests_failed unparseable BUT exit code 0 AND tests_passed > 0
  //     (legacy vitest output format).
  const passed = (metrics.tests_failed !== null && metrics.tests_failed === 0 && metrics.tests_passed > 0)
              || (exitCode === 0 && metrics.tests_passed && metrics.tests_failed === null);
  return {
    command_kind: kind,
    command: String(command).slice(0, 200),
    exit_code: exitCode,
    result: passed ? 'pass' : 'fail',
    tests_total: metrics.tests_total,
    tests_passed: metrics.tests_passed,
    tests_failed: metrics.tests_failed,
  };
}

async function main() {
  try {
    const raw = await readStdin();
    const event = parseEventJson(raw);
    const toolName = event.tool_name || '';
    const command = (event.tool_input && event.tool_input.command) || '';
    const resp = event.tool_response || {};
    const exitCode = typeof resp.exitCode === 'number' ? resp.exitCode : (typeof resp.exit_code === 'number' ? resp.exit_code : null);
    const stdout = typeof resp.stdout === 'string' ? resp.stdout : '';

    const record = decideRecord({ toolName, command, exitCode, stdout });
    if (record) writeSentinel('verify-pass', event.session_id, record);
    exitDecision({ block: false });
  } catch {
    exitDecision({ block: false });
  }
}

const isCli = process.argv[1] && process.argv[1].replace(/\\/g, '/').endsWith('/enforce-verify-record.mjs');
if (isCli) main();
