import { CodeEditor } from "../editors/CodeEditor";
import type {
  ReplacementSyntaxResult,
  ReplacementToken,
  SourceRange,
} from "../regex/model/syntax";
import type { RegexReplacementResult } from "../regex/model/match";
import type {
  ReplacementContributionPreview,
  ReplacementPreview,
} from "../regex/replacement/replacement-preview";
import type { RegexFlavourId } from "../regex/model/flavour";
import {
  DEFAULT_REGEX_LIMITS,
  TEMPLATE_PRESENTATION_LIMITS,
} from "../regex/execution/request-limits";
import { handleTreeNavigation } from "./tree-navigation";
import { ReplacementPreviewPanel } from "./ReplacementPreviewPanel";

const TEMPLATE_FLAVOUR_LABELS: Readonly<Record<RegexFlavourId, string>> = {
  ecmascript: "JavaScript (ECMAScript)",
  pcre2: "PCRE2",
  pcre: "PCRE",
  php: "PHP preg / PCRE2",
  perl: "Perl (legacy WebPerl)",
  python: "Python re",
  ruby: "Ruby Regexp",
  cpp: "C++ std::regex",
  go: "Go regexp",
  rust: "Rust regex",
  dotnet: ".NET Regex",
  java: "Java Pattern",
  scala: "Scala Regex / Java compatibility",
};

function ReplacementTokenRow({
  token,
  selected,
  focusable,
  onSelect,
}: {
  readonly token: ReplacementToken;
  readonly selected: boolean;
  readonly focusable: boolean;
  readonly onSelect: (token: ReplacementToken) => void;
}) {
  const rawPreview =
    token.raw.length <= 160 ? token.raw : `${token.raw.slice(0, 160)}…`;
  return (
    <li role="none">
      <button
        type="button"
        role="treeitem"
        aria-level={1}
        aria-selected={selected}
        tabIndex={focusable ? 0 : -1}
        className={`token-row ${selected ? "is-selected" : ""}`}
        onClick={() => onSelect(token)}
        onKeyDown={handleTreeNavigation}
      >
        <code>{rawPreview || "∅"}</code>
        <span>{token.explanation}</span>
        <small>{token.kind.replaceAll("-", " ")}</small>
      </button>
    </li>
  );
}

