import { useMemo } from "react";
import type { NormalizedRegexNode } from "../regex/model/syntax";
import { handleTreeNavigation } from "./tree-navigation";

const MAXIMUM_RENDERED_EXPLANATION_NODES = 2_000;

export interface ExplanationTreeProps {
  readonly root?: NormalizedRegexNode;
  readonly selectedId?: string;
  readonly flags?: readonly string[];
  readonly stale?: boolean;
  readonly onSelect: (node: NormalizedRegexNode) => void;
}

function countNodes(root: NormalizedRegexNode): number {
  let count = 0;
  const pending = [root];
  while (pending.length > 0) {
    const node = pending.pop();
    if (!node) continue;
    count += 1;
    for (const child of node.children) pending.push(child);
  }
  return count;
}

function boundedRoot(
  root: NormalizedRegexNode,
  maximum: number,
): NormalizedRegexNode {
  let remaining = maximum;
  const takeNode = (
    node: NormalizedRegexNode,
  ): NormalizedRegexNode | undefined => {
    if (remaining <= 0) return undefined;
    remaining -= 1;
    const children: NormalizedRegexNode[] = [];
    for (const child of node.children) {
      const visible = takeNode(child);
      if (!visible) break;
      children.push(visible);
    }
    return { ...node, children };
  };
  return takeNode(root) ?? root;
}

function containsNode(root: NormalizedRegexNode, id: string): boolean {
  const pending = [root];
  while (pending.length > 0) {
    const node = pending.pop();
    if (!node) continue;
    if (node.id === id) return true;
    for (const child of node.children) pending.push(child);
  }
  return false;
}

function lengthLabel(node: NormalizedRegexNode): string {
  const minimum = node.properties.minimumLength;
  const maximum = node.properties.maximumLength;
  if (minimum === undefined && maximum === undefined) return "length unknown";
  return `${minimum ?? "?"}…${maximum === null ? "∞" : (maximum ?? "?")}`;
}

function consumesInputLabel(
  value: NormalizedRegexNode["properties"]["consumesInput"],
): string {
  return value === true ? "yes" : value === false ? "no" : value;
}

function nullableLabel(
  value: NormalizedRegexNode["properties"]["nullable"],
): string {
  return value === true ? "yes" : value === false ? "no" : value;
}

function ExplanationNode({
  node,
  selectedId,
  onSelect,
  depth,
  focusableId,
  flags,
}: {
  readonly node: NormalizedRegexNode;
  readonly selectedId?: string;
  readonly onSelect: (node: NormalizedRegexNode) => void;
  readonly depth: number;
  readonly focusableId: string;
  readonly flags: readonly string[];
}) {
  const compatibility =
    node.support.notes.length > 0
      ? node.support.notes.slice(0, 2).join("; ").slice(0, 240)
      : "no parser warning";
  return (
    <li role="none">
      <button
        type="button"
        role="treeitem"
        aria-level={depth}
        aria-selected={node.id === selectedId}
        tabIndex={node.id === focusableId ? 0 : -1}
        className={`tree-row ${node.id === selectedId ? "is-selected" : ""}`}
        onClick={() => onSelect(node)}
        onKeyDown={handleTreeNavigation}
      >
        <span className="tree-token">{node.raw || "∅"}</span>
        <span className="tree-explanation">{node.explanation}</span>
        <span className="tree-meta">
          {node.capture
            ? `group ${node.capture.number}${node.capture.name ? ` · ${node.capture.name}` : ""}`
            : node.quantifier
              ? `${node.quantifier.minimum}…${node.quantifier.maximum ?? "∞"} · ${node.quantifier.mode}`
              : node.properties.zeroWidth
                ? "zero-width"
                : lengthLabel(node)}
        </span>
        <span className="tree-details">
          <span>
            range: {node.range.startUtf16}…{node.range.endUtf16} UTF-16
          </span>
          <span>
            consumes: {consumesInputLabel(node.properties.consumesInput)}
          </span>
          <span>empty: {nullableLabel(node.properties.nullable)}</span>
          <span>length: {lengthLabel(node)}</span>
          <span>
            active flags:{" "}
            {(node.activeFlags ?? flags).length > 0
              ? (node.activeFlags ?? flags).join("")
              : "none"}
          </span>
          <span>
            flavour: {node.support.flavour} · {node.support.status}
          </span>
          <span>compatibility: {compatibility}</span>
          <span>risk: not assessed in this milestone</span>
          <span>
            parser: {node.provenance.provider} {node.provenance.providerVersion}{" "}
            · {node.provenance.source}
          </span>
        </span>
      </button>
      {node.children.length > 0 ? (
        <ul role="group">
          {node.children.map((child) => (
            <ExplanationNode
              key={child.id}
              node={child}
              selectedId={selectedId}
              onSelect={onSelect}
              depth={depth + 1}
              focusableId={focusableId}
              flags={flags}
            />
          ))}
        </ul>
      ) : null}
    </li>
  );
}

export function ExplanationTree({
  root,
  selectedId,
  flags = [],
  stale = false,
  onSelect,
}: ExplanationTreeProps) {
  const totalNodes = useMemo(() => (root ? countNodes(root) : 0), [root]);
  const visibleRoot = useMemo(
    () =>
      root ? boundedRoot(root, MAXIMUM_RENDERED_EXPLANATION_NODES) : undefined,
    [root],
  );
  const renderedNodes = Math.min(
    totalNodes,
    MAXIMUM_RENDERED_EXPLANATION_NODES,
  );
  const selectedNodeVisible =
    selectedId === undefined ||
    visibleRoot === undefined ||
    containsNode(visibleRoot, selectedId);
  const focusableId =
    selectedId && selectedNodeVisible ? selectedId : (visibleRoot?.id ?? "");

  return (
    <section className="panel tree-panel" aria-labelledby="explanation-heading">
      <header className="panel-heading">
        <div>
          <p className="eyebrow">Pattern structure</p>
          <h2 id="explanation-heading">Explanation tree</h2>
        </div>
        <span
          className={`provenance-badge ${stale ? "is-stale" : ""}`}
          data-testid={stale ? "stale-pattern-syntax" : undefined}
          title={
            stale
              ? "This is the last parsed pattern. Its ranges are not applied to the current editor."
              : undefined
          }
        >
          {stale
            ? "Stale snapshot · checking current pattern"
            : "Deterministic · syntax provider"}
        </span>
      </header>
      {totalNodes > renderedNodes ? (
        <p
          className="render-limit-banner"
          role="status"
          data-testid="explanation-render-limit"
        >
          <strong>Tree preview limited.</strong> Showing the first{" "}
          {renderedNodes.toLocaleString()} of {totalNodes.toLocaleString()}{" "}
          normalized syntax nodes. The complete normalized syntax tree remains
          available to the application.
          {!selectedNodeVisible
            ? " The selected pattern node is outside this bounded tree preview."
            : ""}
        </p>
      ) : null}
      {visibleRoot ? (
        <ul className="tree" role="tree" aria-label="Pattern explanation">
          <ExplanationNode
            node={visibleRoot}
            selectedId={selectedId}
            onSelect={onSelect}
            depth={1}
            focusableId={focusableId}
            flags={flags}
          />
        </ul>
      ) : (
        <p className="empty-state">The syntax worker is preparing the tree.</p>
      )}
    </section>
  );
}
