/**
 * SET 25b — delimiter oracle (runtime side).
 *
 * A recursive-convergence sweep that composes the substrate's existing
 * mechanical healers (regex-literal, string/template closers, brace/bracket
 * balance, parser-guided single steps) into ONE 0-token heal cascade. The
 * frozen list of demarcators lives in delimiterOracle.generated.ts (each
 * fixture verified at gen time); this file drives the corresponding runtime
 * candidates.
 *
 * Convergence contract:
 *   - Depth ≤5 total accepted edits (termination bound)
 *   - Each accepted edit STRICTLY reduces the score, else rolls back
 *     score = 2 if parse fails
 *           = 1 if parse OK but errorHint indicates unhealed regex-eval crash
 *           = 0 if fully clean
 *   - Returns null if no strict reduction is available.
 */
import {
  _registerDelimiterOracle,
  balanceClosers,
  healUnterminatedRegexLiteral,
  parserGuidedImplRepair,
} from './substrate';
import { suiteSyntaxError } from './testExec';
import { lookupDelimiterOracle } from './delimiterOracle.generated';

const MAX_DEPTH = 5;

/** Count unescaped `/` on a line (string/comment-aware). */
function oddUnescapedSlashes(line: string): boolean {
  let count = 0;
  let inStr: string | null = null;
  for (let i = 0; i < line.length; i++) {
    const c = line[i]!;
    if (inStr) {
      if (c === '\\') { i++; continue; }
      if (c === inStr) inStr = null;
      continue;
    }
    if (c === '"' || c === "'" || c === '`') { inStr = c; continue; }
    if (c === '/' && line[i + 1] === '/') break;
    if (c === '/' && line[i + 1] === '*') break;
    if (c === '\\') { i++; continue; }
    if (c === '/') count++;
  }
  return count > 0 && count % 2 === 1;
}

/**
 * Structural regex heal that also closes a hanging `[…` char class. Sucrase
 * parses `/[…/` as a still-unterminated char class (the / doesn't close),
 * so `healUnterminatedRegexLiteral` alone can't fix `/[a-z+;` — that's the
 * canonical run-57877cb1 shape. This walks a suspected regex literal on
 * each line and, when the char class is still open at EOL, appends `]/`.
 * When only the terminating `/` is missing, appends `/`.
 */
export function healUnterminatedRegexCharClass(content: string): string | null {
  const lines = content.split('\n');
  let changed = false;
  const next = lines.map(line => {
    const start = findRegexLiteralStart(line);
    if (start < 0) return line;
    let i = start + 1;
    let inClass = false;
    let closed = false;
    while (i < line.length) {
      const c = line[i]!;
      if (c === '\\') { i += 2; continue; }
      if (!inClass && c === '/') { closed = true; break; }
      if (!inClass && c === '[') inClass = true;
      else if (inClass && c === ']') inClass = false;
      i++;
    }
    if (closed) return line;
    changed = true;
    return line + (inClass ? ']/' : '/');
  });
  return changed ? next.join('\n') : null;
}

