/* ==================================================================
   KinetiX Online Tests — teacher screen
   - Type picker (Blitz / Classical)
   - Paste raw MCQ text → Gemini formats → editable form
   - Save as draft / Save & make live (publishes to KinetiX student portal)
   ================================================================== */

const KXT_COBALT = "#2E5BFF";

const TYPE_PRESETS = [
  {
    id: "blitz",
    label: "Blitz",
    tagline: "Per-question timer · no back-tracking",
    detail: "Best for rapid practice and reflex drills. Each question gets a hard time limit; once it expires, the answer is locked.",
    accent: "#E63946",
  },
  {
    id: "classical",
    label: "Classical",
    tagline: "Total timer · free navigation",
    detail: "Best for full-length tests. One overall timer; students can navigate freely, mark for review, and use the palette.",
    accent: KXT_COBALT,
  },
  {
    id: "descriptive",
    label: "Descriptive",
    tagline: "Students upload photo answers · teacher grades manually",
    detail: "Long-form questions. Students attach photo(s) of their handwritten work per question. No options, no auto-grading — every mark comes from your review.",
    accent: "#FFB627",
  },
];

const KinetiXTests = ({ setScreen }) => {
  const [testType,  setTestType]  = React.useState("classical");
  const [rawText,   setRawText]   = React.useState("");
  const [parsed,    setParsed]    = React.useState(null);
  const [warnings,  setWarnings]  = React.useState([]);
  const [formatting, setFormatting] = React.useState(false);
  const [saving,    setSaving]    = React.useState(false);
  const [err,       setErr]       = React.useState("");
  const [successId, setSuccessId] = React.useState(null);

  const handleFormat = async () => {
    if (!rawText.trim()) { setErr("Paste some MCQs first."); return; }
    setErr(""); setFormatting(true); setWarnings([]);
    try {
      const r = await window.KXApi.post("/online-tests/format", {
        raw_text: rawText, type: testType,
      });
      setParsed(r.parsed);
      setWarnings(Array.isArray(r.parsed?.warnings) ? r.parsed.warnings : []);
    } catch (e) {
      setErr(e.message || "Gemini failed to parse the text");
    } finally {
      setFormatting(false);
    }
  };

  const handleSave = async (publish) => {
    if (!parsed) { setErr("Format the test before saving."); return; }
    setErr(""); setSaving(true);
    try {
      const r = await window.KXApi.post("/online-tests", {
        parsed, type: testType, publish: !!publish,
        // Multi-section: send the chip set. Backend UNIONs students from
        // every section in the array when auto-enrolling.
        class_section_ids: (parsed.class_section_ids && parsed.class_section_ids.length)
          ? parsed.class_section_ids : undefined,
        subject_id: parsed.subject_id || undefined,
      });
      setSuccessId(r.id);
    } catch (e) {
      const errs = e.message?.includes("validation failed") ? e.message : (e.message || "Save failed");
      setErr(errs);
    } finally {
      setSaving(false);
    }
  };

  if (successId) {
    return (
      <KXScreenShell>
        <KXSuccess id={successId} onBack={() => setScreen("library")} onNew={() => {
          setSuccessId(null); setParsed(null); setRawText(""); setWarnings(""); setErr("");
        }}/>
      </KXScreenShell>
    );
  }

  return (
    <KXScreenShell>
      <div style={{ display: "grid", gridTemplateColumns: "260px 1fr", gap: 20 }}>
        {/* Left column — type picker (sticky) */}
        <div style={{ position: "sticky", top: 12, alignSelf: "start" }}>
          <KXLabel>Test type</KXLabel>
          <div style={{ display: "flex", flexDirection: "column", gap: 10, marginTop: 8 }}>
            {TYPE_PRESETS.map((t) => (
              <TypeCard key={t.id} preset={t} selected={testType === t.id}
                        onClick={() => setTestType(t.id)} disabled={!!parsed}/>
            ))}
          </div>
          {parsed && (
            <div style={{ fontSize: 10, color: "var(--ink-3)", marginTop: 10 }}>
              Type is locked once formatted. Click "Reset" below the form to start over.
            </div>
          )}
        </div>

        {/* Right column — paste + form */}
        <div style={{ display: "flex", flexDirection: "column", gap: 16, minWidth: 0 }}>
          {!parsed && (
            <PasteCard
              raw={rawText} setRaw={setRawText}
              formatting={formatting} onFormat={handleFormat}
            />
          )}

          {warnings.length > 0 && (
            <div style={{
              background: "rgba(255, 182, 39, 0.08)",
              border: "1px solid rgba(255, 182, 39, 0.3)",
              borderRadius: 8, padding: "10px 12px",
            }}>
              <div style={{ fontSize: 10, color: "var(--amber)", fontFamily: "var(--mono, monospace)",
                            letterSpacing: ".1em", textTransform: "uppercase", marginBottom: 4 }}>
                Gemini warnings
              </div>
              {warnings.map((w, i) => (
                <div key={i} style={{ fontSize: 12, color: "var(--ink-1)" }}>• {w}</div>
              ))}
            </div>
          )}

          {parsed && (
            <FormEditor parsed={parsed} setParsed={setParsed} testType={testType}
                        onReset={() => { setParsed(null); setWarnings([]); }}/>
          )}

          {err && (
            <div style={{
              background: "rgba(230, 57, 70, 0.08)",
              border: "1px solid rgba(230, 57, 70, 0.3)",
              borderRadius: 8, padding: "10px 12px",
              color: "var(--red)", fontSize: 12, whiteSpace: "pre-wrap",
            }}>{err}</div>
          )}

          {parsed && (
            <div style={{ display: "flex", gap: 8, justifyContent: "flex-end", paddingTop: 8,
                          borderTop: "1px solid var(--line)" }}>
              <button className="btn" disabled={saving} onClick={() => handleSave(false)}>
                {saving ? "Saving…" : "Save as draft"}
              </button>
              <button className="btn primary" disabled={saving} onClick={() => handleSave(true)}
                      style={{ background: KXT_COBALT, borderColor: KXT_COBALT }}>
                {saving ? "Saving…" : "Save & make live"}
              </button>
            </div>
          )}
        </div>
      </div>
    </KXScreenShell>
  );
};

