import { useEffect, useRef, useState } from "react";
import { Pcre2TraceSupervisor } from "../regex/execution/Pcre2TraceSupervisor";
import { WorkerRequestError } from "../regex/execution/WorkerSupervisor";
import { DEFAULT_REGEX_LIMITS } from "../regex/execution/request-limits";
import type {
  RegexEngineOptions,
  RegexFlavourId,
} from "../regex/model/flavour";
import type { Pcre2TraceEvent, Pcre2TraceResult } from "../regex/model/trace";
import type { SourceRange } from "../regex/model/syntax";

const DEFAULT_TRACE_EVENTS = 5_000;
const DEFAULT_TRACE_BYTES = 2 * 1024 * 1024;
const MAXIMUM_RENDERED_TRACE_EVENTS = 2_000;

type TraceState =
  | { readonly status: "idle"; readonly message: string }
  | { readonly status: "running"; readonly message: string }
  | { readonly status: "ready"; readonly message: string }
  | { readonly status: "timeout"; readonly message: string }
  | { readonly status: "error"; readonly message: string };

function movementLabel(event: Pcre2TraceEvent): string {
  switch (event.movement.classification) {
    case "first-event":
      return "first";
    case "same-position":
      return "same position";
    case "apparent-backtrack":
      return "apparent backtrack";
    case "forward":
      return "forward";
  }
}

