/**
 * SET 64c — flawed-exhibit page: a deterministic excerpt of the defective
 * node, attached to EVERY repair-stage assembly (L2 and L3 draws).
 *
 * Doctrine: the exhibit is evidence, not a patch base — rewrite-from-contract
 * stays intact. Position-bearing classes excerpt the enclosing node at the
 * failure position; assertion-family classes fall back to the span (or the
 * provable absence) of the symbol under test.
 */
import type { GeneratedFile } from '../core/types';
import type { ClassifiedFailure } from './substrate';
import { drawHash } from './metaSb';

export const EXHIBIT_HEADER =
  'flawed exhibit — evidence of the defect, not a base to patch; produce the correct node from the contract';

/** Excerpt budget (chars) — one node, line minimum. */
export const EXHIBIT_CAP = 300;

export interface FlawedExhibit {
  /** Full page text including the anti-anchoring header. */
  text: string;
  /** Hash of the excerpt — recorded on metasupercache candidate records. */
  hash: string;
  /** Where the excerpt came from. */
  source: 'position' | 'symbol-span' | 'symbol-absent';
}

/** First `path:line` or `line N` coordinate found in a failure message. */
export function extractLineHint(message: string, file: string): number | null {
  const base = file.replace(/^.*\//, '').replace(/\./g, '\\.');
  const at = new RegExp(`${base}:(\\d+)`).exec(message);
  if (at) return parseInt(at[1]!, 10);
  const ln = /\bline (\d+)\b/.exec(message);
  return ln ? parseInt(ln[1]!, 10) : null;
}

/** Enclosing function/statement span for a 1-based line, brace-matched, capped. */
export function extractLineSpan(content: string, line: number): string | null {
  const lines = content.split('\n');
  if (line < 1 || line > lines.length) return null;
  let start = line - 1;
  for (let i = line - 1; i >= 0; i--) {
    if (/(^|\s)(export\s+)?(async\s+)?function\s|=>\s*\{|(^|\s)const\s+\w+\s*=|(^|\s)class\s+\w+/.test(lines[i]!)) {
      start = i;
      break;
    }
  }
  let depth = 0;
  let end = line - 1;
  let opened = false;
  for (let i = start; i < lines.length; i++) {
    for (const ch of lines[i]!) {
      if (ch === '{') { depth++; opened = true; }
      else if (ch === '}') depth--;
    }
    end = i;
    if (opened && depth <= 0) break;
    if (i - start > 24) break;
  }
  const span = lines.slice(start, end + 1).join('\n').trim();
  return span ? clip(span) : null;
}

/** Span of an exported symbol's declaration node, or null when absent. */
export function extractSymbolSpan(content: string, symbol: string): string | null {
  const sym = symbol.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const decl = new RegExp(
    `(export\\s+)?(async\\s+)?(function\\s+${sym}\\s*\\(|const\\s+${sym}\\s*[:=]|class\\s+${sym}\\b|${sym}\\s*\\([^)]*\\)\\s*\\{)`,
  ).exec(content);
  if (!decl) return null;
  const from = content.lastIndexOf('\n', decl.index) + 1;
  let depth = 0;
  let opened = false;
  let end = content.length;
  for (let i = decl.index; i < content.length; i++) {
    const ch = content[i]!;
    if (ch === '{') { depth++; opened = true; }
    else if (ch === '}') {
      depth--;
      if (opened && depth <= 0) { end = i + 1; break; }
    } else if (!opened && (ch === ';' || ch === '\n') && i > decl.index + decl[0].length) {
      end = i + 1;
      break;
    }
  }
  return clip(content.slice(from, end).trim());
}

function clip(s: string): string {
  return s.length > EXHIBIT_CAP ? `${s.slice(0, EXHIBIT_CAP)}…` : s;
}

/**
 * Build the exhibit for a classified failure against the current artifact.
 * Returns null only when there is neither a position nor a symbol to cite.
 */
export function buildFlawedExhibit(opts: {
  cls: ClassifiedFailure | null | undefined;
  files: GeneratedFile[];
  /** Symbol under test (feature export) for the assertion-family fallback. */
  symbol?: string | null;
  /** Preferred file when the class carries no file attribution. */
  fallbackFile?: string | null;
}): FlawedExhibit | null {
  const cls = opts.cls;
  const filePath = cls?.detail.file && !/\.test\./.test(cls.detail.file)
    ? cls.detail.file
    : opts.fallbackFile ?? null;
  const file = filePath ? opts.files.find(f => f.path === filePath) ?? null : null;

  if (file && cls) {
    const line = extractLineHint(cls.message, file.path);
    if (line != null) {
      const span = extractLineSpan(file.content, line);
      if (span) return finish(`[${file.path}:${line}]\n${span}`, 'position');
    }
  }

  const symbol = cls?.detail.symbol ?? opts.symbol ?? null;
  if (symbol) {
    for (const f of file ? [file] : opts.files) {
      const span = extractSymbolSpan(f.content, symbol);
      if (span) return finish(`[${f.path} · ${symbol}]\n${span}`, 'symbol-span');
    }
    const where = file?.path ?? opts.files.map(f => f.path).join(', ') ?? '(no artifact)';
    return finish(`symbol "${symbol}" is provably absent from ${where}`, 'symbol-absent');
  }
  return null;
}

function finish(body: string, source: FlawedExhibit['source']): FlawedExhibit {
  return {
    text: `Page exhibit — ${EXHIBIT_HEADER}:\n${body}`,
    hash: drawHash(body),
    source,
  };
}
