/**
 * SET 40b — environment-split regression guard.
 *
 * Offscreen / extension contexts forbid `new Function` / `eval` (CSP). The
 * only legal hosts are:
 *   1. extension/sandbox.js (manifest sandbox page)
 *   2. src/tier1/testExec.ts parse/probe helpers — Node sync path OR behind
 *      the sandbox-channel ProgramParseProbe (SET 40a)
 *
 * Any OTHER file under src/tier1 that introduces `new Function(` or `eval(`
 * fails this check. Wire via vitest (tests/tier1-set40-parse.test.ts); also
 * export `checkTier1NoEval` for optional build hooks.
 *
 * Allowlist (documented):
 *   - src/tier1/testExec.ts — all `new Function` / lint-string mentions
 *   - extension/sandbox.js — outside src/tier1 (not scanned here)
 *   - Comments and string/regex literals mentioning the forbidden forms in
 *     non-allowlisted files are still flagged if they look like call sites;
 *     prefer documenting in comments without the call pattern.
 */
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const TIER1 = path.join(ROOT, 'src', 'tier1');

/** Basename allowlist inside src/tier1 — Node/sandbox-probe hosts only. */
export const TIER1_EVAL_ALLOWLIST = new Set([
  'testExec.ts',
]);

const CALL_PATTERNS = [
  /\bnew\s+Function\s*\(/,
  /\beval\s*\(/,
];

export interface Tier1NoEvalResult {
  ok: boolean;
  violations: string[];
}

function walkTsFiles(dir: string): string[] {
  const out: string[] = [];
  if (!fs.existsSync(dir)) return out;
  for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
    const p = path.join(dir, ent.name);
    if (ent.isDirectory()) out.push(...walkTsFiles(p));
    else if (ent.isFile() && /\.tsx?$/.test(ent.name) && !ent.name.endsWith('.generated.ts')) {
      out.push(p);
    }
  }
  return out;
}

/**
 * Scan src/tier1 for direct `new Function(` / `eval(` outside the allowlist.
 */
export function checkTier1NoEval(tier1Dir: string = TIER1): Tier1NoEvalResult {
  const violations: string[] = [];
  for (const file of walkTsFiles(tier1Dir)) {
    const base = path.basename(file);
    if (TIER1_EVAL_ALLOWLIST.has(base)) continue;
    const text = fs.readFileSync(file, 'utf8');
    const lines = text.split('\n');
    for (let i = 0; i < lines.length; i++) {
      const line = lines[i]!;
      const trimmed = line.trim();
      // Skip pure comment lines.
      if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) continue;
      for (const re of CALL_PATTERNS) {
        if (re.test(line)) {
          const rel = path.relative(ROOT, file);
          violations.push(`${rel}:${i + 1}: ${trimmed.slice(0, 100)}`);
        }
      }
    }
  }
  return { ok: violations.length === 0, violations };
}

function main(): void {
  const r = checkTier1NoEval();
  if (!r.ok) {
    console.error('SET 40b check-tier1-no-eval FAILED:\n' + r.violations.join('\n'));
    process.exit(1);
  }
  console.log('SET 40b check-tier1-no-eval: ok (allowlist: ' + [...TIER1_EVAL_ALLOWLIST].join(', ') + ')');
}

const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
if (isMain) main();
