#!/usr/bin/env node
/**
 * enforce-llm-judge-response-scan — Stop-hook wrapper around the pure
 * llm-judge-response-scan engine (router-gate v4.1 §4.7 Layer 4).
 *
 * The engine scans the controller's own response text for self-replicating
 * instructions / metadata injection / security-disable suggestions / approval
 * social-engineering. It is FLAG-ONLY (never blocks). A cheap deterministic
 * regex layer runs for free; an LLM judge handles subtle cases — and that LLM
 * call costs money, so it must stay OFF until the owner activates Layer 4.
 *
 * Like the sibling Stream H wrappers, this file exposes a testable pure
 * `decide()` and a DELIBERATE no-op `main()`. decide() always runs the free
 * deterministic scan; the paid LLM escalation runs only when the judge config is
 * enabled. block is ALWAYS false (Stop-hook semantics).
 *
 * Activation (step 2b — owner-driven, NOT done here):
 *   1. store the API key (keychain `router-gate-llm-judge`/`default` or ROUTER_LLM_KEY),
 *   2. set ROUTER_LLM_JUDGE_ENABLED=1,
 *   3. register this hook (Stop) in .claude/settings.json.
 * Until all three, decide() never escalates and the live main() is a no-op (exit 0).
 */
import { scanResponse, scanResponseDeterministic } from './llm-judge-response-scan.mjs';
import { resolveJudgeConfig } from './llm-judge-config.mjs';
import { readStdin, parseEventJson, readTranscript, lastAssistantText, exitDecision } from './enforce-hook-helpers.mjs';
import { llmJudgeCall } from './llm-judge.mjs';
import { appendFileSync, mkdirSync } from 'node:fs';
import { join } from 'node:path';
import { homedir } from 'node:os';

/**
 * Pure decision. Stop-hook semantics: never blocks. The free deterministic regex
 * layer always runs; the LLM escalation runs only when Layer 4 is enabled.
 *   - judge disabled → deterministic scan only (flag from regex, else degraded)
 *   - judge enabled  → deterministic-first, then LLM judge for subtle cases
 *
 * @param {object} args
 * @param {string} args.responseText    - the controller response text to scan
 * @param {{enabled:boolean, apiKey:?string}} args.judgeConfig - resolveJudgeConfig() output
 * @param {Function} [args.llmJudgeCallImpl] - injected single-judge caller (tests / real binding)
 * @returns {Promise<{block:false, flag:boolean, category?:string, degraded?:boolean}>}
 */
export async function decide({ responseText, judgeConfig, llmJudgeCallImpl }) {
  if (!judgeConfig || !judgeConfig.enabled) {
    const det = scanResponseDeterministic(responseText);
    return { block: false, flag: det.flagged, category: det.category, degraded: !det.flagged };
  }
  const r = await scanResponse({ responseText, apiKey: judgeConfig.apiKey, llmJudgeCallImpl });
  return { block: false, flag: r.flag, category: r.category, degraded: r.degraded };
}

/**
 * Testable wiring core. Stop-hook semantics: block is always false. The free
 * deterministic regex scan runs even when the judge is disabled; the paid LLM
 * escalation runs only when judgeConfig.enabled (handled inside decide()).
 */
export async function runResponseScan({ transcript, judgeConfig, llmJudgeCallImpl, lastAssistantTextImpl = lastAssistantText }) {
  const responseText = lastAssistantTextImpl(transcript || []);
  const r = await decide({ responseText, judgeConfig, llmJudgeCallImpl });
  return { ...r, responseText };
}

function flagToFile({ sessionId, category, excerpt }) {
  try {
    const dir = join(homedir(), '.claude', 'runtime');
    mkdirSync(dir, { recursive: true });
    appendFileSync(join(dir, `rationalization-flags-${sessionId || 'unknown'}.jsonl`),
      JSON.stringify({
        ts: new Date().toISOString(),
        session_id: sessionId || null,
        type: 'controller_response_suspicious',
        category,
        response_excerpt: String(excerpt || '').slice(0, 200),
      }) + '\n');
  } catch { /* ignore */ }
}

async function main() {
  // Live wiring (2b). Stop hook: flag-only, NEVER blocks. The free deterministic
  // regex runs regardless ($0); the paid LLM escalation only when the config is
  // enabled (flag AND key). Fail-quiet.
  try {
    const event = parseEventJson(await readStdin());
    const transcript = readTranscript(event.transcript_path);
    const judgeConfig = resolveJudgeConfig();
    const r = await runResponseScan({
      transcript,
      judgeConfig,
      llmJudgeCallImpl: (opts) => llmJudgeCall(opts),
    });
    if (r.flag) flagToFile({ sessionId: event.session_id, category: r.category, excerpt: r.responseText });
    exitDecision({ block: false });
  } catch {
    exitDecision({ block: false });
  }
}

if ((process.argv[1] || '').replace(/\\/g, '/').endsWith('/enforce-llm-judge-response-scan.mjs')) {
  main().catch(() => process.exit(0));
}
