/**
 * SET 68 — judgment gates: mid-run HIL for spec-under-determination, the one
 * class no model can solve because it is a decision, not a bug.
 *
 * Trigger is DETERMINISTIC: a semantic finding survives L1 and L2, AND the
 * intent-oracle license lookup for the disputed expectation finds no chain
 * (case → edge/export → description → prompt span). Never mere difficulty.
 *
 * Supervised runs pause on a judgment card; unsupervised (⚡ opt-out) runs
 * take ONE fresh decoupled LLM ruling over the identical evidence card,
 * parsed deterministically — unparseable → drop (fail-safe). Rulings never
 * bypass mechanical validators: amended cases still face born-red +
 * execution; directed repairs still face strict-improvement. Provenance is
 * mandatory: operator ruling = license; LLM ruling = provisional, stamped,
 * relitigation candidate.
 */
import type { FeatureNode } from '../core/types';

export type JudgmentAction = 'amend' | 'direct' | 'drop';
export type JudgmentSource = 'operator' | 'llm';

export interface JudgmentEvidence {
  featureId: string;
  caseName: string;
  classKey: string;
  /** The failing case body (the disputed assertion). */
  assertion: string;
  expected?: string;
  received?: string;
  /** SET 64c exhibit page (already headed). */
  exhibit?: string;
  /** SET 64d trace page (already headed). */
  trace?: string;
  /** Human-readable description of the failed license search. */
  licenseSearch: string;
}

export interface JudgmentDecision {
  caseName: string;
  action: JudgmentAction;
  /** `direct` only: one intent line that becomes a licensed spec addendum. */
  intentText?: string;
  source: JudgmentSource;
}

/** Tokens worth searching the intent chain for (drop stopwords + tiny words). */
function keyTokens(text: string): string[] {
  return text.toLowerCase()
    .split(/[^a-z0-9]+/)
    .filter(t => t.length >= 4 && !['this', 'that', 'with', 'from', 'expect', 'toequal', 'tobe', 'const', 'return', 'true', 'false', 'null'].includes(t));
}

/**
 * Intent-oracle license lookup: does the disputed expectation trace to the
 * prompt through the standard chain (case → edge/export → description →
 * prompt span)? Deterministic token containment — no model involvement.
 */
export function licenseChainFor(opts: {
  caseName: string;
  expected?: string | null;
  feature: Pick<FeatureNode, 'description' | 'edgeCases' | 'exports'>;
  prompt: string;
}): { licensed: boolean; searched: string } {
  const corpus = [
    ...(opts.feature.edgeCases ?? []),
    opts.feature.description ?? '',
    ...(opts.feature.exports ?? []).map(x => x.symbol),
    opts.prompt,
  ].join('\n').toLowerCase();
  const probes: string[] = [];
  // The expected literal is the sharpest license: quoted verbatim in the
  // spec/prompt means the expectation is licensed.
  const expected = (opts.expected ?? '').trim().replace(/^["']|["']$/g, '');
  // Short literals (a lone digit, a boolean) cannot carry licensing weight in
  // either direction — only meaningful literals are searched or held against.
  const expectedMeaningful = expected.length >= 2 && !/^(true|false|null|undefined)$/.test(expected);
  if (expectedMeaningful && corpus.includes(expected.toLowerCase())) {
    return { licensed: true, searched: `expected literal "${expected}" found in intent chain` };
  }
  if (expectedMeaningful) probes.push(`expected literal "${expected}"`);
  const tokens = keyTokens(opts.caseName);
  const found = tokens.filter(t => corpus.includes(t));
  // Short names (export-anchor cases like "add") yield no searchable tokens;
  // fall back to raw containment so anchors never read as unlicensed.
  const nameTraces = tokens.length > 0
    ? found.length === tokens.length
    : corpus.includes(opts.caseName.toLowerCase().trim());
  probes.push(tokens.length
    ? `case tokens [${tokens.join(', ')}] → ${found.length}/${tokens.length} in chain`
    : `case name "${opts.caseName}" → ${nameTraces ? 'found' : 'absent'} in chain`);
  // Licensed when the name traces AND no meaningful expected literal is left
  // unvouched; the gate is for expectations the chain cannot answer for.
  const licensed = nameTraces && !expectedMeaningful;
  return {
    licensed,
    searched: `license search (case → edge/export → description → prompt): ${probes.join('; ')} — ${licensed ? 'chain found' : 'NO licensing chain'}`,
  };
}

/** The identical evidence card shown to the operator and to the LLM judge. */
export function formatJudgmentPrompt(e: JudgmentEvidence): string {
  return [
    'JUDGMENT GATE — the following expectation has no licensing chain to the prompt, and honest implementations keep failing it. This is a spec decision, not a bug.',
    `feature: ${e.featureId}`,
    `case: "${e.caseName}" [${e.classKey}]`,
    `assertion:\n${e.assertion}`,
    e.expected != null ? `expected: ${e.expected}` : '',
    e.received != null ? `received: ${e.received}` : '',
    e.exhibit ?? '',
    e.trace ?? '',
    e.licenseSearch,
    'Rule with EXACTLY this format (no prose):',
    'ACTION: amend | direct | drop',
    'INTENT: <one line — required for direct: the missing intent that licenses the expectation; otherwise leave empty>',
  ].filter(Boolean).join('\n\n');
}

/**
 * Deterministic parse of a ruling. Accepts only the canonical two-line form;
 * anything else → null (caller fail-safes to drop).
 */
export function parseLlmRuling(raw: string): { action: JudgmentAction; intentText?: string } | null {
  const m = /^\s*ACTION:\s*(amend|direct|drop)\s*$/im.exec(raw);
  if (!m) return null;
  const action = m[1]!.toLowerCase() as JudgmentAction;
  const im = /^\s*INTENT:\s*(.*)$/im.exec(raw);
  const intentText = im?.[1]?.trim() || undefined;
  if (action === 'direct' && !intentText) return null;
  return { action, intentText: action === 'direct' ? intentText : undefined };
}

export function formatRulingStamp(source: JudgmentSource, ts: string): string {
  return source === 'operator'
    ? `operator decision @ ${ts}`
    : `llm ruling @ ${ts} (provisional)`;
}

export function formatJudgmentEvent(d: JudgmentDecision, ts: string): string {
  const act = d.action === 'amend'
    ? 'amend case to accept implementation behavior'
    : d.action === 'direct'
      ? `direct repair — licensed intent addendum: "${d.intentText ?? ''}"`
      : 'drop case (coverage reduced, honest)';
  return `judgment: case "${d.caseName}" — ${act} [${formatRulingStamp(d.source, ts)}]`;
}
