import type {
  CaptureResult,
  ExtractionNode,
  RegexMatchResult,
} from "../model/match";
import type { CaptureDefinition } from "../model/syntax";

function captureLabel(
  result: CaptureResult,
  definition: CaptureDefinition | undefined,
): string {
  const name = result.groupName ?? definition?.name;
  return `Group ${result.groupNumber}${name ? ` — ${name}` : ""}`;
}

function buildCaptureNode(
  result: CaptureResult,
  definitions: ReadonlyMap<number, CaptureDefinition>,
  childNumbers: ReadonlyMap<number, readonly number[]>,
  results: ReadonlyMap<number, CaptureResult>,
  matchNumber: number,
): ExtractionNode {
  const definition = definitions.get(result.groupNumber);
  const children = (childNumbers.get(result.groupNumber) ?? [])
    .map((number) => results.get(number))
    .filter((capture): capture is CaptureResult => capture !== undefined)
    .map((capture) =>
      buildCaptureNode(
        capture,
        definitions,
        childNumbers,
        results,
        matchNumber,
      ),
    );
  return {
    id: `match-${matchNumber}-capture-${result.groupNumber}`,
    kind: "capture",
    label: captureLabel(result, definition),
    groupNumber: result.groupNumber,
    ...((result.groupName ?? definition?.name)
      ? { groupName: result.groupName ?? definition?.name }
      : {}),
    ...(result.value === undefined ? {} : { value: result.value }),
    ...(result.range ? { range: result.range } : {}),
    ...(result.nativeRange ? { nativeRange: result.nativeRange } : {}),
    status: result.status,
    children,
    provenance: "engine-result",
  };
}

export function buildExtractionTree(
  matches: readonly RegexMatchResult[],
  captures: readonly CaptureDefinition[],
): readonly ExtractionNode[] {
  const definitions = new Map(
    captures.map((capture) => [capture.number, capture]),
  );
  const childNumbers = new Map<number, number[]>();
  const roots: number[] = [];
  for (const capture of captures) {
    if (capture.parentCaptureNumber === undefined) {
      roots.push(capture.number);
      continue;
    }
    const children = childNumbers.get(capture.parentCaptureNumber) ?? [];
    children.push(capture.number);
    childNumbers.set(capture.parentCaptureNumber, children);
  }

  return matches.map((match) => {
    const results = new Map(
      match.captures.map((capture) => [capture.groupNumber, capture]),
    );
    const fullMatch: ExtractionNode = {
      id: `match-${match.matchNumber}-full`,
      kind: "derived",
      label: "Full match",
      value: match.value,
      range: match.range,
      nativeRange: match.nativeRange,
      status:
        match.valueStatus === "truncated"
          ? "truncated"
          : match.value.length === 0
            ? "matched-empty"
            : "participated",
      children: [],
      provenance: "engine-result",
    };
    const captureNodes = roots
      .map((number) => results.get(number))
      .filter((capture): capture is CaptureResult => capture !== undefined)
      .map((capture) =>
        buildCaptureNode(
          capture,
          definitions,
          childNumbers,
          results,
          match.matchNumber,
        ),
      );
    return {
      id: `match-${match.matchNumber}`,
      kind: "match",
      label: `Match ${match.matchNumber} — ${match.range.startUtf16}..${match.range.endUtf16}`,
      value: match.value,
      range: match.range,
      nativeRange: match.nativeRange,
      status:
        match.valueStatus === "truncated"
          ? "truncated"
          : match.value.length === 0
            ? "matched-empty"
            : "participated",
      children: [fullMatch, ...captureNodes],
      provenance: "engine-result",
    };
  });
}