/* ---------- Shell wrapping for the screen body ---------- */
const KXScreenShell = ({ children }) => (
  <div style={{ padding: 24, maxWidth: 1080, margin: "0 auto" }}>
    <div style={{ marginBottom: 18 }}>
      <div style={{ fontSize: 10, color: KXT_COBALT, fontFamily: "monospace",
                    letterSpacing: ".12em", textTransform: "uppercase", marginBottom: 4 }}>
        KinetiX · Online tests
      </div>
      <h1 style={{ margin: 0, color: "var(--ink-0)", fontSize: 22, fontWeight: 600 }}>
        Create online test
      </h1>
      <div style={{ fontSize: 12, color: "var(--ink-2)", marginTop: 4 }}>
        Paste raw MCQs · Gemini formats them · review · publish to the student portal.
      </div>
    </div>
    {children}
  </div>
);

const KXLabel = ({ children }) => (
  <div style={{ fontSize: 10, color: "var(--ink-3)", letterSpacing: ".1em",
                textTransform: "uppercase", fontFamily: "monospace" }}>{children}</div>
);

const TypeCard = ({ preset, selected, onClick, disabled }) => (
  <div onClick={disabled ? null : onClick}
       style={{
         border: `1px solid ${selected ? preset.accent : "var(--line-strong)"}`,
         borderRadius: 10, padding: 12, cursor: disabled ? "not-allowed" : "pointer",
         background: selected ? `${preset.accent}10` : "transparent",
         opacity: disabled && !selected ? 0.5 : 1, transition: "all 120ms",
       }}>
    <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 4 }}>
      <div style={{ width: 8, height: 8, borderRadius: "50%", background: preset.accent }}/>
      <div style={{ color: "var(--ink-0)", fontSize: 13, fontWeight: 600 }}>{preset.label}</div>
    </div>
    <div style={{ color: "var(--ink-2)", fontSize: 11, marginBottom: 6 }}>{preset.tagline}</div>
    <div style={{ color: "var(--ink-3)", fontSize: 10, lineHeight: 1.4 }}>{preset.detail}</div>
  </div>
);

