import { useEffect, useMemo, useRef, useState } from "react";
import type {
  RegexEngineOptions,
  RegexFlavourId,
} from "../regex/model/flavour";
import type { RegexSyntaxResult } from "../regex/model/syntax";
import type { RegexTestCase } from "../regex/tests/test-case.types";
import { WorkerRequestError } from "../regex/execution/WorkerSupervisor";
import { formatEcmaScriptPattern } from "../regex/formatting/ecmascript-format";
import { PatternFormatValidator } from "../regex/formatting/PatternFormatValidator";
import type {
  PatternFormatPreview,
  PatternFormatSnapshotCheck,
  PatternFormatValidationInput,
  PatternFormatValidationResult,
} from "../regex/formatting/formatting.types";
import "./PatternFormatterPanel.css";

const MAXIMUM_VISIBLE_CHANGES = 200;
const MAXIMUM_VISIBLE_CHECKS = 200;

export interface PatternFormatValidatorClient {
  validate(
    input: PatternFormatValidationInput,
    onProgress?: (completed: number, total: number) => void,
  ): Promise<PatternFormatValidationResult>;
  cancel(): void;
  dispose(): void;
}

export interface PatternFormatterPanelProps {
  readonly flavour: RegexFlavourId;
  readonly flavourVersion?: string;
  readonly pattern: string;
  readonly flags: readonly string[];
  readonly options: RegexEngineOptions;
  readonly syntax?: RegexSyntaxResult;
  readonly subject: string;
  readonly replacement: string;
  readonly scanAll: boolean;
  readonly timeoutMs: number;
  readonly tests: readonly RegexTestCase[];
  readonly onApply: (formattedPattern: string) => void;
  readonly onClose?: () => void;
  readonly createValidator?: () => PatternFormatValidatorClient;
}

type PreviewState =
  | { readonly status: "available"; readonly preview: PatternFormatPreview }
  | { readonly status: "unavailable"; readonly reason: string };

interface ValidationConfiguration {
  readonly flavourVersion?: string;
  readonly preview: PatternFormatPreview;
  readonly flags: readonly string[];
  readonly options: RegexEngineOptions;
  readonly subject: string;
  readonly replacement: string;
  readonly scanAll: boolean;
  readonly timeoutMs: number;
  readonly tests: readonly RegexTestCase[];
}

interface ValidationRecord {
  readonly configuration: ValidationConfiguration;
  readonly result: PatternFormatValidationResult;
}

interface ConfirmationRecord {
  readonly configuration: ValidationConfiguration;
  readonly checked: boolean;
}

function previewState(
  flavour: RegexFlavourId,
  pattern: string,
  syntax: RegexSyntaxResult | undefined,
): PreviewState {
  if (flavour !== "ecmascript") {
    return {
      status: "unavailable",
      reason:
        "Canonical formatting is currently available only for ECMAScript. PCRE2 and other flavours remain unchanged because no complete grammar-backed formatter is implemented for them.",
    };
  }
  if (!syntax) {
    return {
      status: "unavailable",
      reason:
        "Wait for the ECMAScript syntax provider to finish parsing the current pattern.",
    };
  }
  try {
    return {
      status: "available",
      preview: formatEcmaScriptPattern(pattern, syntax),
    };
  } catch (cause) {
    return {
      status: "unavailable",
      reason:
        cause instanceof Error
          ? cause.message
          : "The current syntax snapshot cannot be formatted.",
    };
  }
}

function formatDuration(milliseconds: number): string {
  if (milliseconds < 1) return "<1 ms";
  if (milliseconds < 1_000) return `${milliseconds.toFixed(0)} ms`;
  return `${(milliseconds / 1_000).toFixed(2)} s`;
}

function observationIdentity(check: PatternFormatSnapshotCheck): string {
  const before = check.before.engineIdentity ?? check.before.status;
  const after = check.after.engineIdentity ?? check.after.status;
  return before === after ? before : `${before} → ${after}`;
}

function ValidationCheck({
  title,
  check,
}: {
  readonly title: string;
  readonly check: PatternFormatSnapshotCheck;
}) {
  return (
    <article className={`formatter-check is-${check.comparison}`}>
      <header>
        <strong>{title}</strong>
        <span>{check.comparison}</span>
      </header>
      <p>{check.summary}</p>
      <small>{observationIdentity(check)}</small>
      {check.mismatchKinds.length > 0 ? (
        <code>{check.mismatchKinds.join(" · ")}</code>
      ) : null}
    </article>
  );
}

