/* All working test components. Each exports a key in window.TestComponents */

const TestComponents = {};

/* ---------- Text Input ---------- */
TestComponents["text-input"] = function TextInputTest({ resetKey }) {
  const [value, setValue] = useState("");
  const [label, setLabel] = useState("Button");
  useEffect(() => { setValue(""); setLabel("Button"); }, [resetKey]);

  return (
    <div className="tc-stack">
      <input
        className="input"
        data-testid="text-input"
        placeholder="Type your text…"
        value={value}
        onChange={(e) => setValue(e.target.value)}
      />
      <button
        className="btn"
        data-testid="update-button"
        onClick={() => setLabel(value || "Button")}
        style={{ width: "100%" }}
      >
        {label}
      </button>
    </div>
  );
};

/* ---------- Client Side Delay ---------- */
TestComponents["client-side-delay"] = function ClientDelayTest({ resetKey }) {
  const [phase, setPhase] = useState("idle"); // idle | running | done
  useEffect(() => { setPhase("idle"); }, [resetKey]);

  const start = () => {
    setPhase("running");
    setTimeout(() => setPhase("done"), 5000);
  };

  return (
    <div className="tc-stack">
      <button
        className="btn"
        data-testid="start"
        onClick={start}
        disabled={phase === "running"}
      >
        {phase === "running" ? <><span className="spinner" /> Computing…</> : "Run heavy computation"}
      </button>

      {phase === "done" && (
        <div className="test-result success" data-testid="result">
          Computation complete · 42 ops finished
        </div>
      )}
    </div>
  );
};

/* ---------- AJAX Data (real fetch — visible in DevTools Network) ---------- */
TestComponents["ajax-data"] = function AjaxDataTest({ resetKey }) {
  const [phase, setPhase] = useState("idle"); // idle | loading | done | error
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  useEffect(() => { setPhase("idle"); setData(null); setError(null); }, [resetKey]);

  const fetchData = async () => {
    setPhase("loading");
    setError(null);
    setData(null);
    try {
      // POST a fake records payload to httpbin's delay endpoint. The
      // server holds the response for ~3 seconds and then echoes the
      // body back under `json`, giving us:
      //   • a real, slow network request candidates can intercept
      //   • a real records array in the response they can count.
      const RECORD_COUNT = 24;
      const records = Array.from({ length: RECORD_COUNT }, (_, i) => ({
        id: i + 1,
        name: `Record #${i + 1}`,
        value: Math.floor(Math.random() * 1000),
      }));

      const res = await fetch("https://httpbin.org/delay/3", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        cache: "no-store",
        body: JSON.stringify({ records }),
      });
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      const json = await res.json();
      // httpbin's /delay endpoint sometimes echoes the body under `json`
      // (already parsed) and sometimes only as a raw string in `data`.
      // Try both so the record count always reflects what the server
      // actually returned.
      let returned = [];
      if (Array.isArray(json?.json?.records)) {
        returned = json.json.records;
      } else if (typeof json?.data === "string" && json.data.length) {
        try {
          const parsed = JSON.parse(json.data);
          if (Array.isArray(parsed?.records)) returned = parsed.records;
        } catch (_) { /* leave returned as [] */ }
      }
      setData({ count: returned.length, records: returned });
      setPhase("done");
    } catch (e) {
      setError(e.message || "Network error");
      setPhase("error");
    }
  };

  return (
    <div className="tc-stack">
      <button
        className="btn"
        data-testid="fetch"
        onClick={fetchData}
        disabled={phase === "loading"}
      >
        {phase === "loading" ? <><span className="spinner" /> Fetching…</> : "Fetch data"}
      </button>

      {phase === "done" && data && (
        <div className="test-result success" data-testid="ajax-data" data-count={data.count}>
          ✓ Loaded {data.count} records · 200 OK
        </div>
      )}
      {phase === "error" && (
        <div className="test-result danger" data-testid="ajax-error">
          ✗ Request failed: {error}
        </div>
      )}
    </div>
  );
};