/* ---------- Paste card ---------- */
const PasteCard = ({ raw, setRaw, formatting, onFormat }) => (
  <div style={{
    border: "1px solid var(--line)", borderRadius: 10, padding: 16,
    background: "var(--bg-1)",
  }}>
    <KXLabel>Paste MCQs</KXLabel>
    <div style={{ fontSize: 11, color: "var(--ink-3)", marginTop: 4, marginBottom: 10 }}>
      Free-text — Gemini will identify questions, options, and the correct answer if marked.
    </div>
    <textarea
      value={raw} onChange={(e) => setRaw(e.target.value)}
      placeholder={`Example:\n\n1. Which gas is most abundant in Earth's atmosphere?\n(A) Oxygen\n(B) Nitrogen  *\n(C) Carbon dioxide\n(D) Argon\n\n2. ...`}
      style={{
        width: "100%", minHeight: 220, resize: "vertical",
        background: "var(--bg-0)", color: "var(--ink-0)",
        border: "1px solid var(--line)", borderRadius: 6,
        padding: "10px 12px", fontSize: 13, fontFamily: "monospace",
        boxSizing: "border-box",
      }}
    />
    <div style={{ display: "flex", justifyContent: "flex-end", marginTop: 10 }}>
      <button className="btn primary" disabled={formatting || !raw.trim()} onClick={onFormat}
              style={{ background: KXT_COBALT, borderColor: KXT_COBALT }}>
        {formatting ? "Formatting…" : "Format with Gemini ✨"}
      </button>
    </div>
  </div>
);

/* ---------- Form editor ---------- */
const FormEditor = ({ parsed, setParsed, testType, onReset }) => {
  const classSections = window.KX?.CLASS_SECTIONS || [];
  const subjects = window.KX?.SUBJECTS || [];
  const classLevels = Array.from(new Set(classSections.map(cs => cs.class_level))).sort();
  const isDescriptive = testType === "descriptive";

  const set = (path, value) => {
    setParsed((prev) => {
      const next = JSON.parse(JSON.stringify(prev));
      const keys = path.split(".");
      let node = next;
      for (let i = 0; i < keys.length - 1; i++) {
        const k = keys[i];
        node = /^\d+$/.test(k) ? node[Number(k)] : node[k];
      }
      const last = keys[keys.length - 1];
      if (/^\d+$/.test(last)) node[Number(last)] = value;
      else node[last] = value;
      return next;
    });
  };

  const setCorrect = (si, qi, oi) => {
    setParsed((prev) => {
      const next = JSON.parse(JSON.stringify(prev));
      next.sections[si].questions[qi].options.forEach((o, idx) => o.is_correct = (idx === oi));
      return next;
    });
  };

  const addQuestion = (si) => {
    setParsed((prev) => {
      const next = JSON.parse(JSON.stringify(prev));
      const sec = next.sections[si];
      sec.questions.push({
        q_no: sec.questions.length + 1, text: "",
        marks: sec.questions[0]?.marks ?? (isDescriptive ? 5 : 1),
        options: isDescriptive
          ? []
          : ["A","B","C","D"].map((l, i) => ({ letter: l, text: "", is_correct: i === 0 })),
      });
      return next;
    });
  };

  const delQuestion = (si, qi) => {
    setParsed((prev) => {
      const next = JSON.parse(JSON.stringify(prev));
      next.sections[si].questions.splice(qi, 1);
      // Renumber q_no.
      next.sections[si].questions.forEach((q, i) => q.q_no = i + 1);
      return next;
    });
  };

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      {/* Meta block */}
      <div style={{ border: "1px solid var(--line)", borderRadius: 10,
                    background: "var(--bg-1)", padding: 16 }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 12 }}>
          <KXLabel>Test details</KXLabel>
          <button className="btn sm" onClick={onReset} title="Discard and paste again">↺ Reset</button>
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
          <Field label="Title" required>
            <Input value={parsed.title || ""} onChange={(v) => set("title", v)}/>
          </Field>
          <Field label="Class" required>
            <Dropdown value={parsed.className || ""} placeholder="Select class"
                      options={classLevels}
                      onChange={(v) => {
                        // Reset section set to the first available section so
                        // the chip row isn't empty after the class change.
                        const first = classSections.find(cs => cs.class_level === v);
                        setParsed(prev => ({
                          ...prev, className: v,
                          class_section_ids: first ? [first.id] : [],
                        }));
                      }}/>
          </Field>
          <Field label="Subject" required>
            <Dropdown value={parsed.subject_id || ""} placeholder="Select subject"
                      options={subjects.map(s => ({ value: s.id, label: s.name }))}
                      onChange={(v) => {
                        const s = subjects.find(x => x.id === v);
                        setParsed(prev => ({ ...prev, subject_id: v, subject: s?.name || "" }));
                      }}/>
          </Field>
          <div style={{ gridColumn: "1 / span 2" }}>
            <Field label="Sections — tap to add (one test can target several)" required>
              <window.KXUI.SectionChips classLevel={parsed.className}
                selectedIds={parsed.class_section_ids || []}
                onChange={(ids) => setParsed(prev => ({ ...prev, class_section_ids: ids }))}/>
            </Field>
          </div>
          {/* Chapter + topic dropdowns sourced from the admin catalog (see
              screen-manual.jsx for the shared picker). Plays nicely with the
              existing `parsed.topic` / `parsed.chapter` text fields — the
              picker reads + writes through them. The wrapper just adapts
              {meta,setMeta} → {parsed,setParsed} so the existing component
              can be reused unchanged. */}
          <window.ChapterTopicFields
            meta={parsed}
            setMeta={(next) => setParsed(next)}
            subjects={subjects}/>
          <Field label="Scheduled date">
            <Input type="date" value={parsed.scheduled_date || ""}
                   onChange={(v) => set("scheduled_date", v || null)}/>
          </Field>
        </div>
      </div>

      {/* Sections */}
      {(parsed.sections || []).map((sec, si) => (
        <div key={si} style={{ border: "1px solid var(--line)", borderRadius: 10,
                               background: "var(--bg-1)", padding: 16 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 12 }}>
            <KXLabel>Section {sec.label}</KXLabel>
            <input value={sec.title || ""} placeholder="Optional section title"
                   onChange={(e) => set(`sections.${si}.title`, e.target.value)}
                   style={{ flex: 1, background: "transparent", border: "none",
                            color: "var(--ink-1)", fontSize: 13, padding: 2, outline: "none" }}/>
            <span style={{ fontSize: 11, color: "var(--ink-3)" }}>{sec.questions.length} questions</span>
          </div>

          {sec.questions.map((q, qi) => (
            <QuestionEditor key={qi} q={q} si={si} qi={qi} set={set}
                            isDescriptive={isDescriptive}
                            setCorrect={setCorrect} onDelete={() => delQuestion(si, qi)}/>
          ))}

          <button className="btn sm" onClick={() => addQuestion(si)}
                  style={{ marginTop: 8 }}>+ Add question</button>
        </div>
      ))}
    </div>
  );
};

