import { useEffect, useMemo, useRef, useState } from "react";
import { writeClipboardText } from "../browser/clipboard";
import {
  AVAILABLE_REGEX_FLAVOURS,
  defaultRegexOptions,
} from "../regex/flavours/flavour-registry";
import {
  DEFAULT_REGEX_LIMITS,
  utf8ByteLength,
} from "../regex/execution/request-limits";
import type {
  RegexEngineOptions,
  RegexFlavourId,
} from "../regex/model/flavour";
import type { RegexTestExpectation } from "../regex/tests/test-case.types";
import { SubjectMinimizer } from "../regex/minimization/SubjectMinimizer";
import type {
  EngineFailureOracle,
  SubjectMinimizationProgress,
  SubjectMinimizationRequest,
  SubjectMinimizationResult,
} from "../regex/minimization/minimization.types";
import "./MinimizePanel.css";

type MinimizationMode =
  | "unit-test-failure"
  | "capture-range-mismatch"
  | "engine-timeout"
  | "comparison-mismatch";

type UnitExpectationKind = RegexTestExpectation["kind"];

export interface SubjectMinimizerClient {
  minimize(
    request: SubjectMinimizationRequest,
    onProgress?: (progress: SubjectMinimizationProgress) => void,
  ): Promise<SubjectMinimizationResult>;
  cancel(): void;
  dispose(): void;
}

export interface MinimizePanelProps {
  readonly activeFlavour: RegexFlavourId;
  readonly activeFlavourVersion: string;
  readonly activeFlags: readonly string[];
  readonly activeOptions: RegexEngineOptions;
  readonly pattern: string;
  readonly subject: string;
  readonly replacement: string;
  readonly scanAll: boolean;
  readonly timeoutMs: number;
  readonly onUseSubject: (subject: string) => void;
  readonly onClose: () => void;
  readonly createMinimizer?: () => SubjectMinimizerClient;
}

const ECMASCRIPT = AVAILABLE_REGEX_FLAVOURS.require("ecmascript");
const PCRE2 = AVAILABLE_REGEX_FLAVOURS.require("pcre2");

function flagsFor(
  activeFlavour: RegexFlavourId,
  activeFlags: readonly string[],
  flavour: "ecmascript" | "pcre2",
): readonly string[] {
  const definition = flavour === "ecmascript" ? ECMASCRIPT : PCRE2;
  return activeFlavour === flavour
    ? definition.flags
        .map((flag) => flag.value)
        .filter((flag) => activeFlags.includes(flag))
    : definition.defaultFlags;
}

function optionsFor(
  activeFlavour: RegexFlavourId,
  activeOptions: RegexEngineOptions,
  flavour: "ecmascript" | "pcre2",
): RegexEngineOptions {
  if (activeFlavour === flavour) return activeOptions;
  return defaultRegexOptions(flavour === "ecmascript" ? ECMASCRIPT : PCRE2);
}

function parseCaptureSelector(value: string): {
  readonly groupNumber?: number;
  readonly groupName?: string;
} {
  const trimmed = value.trim();
  const number = Number(trimmed);
  return trimmed !== "" && Number.isSafeInteger(number) && number > 0
    ? { groupNumber: number }
    : { groupName: trimmed };
}

function resultLabel(result: SubjectMinimizationResult): string {
  if (result.locallyMinimal) {
    return "Locally minimal under the documented transforms";
  }
  return result.stopReason.replaceAll("-", " ");
}

function Observation({
  label,
  observation,
}: {
  readonly label: string;
  readonly observation: SubjectMinimizationResult["baseline"];
}) {
  return (
    <article className={`minimizer-observation is-${observation.status}`}>
      <strong>{label}</strong>
      <span>
        {observation.status} ·{" "}
        {observation.reproduced ? "predicate reproduced" : "not reproduced"}
      </span>
      <p>{observation.summary}</p>
      {observation.fingerprint.length > 0 ? (
        <code>{observation.fingerprint.join(" · ")}</code>
      ) : null}
      {observation.sides?.length ? (
        <small>
          {observation.sides
            .map(
              (side) =>
                `${side.flavour}: ${side.status}${
                  side.engineIdentity ? ` · ${side.engineIdentity}` : ""
                }`,
            )
            .join(" | ")}
        </small>
      ) : null}
    </article>
  );
}

