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 = /\\.|\$\{[^}]*\}|\$[0-9]/gu;

function literalToken(
  id: number,
  raw: string,
  start: number,
): ReplacementToken {
  return {
    id: `java-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 number = /^\$([0-9]+)$/u.exec(raw)?.[1];
  if (number !== undefined) {
    const captureNumber = Number.parseInt(number, 10);
    return {
      id: `java-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 }),
    };
  }
  const name = /^\$\{([A-Za-z][A-Za-z0-9]*)\}$/u.exec(raw)?.[1];
  if (name) {
    return {
      id: `java-replacement-${id}`,
      kind: "named-capture",
      raw,
      range,
      explanation: `Java SE named capture “${name}” (unavailable in the bundled TeaVM 0.15.0 replacement runtime)`,
      captureName: name,
    };
  }
  return {
    id: `java-replacement-${id}`,
    kind: "engine-special",
    raw,
    range,
    explanation:
      "Java Matcher evaluates this quoted replacement character; the engine output is authoritative",
  };
}

export function parseJavaReplacement(
  replacement: string,
  captures: readonly CaptureDefinition[],
): ReplacementSyntaxResult {
  if (
    replacement.length > DEFAULT_REGEX_LIMITS.maximumReplacementTemplateUtf16
  ) {
    return {
      accepted: false,
      tokens: [],
      diagnostics: [
        {
          id: "java-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: "java",
          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: `java-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. Java Matcher remains authoritative at execution.`,
        range: token.range,
        flavour: "java",
        provenance: "derived",
      });
    } else if (token.kind === "named-capture") {
      diagnostics.push({
        id: `java-replacement-named-unsupported-${start}`,
        source: "syntax-provider",
        severity: "error",
        code: "engine-compatibility",
        message:
          "TeaVM 0.15.0 Matcher replacement implements only single-digit $n references. Java SE ${name} replacement is not available in this compatibility runtime.",
        range: token.range,
        flavour: "java",
        provenance: "reported",
      });
      if (
        token.captureName !== undefined &&
        names.size > 0 &&
        !names.has(token.captureName)
      ) {
        diagnostics.push({
          id: `java-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: "java",
          provenance: "derived",
        });
      }
    }
    cursor = start + raw.length;
  }
  if (cursor < replacement.length) {
    tokens.push(literalToken(tokens.length, replacement.slice(cursor), cursor));
  }
  return {
    accepted: !diagnostics.some(
      (diagnostic) => diagnostic.severity === "error",
    ),
    tokens,
    diagnostics,
    totalDiagnostics: diagnostics.length,
    diagnosticsTruncated: false,
  };
}