/* ---------- Image upload helper ---------- */
async function uploadImage(file) {
  if (!file) return null;
  if (!/^image\//.test(file.type)) throw new Error("Not an image file");
  if (file.size > 5 * 1024 * 1024) throw new Error("Image is larger than 5 MB");
  const form = new FormData();
  form.append("file", file, file.name);
  const r = await fetch("/api/online-tests/upload-image", { method: "POST", body: form });
  if (!r.ok) throw new Error(`upload ${r.status}: ${await r.text()}`);
  return (await r.json()).url;
}

/* ---------- Multi-image list (used for question + solution images) ---------- */
const ImageList = ({ label, urls, onAdd, onRemove }) => {
  const [busy, setBusy] = React.useState(false);
  const inputRef = React.useRef(null);
  const list = Array.isArray(urls) ? urls : [];

  const pick = async (file) => {
    if (!file) return;
    setBusy(true);
    try { onAdd(await uploadImage(file)); }
    catch (e) { alert("Upload failed: " + (e?.message || e)); }
    finally { setBusy(false); if (inputRef.current) inputRef.current.value = ""; }
  };

  return (
    <div style={{ marginTop: 8 }}>
      <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 6 }}>
        <span style={{ fontSize: 10, color: "var(--ink-3)", fontFamily: "monospace",
                       letterSpacing: ".1em", textTransform: "uppercase" }}>{label}</span>
        <button type="button" className="btn sm" disabled={busy}
                onClick={() => inputRef.current?.click()}>
          {busy ? "Uploading…" : "+ image"}
        </button>
        <input ref={inputRef} type="file" accept="image/png,image/jpeg,image/webp,image/gif"
               style={{ display: "none" }}
               onChange={(e) => pick(e.target.files?.[0])}/>
      </div>
      {list.length > 0 && (
        <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
          {list.map((src, i) => (
            <div key={i} style={{ position: "relative", border: "1px solid var(--line)", borderRadius: 6, padding: 3, background: "var(--bg-1)" }}>
              <img src={src} alt="" style={{ display: "block", maxHeight: 80, maxWidth: 140, borderRadius: 4 }}/>
              <button type="button" title="Remove"
                      onClick={() => onRemove(i)}
                      style={{ position: "absolute", top: -6, right: -6, width: 18, height: 18,
                               borderRadius: "50%", border: "1px solid var(--line-strong)",
                               background: "var(--bg-2)", color: "var(--red)", fontSize: 11, lineHeight: "16px",
                               cursor: "pointer", padding: 0 }}>×</button>
            </div>
          ))}
        </div>
      )}
    </div>
  );
};