/** Locate a `/` at expression position on a line (rough — never inside a string). */
function findRegexLiteralStart(line: string): number {
  let inStr: string | null = null;
  for (let i = 0; i < line.length; i++) {
    const c = line[i]!;
    if (inStr) {
      if (c === '\\') { i++; continue; }
      if (c === inStr) inStr = null;
      continue;
    }
    if (c === '"' || c === "'" || c === '`') { inStr = c; continue; }
    if (c === '/' && line[i + 1] === '/') return -1; // line comment starts, no regex
    if (c === '/' && line[i + 1] === '*') return -1;
    if (c === '/') {
      const prev = line.slice(0, i).replace(/\s+$/, '');
      const p = prev[prev.length - 1];
      // Regex-position: after operator/opener or start of line.
      if (p == null || /[=(,\[!&|?:;+\-*<>~]/.test(p) || /(return|typeof|in|of|void|delete)\s*$/.test(prev)) {
        return i;
      }
      // Division-position — skip.
      return -1;
    }
  }
  return -1;
}

/** Append `*/` when a `/* …` block comment runs to EOF unclosed. */
function healUnclosedBlockComment(content: string): string | null {
  let i = 0;
  let inStr: string | null = null;
  while (i < content.length) {
    const c = content[i]!;
    if (inStr) {
      if (c === '\\') { i += 2; continue; }
      if (c === inStr) inStr = null;
      i++;
      continue;
    }
    if (c === '"' || c === "'" || c === '`') { inStr = c; i++; continue; }
    if (c === '/' && content[i + 1] === '/') {
      while (i < content.length && content[i] !== '\n') i++;
      continue;
    }
    if (c === '/' && content[i + 1] === '*') {
      const closeAt = content.indexOf('*/', i + 2);
      if (closeAt < 0) return content + '*/';
      i = closeAt + 2;
      continue;
    }
    i++;
  }
  return null;
}

/** Close whichever string/template quote is still open at EOF. */
function healUnclosedStringLiteral(content: string): string | null {
  let inStr: string | null = null;
  for (let i = 0; i < content.length; i++) {
    const c = content[i]!;
    if (inStr) {
      if (c === '\\') { i++; continue; }
      if (c === inStr) inStr = null;
      continue;
    }
    if (c === '"' || c === "'" || c === '`') { inStr = c; continue; }
    if (c === '/' && content[i + 1] === '/') {
      while (i < content.length && content[i] !== '\n') i++;
      continue;
    }
    if (c === '/' && content[i + 1] === '*') {
      const end = content.indexOf('*/', i + 2);
      if (end < 0) break;
      i = end + 1;
      continue;
    }
  }
  return inStr == null ? null : content + inStr;
}

/** Append `}` up to unmatched-open count at file scope (string/comment-aware). */
function healUnclosedBraces(content: string): string | null {
  let depth = 0;
  let inStr: string | null = null;
  let i = 0;
  while (i < content.length) {
    const c = content[i]!;
    if (inStr) {
      if (c === '\\') { i += 2; continue; }
      if (c === inStr) inStr = null;
      i++;
      continue;
    }
    if (c === '"' || c === "'" || c === '`') { inStr = c; i++; continue; }
    if (c === '/' && content[i + 1] === '/') {
      while (i < content.length && content[i] !== '\n') i++;
      continue;
    }
    if (c === '/' && content[i + 1] === '*') {
      const end = content.indexOf('*/', i + 2);
      i = end < 0 ? content.length : end + 2;
      continue;
    }
    if (c === '{') depth++;
    else if (c === '}') depth = Math.max(0, depth - 1);
    i++;
  }
  return depth > 0 ? content + '}'.repeat(depth) : null;
}

/**
 * Score: 0 = clean, 1 = eval-crash pending, 2 = parse-broken.
 * `errorHint` may be the runtime crash message (regex-literal at eval-time
 * doesn't necessarily fail sucrase parse — we still want to heal it).
 */
function scoreContent(content: string, errorHint?: string): number {
  const parseErr = suiteSyntaxError(content);
  if (parseErr != null) return 2;
  if (errorHint && /Invalid regular expression/i.test(errorHint)) {
    const lines = content.split('\n');
    const hasBadRegex = lines.some(oddUnescapedSlashes) || /\/\[[^\]]*$/m.test(content);
    return hasBadRegex ? 1 : 0;
  }
  return 0;
}

interface Candidate { next: string; classKey: string }

function gatherCandidates(current: string, hint?: string): Candidate[] {
  const out: Candidate[] = [];
  const push = (next: string | null | undefined, classKey: string) => {
    if (next && next !== current) out.push({ next, classKey });
  };

  // 1. Structural regex heals — special-cased for the run-57877cb1 shape.
  push(healUnterminatedRegexCharClass(current), 'regex-literal');
  push(healUnterminatedRegexLiteral(current), 'regex-literal');

  // 2. Unclosed string / template.
  push(healUnclosedStringLiteral(current), 'quote');

  // 3. Unclosed block comment.
  push(healUnclosedBlockComment(current), 'block-comment');

  // 4. Balance closers (paren / bracket / brace) + unclosed top-level braces.
  push(balanceClosers(current), 'balance');
  push(healUnclosedBraces(current), 'brace');

  // 5. Parser-guided single step. Only when sucrase reports a parse error.
  const parseErr = suiteSyntaxError(current);
  if (parseErr) {
    const guided = parserGuidedImplRepair(current, parseErr, 1);
    if (guided && guided.content !== current) {
      const entry = lookupDelimiterOracle(parseErr);
      push(guided.content, `parser-guided:${entry?.id ?? guided.token}`);
    }
  } else if (hint) {
    const guided = parserGuidedImplRepair(current, hint, 1);
    if (guided && guided.content !== current) {
      push(guided.content, `parser-guided:${guided.token}`);
    }
  }
  return out;
}

/**
 * SET 25b — recursive delimiter heal. Zero tokens. Applies mechanical edits
 * repeatedly (depth ≤5); every accepted edit STRICTLY reduces the parse-error
 * score. Regex-eval crashes are reduced when healUnterminatedRegex(*) succeeds
 * even if parse was already clean.
 *
 * @param content — file content (impl or frozen suite)
 * @param errorHint — the parse or eval error message that motivated the sweep
 * @returns healed content + classKey (of the first accepted edit) + edit count,
 *          or null when no improvement was possible
 */
export function applyDelimiterOracle(
  content: string,
  errorHint?: string,
): { content: string; classKey: string; edits: number } | null {
  const initialScore = scoreContent(content, errorHint);
  if (initialScore === 0) return null;

  let current = content;
  let edits = 0;
  let firstClass: string | null = null;

  for (let depth = 0; depth < MAX_DEPTH; depth++) {
    const before = scoreContent(current, errorHint);
    if (before === 0) break;

    const candidates = gatherCandidates(current, errorHint);
    let best: Candidate | null = null;
    let bestScore = before;
    for (const c of candidates) {
      const after = scoreContent(c.next, errorHint);
      if (after < bestScore) { best = c; bestScore = after; }
    }
    if (!best) break;
    if (firstClass == null) firstClass = best.classKey;
    current = best.next;
    edits++;
  }

  if (edits === 0 || current === content) return null;
  return { content: current, classKey: firstClass ?? 'unknown', edits };
}

/** Convenience: shape a delimiter-oracle heal into the mechanical-fix shape. */
export function delimiterOracleAsMechFix(
  path: string,
  originalContent: string,
  errorHint: string | undefined,
  crashClassKey: string,
): { path: string; content: string; note: string; classKey: string } | null {
  const heal = applyDelimiterOracle(originalContent, errorHint);
  if (!heal) return null;
  return {
    path,
    content: heal.content,
    note: `delimiter oracle heal (${heal.classKey}, ${heal.edits} edit${heal.edits === 1 ? '' : 's'}) in ${path}`,
    classKey: crashClassKey,
  };
}

// Register with the substrate so polishParseCrashContent can drive us without
// creating a static import cycle (substrate → delimiterOracle → substrate).
_registerDelimiterOracle(applyDelimiterOracle);