export function ReplacementPanel({
  flavour,
  replacement,
  onReplacementChange,
  syntax,
  result,
  preview,
  selectedToken,
  selectedContributionId,
  selectedOutputRange,
  onSelectToken,
  onSelectContribution,
}: {
  readonly flavour?: RegexFlavourId;
  readonly replacement: string;
  readonly onReplacementChange: (value: string) => void;
  readonly syntax?: ReplacementSyntaxResult;
  readonly result?: RegexReplacementResult;
  readonly preview?: ReplacementPreview;
  readonly selectedToken?: ReplacementToken;
  readonly selectedContributionId?: string;
  readonly selectedOutputRange?: SourceRange;
  readonly onSelectToken: (token: ReplacementToken) => void;
  readonly onSelectContribution: (
    contribution: ReplacementContributionPreview,
  ) => void;
}) {
  const activeFlavour =
    flavour ?? result?.execution.engine.flavour ?? "ecmascript";
  const engineLabel = TEMPLATE_FLAVOUR_LABELS[activeFlavour];
  const retainedTokens = syntax?.tokens ?? [];
  const editorTokens = retainedTokens.slice(
    0,
    TEMPLATE_PRESENTATION_LIMITS.replacementEditorMarks,
  );
  const renderedTokens = retainedTokens.slice(
    0,
    TEMPLATE_PRESENTATION_LIMITS.replacementTokenRows,
  );
  const selectedTokenRendered = renderedTokens.some(
    (token) => token.id === selectedToken?.id,
  );
  const inputLimited =
    replacement.length > DEFAULT_REGEX_LIMITS.maximumReplacementTemplateUtf16;

  return (
    <div className="mode-grid">
      <section className="panel editor-panel">
        <header className="panel-heading">
          <div>
            <p className="eyebrow">{engineLabel} template syntax</p>
            <h2>Replacement</h2>
          </div>
          <span className="provenance-badge">Templates only · no code</span>
        </header>
        <CodeEditor
          value={replacement}
          onChange={onReplacementChange}
          label="Replacement template"
          className="replacement-editor"
          maximumLengthUtf16={
            DEFAULT_REGEX_LIMITS.maximumReplacementTemplateUtf16
          }
          selectedRange={selectedToken?.range}
          marks={[
            ...editorTokens.map((token) => ({
              id: token.id,
              range: token.range,
              className: `cm-replacement-token cm-replacement-${token.kind}`,
              label: token.explanation,
            })),
            ...(selectedToken
              ? [
                  {
                    id: "selected-replacement-token",
                    range: selectedToken.range,
                    className: "cm-selected-range",
                    label: "Selected replacement token",
                  },
                ]
              : []),
          ]}
          placeholder="Use $&, $1, or $<name>"
        />
        {inputLimited ? (
          <p
            className="render-limit-banner"
            role="alert"
            data-testid="replacement-input-limit"
          >
            <strong>Replacement template rejected.</strong> This input has{" "}
            {replacement.length.toLocaleString()} UTF-16 units; parsing and
            execution stop above the{" "}
            {DEFAULT_REGEX_LIMITS.maximumReplacementTemplateUtf16.toLocaleString()}{" "}
            unit limit.
          </p>
        ) : null}
        {retainedTokens.length >
        TEMPLATE_PRESENTATION_LIMITS.replacementEditorMarks ? (
          <p
            className="render-limit-banner"
            data-testid="replacement-mark-render-limit"
          >
            <strong>Editor decorations limited.</strong> Showing the first{" "}
            {TEMPLATE_PRESENTATION_LIMITS.replacementEditorMarks.toLocaleString()}{" "}
            of {retainedTokens.length.toLocaleString()} complete replacement
            tokens.
          </p>
        ) : null}
      </section>
      <section
        className="panel token-panel"
        aria-labelledby="replacement-tree-heading"
      >
        <header className="panel-heading">
          <div>
            <p className="eyebrow">Derived token explanation</p>
            <h2 id="replacement-tree-heading">Replacement tokens</h2>
          </div>
        </header>
        {renderedTokens.length > 0 ? (
          <>
            {retainedTokens.length >
            TEMPLATE_PRESENTATION_LIMITS.replacementTokenRows ? (
              <p
                className="render-limit-banner"
                data-testid="replacement-token-render-limit"
              >
                <strong>Token list limited.</strong> Showing the first{" "}
                {TEMPLATE_PRESENTATION_LIMITS.replacementTokenRows.toLocaleString()}{" "}
                of {retainedTokens.length.toLocaleString()} complete tokens. The
                full template remains the execution input.
              </p>
            ) : null}
            <ul
              className="token-list"
              role="tree"
              aria-label="Replacement token explanation"
            >
              {renderedTokens.map((token) => (
                <ReplacementTokenRow
                  key={token.id}
                  token={token}
                  selected={selectedToken?.id === token.id}
                  focusable={
                    selectedTokenRendered
                      ? selectedToken?.id === token.id
                      : token.id === renderedTokens[0]?.id
                  }
                  onSelect={onSelectToken}
                />
              ))}
            </ul>
          </>
        ) : (
          <p className="empty-state">
            {syntax?.accepted === false
              ? "Token parsing is unavailable until the template is within the input limit."
              : "Enter a replacement template."}
          </p>
        )}
        {syntax?.diagnosticsTruncated ? (
          <p
            className="render-limit-banner"
            data-testid="replacement-diagnostic-render-limit"
          >
            <strong>Diagnostics limited.</strong> The diagnostics panel retains
            at most{" "}
            {TEMPLATE_PRESENTATION_LIMITS.replacementDiagnostics.toLocaleString()}{" "}
            entries, including a summary, for{" "}
            {(
              syntax.totalDiagnostics ?? syntax.diagnostics.length
            ).toLocaleString()}{" "}
            findings.
          </p>
        ) : null}
      </section>
      <ReplacementPreviewPanel
        preview={preview}
        selectedContributionId={selectedContributionId}
        onSelectContribution={onSelectContribution}
      />
      <section className="panel output-panel mode-span">
        <header className="panel-heading">
          <div>
            <p className="eyebrow">
              Native matches · bounded {engineLabel} substitution
            </p>
            <h2>Replacement output</h2>
          </div>
          <span>
            {!result
              ? "Waiting for a valid pattern"
              : result.outputTruncated
                ? `≥ ${result.outputBytes.toLocaleString()} UTF-8 bytes · output preview truncated`
                : result.execution.truncated
                  ? `${result.outputBytes.toLocaleString()} UTF-8 bytes · partial · match/capture limit reached`
                  : `${result.outputBytes.toLocaleString()} UTF-8 bytes · complete`}
          </span>
        </header>
        {result?.execution.truncated ? (
          <p
            className="render-limit-banner"
            role="alert"
            data-testid="replacement-match-limit"
          >
            <strong>Replacement application is partial.</strong> Only the
            authoritative matches returned before the match or capture-row limit
            were replaced; later subject text remains unchanged.
          </p>
        ) : null}
        <CodeEditor
          value={result?.output ?? ""}
          label="Replacement output"
          className="output-editor"
          readOnly
          lineNumberGutter
          selectedRange={selectedOutputRange}
          marks={
            selectedOutputRange
              ? [
                  {
                    id: "selected-replacement-output",
                    range: selectedOutputRange,
                    className: "cm-selected-range",
                    label: "Selected replacement contribution",
                    zeroWidth:
                      selectedOutputRange.startUtf16 ===
                      selectedOutputRange.endUtf16,
                  },
                ]
              : []
          }
        />
      </section>
    </div>
  );
}