export function PatternFormatterPanel({
  flavour,
  flavourVersion,
  pattern,
  flags,
  options,
  syntax,
  subject,
  replacement,
  scanAll,
  timeoutMs,
  tests,
  onApply,
  onClose,
  createValidator = () => new PatternFormatValidator(),
}: PatternFormatterPanelProps) {
  const preview = useMemo(
    () => previewState(flavour, pattern, syntax),
    [flavour, pattern, syntax],
  );
  const configuration = useMemo<ValidationConfiguration | undefined>(
    () =>
      preview.status === "available"
        ? {
            flavourVersion,
            preview: preview.preview,
            flags,
            options,
            subject,
            replacement,
            scanAll,
            timeoutMs,
            tests,
          }
        : undefined,
    [
      flags,
      flavourVersion,
      options,
      preview,
      replacement,
      scanAll,
      subject,
      tests,
      timeoutMs,
    ],
  );
  const validator = useRef<PatternFormatValidatorClient | undefined>(undefined);
  const revision = useRef(0);
  const [running, setRunning] = useState(false);
  const [progress, setProgress] = useState({ completed: 0, total: 0 });
  const [message, setMessage] = useState(
    "Review the exact preview before running bounded validation.",
  );
  const [error, setError] = useState<string>();
  const [record, setRecord] = useState<ValidationRecord>();
  const [confirmation, setConfirmation] = useState<ConfirmationRecord>();

  const currentRecord =
    record?.configuration === configuration ? record : undefined;
  const confirmed =
    confirmation !== undefined &&
    confirmation.configuration === configuration &&
    confirmation.checked;

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

  const cancel = () => {
    revision.current += 1;
    validator.current?.cancel();
    setRunning(false);
    setMessage("Validation cancelled; formatter workers were terminated.");
  };

  const runValidation = async () => {
    if (
      !configuration ||
      !configuration.preview.changed ||
      !configuration.preview.withinPatternLimit
    ) {
      return;
    }
    const currentValidator =
      validator.current ?? (validator.current = createValidator());
    currentValidator.cancel();
    revision.current += 1;
    const runRevision = revision.current;
    setRunning(true);
    setConfirmation(undefined);
    setError(undefined);
    setRecord(undefined);
    setProgress({ completed: 0, total: 0 });
    setMessage(
      "Reparsing, compiling and comparing exact source/candidate snapshots…",
    );
    try {
      const result = await currentValidator.validate(
        {
          flavour: "ecmascript",
          ...(configuration.flavourVersion === undefined
            ? {}
            : { flavourVersion: configuration.flavourVersion }),
          sourcePattern: configuration.preview.sourcePattern,
          formattedPattern: configuration.preview.formattedPattern,
          flags: configuration.flags,
          options: configuration.options,
          subject: configuration.subject,
          replacement: configuration.replacement,
          scanAll: configuration.scanAll,
          timeoutMs: configuration.timeoutMs,
          tests: configuration.tests,
        },
        (completed, total) => {
          if (revision.current !== runRevision) return;
          setProgress({ completed, total });
          setMessage(
            total === 0
              ? "The current subject/replacement snapshot is being compared."
              : `Validated ${completed.toLocaleString()} of ${total.toLocaleString()} applicable unit tests.`,
          );
        },
      );
      if (revision.current !== runRevision) return;
      setRecord({ configuration, result });
      setMessage(
        result.status === "equivalent"
          ? "All completed safety gates retained the same observable behavior."
          : result.status === "different"
            ? "Formatting changed at least one checked observation."
            : "Validation was inconclusive; the candidate cannot be applied.",
      );
    } catch (cause) {
      if (revision.current !== runRevision) return;
      const cancelled =
        cause instanceof WorkerRequestError && cause.kind === "cancelled";
      setError(
        cancelled
          ? "Validation was cancelled."
          : cause instanceof Error
            ? cause.message
            : "Formatter validation failed.",
      );
      setMessage(
        cancelled
          ? "Validation cancelled; no candidate can be applied."
          : "Validation failed; no candidate can be applied.",
      );
    } finally {
      if (revision.current === runRevision) setRunning(false);
    }
  };

  const close = () => {
    if (running) cancel();
    onClose?.();
  };

  const result = currentRecord?.result;
  const canApply =
    result?.canApply === true &&
    confirmed &&
    configuration?.preview.changed === true;

  return (
    <section
      className="panel formatter-panel"
      aria-labelledby="formatter-heading"
    >
      <header className="panel-heading">
        <div>
          <p className="eyebrow">
            Grammar-backed preview + bounded proof gates
          </p>
          <h2 id="formatter-heading">Format pattern</h2>
        </div>
        <div className="panel-heading-actions">
          <span className="provenance-badge">ECMAScript only</span>
          {onClose ? (
            <button
              type="button"
              className="icon-button"
              aria-label="Close pattern formatter"
              data-dialog-initial-focus
              onClick={close}
            >
              ×
            </button>
          ) : null}
        </div>
      </header>

      {preview.status === "unavailable" ? (
        <p className="formatter-unavailable" role="status">
          {preview.reason}
        </p>
      ) : (
        <div className="formatter-body">
          <p className="formatter-advisory">
            This deliberately narrow formatter escapes literal slashes and raw
            control/line-separator characters. It does not insert insignificant
            whitespace—ECMAScript patterns generally have no such whitespace.
          </p>

          <section className="formatter-preview" aria-label="Format preview">
            <header>
              <div>
                <p className="eyebrow">
                  {preview.preview.formatter.id} · v
                  {preview.preview.formatter.version}
                </p>
                <h3>Exact preview</h3>
              </div>
              <span>
                {preview.preview.totalChanges.toLocaleString()} transformation
                {preview.preview.totalChanges === 1 ? "" : "s"}
              </span>
            </header>
            <div className="formatter-pattern-grid">
              <article>
                <strong>Source</strong>
                <pre>
                  <code>{preview.preview.sourcePattern}</code>
                </pre>
              </article>
              <article>
                <strong>Formatted candidate</strong>
                <pre data-testid="formatted-pattern-preview">
                  <code>{preview.preview.formattedPattern}</code>
                </pre>
              </article>
            </div>

            {preview.preview.changed ? (
              <div className="formatter-table-scroll">
                <table aria-label="Formatter transformations">
                  <thead>
                    <tr>
                      <th scope="col">UTF-16 range</th>
                      <th scope="col">Kind</th>
                      <th scope="col">Before</th>
                      <th scope="col">After</th>
                    </tr>
                  </thead>
                  <tbody>
                    {preview.preview.changes
                      .slice(0, MAXIMUM_VISIBLE_CHANGES)
                      .map((change) => (
                        <tr
                          key={`${change.startUtf16}:${change.endUtf16}:${change.kind}`}
                          title={change.explanation}
                        >
                          <td>
                            {change.startUtf16}–{change.endUtf16}
                          </td>
                          <td>{change.kind.replaceAll("-", " ")}</td>
                          <td>
                            <code>{JSON.stringify(change.before)}</code>
                          </td>
                          <td>
                            <code>{JSON.stringify(change.after)}</code>
                          </td>
                        </tr>
                      ))}
                  </tbody>
                </table>
                {preview.preview.totalChanges > MAXIMUM_VISIBLE_CHANGES ? (
                  <p>
                    The table shows the first{" "}
                    {MAXIMUM_VISIBLE_CHANGES.toLocaleString()} transformations.
                  </p>
                ) : null}
              </div>
            ) : (
              <p className="formatter-empty">
                This pattern already uses the formatter’s canonical literal and
                control escapes.
              </p>
            )}

            <ul className="formatter-notices">
              {preview.preview.warnings.map((warning) => (
                <li key={warning}>{warning}</li>
              ))}
            </ul>
          </section>

          {preview.preview.changed ? (
            <section className="formatter-validation">
              <header>
                <div>
                  <p className="eyebrow">Required before apply</p>
                  <h3>Safety validation</h3>
                </div>
                <span>
                  {result?.status ?? (running ? "running" : "not run")}
                </span>
              </header>
              <div
                className={`formatter-status is-${result?.status ?? (error ? "different" : "idle")}`}
                role="status"
                aria-live="polite"
              >
                <strong>{message}</strong>
                {error ? <p>{error}</p> : null}
              </div>
              {running ? (
                <div className="formatter-progress">
                  <progress
                    max={Math.max(1, progress.total)}
                    value={Math.min(progress.completed, progress.total)}
                  />
                  <span>
                    {progress.completed.toLocaleString()} /{" "}
                    {progress.total.toLocaleString()} tests
                  </span>
                </div>
              ) : null}
              <div className="formatter-actions">
                <button
                  type="button"
                  className="primary-button"
                  disabled={
                    running ||
                    !preview.preview.withinPatternLimit ||
                    !preview.preview.changed
                  }
                  onClick={() => void runValidation()}
                >
                  Validate exact snapshots
                </button>
                {running ? (
                  <button
                    type="button"
                    className="secondary-button"
                    onClick={cancel}
                  >
                    Cancel
                  </button>
                ) : null}
              </div>

              {result ? (
                <div className="formatter-result">
                  <dl>
                    <div>
                      <dt>Syntax</dt>
                      <dd>
                        {result.sourceSyntaxAccepted &&
                        result.formattedSyntaxAccepted
                          ? "both accepted"
                          : "rejected"}
                      </dd>
                    </div>
                    <div>
                      <dt>Capture shape</dt>
                      <dd>
                        {result.captureShapePreserved ? "preserved" : "changed"}
                      </dd>
                    </div>
                    <div>
                      <dt>Unit tests</dt>
                      <dd>
                        {result.completedTestCount.toLocaleString()} /{" "}
                        {result.applicableTestCount.toLocaleString()} applicable
                      </dd>
                    </div>
                    <div>
                      <dt>Unrelated / disabled</dt>
                      <dd>{result.skippedTestCount.toLocaleString()}</dd>
                    </div>
                    <div>
                      <dt>Elapsed</dt>
                      <dd>{formatDuration(result.elapsedMs)}</dd>
                    </div>
                  </dl>

                  <div className="formatter-checks">
                    <ValidationCheck
                      title="Current subject and replacement"
                      check={result.current}
                    />
                    {result.tests
                      .slice(0, MAXIMUM_VISIBLE_CHECKS)
                      .map((test) => (
                        <ValidationCheck
                          key={test.testId}
                          title={test.name}
                          check={test}
                        />
                      ))}
                  </div>
                  {result.tests.length > MAXIMUM_VISIBLE_CHECKS ? (
                    <p className="formatter-result-note">
                      The panel shows the first{" "}
                      {MAXIMUM_VISIBLE_CHECKS.toLocaleString()} completed test
                      comparisons.
                    </p>
                  ) : null}

                  {result.differences.length > 0 ? (
                    <section
                      className="formatter-differences"
                      aria-labelledby="formatter-differences-heading"
                    >
                      <h4 id="formatter-differences-heading">
                        Differences and inconclusive gates
                      </h4>
                      <ul>
                        {result.differences
                          .slice(0, MAXIMUM_VISIBLE_CHECKS)
                          .map((difference, index) => (
                            <li
                              key={`${difference.code}:${difference.testId ?? ""}:${index}`}
                            >
                              <strong>{difference.code}</strong>{" "}
                              {difference.summary}
                            </li>
                          ))}
                      </ul>
                    </section>
                  ) : null}
                  <ul className="formatter-notices">
                    {result.notices.map((notice) => (
                      <li key={notice}>{notice}</li>
                    ))}
                  </ul>

                  <label className="check-control formatter-confirmation">
                    <input
                      type="checkbox"
                      checked={confirmed}
                      disabled={!result.canApply}
                      onChange={(event) => {
                        if (!configuration) return;
                        setConfirmation({
                          configuration,
                          checked: event.target.checked,
                        });
                      }}
                    />
                    <span>
                      I understand that this bounded validation is not a proof
                      for every possible subject, and I want to apply the exact
                      candidate shown above.
                    </span>
                  </label>
                  <div className="formatter-actions">
                    <button
                      type="button"
                      className="primary-button"
                      disabled={!canApply}
                      onClick={() => {
                        if (!canApply || !configuration) return;
                        onApply(configuration.preview.formattedPattern);
                      }}
                    >
                      Apply formatted pattern
                    </button>
                  </div>
                </div>
              ) : null}
            </section>
          ) : null}
        </div>
      )}
    </section>
  );
}