export function MinimizePanel({
  activeFlavour,
  activeFlavourVersion,
  activeFlags,
  activeOptions,
  pattern: initialPattern,
  subject: initialSubject,
  replacement: initialReplacement,
  scanAll: initialScanAll,
  timeoutMs: initialTimeoutMs,
  onUseSubject,
  onClose,
  createMinimizer = () => new SubjectMinimizer(),
}: MinimizePanelProps) {
  const [mode, setMode] = useState<MinimizationMode>("unit-test-failure");
  const [subject, setSubject] = useState(initialSubject);
  const [scanAll, setScanAll] = useState(initialScanAll);
  const [unitKind, setUnitKind] =
    useState<UnitExpectationKind>("should-not-match");
  const [expectedCount, setExpectedCount] = useState(1);
  const [matchIndex, setMatchIndex] = useState(0);
  const [expectedValue, setExpectedValue] = useState("");
  const [captureSelector, setCaptureSelector] = useState("1");
  const [captureStatus, setCaptureStatus] = useState<
    "participated" | "did-not-participate" | "matched-empty"
  >("participated");
  const [checkCaptureValue, setCheckCaptureValue] = useState(false);
  const [expectedStart, setExpectedStart] = useState(0);
  const [expectedEnd, setExpectedEnd] = useState(0);
  const [durationLimitMs, setDurationLimitMs] = useState(10);
  const [comparisonOperation, setComparisonOperation] = useState<
    "match" | "replace"
  >("match");
  const [ecmaPattern, setEcmaPattern] = useState(initialPattern);
  const [pcrePattern, setPcrePattern] = useState(initialPattern);
  const [ecmaReplacement, setEcmaReplacement] = useState(initialReplacement);
  const [pcreReplacement, setPcreReplacement] = useState(initialReplacement);
  const [maximumEvaluations, setMaximumEvaluations] = useState(500);
  const [maximumWallTimeMs, setMaximumWallTimeMs] = useState(15_000);
  const [candidateTimeoutMs, setCandidateTimeoutMs] = useState(
    Math.min(
      DEFAULT_REGEX_LIMITS.advancedMaximumTimeoutMs,
      Math.max(1, initialTimeoutMs),
    ),
  );
  const [running, setRunning] = useState(false);
  const [status, setStatus] = useState(
    "Select an exact failure predicate, then verify the baseline.",
  );
  const [progress, setProgress] = useState<SubjectMinimizationProgress>();
  const [result, setResult] = useState<SubjectMinimizationResult>();
  const [copyStatus, setCopyStatus] = useState("");
  const minimizer = useRef<SubjectMinimizerClient | null>(null);
  const revision = useRef(0);

  useEffect(
    () => () => {
      revision.current += 1;
      minimizer.current?.dispose();
      minimizer.current = null;
    },
    [],
  );

  const ecmaFlags = useMemo(
    () => flagsFor(activeFlavour, activeFlags, "ecmascript"),
    [activeFlags, activeFlavour],
  );
  const pcreFlags = useMemo(
    () => flagsFor(activeFlavour, activeFlags, "pcre2"),
    [activeFlags, activeFlavour],
  );
  const ecmaOptions = useMemo(
    () => optionsFor(activeFlavour, activeOptions, "ecmascript"),
    [activeFlavour, activeOptions],
  );
  const pcreOptions = useMemo(
    () => optionsFor(activeFlavour, activeOptions, "pcre2"),
    [activeFlavour, activeOptions],
  );
  const activeEngineSupported =
    activeFlavour === "ecmascript" || activeFlavour === "pcre2";

  const unitExpectation = (): RegexTestExpectation => {
    switch (unitKind) {
      case "should-match":
      case "should-not-match":
      case "must-time-out":
        return { kind: unitKind };
      case "match-count":
        return { kind: unitKind, count: expectedCount };
      case "full-match":
        return {
          kind: unitKind,
          matchIndex,
          value: expectedValue,
        };
      case "capture":
        return {
          kind: unitKind,
          matchIndex,
          ...parseCaptureSelector(captureSelector),
          status: captureStatus,
          ...(checkCaptureValue ? { value: expectedValue } : {}),
        };
      case "replacement":
        return { kind: unitKind, expected: expectedValue };
      case "must-complete-within":
        return { kind: unitKind, milliseconds: durationLimitMs };
    }
  };

  const activeSide = {
    flavour:
      activeFlavour === "pcre2" ? ("pcre2" as const) : ("ecmascript" as const),
    flavourVersion: activeEngineSupported
      ? activeFlavourVersion
      : ECMASCRIPT.defaultVersion,
    pattern: initialPattern,
    flags: activeEngineSupported ? activeFlags : ECMASCRIPT.defaultFlags,
    options: activeEngineSupported
      ? activeOptions
      : defaultRegexOptions(ECMASCRIPT),
    ...(unitKind === "replacement" ? { replacement: initialReplacement } : {}),
  };

  const request = (): SubjectMinimizationRequest => {
    let target: SubjectMinimizationRequest["target"];
    if (mode === "comparison-mismatch") {
      target = {
        kind: "comparison-mismatch",
        operation: comparisonOperation,
        scanAll,
        sides: [
          {
            flavour: "ecmascript",
            flavourVersion: ECMASCRIPT.defaultVersion,
            pattern: ecmaPattern,
            flags: ecmaFlags,
            options: ecmaOptions,
            ...(comparisonOperation === "replace"
              ? { replacement: ecmaReplacement }
              : {}),
          },
          {
            flavour: "pcre2",
            flavourVersion: PCRE2.defaultVersion,
            pattern: pcrePattern,
            flags: pcreFlags,
            options: pcreOptions,
            ...(comparisonOperation === "replace"
              ? { replacement: pcreReplacement }
              : {}),
          },
        ],
      };
    } else {
      let oracle: EngineFailureOracle;
      if (mode === "engine-timeout") {
        oracle = { kind: "engine-timeout" };
      } else if (mode === "capture-range-mismatch") {
        oracle = {
          kind: "capture-range-mismatch",
          matchIndex,
          ...parseCaptureSelector(captureSelector),
          expectedRange: {
            startUtf16: expectedStart,
            endUtf16: expectedEnd,
          },
        };
      } else {
        oracle = {
          kind: "unit-test-failure",
          expectation: unitExpectation(),
        };
      }
      target = {
        kind: "engine",
        side: activeSide,
        scanAll,
        oracle,
      };
    }
    return {
      schemaVersion: 1,
      subject,
      target,
      budgets: {
        maximumEvaluations,
        maximumWallTimeMs,
        candidateTimeoutMs,
      },
      maximumMatches: 1_000,
      maximumCaptureRows: 10_000,
      maximumOutputBytes: 1024 * 1024,
    };
  };

  const run = async () => {
    if (mode !== "comparison-mismatch" && !activeEngineSupported) {
      setStatus(
        `Subject minimization is not yet implemented for ${activeFlavour}; select ECMAScript or PCRE2, or use the explicit ECMAScript ↔ PCRE2 comparison target.`,
      );
      return;
    }
    const currentRevision = ++revision.current;
    setRunning(true);
    setResult(undefined);
    setCopyStatus("");
    setStatus("Verifying the baseline with the selected real engine worker…");
    try {
      minimizer.current ??= createMinimizer();
      const minimized = await minimizer.current.minimize(request(), (value) => {
        if (currentRevision !== revision.current) return;
        setProgress(value);
        setStatus(
          `${value.phase.replaceAll("-", " ")} · ${value.evaluations}/${value.maximumEvaluations} evaluations · ${value.acceptedReductions} accepted`,
        );
      });
      if (currentRevision !== revision.current) return;
      setResult(minimized);
      setStatus(resultLabel(minimized));
    } catch (error) {
      if (currentRevision !== revision.current) return;
      setStatus(error instanceof Error ? error.message : String(error));
    } finally {
      if (currentRevision === revision.current) setRunning(false);
    }
  };

  const cancel = () => {
    revision.current += 1;
    minimizer.current?.cancel();
    setRunning(false);
    setStatus("Cancelled; active syntax and engine workers were terminated.");
  };

  return (
    <section
      className="panel minimize-panel"
      aria-labelledby="minimize-heading"
    >
      <header className="panel-heading">
        <div>
          <p className="eyebrow">Bounded failing-subject reduction</p>
          <h2 id="minimize-heading">Minimize a reproducer</h2>
        </div>
        <button
          type="button"
          className="icon-button"
          aria-label="Close subject minimizer"
          data-dialog-initial-focus
          onClick={onClose}
        >
          ×
        </button>
      </header>

      <div className="minimizer-body">
        <p className="minimizer-advisory">
          This reducer proves only local minimality under deterministic
          Unicode-scalar chunk deletion, single-scalar deletion and canonical
          replacement. It does not claim a globally or semantically minimal
          example.
        </p>
        {!activeEngineSupported ? (
          <p className="minimizer-advisory" role="status">
            The selected flavour remains available in the main workbench, corpus
            and unit-test modes. This reducer currently has verified failure
            oracles only for ECMAScript and PCRE2.
          </p>
        ) : null}

        <section className="minimizer-config" aria-labelledby="minimize-target">
          <header>
            <h3 id="minimize-target">Exact predicate</h3>
            <span>
              {activeFlavour} · {utf8ByteLength(subject).toLocaleString()} bytes
            </span>
          </header>
          <div className="minimizer-control-grid">
            <label>
              <span>Failure kind</span>
              <select
                aria-label="Minimization failure kind"
                value={mode}
                disabled={running}
                onChange={(event) =>
                  setMode(event.target.value as MinimizationMode)
                }
              >
                <option value="unit-test-failure">
                  Failing unit-test assertion
                </option>
                <option value="capture-range-mismatch">
                  Wrong capture range
                </option>
                <option value="engine-timeout">Exact engine timeout</option>
                <option value="comparison-mismatch">
                  ECMAScript ↔ PCRE2 mismatch
                </option>
              </select>
            </label>
            <label className="check-control minimizer-check">
              <input
                type="checkbox"
                checked={scanAll}
                disabled={running}
                onChange={(event) => setScanAll(event.target.checked)}
              />
              Scan all
            </label>
          </div>

          {mode === "unit-test-failure" ? (
            <div className="minimizer-control-grid">
              <label>
                <span>Saved assertion</span>
                <select
                  aria-label="Unit-test expectation"
                  value={unitKind}
                  disabled={running}
                  onChange={(event) =>
                    setUnitKind(event.target.value as UnitExpectationKind)
                  }
                >
                  <option value="should-match">Should match</option>
                  <option value="should-not-match">Should not match</option>
                  <option value="match-count">Exact match count</option>
                  <option value="full-match">Full-match value</option>
                  <option value="capture">Capture status/value</option>
                  <option value="replacement">Replacement output</option>
                  <option value="must-complete-within">
                    Must complete within
                  </option>
                  <option value="must-time-out">Must time out</option>
                </select>
              </label>
              {unitKind === "match-count" ? (
                <label>
                  <span>Expected count</span>
                  <input
                    type="number"
                    value={expectedCount}
                    min={0}
                    max={DEFAULT_REGEX_LIMITS.maximumMatches}
                    onChange={(event) =>
                      setExpectedCount(Number(event.target.value))
                    }
                  />
                </label>
              ) : null}
              {unitKind === "full-match" || unitKind === "capture" ? (
                <label>
                  <span>Zero-based match index</span>
                  <input
                    type="number"
                    value={matchIndex}
                    min={0}
                    onChange={(event) =>
                      setMatchIndex(Number(event.target.value))
                    }
                  />
                </label>
              ) : null}
              {unitKind === "capture" ? (
                <>
                  <label>
                    <span>Capture number or name</span>
                    <input
                      type="text"
                      value={captureSelector}
                      onChange={(event) =>
                        setCaptureSelector(event.target.value)
                      }
                    />
                  </label>
                  <label>
                    <span>Expected participation</span>
                    <select
                      value={captureStatus}
                      onChange={(event) =>
                        setCaptureStatus(
                          event.target.value as typeof captureStatus,
                        )
                      }
                    >
                      <option value="participated">Participated</option>
                      <option value="did-not-participate">
                        Did not participate
                      </option>
                      <option value="matched-empty">Matched empty</option>
                    </select>
                  </label>
                  <label className="check-control minimizer-check">
                    <input
                      type="checkbox"
                      checked={checkCaptureValue}
                      onChange={(event) =>
                        setCheckCaptureValue(event.target.checked)
                      }
                    />
                    Assert value too
                  </label>
                </>
              ) : null}
              {unitKind === "full-match" ||
              unitKind === "replacement" ||
              (unitKind === "capture" && checkCaptureValue) ? (
                <label className="minimizer-wide-control">
                  <span>Expected value</span>
                  <input
                    type="text"
                    value={expectedValue}
                    onChange={(event) => setExpectedValue(event.target.value)}
                  />
                </label>
              ) : null}
              {unitKind === "must-complete-within" ? (
                <label>
                  <span>Assertion milliseconds</span>
                  <input
                    type="number"
                    value={durationLimitMs}
                    min={1}
                    onChange={(event) =>
                      setDurationLimitMs(Number(event.target.value))
                    }
                  />
                </label>
              ) : null}
            </div>
          ) : null}

          {mode === "capture-range-mismatch" ? (
            <div className="minimizer-control-grid">
              <label>
                <span>Zero-based match index</span>
                <input
                  type="number"
                  value={matchIndex}
                  min={0}
                  onChange={(event) =>
                    setMatchIndex(Number(event.target.value))
                  }
                />
              </label>
              <label>
                <span>Capture number or name</span>
                <input
                  type="text"
                  value={captureSelector}
                  onChange={(event) => setCaptureSelector(event.target.value)}
                />
              </label>
              <label>
                <span>Expected UTF-16 start</span>
                <input
                  type="number"
                  value={expectedStart}
                  min={0}
                  max={subject.length}
                  onChange={(event) =>
                    setExpectedStart(Number(event.target.value))
                  }
                />
              </label>
              <label>
                <span>Expected UTF-16 end</span>
                <input
                  type="number"
                  value={expectedEnd}
                  min={0}
                  max={subject.length}
                  onChange={(event) =>
                    setExpectedEnd(Number(event.target.value))
                  }
                />
              </label>
            </div>
          ) : null}

          {mode === "comparison-mismatch" ? (
            <>
              <div className="minimizer-control-grid">
                <label>
                  <span>Operation</span>
                  <select
                    value={comparisonOperation}
                    onChange={(event) =>
                      setComparisonOperation(
                        event.target.value as "match" | "replace",
                      )
                    }
                  >
                    <option value="match">Match</option>
                    <option value="replace">Replace</option>
                  </select>
                </label>
              </div>
              <div className="minimizer-variant-grid">
                <label>
                  <span>ECMAScript pattern</span>
                  <textarea
                    value={ecmaPattern}
                    onChange={(event) => setEcmaPattern(event.target.value)}
                  />
                </label>
                <label>
                  <span>PCRE2 pattern</span>
                  <textarea
                    value={pcrePattern}
                    onChange={(event) => setPcrePattern(event.target.value)}
                  />
                </label>
                {comparisonOperation === "replace" ? (
                  <>
                    <label>
                      <span>ECMAScript replacement</span>
                      <textarea
                        value={ecmaReplacement}
                        onChange={(event) =>
                          setEcmaReplacement(event.target.value)
                        }
                      />
                    </label>
                    <label>
                      <span>PCRE2 replacement</span>
                      <textarea
                        value={pcreReplacement}
                        onChange={(event) =>
                          setPcreReplacement(event.target.value)
                        }
                      />
                    </label>
                  </>
                ) : null}
              </div>
            </>
          ) : null}

          <label className="minimizer-subject">
            <span>Failing subject · local text only</span>
            <textarea
              aria-label="Subject to minimize"
              value={subject}
              disabled={running}
              onChange={(event) => setSubject(event.target.value)}
            />
          </label>
        </section>

        <section className="minimizer-config" aria-labelledby="minimize-bounds">
          <header>
            <h3 id="minimize-bounds">Hard bounds</h3>
            <span>Fixed deadline per candidate</span>
          </header>
          <div className="minimizer-control-grid">
            <label>
              <span>Candidate evaluations</span>
              <input
                type="number"
                value={maximumEvaluations}
                min={1}
                max={DEFAULT_REGEX_LIMITS.maximumMinimizerRuns}
                disabled={running}
                onChange={(event) =>
                  setMaximumEvaluations(Number(event.target.value))
                }
              />
            </label>
            <label>
              <span>Aggregate wall time ms</span>
              <input
                type="number"
                value={maximumWallTimeMs}
                min={1}
                max={60_000}
                disabled={running}
                onChange={(event) =>
                  setMaximumWallTimeMs(Number(event.target.value))
                }
              />
            </label>
            <label>
              <span>Each candidate ms</span>
              <input
                type="number"
                value={candidateTimeoutMs}
                min={1}
                max={DEFAULT_REGEX_LIMITS.advancedMaximumTimeoutMs}
                disabled={running}
                onChange={(event) =>
                  setCandidateTimeoutMs(Number(event.target.value))
                }
              />
            </label>
          </div>
        </section>

        <div className="minimizer-actions">
          <button
            type="button"
            className="primary-button"
            disabled={
              running ||
              (mode !== "comparison-mismatch" && !activeEngineSupported)
            }
            onClick={() => void run()}
          >
            {running ? "Minimizing…" : "Verify & minimize"}
          </button>
          {running ? (
            <button type="button" className="secondary-button" onClick={cancel}>
              Cancel
            </button>
          ) : null}
          <p className="minimizer-status" aria-live="polite">
            {status}
          </p>
        </div>

        {progress ? (
          <div className="minimizer-progress">
            <progress
              aria-label="Minimizer evaluation progress"
              value={progress.evaluations}
              max={progress.maximumEvaluations}
            />
            <span>
              {progress.currentScalars.toLocaleString()} scalars ·{" "}
              {progress.currentUtf8Bytes.toLocaleString()} bytes ·{" "}
              {progress.elapsedMs.toFixed(0)}/
              {progress.maximumWallTimeMs.toLocaleString()} ms
            </span>
          </div>
        ) : null}

        {result ? (
          <section
            className="minimizer-result"
            aria-labelledby="minimize-result"
          >
            <header>
              <div>
                <p className="eyebrow">Exact target {result.targetIdentity}</p>
                <h3 id="minimize-result">{resultLabel(result)}</h3>
              </div>
              <span className={`minimizer-result-badge is-${result.status}`}>
                {result.status}
              </span>
            </header>
            <dl>
              <div>
                <dt>Scalars</dt>
                <dd>
                  {result.originalScalars.toLocaleString()} →{" "}
                  {result.minimizedScalars.toLocaleString()}
                </dd>
              </div>
              <div>
                <dt>UTF-8 bytes</dt>
                <dd>
                  {result.originalUtf8Bytes.toLocaleString()} →{" "}
                  {result.minimizedUtf8Bytes.toLocaleString()}
                </dd>
              </div>
              <div>
                <dt>Evaluations</dt>
                <dd>
                  {result.evaluations.toLocaleString()} /{" "}
                  {result.budgets.maximumEvaluations.toLocaleString()}
                </dd>
              </div>
              <div>
                <dt>Accepted reductions</dt>
                <dd>{result.acceptedReductions.toLocaleString()}</dd>
              </div>
              <div>
                <dt>Wall time</dt>
                <dd>{result.elapsedMs.toFixed(1)} ms</dd>
              </div>
              <div>
                <dt>Stop reason</dt>
                <dd>{result.stopReason.replaceAll("-", " ")}</dd>
              </div>
            </dl>
            <div className="minimizer-observation-grid">
              <Observation
                label="Baseline verification"
                observation={result.baseline}
              />
              <Observation
                label="Final accepted candidate"
                observation={result.final}
              />
            </div>
            <label className="minimizer-output">
              <span>Minimized subject</span>
              <textarea
                aria-label="Minimized subject"
                readOnly
                value={result.minimizedSubject}
              />
            </label>
            <div className="minimizer-actions">
              <button
                type="button"
                className="primary-button"
                disabled={!result.final.reproduced}
                onClick={() => onUseSubject(result.minimizedSubject)}
              >
                Use minimized subject
              </button>
              <button
                type="button"
                className="secondary-button"
                onClick={() => {
                  void writeClipboardText(result.minimizedSubject)
                    .then(() => setCopyStatus("Copied locally."))
                    .catch((error: unknown) =>
                      setCopyStatus(
                        error instanceof Error ? error.message : String(error),
                      ),
                    );
                }}
              >
                Copy
              </button>
              <p aria-live="polite">{copyStatus}</p>
            </div>
          </section>
        ) : null}
      </div>
    </section>
  );
}