/* ---------- Scrollbars ---------- */
TestComponents["scrollbars"] = function ScrollbarsTest({ resetKey }) {
  const [clicked, setClicked] = useState(false);
  const containerRef = useRef(null);
  useEffect(() => {
    setClicked(false);
    if (containerRef.current) containerRef.current.scrollTo(0, 0);
  }, [resetKey]);

  return (
    <div className="tc-stack">
      <div className="sb-container" data-testid="scroll-container" ref={containerRef}>
        <div className="sb-inner">
          <button
            className="btn sb-button"
            data-testid="scroll-target"
            onClick={() => setClicked(true)}
          >
            Click me
          </button>
        </div>
      </div>
      {clicked && (
        <div className="test-result success" data-testid="result">
          ✓ Target reached and clicked.
        </div>
      )}
    </div>
  );
};

/* ---------- Dynamic Table ---------- */
TestComponents["dynamic-table"] = function DynamicTableTest({ resetKey }) {
  const rows = [
    { Name: "Chrome",   CPU: "12.3%", Memory: "1.2 GB", Network: "245 KB/s", Battery: "—"   },
    { Name: "Firefox",  CPU: "8.1%",  Memory: "896 MB", Network: "112 KB/s", Battery: "—"   },
    { Name: "Safari",   CPU: "3.4%",  Memory: "612 MB", Network: "0 KB/s",   Battery: "—"   },
    { Name: "Edge",     CPU: "5.8%",  Memory: "742 MB", Network: "82 KB/s",  Battery: "—"   },
    { Name: "Slack",    CPU: "2.1%",  Memory: "412 MB", Network: "14 KB/s",  Battery: "—"   },
  ];
  const columnOrders = useMemo(() => [
    ["Name", "CPU", "Memory", "Network", "Battery"],
    ["Name", "Memory", "CPU", "Battery", "Network"],
    ["Name", "Battery", "Network", "CPU", "Memory"],
    ["Name", "Network", "CPU", "Battery", "Memory"],
  ], []);
  const [orderIdx, setOrderIdx] = useState(0);
  useEffect(() => {
    setOrderIdx((i) => (i + 1) % columnOrders.length);
  }, [resetKey, columnOrders.length]);

  const cols = columnOrders[orderIdx];
  const chrome = rows.find((r) => r.Name === "Chrome");

  return (
    <div className="tc-stack" style={{ maxWidth: 640, width: "100%" }}>
      <div className="dt-cpu-label" data-testid="chrome-cpu-label">
        Chrome CPU: <strong>{chrome.CPU}</strong>
      </div>
      <table className="dt-table" data-testid="data-table">
        <thead>
          <tr>
            {cols.map((c) => (
              <th key={c} data-testid={`header-${c.toLowerCase()}`}>{c}</th>
            ))}
          </tr>
        </thead>
        <tbody>
          {rows.map((r) => (
            <tr key={r.Name} data-testid={`row-${r.Name.toLowerCase()}`}>
              {cols.map((c) => (
                <td key={c} data-col={c.toLowerCase()}>{r[c]}</td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
};

/* ---------- Progress Bar (no visible % label) ---------- */
TestComponents["progress-bar"] = function ProgressBarTest({ resetKey }) {
  const [pct, setPct] = useState(0);
  const [running, setRunning] = useState(false);
  const tickRef = useRef(null);

  useEffect(() => {
    setPct(0);
    setRunning(false);
    if (tickRef.current) clearInterval(tickRef.current);
  }, [resetKey]);

  useEffect(() => () => { if (tickRef.current) clearInterval(tickRef.current); }, []);

  const start = () => {
    if (running || pct >= 100) return;
    setRunning(true);
    tickRef.current = setInterval(() => {
      setPct((p) => {
        if (p >= 100) { clearInterval(tickRef.current); return 100; }
        return p + 1;
      });
    }, 100);
  };
  const stop = () => {
    setRunning(false);
    if (tickRef.current) clearInterval(tickRef.current);
  };

  const finishedAt75 = !running && pct === 75;

  return (
    <div className="tc-stack" style={{ maxWidth: 480, width: "100%" }}>
      <div
        className="pb-track"
        data-testid="progress-bar"
        role="progressbar"
        aria-valuenow={pct}
        aria-valuemin={0}
        aria-valuemax={100}
        aria-label="progress"
      >
        <div
          className="pb-fill"
          data-testid="progress-fill"
          data-value={pct}
          style={{ width: `${pct}%` }}
        />
      </div>
      <div className="tc-row">
        <button className="btn" data-testid="start" onClick={start} disabled={running || pct >= 100}>Start</button>
        <button className="btn btn-secondary" data-testid="stop" onClick={stop} disabled={!running}>Stop</button>
      </div>
      {!running && pct > 0 && (
        <div
          className={"test-result " + (finishedAt75 ? "success" : "danger")}
          data-testid="result"
        >
          {finishedAt75 ? `✓ Stopped at exactly 75%.` : `✗ Stopped at ${pct}%. Target: 75%.`}
        </div>
      )}
    </div>
  );
};

/* ---------- Visibility ---------- */
TestComponents["visibility"] = function VisibilityTest({ resetKey }) {
  const [hidden, setHidden] = useState(null);
  useEffect(() => { setHidden(null); }, [resetKey]);

  const targetStyles = {
    null:        {},
    display:     { display: "none" },
    visibility:  { visibility: "hidden" },
    opacity:     { opacity: 0, pointerEvents: "none" },
    offscreen:   { position: "absolute", left: "-9999px" },
    "zero-size": { width: 0, height: 0, padding: 0, overflow: "hidden", border: 0 },
    covered:     {},
  };

  const techniques = [
    { key: "display",    label: "Hide A" },
    { key: "visibility", label: "Hide B" },
    { key: "opacity",    label: "Hide C" },
    { key: "offscreen",  label: "Hide D" },
    { key: "zero-size",  label: "Hide E" },
    { key: "covered",    label: "Hide F" },
  ];

  return (
    <div className="tc-stack" style={{ maxWidth: 520, width: "100%" }}>
      <div
        style={{
          position: "relative",
          padding: 32,
          background: "var(--bg-muted)",
          border: "1px solid var(--border)",
          borderRadius: 8,
          display: "flex",
          justifyContent: "center",
          minHeight: 84,
        }}
      >
        <span
          className="vs-target"
          data-testid="target"
          style={targetStyles[hidden] || {}}
        >
          I am the target
        </span>
        {hidden === "covered" && (
          <div
            data-testid="cover-overlay"
            style={{
              position: "absolute",
              inset: 0,
              background: "var(--bg-muted)",
              borderRadius: 8,
            }}
          />
        )}
      </div>

      <div className="vs-controls" data-testid="hide-buttons">
        {techniques.map((t) => (
          <button
            key={t.key}
            className="btn btn-secondary btn-sm"
            data-testid={`hide-${t.key}`}
            onClick={() => setHidden(t.key)}
          >
            {t.label}
          </button>
        ))}
      </div>
      <button className="btn btn-ghost btn-sm" data-testid="visibility-reset" onClick={() => setHidden(null)}>
        Reset target
      </button>
    </div>
  );
};

/* ---------- Overlapped Element (must click first) ---------- */
TestComponents["overlapped-element"] = function OverlappedTest({ resetKey }) {
  const [email, setEmail] = useState("");
  const [clickedField, setClickedField] = useState(false);
  const [warn, setWarn] = useState(false);
  const ref = useRef(null);
  useEffect(() => {
    setEmail(""); setClickedField(false); setWarn(false);
    if (ref.current) ref.current.scrollTo(0, 0);
  }, [resetKey]);

  // Capture typing-without-click as failure: track keydown when not focused via click
  const onKeyDown = (e) => {
    if (!clickedField) {
      e.preventDefault();
      setWarn(true);
    }
  };

  return (
    <div className="tc-stack">
      <div className="ov-container" data-testid="overlapped-container" ref={ref}>
        <div className="ov-fixed">⓵ Personal info (scroll inside this box)</div>
        <label className="field-label" htmlFor="ov-name">Name</label>
        <input
          id="ov-name"
          className="input"
          data-testid="name-input"
          placeholder="Jane Doe"
          style={{ marginBottom: 12 }}
        />
        <label className="field-label" htmlFor="ov-email">Email</label>
        <input
          id="ov-email"
          className="input"
          data-testid="overlapped-input"
          value={email}
          onClick={() => { setClickedField(true); setWarn(false); }}
          onKeyDown={onKeyDown}
          onChange={(e) => clickedField && setEmail(e.target.value)}
          onBlur={() => setClickedField(false)}
          placeholder="you@example.com"
        />
      </div>
      {warn && (
        <div className="test-result danger" data-testid="result">
          ✗ Typed without clicking the field first. Click the email field first, then type.
        </div>
      )}
      {!warn && email && (
        <div className="test-result success" data-testid="result">
          ✓ Email captured: <strong>{email}</strong>
        </div>
      )}
    </div>
  );
};

/* ---------- Shadow DOM ---------- */
TestComponents["shadow-dom"] = function ShadowDomTest({ resetKey }) {
  const hostRef = useRef(null);
  const [outerEcho, setOuterEcho] = useState("");

  useEffect(() => {
    const host = hostRef.current;
    if (!host) return;
    let root = host.shadowRoot;
    if (!root) root = host.attachShadow({ mode: "open" });
    root.innerHTML = `
      <style>
        :host { display: block; }
        .wrap {
          padding: 16px;
          background: #f4f4f5;
          border: 1px dashed #d4d4d8;
          border-radius: 10px;
          display: flex;
          flex-direction: column;
          gap: 10px;
          font-family: ui-sans-serif, system-ui, sans-serif;
        }
        .badge {
          align-self: flex-start;
          font-size: 10px;
          font-weight: 600;
          text-transform: uppercase;
          letter-spacing: 0.08em;
          padding: 3px 8px;
          border-radius: 999px;
          background: #eef2ff;
          color: #3730a3;
        }
        input {
          height: 36px;
          padding: 0 12px;
          border-radius: 6px;
          border: 1px solid #d4d4d8;
          font-size: 14px;
          font-family: inherit;
        }
        button {
          height: 36px;
          padding: 0 14px;
          border-radius: 6px;
          border: none;
          background: var(--accent, #4f46e5);
          color: white;
          font-weight: 500;
          font-size: 14px;
          cursor: pointer;
          font-family: inherit;
        }
        .result {
          font-family: ui-monospace, monospace;
          font-size: 12px;
          padding: 8px 10px;
          background: #dcfce7;
          color: #16a34a;
          border-radius: 6px;
        }
      </style>
      <div class="wrap">
        <span class="badge">custom component</span>
        <input data-testid="shadow-input" placeholder="Type here…" />
        <button data-testid="shadow-submit">Submit</button>
        <div class="result" data-testid="shadow-result" style="display:none"></div>
      </div>
    `;
    const input = root.querySelector('[data-testid="shadow-input"]');
    const submit = root.querySelector('[data-testid="shadow-submit"]');
    const result = root.querySelector('[data-testid="shadow-result"]');
    const handler = () => {
      const v = input.value;
      result.style.display = v ? "block" : "none";
      result.textContent = v ? `✓ Submitted: ${v}` : "";
      setOuterEcho(v);
    };
    submit.addEventListener("click", handler);
    return () => submit.removeEventListener("click", handler);
  }, [resetKey]);

  return (
    <div className="tc-stack">
      <shadow-input ref={hostRef} data-testid="shadow-host"></shadow-input>
      {outerEcho && (
        <div className="test-result success" data-testid="outer-echo">
          Outer page received: <strong>{outerEcho}</strong>
        </div>
      )}
    </div>
  );
};

/* ---------- File Upload ---------- */
TestComponents["file-upload"] = function FileUploadTest({ resetKey }) {
  const [files, setFiles] = useState([]);
  const [dragging, setDragging] = useState(false);
  const inputRef = useRef(null);
  useEffect(() => { setFiles([]); if (inputRef.current) inputRef.current.value = ""; }, [resetKey]);

  const addFiles = (list) => {
    const next = Array.from(list).map((f) => ({ name: f.name, size: f.size, type: f.type || "application/octet-stream" }));
    setFiles((prev) => [...prev, ...next]);
  };

  return (
    <div className="tc-stack">
      <label
        className={"fu-drop" + (dragging ? " dragging" : "")}
        data-testid="file-drop"
        onDragOver={(e) => { e.preventDefault(); setDragging(true); }}
        onDragLeave={() => setDragging(false)}
        onDrop={(e) => { e.preventDefault(); setDragging(false); addFiles(e.dataTransfer.files); }}
      >
        <Icon.Upload className="fu-drop-icon" />
        <div style={{ fontSize: 14, fontWeight: 500, color: "var(--fg)" }}>
          Drop files here or click to browse
        </div>
        <div style={{ fontSize: 12, color: "var(--fg-muted)", marginTop: 4 }}>
          Any file type, any size
        </div>
        <input
          ref={inputRef}
          type="file"
          data-testid="file-input"
          multiple
          onChange={(e) => addFiles(e.target.files)}
        />
      </label>
      {files.length > 0 && (
        <div className="fu-files" data-testid="uploaded-files">
          {files.map((f, i) => (
            <div key={i} className="fu-file" data-testid="uploaded-file">
              <Icon.File /> <span>{f.name}</span>
              <span style={{ marginLeft: "auto", color: "var(--fg-faint)" }}>{(f.size/1024).toFixed(1)} KB</span>
            </div>
          ))}
        </div>
      )}
    </div>
  );
};

/* ---------- Mystery Button (was Frames; no iframe hint anywhere on the page) ---------- */
TestComponents["mystery-button"] = function MysteryButtonTest({ resetKey }) {
  const [counter, setCounter] = useState(0);
  useEffect(() => { setCounter(0); }, [resetKey]);

  const frameHtml = useMemo(() => {
    return `<!DOCTYPE html><html><head><style>
      html, body { margin: 0; padding: 0; font-family: ui-sans-serif, system-ui, sans-serif; background: #fafafa; color: #09090b; }
      .wrap { padding: 24px; text-align: center; display: flex; flex-direction: column; gap: 12px; align-items: center; justify-content: center; height: 100%; box-sizing: border-box; }
      button { height: 38px; padding: 0 16px; border-radius: 6px; border: none; background: #4f46e5; color: white; font-weight: 500; font-size: 14px; cursor: pointer; }
      button:hover { background: #4338ca; }
      .hint { font-size: 12px; color: #71717a; }
    </style></head><body><div class="wrap">
      <button data-testid="frame-button" id="b">Click me from the parent</button>
      <div class="hint">(button must be clicked to win)</div>
    </div>
    <script>
      const b = document.getElementById('b');
      b.addEventListener('click', () => {
        parent.postMessage({ type: 'mystery-click' }, '*');
      });
    </script>
    </body></html>`;
  }, []);

  useEffect(() => {
    const onMsg = (e) => {
      if (e.data?.type === "mystery-click") setCounter((n) => n + 1);
    };
    window.addEventListener("message", onMsg);
    return () => window.removeEventListener("message", onMsg);
  }, []);

  const [k, setK] = useState(0);
  useEffect(() => { setK((x) => x + 1); }, [resetKey]);

  return (
    <div className="tc-stack">
      <div className="frame-wrap">
        <iframe
          key={k}
          data-testid="sandbox-frame"
          title="content"
          srcDoc={frameHtml}
          sandbox="allow-scripts allow-same-origin"
        />
      </div>
      <div
        className={"test-result " + (counter > 0 ? "success" : "")}
        data-testid="outer-counter"
        data-count={counter}
      >
        Counter: <strong>{counter}</strong>
      </div>
    </div>
  );
};

/* ---------- Disabled Input ---------- */
TestComponents["disabled-input"] = function DisabledInputTest({ resetKey }) {
  const [enabled, setEnabled] = useState(false);
  const [activating, setActivating] = useState(false);
  const [value, setValue] = useState("");

  useEffect(() => { setEnabled(false); setActivating(false); setValue(""); }, [resetKey]);

  const activate = () => {
    setActivating(true);
    setTimeout(() => { setEnabled(true); setActivating(false); }, 4000);
  };

  return (
    <div className="tc-stack">
      <button
        className="btn"
        data-testid="activate"
        onClick={activate}
        disabled={activating || enabled}
      >
        {activating ? <><span className="spinner" /> Unlocking input…</> :
         enabled ? "Input unlocked" : "Activate input"}
      </button>
      <input
        className="input"
        data-testid="disabled-input"
        placeholder={enabled ? "Type here" : "Waiting to be unlocked…"}
        disabled={!enabled}
        value={value}
        onChange={(e) => setValue(e.target.value)}
      />
      {enabled && value && (
        <div className="test-result success" data-testid="result">
          ✓ Captured: <strong>{value}</strong>
        </div>
      )}
    </div>
  );
};

/* ---------- Chart Interaction (Google Charts) ---------- */
TestComponents["chart-interaction"] = function ChartInteractionTest({ resetKey }) {
  // Categories + expected values shown to the candidate
  const data = useMemo(
    () => [
      { category: "Mon", expected: 124 },
      { category: "Tue", expected: 218 },
      { category: "Wed", expected: 167 },
      { category: "Thu", expected: 305 },
      { category: "Fri", expected: 280 },
      { category: "Sat", expected: 92 },
      { category: "Sun", expected: 148 },
    ],
    []
  );

  const chartRef = useRef(null);
  const [hoveredIdx, setHoveredIdx] = useState(null);

  useEffect(() => {
    // Load Google Charts once
    const ensure = () => new Promise((resolve) => {
      if (window.google && window.google.charts) return resolve();
      const existing = document.querySelector('script[data-gcharts]');
      if (existing) {
        existing.addEventListener("load", resolve);
        return;
      }
      const s = document.createElement("script");
      s.src = "https://www.gstatic.com/charts/loader.js";
      s.dataset.gcharts = "1";
      s.onload = resolve;
      document.head.appendChild(s);
    });

    let chart;
    let cancelled = false;
    ensure().then(() => {
      if (cancelled) return;
      window.google.charts.load("current", { packages: ["corechart"] });
      window.google.charts.setOnLoadCallback(() => {
        if (cancelled || !chartRef.current) return;
        const dt = new window.google.visualization.DataTable();
        dt.addColumn("string", "Day");
        dt.addColumn("number", "Visitors");
        dt.addRows(data.map((d) => [d.category, d.expected]));

        chart = new window.google.visualization.ColumnChart(chartRef.current);
        const styles = getComputedStyle(document.documentElement);
        const accent = styles.getPropertyValue("--accent").trim() || "#4f46e5";
        const fg     = styles.getPropertyValue("--fg").trim() || "#09090b";
        const muted  = styles.getPropertyValue("--fg-muted").trim() || "#52525b";
        const border = styles.getPropertyValue("--border").trim() || "#e4e4e7";

        chart.draw(dt, {
          backgroundColor: "transparent",
          colors: [accent],
          chartArea: { left: 56, top: 24, right: 16, bottom: 40, width: "100%", height: "100%" },
          legend: { position: "none" },
          tooltip: { isHtml: true, trigger: "focus" },
          hAxis: { textStyle: { color: muted, fontSize: 12 } },
          vAxis: { textStyle: { color: muted, fontSize: 12 }, gridlines: { color: border }, baselineColor: border },
          height: 280,
          animation: { startup: true, duration: 400, easing: "out" },
          bar: { groupWidth: "60%" },
          fontName: "Geist, ui-sans-serif, system-ui, sans-serif",
        });

        window.google.visualization.events.addListener(chart, "onmouseover", (e) => {
          if (typeof e.row === "number") setHoveredIdx(e.row);
        });
        window.google.visualization.events.addListener(chart, "onmouseout", () => {
          setHoveredIdx(null);
        });
      });
    });

    return () => { cancelled = true; if (chart) chart.clearChart && chart.clearChart(); };
  }, [resetKey, data]);

  return (
    <div className="tc-stack" style={{ maxWidth: 720, width: "100%" }}>
      <div className="chart-layout">
        <div className="chart-host" data-testid="chart" ref={chartRef} />
        <div className="chart-legend" data-testid="expected-values">
          <div className="chart-legend-title">Expected values</div>
          <ul className="chart-legend-list">
            {data.map((d, i) => (
              <li
                key={d.category}
                data-testid={`expected-${d.category.toLowerCase()}`}
                data-category={d.category}
                data-value={d.expected}
                className={hoveredIdx === i ? "active" : ""}
              >
                <span className="cat">{d.category}</span>
                <span className="val">{d.expected}</span>
              </li>
            ))}
          </ul>
        </div>
      </div>
      <div
        className="test-result"
        data-testid="hovered-value"
        data-row={hoveredIdx ?? ""}
        data-category={hoveredIdx != null ? data[hoveredIdx].category : ""}
        data-value={hoveredIdx != null ? data[hoveredIdx].expected : ""}
      >
        {hoveredIdx != null
          ? <>Hovering <strong>{data[hoveredIdx].category}</strong> · value <strong>{data[hoveredIdx].expected}</strong></>
          : <>Hover any column to read its value.</>}
      </div>
    </div>
  );
};

/* ---------- Auto Wait (strict: exactly one click at the right moment) ---------- */
TestComponents["auto-wait"] = function AutoWaitTest({ resetKey }) {
  // stages: 0 idle, 1 visible+disabled (Loading), 2 enabled but Almost, 3 ready (Click me now)
  const [stage, setStage] = useState(0);
  const [clickCount, setClickCount] = useState(0);
  const [verdict, setVerdict] = useState(null); // "success" | "early" | "extra"
  const [log, setLog] = useState([]);
  const timers = useRef([]);

  useEffect(() => {
    timers.current.forEach((t) => clearTimeout(t));
    timers.current = [];
    setStage(0); setClickCount(0); setVerdict(null); setLog([]);
  }, [resetKey]);

  useEffect(() => () => { timers.current.forEach((t) => clearTimeout(t)); }, []);

  const pushLog = (t) => setLog((l) => [...l, `[${new Date().toLocaleTimeString()}] ${t}`]);

  const start = () => {
    timers.current.forEach((t) => clearTimeout(t));
    timers.current = [];
    setStage(0); setClickCount(0); setVerdict(null); setLog([]);
    pushLog("sequence started");
    timers.current.push(setTimeout(() => { setStage(1); pushLog("target rendered (Loading…)"); }, 2000));
    timers.current.push(setTimeout(() => { setStage(2); pushLog("target enabled (Almost…)"); }, 4000));
    timers.current.push(setTimeout(() => { setStage(3); pushLog("target ready (Click me now)"); }, 6000));
  };

  const onTargetClick = () => {
    setClickCount((c) => {
      const next = c + 1;
      if (next === 1) {
        if (stage < 3) {
          setVerdict("early");
          pushLog(`✗ clicked too early (stage ${stage})`);
        } else {
          setVerdict("success");
          pushLog("✓ clicked at the right moment");
        }
      } else {
        setVerdict("extra");
        pushLog(`✗ extra click registered (total ${next})`);
      }
      return next;
    });
  };

  const targetLabel =
    stage === 0 ? "—" :
    stage === 1 ? "Loading…" :
    stage === 2 ? "Almost…" :
    "Click me now";

  return (
    <div className="aw-stage">
      <div className="tc-row">
        <button className="btn" data-testid="start" onClick={start}>Start sequence</button>
        {stage >= 1 && (
          <button
            className={"btn " + (stage >= 3 ? "" : "btn-secondary")}
            data-testid="target"
            data-stage={stage}
            disabled={stage < 2}
            onClick={onTargetClick}
          >
            {targetLabel}
          </button>
        )}
      </div>

      {log.length > 0 && (
        <div className="aw-log" data-testid="event-log">
          {log.map((l, i) => <div key={i}>{l}</div>)}
        </div>
      )}

      {verdict === "success" && (
        <div className="test-result success" data-testid="result">
          ✓ Target clicked exactly once at the right moment.
        </div>
      )}
      {verdict === "early" && (
        <div className="test-result danger" data-testid="result">
          ✗ Clicked the target before it was ready. Wait for the label to read "Click me now".
        </div>
      )}
      {verdict === "extra" && (
        <div className="test-result danger" data-testid="result">
          ✗ Target was clicked more than once ({clickCount} total). It must be clicked exactly once.
        </div>
      )}
    </div>
  );
};

window.TestComponents = TestComponents;
