/**
 * SET 64d — runtime trace page: value provenance for semantic findings.
 *
 * Deterministically extracts literal-arg calls of the tested symbols from a
 * failing case body, replays them against the real artifact (testExec trace
 * channel), and formats a bounded `trace` page for the repair dossier. This
 * converts semantic repair from "reconstruct intent from spec" into "map
 * these inputs to that output" — a local transformation. 0 tokens.
 */
import type { FailureKind } from './substrate';
import type { TraceCall, TraceLine } from './testExec';

/** Total page budget (chars). */
export const TRACE_CAP = 400;
/** Max replayed calls per page. */
export const TRACE_MAX_CALLS = 6;

/** Classes whose repair dossier gets a trace page (semantic-procedure family). */
export function traceEligible(kind: FailureKind | undefined | null): boolean {
  return kind === 'assertion' || kind === 'throw-mismatch' || kind === 'catalog-drift';
}

/**
 * True when an argument source is literal-only: numbers, strings, arrays,
 * object literals with identifier keys, booleans, null/undefined. Rejects
 * nested calls and free identifiers — replay must not execute suite logic.
 */
export function isLiteralArgs(argsSrc: string): boolean {
  if (argsSrc.includes('(') || argsSrc.includes('`')) return false;
  const stripped = argsSrc.replace(/'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/g, '""');
  for (const m of stripped.matchAll(/[A-Za-z_$][\w$]*/g)) {
    const word = m[0]!;
    if (['true', 'false', 'null', 'undefined', 'NaN', 'Infinity'].includes(word)) continue;
    // Object-literal keys (`{ a: 1 }`) are fine; free identifiers are not.
    const after = stripped.slice(m.index! + word.length);
    if (/^\s*:/.test(after)) continue;
    return false;
  }
  return true;
}

/** Balanced-paren scan for `sym(...)` call sites with literal-only args. */
export function extractTraceCalls(
  caseBody: string,
  symbols: string[],
  cap = TRACE_MAX_CALLS,
): TraceCall[] {
  const out: TraceCall[] = [];
  const seen = new Set<string>();
  for (const symbol of symbols) {
    const sym = symbol.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    const re = new RegExp(`(?<![\\w$.])${sym}\\s*\\(`, 'g');
    for (const m of caseBody.matchAll(re)) {
      let depth = 1;
      const start = m.index! + m[0].length;
      let i = start;
      for (; i < caseBody.length && depth > 0; i++) {
        const ch = caseBody[i]!;
        if (ch === '(') depth++;
        else if (ch === ')') depth--;
      }
      if (depth !== 0) continue;
      const argsSrc = caseBody.slice(start, i - 1).trim();
      const key = `${symbol}(${argsSrc})`;
      if (seen.has(key) || !isLiteralArgs(argsSrc)) continue;
      seen.add(key);
      out.push({ symbol, argsSrc });
      if (out.length >= cap) return out;
    }
  }
  return out;
}

export const TRACE_HEADER =
  'Page trace — observed values at the symbol boundary (deterministic replay of the failing case’s calls):';

/** Format replayed lines into the bounded trace page (null when empty). */
export function buildTracePage(lines: TraceLine[] | null): string | null {
  if (!lines || !lines.length) return null;
  let body = '';
  for (const l of lines) {
    const next = `${body}${body ? '\n' : ''}${l.call} ${l.outcome}`;
    if (next.length > TRACE_CAP) break;
    body = next;
  }
  return body ? `${TRACE_HEADER}\n${body}` : null;
}