/* ---------- Single-image slot (per option) ---------- */
const OptionImage = ({ url, onChange }) => {
  const [busy, setBusy] = React.useState(false);
  const inputRef = React.useRef(null);

  const pick = async (file) => {
    if (!file) return;
    setBusy(true);
    try { onChange(await uploadImage(file)); }
    catch (e) { alert("Upload failed: " + (e?.message || e)); }
    finally { setBusy(false); if (inputRef.current) inputRef.current.value = ""; }
  };

  if (url) {
    return (
      <div style={{ position: "relative", marginLeft: 4 }}>
        <img src={url} alt="" style={{ display: "block", height: 28, borderRadius: 3, border: "1px solid var(--line)" }}/>
        <button type="button" title="Remove" onClick={() => onChange(null)}
                style={{ position: "absolute", top: -5, right: -5, width: 14, height: 14, borderRadius: "50%",
                         border: "1px solid var(--line-strong)", background: "var(--bg-2)",
                         color: "var(--red)", fontSize: 9, lineHeight: "12px", padding: 0, cursor: "pointer" }}>×</button>
      </div>
    );
  }
  return (
    <>
      <button type="button" className="btn sm" disabled={busy}
              onClick={() => inputRef.current?.click()}
              title="Attach option image"
              style={{ marginLeft: 4, padding: "2px 6px", fontSize: 10 }}>
        {busy ? "…" : "+img"}
      </button>
      <input ref={inputRef} type="file" accept="image/png,image/jpeg,image/webp,image/gif"
             style={{ display: "none" }}
             onChange={(e) => pick(e.target.files?.[0])}/>
    </>
  );
};

const QuestionEditor = ({ q, si, qi, set, setCorrect, onDelete, isDescriptive }) => {
  const imgs = q.images || { question: [], solution: [] };
  const setImages = (next) => set(`sections.${si}.questions.${qi}.images`, next);
  const addQImage = (url) => setImages({ ...imgs, question: [...(imgs.question || []), url] });
  const rmQImage = (i)   => setImages({ ...imgs, question: (imgs.question || []).filter((_, k) => k !== i) });
  const addSImage = (url) => setImages({ ...imgs, solution: [...(imgs.solution || []), url] });
  const rmSImage = (i)   => setImages({ ...imgs, solution: (imgs.solution || []).filter((_, k) => k !== i) });
  const options = Array.isArray(q.options) ? q.options : [];

  return (
    <div style={{ border: "1px solid var(--line)", borderRadius: 8, padding: 12,
                  background: "var(--bg-0)", marginBottom: 8 }}>
      <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 8 }}>
        <span style={{ fontFamily: "monospace", fontSize: 10, color: "var(--ink-3)",
                       letterSpacing: ".1em" }}>Q{q.q_no}</span>
        <input type="number" value={q.marks ?? 1} min={1} step={0.25}
               onChange={(e) => set(`sections.${si}.questions.${qi}.marks`, Number(e.target.value))}
               style={{ width: 60, background: "var(--bg-1)", border: "1px solid var(--line)",
                        borderRadius: 4, padding: "2px 6px", color: "var(--ink-1)", fontSize: 11 }}/>
        <span style={{ fontSize: 10, color: "var(--ink-3)" }}>marks</span>
        <button className="btn sm" onClick={onDelete} title="Delete question"
                style={{ marginLeft: "auto", color: "var(--red)" }}>×</button>
      </div>
      <textarea value={q.text || ""} placeholder="Question text"
                onChange={(e) => set(`sections.${si}.questions.${qi}.text`, e.target.value)}
                style={{
                  width: "100%", minHeight: 60, resize: "vertical",
                  background: "var(--bg-1)", border: "1px solid var(--line)", borderRadius: 6,
                  padding: "8px 10px", color: "var(--ink-0)", fontSize: 13,
                  boxSizing: "border-box",
                }}/>

      <ImageList label="Question images" urls={imgs.question} onAdd={addQImage} onRemove={rmQImage}/>

      {isDescriptive ? (
        <div style={{
          marginTop: 10, padding: "10px 12px",
          border: "1px dashed rgba(46,91,255,0.35)", borderRadius: 6,
          background: "rgba(46,91,255,0.04)", fontSize: 11.5, color: "var(--ink-2)",
        }}>
          Students will upload photo(s) of their handwritten answer for this question. Set marks above; grade after submission from the Submissions modal.
        </div>
      ) : (
        <div style={{ display: "flex", flexDirection: "column", gap: 6, marginTop: 8 }}>
          {options.map((o, oi) => (
            <div key={oi} style={{ display: "flex", alignItems: "center", gap: 8 }}>
              <input type="radio" name={`correct-${si}-${qi}`} checked={!!o.is_correct}
                     onChange={() => setCorrect(si, qi, oi)} title="Correct answer"/>
              <span style={{ fontFamily: "monospace", fontSize: 11, color: "var(--ink-2)", width: 14 }}>
                {o.letter}.
              </span>
              <input value={o.text || ""}
                     onChange={(e) => set(`sections.${si}.questions.${qi}.options.${oi}.text`, e.target.value)}
                     placeholder={`Option ${o.letter}`}
                     style={{ flex: 1, background: "var(--bg-1)", border: "1px solid var(--line)",
                              borderRadius: 4, padding: "5px 8px", color: "var(--ink-0)", fontSize: 12 }}/>
              <OptionImage url={o.image || null}
                           onChange={(url) => set(`sections.${si}.questions.${qi}.options.${oi}.image`, url)}/>
            </div>
          ))}
        </div>
      )}

      <ImageList label="Solution images" urls={imgs.solution} onAdd={addSImage} onRemove={rmSImage}/>
    </div>
  );
};

