import { DEFAULT_REGEX_LIMITS } from "../../../execution/request-limits";
import type { RegexDiagnostic } from "../../../model/diagnostics";
import type {
  CaptureDefinition,
  ReplacementSyntaxResult,
  ReplacementToken,
} from "../../../model/syntax";

const REPLACEMENT_TOKEN = /\\g<[^>]*>|\\[1-9][0-9]*|\\./gu;

function literalToken(
  id: number,
  raw: string,
  start: number,
): ReplacementToken {
  return {
    id: `python-replacement-${id}`,
    kind: "literal",
    raw,
    range: { startUtf16: start, endUtf16: start + raw.length },
    explanation: `Insert literal text ${JSON.stringify(raw.slice(0, 160))}${
      raw.length > 160 ? "…" : ""
    }`,
  };
}

function specialToken(
  id: number,
  raw: string,
  start: number,
): ReplacementToken {
  const range = { startUtf16: start, endUtf16: start + raw.length };
  const explicit = /^\\g<([^>]+)>$/u.exec(raw)?.[1];
  if (explicit && /^[0-9]+$/u.test(explicit)) {
    const captureNumber = Number.parseInt(explicit, 10);
    return {
      id: `python-replacement-${id}`,
      kind: captureNumber === 0 ? "full-match" : "numbered-capture",
      raw,
      range,
      explanation:
        captureNumber === 0
          ? "Insert the complete match"
          : `Insert numbered capture ${captureNumber}`,
      ...(captureNumber === 0 ? {} : { captureNumber }),
    };
  }
  if (explicit && /^[A-Za-z_][A-Za-z0-9_]*$/u.test(explicit)) {
    return {
      id: `python-replacement-${id}`,
      kind: "named-capture",
      raw,
      range,
      explanation: `Insert named capture “${explicit}”`,
      captureName: explicit,
    };
  }
  const shortNumber = /^\\([1-9][0-9]?)$/u.exec(raw)?.[1];
  if (shortNumber) {
    const captureNumber = Number.parseInt(shortNumber, 10);
    return {
      id: `python-replacement-${id}`,
      kind: "numbered-capture",
      raw,
      range,
      explanation: `Insert numbered capture ${captureNumber}`,
      captureNumber,
    };
  }
  return {
    id: `python-replacement-${id}`,
    kind: "engine-special",
    raw,
    range,
    explanation:
      "Python re evaluates this replacement escape; the engine output is authoritative",
  };
}

export function parsePythonReplacement(
  replacement: string,
  captures: readonly CaptureDefinition[],
): ReplacementSyntaxResult {
  if (
    replacement.length > DEFAULT_REGEX_LIMITS.maximumReplacementTemplateUtf16
  ) {
    return {
      accepted: false,
      tokens: [],
      diagnostics: [
        {
          id: "python-replacement-template-input-limit",
          source: "syntax-provider",
          severity: "error",
          code: "replacement-template-limit",
          message: `Replacement template exceeds the ${DEFAULT_REGEX_LIMITS.maximumReplacementTemplateUtf16.toLocaleString()} UTF-16 unit limit; it was not parsed or executed.`,
          flavour: "python",
          provenance: "derived",
        },
      ],
      totalDiagnostics: 1,
      diagnosticsTruncated: false,
    };
  }
  const numbers = new Set(captures.map((capture) => capture.number));
  const names = new Set(
    captures.flatMap((capture) => (capture.name ? [capture.name] : [])),
  );
  const tokens: ReplacementToken[] = [];
  const diagnostics: RegexDiagnostic[] = [];
  let cursor = 0;
  for (const match of replacement.matchAll(REPLACEMENT_TOKEN)) {
    const start = match.index;
    const raw = match[0];
    if (start > cursor) {
      tokens.push(
        literalToken(tokens.length, replacement.slice(cursor, start), cursor),
      );
    }
    const token = specialToken(tokens.length, raw, start);
    tokens.push(token);
    if (
      token.kind === "numbered-capture" &&
      token.captureNumber !== undefined &&
      !numbers.has(token.captureNumber)
    ) {
      diagnostics.push({
        id: `python-replacement-missing-number-${start}`,
        source: "syntax-provider",
        severity: "warning",
        code: "replacement-unknown-capture",
        message: `Replacement references capture ${token.captureNumber}, which the partial lexical provider did not find. Python re remains authoritative at execution.`,
        range: token.range,
        flavour: "python",
        provenance: "derived",
      });
    } else if (
      token.kind === "named-capture" &&
      token.captureName !== undefined &&
      names.size > 0 &&
      !names.has(token.captureName)
    ) {
      diagnostics.push({
        id: `python-replacement-missing-name-${start}`,
        source: "syntax-provider",
        severity: "warning",
        code: "replacement-unknown-capture",
        message: `Replacement references unknown named capture “${token.captureName}”.`,
        range: token.range,
        flavour: "python",
        provenance: "derived",
      });
    }
    cursor = start + raw.length;
  }
  if (cursor < replacement.length) {
    tokens.push(literalToken(tokens.length, replacement.slice(cursor), cursor));
  }
  return {
    accepted: true,
    tokens,
    diagnostics,
    totalDiagnostics: diagnostics.length,
    diagnosticsTruncated: false,
  };
}