export function Pcre2TracePanel({
  active,
  flavour,
  pattern,
  flags,
  options,
  subject,
  timeoutMs,
  onClose,
  onSelectRanges,
}: {
  readonly active: boolean;
  readonly flavour: RegexFlavourId;
  readonly pattern: string;
  readonly flags: readonly string[];
  readonly options: RegexEngineOptions;
  readonly subject: string;
  readonly timeoutMs: number;
  readonly onClose: () => void;
  readonly onSelectRanges: (
    patternRange: SourceRange,
    subjectRange: SourceRange,
  ) => void;
}) {
  const supervisor = useRef<Pcre2TraceSupervisor | null>(null);
  const requestRevision = useRef(0);
  const [maximumEvents, setMaximumEvents] = useState(DEFAULT_TRACE_EVENTS);
  const [maximumBytes, setMaximumBytes] = useState(DEFAULT_TRACE_BYTES);
  const [completed, setCompleted] = useState<{
    readonly result: Pcre2TraceResult;
    readonly input: {
      readonly pattern: string;
      readonly subject: string;
      readonly flags: string;
      readonly options: string;
    };
  }>();
  const [state, setState] = useState<TraceState>({
    status: "idle",
    message: "Run an isolated PCRE2 automatic-callout trace.",
  });

  const traceEngine = () => {
    supervisor.current ??= new Pcre2TraceSupervisor();
    return supervisor.current;
  };

  useEffect(() => {
    return () => supervisor.current?.dispose();
  }, []);

  useEffect(() => {
    if (active) return;
    supervisor.current?.cancel();
  }, [active]);

  const limitsValid =
    Number.isSafeInteger(maximumEvents) &&
    maximumEvents >= 1 &&
    maximumEvents <= DEFAULT_REGEX_LIMITS.maximumTraceEvents &&
    Number.isSafeInteger(maximumBytes) &&
    maximumBytes >= 32 &&
    maximumBytes <= DEFAULT_REGEX_LIMITS.maximumTraceBytes;

  const runTrace = async () => {
    if (flavour !== "pcre2" || !limitsValid) return;
    const revision = (requestRevision.current += 1);
    setCompleted(undefined);
    setState({
      status: "running",
      message: "Collecting reported callouts in a dedicated worker…",
    });
    try {
      const next = await traceEngine().trace(
        {
          flavour,
          pattern,
          flags,
          options,
          subject,
          maximumTraceEvents: maximumEvents,
          maximumTraceBytes: maximumBytes,
        },
        timeoutMs,
      );
      if (revision !== requestRevision.current) return;
      setCompleted({
        result: next,
        input: {
          pattern,
          subject,
          flags: flags.join(""),
          options: JSON.stringify(options),
        },
      });
      const error = next.diagnostics.find(
        (diagnostic) => diagnostic.severity === "error",
      );
      setState(
        next.accepted
          ? {
              status: "ready",
              message: next.truncated
                ? "Bounded trace complete; collection stopped at a configured cap."
                : "Bounded trace complete.",
            }
          : {
              status: "error",
              message: error?.message ?? "PCRE2 rejected the trace request.",
            },
      );
    } catch (error) {
      if (revision !== requestRevision.current) return;
      if (error instanceof WorkerRequestError && error.kind === "timeout") {
        setState({
          status: "timeout",
          message: `${error.message}; the trace worker was terminated.`,
        });
      } else if (
        error instanceof WorkerRequestError &&
        error.kind === "cancelled"
      ) {
        setState({
          status: "idle",
          message: "Trace cancelled; its worker was terminated.",
        });
      } else {
        setState({
          status: "error",
          message:
            error instanceof Error
              ? error.message
              : "The PCRE2 trace worker failed.",
        });
      }
    }
  };

  const resultIsCurrent =
    completed?.input.pattern === pattern &&
    completed.input.subject === subject &&
    completed.input.flags === flags.join("") &&
    completed.input.options === JSON.stringify(options);
  const visibleResult = resultIsCurrent ? completed.result : undefined;
  const visibleState =
    resultIsCurrent || state.status === "running"
      ? state
      : {
          status: "idle" as const,
          message: "Inputs changed; run a new isolated trace.",
        };
  const renderedEvents =
    visibleResult?.events.slice(0, MAXIMUM_RENDERED_TRACE_EVENTS) ?? [];
  return (
    <section className="panel trace-panel" aria-labelledby="trace-heading">
      <header className="panel-heading">
        <div>
          <p className="eyebrow">PCRE2 10.47 · reported automatic callouts</p>
          <h2 id="trace-heading">Execution trace</h2>
        </div>
        <button
          type="button"
          className="icon-button"
          aria-label="Close execution trace"
          data-dialog-initial-focus
          onClick={onClose}
        >
          ×
        </button>
      </header>
      <p className="trace-disclosure">
        This is an actual stream reported by PCRE2_AUTO_CALLOUT for one{" "}
        <code>pcre2_match()</code> invocation. Movement labels are derived only
        from adjacent reported subject positions; the stream is not a complete
        record of every internal engine action.
      </p>
      <div className="trace-controls">
        <label>
          <span>Maximum events</span>
          <input
            type="number"
            min={1}
            max={DEFAULT_REGEX_LIMITS.maximumTraceEvents}
            step={1}
            value={maximumEvents}
            onChange={(event) => setMaximumEvents(Number(event.target.value))}
          />
        </label>
        <label>
          <span>Maximum serialized bytes</span>
          <input
            type="number"
            min={32}
            max={DEFAULT_REGEX_LIMITS.maximumTraceBytes}
            step={1024}
            value={maximumBytes}
            onChange={(event) => setMaximumBytes(Number(event.target.value))}
          />
        </label>
        <button
          type="button"
          className="primary-button"
          disabled={
            flavour !== "pcre2" ||
            !limitsValid ||
            visibleState.status === "running"
          }
          onClick={() => void runTrace()}
        >
          {visibleState.status === "running" ? "Tracing…" : "Run trace"}
        </button>
        {visibleState.status === "running" ? (
          <button
            type="button"
            className="secondary-button"
            onClick={() => {
              requestRevision.current += 1;
              supervisor.current?.cancel();
              setState({
                status: "idle",
                message: "Trace cancelled; its worker was terminated.",
              });
            }}
          >
            Cancel trace
          </button>
        ) : null}
      </div>
      {flavour !== "pcre2" ? (
        <p className="empty-state" role="status">
          Select PCRE2 to enable its automatic-callout trace.
        </p>
      ) : null}
      <div
        className={`trace-status status-${visibleState.status}`}
        role="status"
        aria-live="polite"
      >
        <strong>{visibleState.message}</strong>
        {visibleResult?.accepted ? (
          <small>
            {visibleResult.events.length.toLocaleString()} complete retained
            events of {visibleResult.totalEventCount.toLocaleString()} callback
            event(s) observed before return ·{" "}
            {visibleResult.traceBytes.toLocaleString()} serialized bytes · last
            complete event{" "}
            {visibleResult.lastCompleteEvent?.toLocaleString() ?? "none"} ·{" "}
            native status {visibleResult.nativeMatchStatus} ·{" "}
            {visibleResult.matched ? "matched" : "not matched or bounded stop"}{" "}
            · {visibleResult.elapsedMs.toFixed(2)} ms
          </small>
        ) : null}
      </div>
      {visibleResult?.diagnostics.length ? (
        <ul className="trace-diagnostics">
          {visibleResult.diagnostics.map((diagnostic) => (
            <li
              key={diagnostic.id}
              className={`diagnostic-${diagnostic.severity}`}
            >
              <strong>{diagnostic.severity}</strong> {diagnostic.message}
            </li>
          ))}
        </ul>
      ) : null}
      {visibleResult && visibleResult.events.length > renderedEvents.length ? (
        <p className="render-limit-banner" role="status">
          <strong>Trace viewer limited.</strong> Showing the first{" "}
          {renderedEvents.length.toLocaleString()} of{" "}
          {visibleResult.events.length.toLocaleString()} complete retained
          events. The worker result remains bounded by both configured caps.
        </p>
      ) : null}
      {renderedEvents.length > 0 ? (
        <div className="trace-table-scroll">
          <table className="trace-table">
            <thead>
              <tr>
                <th scope="col">Event</th>
                <th scope="col">Callout</th>
                <th scope="col">Pattern</th>
                <th scope="col">Next item</th>
                <th scope="col">Subject</th>
                <th scope="col">Captures</th>
                <th scope="col">Mark</th>
                <th scope="col">Movement</th>
              </tr>
            </thead>
            <tbody>
              {renderedEvents.map((event) => (
                <tr key={event.eventNumber}>
                  <td>
                    <button
                      type="button"
                      className="trace-event-select"
                      aria-label={`Select trace event ${event.eventNumber}`}
                      onClick={() =>
                        onSelectRanges(
                          {
                            startUtf16:
                              event.reported.nextPatternItem.startUtf16,
                            endUtf16: event.reported.nextPatternItem.endUtf16,
                          },
                          {
                            startUtf16: event.reported.subjectPosition.utf16,
                            endUtf16: event.reported.subjectPosition.utf16,
                          },
                        )
                      }
                    >
                      {event.eventNumber}
                    </button>
                  </td>
                  <td>{event.reported.calloutNumber}</td>
                  <td>
                    {event.reported.patternPosition.utf16} UTF-16
                    <small>
                      {event.reported.patternPosition.nativeByte} byte
                    </small>
                  </td>
                  <td>
                    <code>
                      {pattern.slice(
                        event.reported.nextPatternItem.startUtf16,
                        event.reported.nextPatternItem.endUtf16,
                      ) || "end"}
                    </code>
                    <small>
                      {event.reported.nextPatternItem.startNativeByte}…
                      {event.reported.nextPatternItem.endNativeByte} bytes
                    </small>
                  </td>
                  <td>
                    {event.reported.subjectPosition.utf16} UTF-16
                    <small>
                      {event.reported.subjectPosition.nativeByte} byte
                    </small>
                  </td>
                  <td>
                    top {event.reported.captureTop} · last{" "}
                    {event.reported.captureLast}
                  </td>
                  <td>
                    {event.reported.mark ? (
                      <code>{event.reported.mark}</code>
                    ) : (
                      "—"
                    )}
                  </td>
                  <td>
                    <span
                      className={`trace-movement trace-${event.movement.classification}`}
                      title={event.movement.explanation}
                    >
                      {movementLabel(event)}
                    </span>
                    <small>
                      {event.movement.subjectDeltaBytes > 0 ? "+" : ""}
                      {event.movement.subjectDeltaBytes} bytes · derived
                    </small>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      ) : visibleResult?.accepted ? (
        <p className="empty-state">No callout event was retained.</p>
      ) : null}
    </section>
  );
}