const Field = ({ label, required, children }) => (
  <label style={{ display: "flex", flexDirection: "column", gap: 4 }}>
    <span style={{ fontSize: 10, color: "var(--ink-3)", letterSpacing: ".08em",
                   textTransform: "uppercase", fontFamily: "monospace" }}>
      {label}{required && <span style={{ color: KXT_COBALT, marginLeft: 4 }}>*</span>}
    </span>
    {children}
  </label>
);

const FIELD_STYLE = {
  background: "var(--bg-0)", border: "1px solid var(--line)", borderRadius: 5,
  padding: "6px 10px", color: "var(--ink-0)", fontSize: 12, width: "100%",
  boxSizing: "border-box",
};

const Input = ({ value, onChange, placeholder, type = "text" }) => (
  <input type={type} value={value ?? ""} placeholder={placeholder}
         onChange={(e) => onChange(e.target.value)}
         // colorScheme: dark so the native date picker chrome (calendar icon,
         // popover) renders dark too instead of a white square on dark theme.
         style={{ ...FIELD_STYLE, colorScheme: "dark" }}/>
);

const Dropdown = ({ value, onChange, options, placeholder }) => (
  <select value={value ?? ""} onChange={(e) => onChange(e.target.value)}
          style={{ ...FIELD_STYLE, appearance: "auto", colorScheme: "dark" }}>
    {placeholder && <option value="" disabled>{placeholder}</option>}
    {options.map((o) => (
      typeof o === "string"
        ? <option key={o} value={o}>{o}</option>
        : <option key={o.value} value={o.value}>{o.label}</option>
    ))}
  </select>
);

/* ---------- Success splash ---------- */
const KXSuccess = ({ id, onBack, onNew }) => (
  <div style={{ border: "1px solid var(--line)", borderRadius: 10,
                background: "var(--bg-1)", padding: 32, textAlign: "center" }}>
    <div style={{ width: 48, height: 48, borderRadius: "50%",
                  background: `${KXT_COBALT}20`, border: `1px solid ${KXT_COBALT}60`,
                  margin: "0 auto 16px", display: "grid", placeItems: "center",
                  color: KXT_COBALT, fontSize: 22 }}>✓</div>
    <div style={{ color: "var(--ink-0)", fontSize: 16, fontWeight: 600, marginBottom: 4 }}>
      Test {id} saved
    </div>
    <div style={{ fontSize: 12, color: "var(--ink-2)", marginBottom: 20 }}>
      Use the ⚙ button on the test card if you want to tweak the engine config later.
    </div>
    <div style={{ display: "flex", gap: 8, justifyContent: "center" }}>
      <button className="btn" onClick={onBack}>← Back to library</button>
      <button className="btn primary" onClick={onNew}
              style={{ background: KXT_COBALT, borderColor: KXT_COBALT }}>+ Create another</button>
    </div>
  </div>
);

window.KinetiXTests = KinetiXTests;
