// Source: index.html script block (extracted). Loaded via <script type="text/babel"> in index.html.
//
// Manager-only "Task generator" blade. 5-step human-in-the-loop wizard that
// drafts tau2 tasks from the PrimeKG static index (built by the
// scripts/build_primekg_index.mjs pipeline, served at /primekg/*). The LLM
// draft step calls window.generateTaskFromKG; if the helper or the static
// data isn't deployed yet the UI renders a friendly banner instead of
// crashing.

const GEN_DRAFT_KEY = "caa_generator_draft";
// Soft cap for the no-search view so we don't render 4,952 DOM rows at once.
// When the user is actively searching, we render every match (filter typically
// narrows to <200 anyway) so nothing is hidden behind the cap.
const GEN_DISEASE_CAP = 500;
const GEN_STEP_LABELS_KEYS = [
  "gen_step_disease",
  "gen_step_phenotypes",
  "gen_step_extra",
  "gen_step_refine",
  "gen_step_commit",
];

function genEmptyDraft() {
  return {
    disease: null,
    selectedPhenos: [],
    extraSymptoms: [],
    llm: null,
  };
}

function genLoadDraft() {
  try {
    const raw = localStorage.getItem(GEN_DRAFT_KEY);
    if (!raw) return genEmptyDraft();
    const parsed = JSON.parse(raw);
    if (!parsed || typeof parsed !== "object") return genEmptyDraft();
    return {
      disease: parsed.disease || null,
      selectedPhenos: Array.isArray(parsed.selectedPhenos) ? parsed.selectedPhenos : [],
      extraSymptoms: Array.isArray(parsed.extraSymptoms) ? parsed.extraSymptoms : [],
      llm: parsed.llm && typeof parsed.llm === "object" ? parsed.llm : null,
    };
  } catch { return genEmptyDraft(); }
}

function genSaveDraft(draft) {
  try { localStorage.setItem(GEN_DRAFT_KEY, JSON.stringify(draft || {})); } catch {}
}

function genAsArray(v) { return Array.isArray(v) ? v : []; }

// Convert the wizard's draft + llm output into the internal flat task shape
// used by the Editor / importCohort endpoint. Leans on createBlankTask for the
// safe defaults and only overrides fields the generator actually produced.
function genBuildTask({ user, draft, cohortId }) {
  const llm = (draft && draft.llm) || {};
  const base = window.createBlankTask({ author: (user && user.name) || "generator" });
  const diseaseName = (draft && draft.disease && draft.disease.name) || "";
  const chief = llm.chief || llm.reasonForCall || "";
  const presentPhenos = genAsArray(draft && draft.selectedPhenos).filter(p => !p.absent).map(p => p.name);
  const absentPhenos = genAsArray(draft && draft.selectedPhenos).filter(p => p.absent).map(p => p.name);
  const historySeed = presentPhenos.concat(genAsArray(draft && draft.extraSymptoms));
  return {
    ...base,
    id: (llm.id && String(llm.id).trim()) || `primekg_gen_${Date.now().toString(36)}`,
    status: "needs-review",
    domain: llm.domain || "primekg",
    chief,
    reasonForCall: llm.reasonForCall || chief,
    taskInstructions: llm.taskInstructions || "",
    diagnosis: llm.diagnosis || diseaseName,
    requiredTools: genAsArray(llm.requiredTools),
    optionalTools: genAsArray(llm.optionalTools),
    forbiddenTools: genAsArray(llm.forbiddenTools),
    reasoning: genAsArray(llm.reasoning),
    mustAskAbout: genAsArray(llm.mustAskAbout),
    mustNotLeak: genAsArray(llm.mustNotLeak),
    history: historySeed,
    cohortId,
    primekg: {
      disease_id: (draft && draft.disease && draft.disease.id) || null,
      disease_name: diseaseName,
      phenotypes_present: presentPhenos,
      phenotypes_absent: absentPhenos,
      extra_symptoms: genAsArray(draft && draft.extraSymptoms),
    },
  };
}

// ---- disease search hook -----------------------------------------------------
function useGenDiseases() {
  const [state, setState] = React.useState({ items: [], loading: true, error: null });
  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const res = await fetch("/primekg/index.json", { credentials: "same-origin" });
        if (!res.ok) throw new Error("HTTP " + res.status);
        const data = await res.json();
        const items = Array.isArray(data && data.diseases) ? data.diseases : [];
        if (!cancelled) setState({ items, loading: false, error: null });
      } catch (err) {
        if (!cancelled) setState({ items: [], loading: false, error: (err && err.message) || String(err) });
      }
    })();
    return () => { cancelled = true; };
  }, []);
  return state;
}

// ---- per-disease bundle loader (session cache) --------------------------------
const __GEN_BUNDLE_CACHE = new Map();
function useGenDiseaseBundle(diseaseId) {
  const [state, setState] = React.useState({ bundle: null, loading: false, error: null });
  React.useEffect(() => {
    if (!diseaseId) { setState({ bundle: null, loading: false, error: null }); return; }
    const hit = __GEN_BUNDLE_CACHE.get(diseaseId);
    if (hit) { setState({ bundle: hit, loading: false, error: null }); return; }
    let cancelled = false;
    setState({ bundle: null, loading: true, error: null });
    (async () => {
      try {
        const res = await fetch("/primekg/disease/" + encodeURIComponent(diseaseId) + ".json", {
          credentials: "same-origin",
        });
        if (!res.ok) throw new Error("HTTP " + res.status);
        const data = await res.json();
        __GEN_BUNDLE_CACHE.set(diseaseId, data);
        if (!cancelled) setState({ bundle: data, loading: false, error: null });
      } catch (err) {
        if (!cancelled) setState({ bundle: null, loading: false, error: (err && err.message) || String(err) });
      }
    })();
    return () => { cancelled = true; };
  }, [diseaseId]);
  return state;
}

// ---- main page --------------------------------------------------------------

function GeneratorPage({ user }) {
  const { t: tr } = useLang();

  if (!user || user.kind !== "manager") {
    return (
      <div className="gen" style={{padding:"22px 32px", color:"var(--ink-dim)"}}>Managers only</div>
    );
  }

  const [step, setStep] = React.useState(1);
  const [draft, setDraft] = React.useState(() => genLoadDraft());
  React.useEffect(() => { genSaveDraft(draft); }, [draft]);

  const diseaseState = useGenDiseases();
  const bundleState = useGenDiseaseBundle(step >= 2 && draft.disease ? draft.disease.id : null);

  const patchDraft = React.useCallback((patch) => {
    setDraft(prev => ({ ...prev, ...patch }));
  }, []);
  const resetDraft = () => {
    setDraft(genEmptyDraft());
    setStep(1);
  };

  const [notice, setNotice] = React.useState(null);
  const showNotice = React.useCallback((msg) => {
    setNotice(msg);
    window.setTimeout(() => setNotice(n => (n === msg ? null : n)), 4500);
  }, []);

  const canAdvance =
    (step === 1 && !!draft.disease) ||
    (step === 2 && draft.selectedPhenos.length > 0) ||
    (step === 3) ||
    (step === 4 && !!draft.llm) ||
    (step === 5);

  return (
    <div className="gen">
      <div className="gen-head">
        <div>
          <div style={{fontSize:11.5, color:"var(--ink-mute)", fontFamily:"var(--font-mono)", textTransform:"uppercase", letterSpacing:"0.06em"}}>
            Admin · manager
          </div>
          <h1>{tr("gen_title")}</h1>
          <p>{tr("gen_subtitle")}</p>
        </div>
        <div style={{marginLeft:"auto", display:"flex", gap:8, alignItems:"center"}}>
          <button className="btn ghost" onClick={resetDraft} title="Clear draft and restart">
            {Ico.reset ? Ico.reset() : null} Reset draft
          </button>
        </div>
      </div>

      {diseaseState.error && !diseaseState.loading && (
        <div className="gen-banner">
          {Ico.warn()} {tr("gen_no_data")}
          <span style={{marginLeft:"auto", fontFamily:"var(--font-mono)", fontSize:11}}>{diseaseState.error}</span>
        </div>
      )}
      {notice && <div className="gen-notice">{notice}</div>}

      <div className="gen-stepper">
        {GEN_STEP_LABELS_KEYS.map((k, i) => {
          const n = i + 1;
          const state = step > n ? "done" : step === n ? "active" : "pending";
          return (
            <button
              key={k}
              className="gen-step"
              data-state={state}
              onClick={() => {
                // Allow jumping back freely; forward only if prior steps pass gates.
                if (n <= step) setStep(n);
                else if (n === 2 && draft.disease) setStep(2);
                else if (n === 3 && draft.selectedPhenos.length > 0) setStep(3);
                else if (n === 4 && draft.selectedPhenos.length > 0) setStep(4);
                else if (n === 5 && draft.llm) setStep(5);
              }}
              title={tr(k)}
            >
              <span className="gen-step-n">{n}</span>
              <span>{tr(k)}</span>
            </button>
          );
        })}
      </div>

      <div className="gen-body">
        {step === 1 && (
          <GenStepDisease
            diseases={diseaseState.items}
            loading={diseaseState.loading}
            selected={draft.disease}
            onPick={(d) => { patchDraft({ disease: d, selectedPhenos: [], extraSymptoms: draft.extraSymptoms, llm: null }); setStep(2); }}
            tr={tr}
          />
        )}
        {step === 2 && (
          <GenStepPhenotypes
            disease={draft.disease}
            bundle={bundleState.bundle}
            loading={bundleState.loading}
            error={bundleState.error}
            selected={draft.selectedPhenos}
            onToggle={(p) => {
              const has = draft.selectedPhenos.some(x => x.id === p.id);
              const next = has
                ? draft.selectedPhenos.filter(x => x.id !== p.id)
                : draft.selectedPhenos.concat([p]);
              patchDraft({ selectedPhenos: next });
            }}
            tr={tr}
          />
        )}
        {step === 3 && (
          <GenStepExtras
            bundle={bundleState.bundle}
            disease={draft.disease}
            selectedPhenos={draft.selectedPhenos}
            extras={draft.extraSymptoms}
            onChange={(arr) => patchDraft({ extraSymptoms: arr })}
            tr={tr}
          />
        )}
        {step === 4 && (
          <GenStepRefine
            draft={draft}
            onLlm={(llm) => patchDraft({ llm })}
            tr={tr}
          />
        )}
        {step === 5 && (
          <GenStepCommit
            user={user}
            draft={draft}
            onCommitted={(task) => {
              showNotice(`Task created: ${task.id}`);
              try { localStorage.removeItem(GEN_DRAFT_KEY); } catch {}
              setDraft(genEmptyDraft());
              setStep(1);
              window.dispatchEvent(new CustomEvent("caa-set-route", {
                detail: { route: "editor", activeId: task.id },
              }));
            }}
            onNotice={showNotice}
            tr={tr}
          />
        )}
      </div>

      <div className="gen-foot">
        {step > 1 && (
          <button className="btn ghost" onClick={() => setStep(step - 1)}>← Back</button>
        )}
        <div style={{marginLeft:"auto", display:"flex", gap:8}}>
          {step < 5 && (
            <button
              className="btn primary"
              onClick={() => setStep(step + 1)}
              disabled={!canAdvance}
              title={!canAdvance ? "Complete this step first" : ""}
            >
              Next →
            </button>
          )}
        </div>
      </div>
    </div>
  );
}

// ---- step 1: pick disease ----------------------------------------------------

function GenStepDisease({ diseases, loading, selected, onPick, tr }) {
  const [input, setInput] = React.useState("");
  const [q, setQ] = React.useState("");
  const [page, setPage] = React.useState(0);
  React.useEffect(() => {
    const id = window.setTimeout(() => setQ(input.trim().toLowerCase()), 200);
    return () => window.clearTimeout(id);
  }, [input]);
  // Reset to first page whenever the search changes so the user doesn't stay
  // on an out-of-range page of a narrower result set.
  React.useEffect(() => { setPage(0); }, [q]);

  // Filter first (across all diseases), then paginate. Page size matches
  // GEN_DISEASE_CAP so the soft-cap doubles as the page step.
  const matches = React.useMemo(() => {
    if (!q) return diseases;
    const out = [];
    for (let i = 0; i < diseases.length; i++) {
      const d = diseases[i];
      if (d && d.name && d.name.toLowerCase().indexOf(q) !== -1) out.push(d);
    }
    return out;
  }, [q, diseases]);
  const pageSize = GEN_DISEASE_CAP;
  const pageCount = Math.max(1, Math.ceil(matches.length / pageSize));
  const safePage = Math.min(page, pageCount - 1);
  const startIdx = safePage * pageSize;
  const endIdx = Math.min(matches.length, startIdx + pageSize);
  const pageSlice = matches.slice(startIdx, endIdx);

  return (
    <div>
      <div style={{position:"relative", marginBottom:12}}>
        <span style={{position:"absolute", top:8, left:10, color:"var(--ink-mute)"}}>{Ico.search ? Ico.search() : null}</span>
        <input
          className="inp"
          placeholder={tr("gen_search_disease_ph")}
          value={input}
          onChange={e => setInput(e.target.value)}
          style={{paddingLeft:30, width:"100%"}}
          autoFocus
        />
      </div>
      <div style={{display:"flex", justifyContent:"space-between", alignItems:"center", fontSize:11.5, color:"var(--ink-mute)", fontFamily:"var(--font-mono)", marginBottom:8, gap:8}}>
        <span>{loading ? "Loading diseases…" : `${diseases.length.toLocaleString()} diseases indexed · ${matches.length.toLocaleString()} match${matches.length === 1 ? "" : "es"}`}</span>
        <span style={{display:"inline-flex", alignItems:"center", gap:6}}>
          {matches.length > 0 && (
            <span>
              {(startIdx + 1).toLocaleString()}–{endIdx.toLocaleString()}
            </span>
          )}
          <button
            className="btn ghost"
            style={{padding:"2px 8px", fontSize:11}}
            disabled={safePage === 0}
            onClick={() => setPage(p => Math.max(0, p - 1))}
            title="Previous page"
          >← prev</button>
          <span>
            page {safePage + 1} / {pageCount}
          </span>
          <button
            className="btn ghost"
            style={{padding:"2px 8px", fontSize:11}}
            disabled={safePage >= pageCount - 1}
            onClick={() => setPage(p => Math.min(pageCount - 1, p + 1))}
            title="Next page"
          >next →</button>
        </span>
      </div>
      <div className="gen-list">
        {matches.length === 0 && !loading && (
          <div style={{padding:"20px 14px", color:"var(--ink-mute)", fontSize:13}}>
            No matches. Try a different term.
          </div>
        )}
        {pageSlice.map(d => (
          <button
            key={d.id}
            className="gen-list-row"
            data-active={!!(selected && selected.id === d.id)}
            onClick={() => onPick(d)}
          >
            <span style={{flex:1}}>{d.name}</span>
            <code style={{fontFamily:"var(--font-mono)", fontSize:11, color:"var(--ink-mute)"}}>{d.id}</code>
          </button>
        ))}
      </div>
      {pageCount > 1 && (
        <div style={{display:"flex", justifyContent:"center", gap:8, marginTop:10}}>
          <button className="btn" style={{padding:"3px 10px", fontSize:11.5}} disabled={safePage === 0} onClick={() => setPage(0)} title="Jump to first page">⇤ first</button>
          <button className="btn" style={{padding:"3px 10px", fontSize:11.5}} disabled={safePage === 0} onClick={() => setPage(p => Math.max(0, p - 1))}>← prev</button>
          <span style={{fontSize:11.5, color:"var(--ink-mute)", fontFamily:"var(--font-mono)", alignSelf:"center"}}>page {safePage + 1} of {pageCount}</span>
          <button className="btn" style={{padding:"3px 10px", fontSize:11.5}} disabled={safePage >= pageCount - 1} onClick={() => setPage(p => Math.min(pageCount - 1, p + 1))}>next →</button>
          <button className="btn" style={{padding:"3px 10px", fontSize:11.5}} disabled={safePage >= pageCount - 1} onClick={() => setPage(pageCount - 1)} title="Jump to last page">last ⇥</button>
        </div>
      )}
    </div>
  );
}

// ---- step 2: pick phenotypes -------------------------------------------------

function GenStepPhenotypes({ disease, bundle, loading, error, selected, onToggle, tr }) {
  const [input, setInput] = React.useState("");
  const [q, setQ] = React.useState("");
  React.useEffect(() => {
    const id = window.setTimeout(() => setQ(input.trim().toLowerCase()), 150);
    return () => window.clearTimeout(id);
  }, [input]);

  const phenotypes = genAsArray(bundle && bundle.phenotypes);
  const match = (p) => !q || (p.name && p.name.toLowerCase().indexOf(q) !== -1);
  const present = phenotypes.filter(p => !p.absent && match(p));
  const absent = phenotypes.filter(p => p.absent && match(p));
  const isSelected = (p) => selected.some(s => s.id === p.id);

  return (
    <div>
      <div style={{marginBottom:12, display:"flex", alignItems:"center", gap:10}}>
        <div style={{fontSize:13}}>
          <b>{(disease && disease.name) || "—"}</b>
          <code style={{fontFamily:"var(--font-mono)", fontSize:11, color:"var(--ink-mute)", marginLeft:8}}>{disease && disease.id}</code>
        </div>
        <div style={{marginLeft:"auto", fontSize:11.5, color:"var(--ink-mute)", fontFamily:"var(--font-mono)"}}>
          {phenotypes.length} phenotypes · {selected.length} selected
        </div>
      </div>

      {loading && <div style={{padding:"12px 14px", color:"var(--ink-mute)", fontSize:12.5}}>{tr("gen_loading_disease")}</div>}
      {error && !loading && (
        <div className="gen-banner">
          {Ico.warn()} Failed to load disease bundle.
          <span style={{marginLeft:"auto", fontFamily:"var(--font-mono)", fontSize:11}}>{error}</span>
        </div>
      )}

      {!loading && !error && (
        <>
          <div style={{position:"relative", marginBottom:12}}>
            <span style={{position:"absolute", top:8, left:10, color:"var(--ink-mute)"}}>{Ico.search ? Ico.search() : null}</span>
            <input
              className="inp"
              placeholder={tr("gen_search_phenotype_ph")}
              value={input}
              onChange={e => setInput(e.target.value)}
              style={{paddingLeft:30, width:"100%"}}
            />
          </div>
          <div className="gen-two-col">
            <div>
              <div className="gen-section-label">Present ({present.length})</div>
              <div className="gen-list">
                {present.length === 0 && <div style={{padding:"12px 14px", color:"var(--ink-mute)", fontSize:12}}>No present phenotypes.</div>}
                {present.map(p => (
                  <label key={p.id} className="gen-phen-row">
                    <input type="checkbox" checked={isSelected(p)} onChange={() => onToggle(p)}/>
                    <span style={{flex:1}}>{p.name}</span>
                    <code style={{fontFamily:"var(--font-mono)", fontSize:10.5, color:"var(--ink-mute)"}}>{p.id}</code>
                  </label>
                ))}
              </div>
              {absent.length > 0 && (
                <>
                  <div className="gen-section-label" style={{marginTop:14}}>Absent / ruled out ({absent.length})</div>
                  <div className="gen-list">
                    {absent.map(p => (
                      <label key={p.id} className="gen-phen-row" data-absent="true">
                        <input type="checkbox" checked={isSelected(p)} onChange={() => onToggle(p)}/>
                        <span style={{flex:1}}>{p.name}</span>
                        <span className="tag red" style={{fontSize:10}}>absent</span>
                      </label>
                    ))}
                  </div>
                </>
              )}
            </div>
            <div>
              <div className="gen-section-label">Selected ({selected.length})</div>
              <div className="gen-selected-panel">
                {selected.length === 0 && <div style={{padding:"12px 14px", color:"var(--ink-mute)", fontSize:12}}>Pick at least one phenotype to continue.</div>}
                {selected.map(p => (
                  <button key={p.id} className="gen-chip" onClick={() => onToggle(p)} title="Click to remove">
                    {p.absent ? <span className="tag red" style={{fontSize:10}}>absent</span> : null}
                    <span>{p.name}</span>
                    {Ico.x()}
                  </button>
                ))}
              </div>
            </div>
          </div>
        </>
      )}
    </div>
  );
}

// ---- step 3: differentiating symptoms ----------------------------------------

function GenStepExtras({ bundle, disease, selectedPhenos, extras, onChange, tr }) {
  const [input, setInput] = React.useState("");
  const related = genAsArray(bundle && bundle.related_diseases).slice(0, 5);

  const [sugBusy, setSugBusy] = React.useState(false);
  const [sugErr, setSugErr] = React.useState(null);
  const [suggestions, setSuggestions] = React.useState([]);

  const addExtra = () => {
    const v = input.trim();
    if (!v) return;
    if (extras.indexOf(v) === -1) onChange(extras.concat([v]));
    setInput("");
  };
  const removeAt = (i) => onChange(extras.filter((_, idx) => idx !== i));

  const addSuggested = (sym) => {
    const v = String(sym || "").trim();
    if (!v) return;
    if (extras.indexOf(v) === -1) onChange(extras.concat([v]));
    setSuggestions(prev => prev.filter(s => s.symptom !== sym));
  };

  const runSuggest = async () => {
    if (typeof window.suggestDifferentiatingSymptoms !== "function") {
      setSugErr("Helper not available (window.suggestDifferentiatingSymptoms missing).");
      return;
    }
    setSugBusy(true); setSugErr(null);
    try {
      const res = await window.suggestDifferentiatingSymptoms({
        disease,
        phenotypes: genAsArray(selectedPhenos),
        relatedDiseases: related,
        bundle,
        extraSymptoms: extras,
      });
      setSuggestions(genAsArray(res && res.suggestions));
    } catch (e) {
      setSugErr((e && e.message) || String(e));
    } finally {
      setSugBusy(false);
    }
  };

  return (
    <div>
      <div style={{fontSize:13, color:"var(--ink-dim)", marginBottom:10, maxWidth:640}}>
        Add extra symptoms to make this case distinguishable from similar diseases.
      </div>
      {related.length > 0 && (
        <>
          <div className="gen-section-label">Related diseases (by shared phenotypes)</div>
          <div style={{display:"flex", flexWrap:"wrap", gap:6, marginBottom:14}}>
            {related.map(r => (
              <span
                key={r.id}
                className="tag"
                title={`Shares ${r.shared_phenotypes || 0} phenotypes with the picked disease.`}
              >
                {r.name}
                <span style={{marginLeft:6, color:"var(--ink-mute)", fontFamily:"var(--font-mono)", fontSize:10.5}}>
                  ({r.shared_phenotypes || 0} shared)
                </span>
              </span>
            ))}
          </div>
        </>
      )}

      <div className="gen-section-label" style={{display:"flex", alignItems:"center", gap:8}}>
        <span>LLM-suggested differentiators</span>
        <button
          className="btn ghost"
          style={{marginLeft:"auto", padding:"2px 10px", fontSize:11}}
          onClick={runSuggest}
          disabled={sugBusy || !disease}
          title="Ask the LLM for additional symptoms that distinguish this disease from its confounders"
        >
          {sugBusy ? "Suggesting…" : (<>{Ico.play ? Ico.play() : null} Suggest differentiators</>)}
        </button>
      </div>
      {sugErr && <div className="gen-banner" style={{marginBottom:10}}>{Ico.warn()} {sugErr}</div>}
      {suggestions.length > 0 && (
        <div style={{display:"flex", flexWrap:"wrap", gap:6, marginBottom:14}}>
          {suggestions.map((s, i) => (
            <button
              key={s.symptom + "_" + i}
              className="gen-chip"
              onClick={() => addSuggested(s.symptom)}
              title={s.rationale + (s.differentiates_from && s.differentiates_from.length ? ` — vs ${s.differentiates_from.join(", ")}` : "")}
            >
              {Ico.plus ? Ico.plus() : null}
              <span>{s.symptom}</span>
            </button>
          ))}
        </div>
      )}

      <div className="gen-section-label">Extra symptoms</div>
      <div style={{display:"flex", gap:6, alignItems:"center", marginBottom:8}}>
        <input
          className="inp"
          placeholder={tr("gen_add_symptom_ph")}
          value={input}
          onChange={e => setInput(e.target.value)}
          onKeyDown={e => { if (e.key === "Enter") { e.preventDefault(); addExtra(); } }}
          style={{flex:1}}
        />
        <button className="btn" onClick={addExtra} disabled={!input.trim()}>{Ico.plus()} Add</button>
      </div>
      {extras.length === 0 ? (
        <div style={{fontSize:12, color:"var(--ink-mute)", fontStyle:"italic", padding:"6px 0"}}>No extras yet — this step is optional.</div>
      ) : (
        <div style={{display:"flex", flexWrap:"wrap", gap:6}}>
          {extras.map((s, i) => (
            <button key={s + "_" + i} className="gen-chip" onClick={() => removeAt(i)} title="Click to remove">
              <span>{s}</span>
              {Ico.x()}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

// ---- step 4: LLM refine ------------------------------------------------------

function GenStepRefine({ draft, onLlm, tr }) {
  const cfg = (typeof getLlmConfig === "function") ? getLlmConfig() : { apiKey: "" };
  const hasKey = !!cfg.apiKey;
  const hasHelper = typeof window.generateTaskFromKG === "function";
  const hasCriteriaHelper = typeof window.suggestEvaluationCriteria === "function";
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState(null);
  const [critBusy, setCritBusy] = React.useState(false);
  const [critErr, setCritErr] = React.useState(null);
  const [critFilter, setCritFilter] = React.useState("all"); // all | grounded | model | safety

  const run = async () => {
    if (!hasHelper) { setErr("LLM helper not available (window.generateTaskFromKG missing)."); return; }
    if (!hasKey) { setErr(tr("gen_no_key")); return; }
    setBusy(true);
    setErr(null);
    try {
      const res = await window.generateTaskFromKG(draft);
      onLlm(res || {});
    } catch (e) {
      setErr((e && e.message) || String(e));
    } finally {
      setBusy(false);
    }
  };

  const llm = draft.llm || null;

  const patchField = (key, value) => {
    onLlm({ ...(llm || {}), [key]: value });
  };

  const runCriteria = async () => {
    if (!hasCriteriaHelper) { setCritErr("LLM helper window.suggestEvaluationCriteria missing."); return; }
    if (!hasKey) { setCritErr(tr("gen_no_key")); return; }
    setCritBusy(true);
    setCritErr(null);
    try {
      const res = await window.suggestEvaluationCriteria(draft);
      const criteria = (res && Array.isArray(res.criteria)) ? res.criteria : [];
      patchField("criteria", criteria);
    } catch (e) {
      setCritErr((e && e.message) || String(e));
    } finally {
      setCritBusy(false);
    }
  };

  const patchCriterionAt = (idx, patch) => {
    const cur = genAsArray(llm && llm.criteria);
    const next = cur.map((c, i) => (i === idx ? { ...c, ...patch } : c));
    patchField("criteria", next);
  };
  const removeCriterionAt = (idx) => {
    const cur = genAsArray(llm && llm.criteria);
    patchField("criteria", cur.filter((_, i) => i !== idx));
  };
  const addBlankCriterion = () => {
    const cur = genAsArray(llm && llm.criteria);
    patchField("criteria", cur.concat([{
      field: "mustAskAbout", value: "", rationale: "",
      weight: "required", source: "clinical_reasoning",
      related_disease_ruled_out: null, accepted: true,
    }]));
  };

  const SOURCE_GROUPS = {
    grounded: ["PrimeKG_phenotype","PrimeKG_related","PrimeKG_indication","PrimeKG_contraindication","treatment_guideline"],
    model:    ["clinical_reasoning"],
    safety:   ["safety_default"],
  };
  const sourceGroup = (s) =>
    SOURCE_GROUPS.grounded.includes(s) ? "grounded"
    : SOURCE_GROUPS.safety.includes(s) ? "safety"
    : "model";
  const sourceColor = (grp) =>
    grp === "grounded" ? "var(--green, #2e8a53)"
    : grp === "safety" ? "var(--accent, #b45309)"
    : "var(--ink-mute)";

  return (
    <div>
      <div style={{display:"flex", gap:8, alignItems:"center", marginBottom:12}}>
        <button className="btn primary" onClick={run} disabled={busy || !hasKey || !hasHelper}>
          {busy ? (tr("gen_refining")) : (<>{Ico.play()} {tr("gen_refine_btn")}</>)}
        </button>
        {!hasKey && <span style={{fontSize:12, color:"var(--red, #b91c1c)"}}>{tr("gen_no_key")}</span>}
        {!hasHelper && hasKey && <span style={{fontSize:12, color:"var(--amber, #b45309)"}}>LLM helper not available.</span>}
      </div>

      {err && <div className="gen-banner">{Ico.warn()} {err}</div>}

      {!llm && !busy && (
        <div style={{fontSize:12.5, color:"var(--ink-mute)", padding:"20px 12px", textAlign:"center", border:"1px dashed var(--line)", borderRadius:6}}>
          Draft will appear here. The helper reads your picked disease, phenotypes, and extras and returns a tau2 skeleton you can edit before committing.
        </div>
      )}

      {llm && (
        <div className="gen-refine-grid">
          <div className="form-field"><label>Chief complaint</label>
            <input value={llm.chief || ""} onChange={e => patchField("chief", e.target.value)}/>
          </div>
          <div className="form-field"><label>Reason for call</label>
            <input value={llm.reasonForCall || ""} onChange={e => patchField("reasonForCall", e.target.value)}/>
          </div>
          <div className="form-field" style={{gridColumn:"1 / -1"}}><label>Task instructions (system)</label>
            <textarea className="ta" rows={3} value={llm.taskInstructions || ""} onChange={e => patchField("taskInstructions", e.target.value)}/>
          </div>
          <div className="form-field"><label>Required diagnosis</label>
            <input value={llm.diagnosis || ""} onChange={e => patchField("diagnosis", e.target.value)}/>
          </div>
          <div className="form-field"><label>Required tools (comma)</label>
            <input
              value={genAsArray(llm.requiredTools).join(", ")}
              onChange={e => patchField("requiredTools", e.target.value.split(",").map(s => s.trim()).filter(Boolean))}
            />
          </div>
          <div className="form-field"><label>Optional tools (comma)</label>
            <input
              value={genAsArray(llm.optionalTools).join(", ")}
              onChange={e => patchField("optionalTools", e.target.value.split(",").map(s => s.trim()).filter(Boolean))}
            />
          </div>
          <div className="form-field"><label>Forbidden tools (comma)</label>
            <input
              value={genAsArray(llm.forbiddenTools).join(", ")}
              onChange={e => patchField("forbiddenTools", e.target.value.split(",").map(s => s.trim()).filter(Boolean))}
            />
          </div>
          <div style={{gridColumn:"1 / -1"}}>
            <EditableList
              title="Reasoning steps"
              items={genAsArray(llm.reasoning)}
              numbered multiline
              placeholder="e.g. Ask about duration and severity first"
              addLabel="Add step"
              onChange={(next) => patchField("reasoning", next)}
            />
            <EditableList
              title="Must ask about"
              items={genAsArray(llm.mustAskAbout)}
              tag={{ text: "ask", cls: "accent" }}
              placeholder="e.g. recent travel history"
              addLabel="Add item"
              onChange={(next) => patchField("mustAskAbout", next)}
            />
            <EditableList
              title="Must not leak"
              items={genAsArray(llm.mustNotLeak)}
              tag={{ text: "leak", cls: "red" }}
              placeholder="e.g. true diagnosis label"
              addLabel="Add item"
              onChange={(next) => patchField("mustNotLeak", next)}
            />
          </div>
          {/* ── per-criterion review panel ─────────────────────────────
              Every item carries source + rationale + weight, so the annotator
              can audit *why* the LLM proposed each criterion and accept/edit/
              remove individually. Accepted criteria are folded back into the
              task on commit via window.applyCriteriaToTask. */}
          <div style={{gridColumn:"1 / -1", marginTop:18, paddingTop:14, borderTop:"1px solid var(--line)"}}>
            <div style={{display:"flex", alignItems:"center", gap:10, marginBottom:10, flexWrap:"wrap"}}>
              <div className="gen-section-label" style={{margin:0}}>
                Evaluation criteria
                {genAsArray(llm.criteria).length > 0 && (
                  <span style={{marginLeft:6, color:"var(--ink-mute)", fontSize:11, fontFamily:"var(--font-mono)", textTransform:"none", letterSpacing:0}}>
                    · {genAsArray(llm.criteria).filter(c => c.accepted).length} accepted / {genAsArray(llm.criteria).length} proposed
                  </span>
                )}
              </div>
              <button
                className="btn"
                style={{padding:"3px 10px", fontSize:11.5, marginLeft:"auto"}}
                onClick={runCriteria}
                disabled={critBusy || !hasKey || !hasCriteriaHelper}
                title={!hasCriteriaHelper ? "window.suggestEvaluationCriteria missing" : "Call the LLM to propose rubric items with provenance"}
              >{critBusy ? "Proposing…" : (genAsArray(llm.criteria).length ? "↻ Re-propose criteria" : "Propose criteria with LLM")}</button>
              <button className="btn ghost" style={{padding:"3px 8px", fontSize:11.5}} onClick={addBlankCriterion} title="Add a blank criterion manually">
                {Ico.plus ? Ico.plus() : "+"} add
              </button>
            </div>
            {critErr && <div className="gen-banner" style={{marginBottom:10}}>{Ico.warn ? Ico.warn() : "!"} {critErr}</div>}
            {genAsArray(llm.criteria).length === 0 && !critBusy && (
              <div style={{fontSize:12.5, color:"var(--ink-mute)", padding:"14px 12px", textAlign:"center", border:"1px dashed var(--line)", borderRadius:6}}>
                No criteria proposed yet. Click "Propose criteria with LLM" to generate an auditable rubric — each item comes with a rationale and a source (PrimeKG edge, guideline, safety default, or model inference) you can approve or reject individually.
              </div>
            )}
            {genAsArray(llm.criteria).length > 0 && (
              <>
                <div style={{display:"flex", gap:6, marginBottom:10, flexWrap:"wrap"}}>
                  {["all","grounded","model","safety"].map(f => (
                    <button
                      key={f}
                      className="filter-pill"
                      data-active={critFilter === f}
                      onClick={() => setCritFilter(f)}
                      style={{fontSize:11}}
                      title={f === "all" ? "Show every criterion"
                            : f === "grounded" ? "Show criteria backed by PrimeKG or a treatment guideline"
                            : f === "model" ? "Show LLM-inferred criteria (review carefully)"
                            : "Show stock safety defaults"}
                    >{f}{f !== "all" ? ` (${genAsArray(llm.criteria).filter(c => sourceGroup(c.source) === f).length})` : ` (${genAsArray(llm.criteria).length})`}</button>
                  ))}
                </div>
                <div style={{display:"grid", gap:10}}>
                  {genAsArray(llm.criteria).map((c, i) => {
                    const grp = sourceGroup(c.source);
                    if (critFilter !== "all" && critFilter !== grp) return null;
                    return (
                      <div
                        key={i}
                        style={{
                          border: "1px solid var(--line)",
                          borderLeft: `3px solid ${sourceColor(grp)}`,
                          borderRadius: 4,
                          padding: "8px 12px",
                          background: c.accepted ? "var(--bg)" : "var(--bg-sunken)",
                          opacity: c.accepted ? 1 : 0.55,
                        }}
                      >
                        <div style={{display:"flex", alignItems:"center", gap:8, flexWrap:"wrap", marginBottom:4}}>
                          <span style={{fontSize:10.5, fontFamily:"var(--font-mono)", color:"var(--ink-mute)", textTransform:"uppercase", letterSpacing:"0.04em"}}>
                            {c.field}
                          </span>
                          <span style={{fontSize:10, color: sourceColor(grp), fontFamily:"var(--font-mono)", border:`1px solid ${sourceColor(grp)}`, borderRadius:3, padding:"0 5px"}}>
                            {c.source}
                          </span>
                          <select
                            value={c.weight || "required"}
                            onChange={e => patchCriterionAt(i, { weight: e.target.value })}
                            style={{fontSize:10.5, fontFamily:"var(--font-mono)", padding:"1px 4px", border:"1px solid var(--line)", borderRadius:3}}
                          >
                            <option value="required">required</option>
                            <option value="optional">optional</option>
                          </select>
                          <div style={{marginLeft:"auto", display:"flex", gap:4}}>
                            <button
                              className="btn ghost"
                              style={{padding:"1px 8px", fontSize:10.5}}
                              onClick={() => patchCriterionAt(i, { accepted: !c.accepted })}
                              title={c.accepted ? "Mark as rejected (excluded from committed task)" : "Re-accept"}
                            >{c.accepted ? "✓ accepted" : "✗ rejected"}</button>
                            <button
                              className="btn ghost"
                              style={{padding:"1px 8px", fontSize:10.5, color:"var(--red, #b91c1c)"}}
                              onClick={() => removeCriterionAt(i)}
                              title="Remove this criterion entirely"
                            >delete</button>
                          </div>
                        </div>
                        <input
                          value={c.value || ""}
                          onChange={e => patchCriterionAt(i, { value: e.target.value })}
                          className="inp"
                          style={{width:"100%", padding:"3px 6px", fontSize:12.5, marginBottom:4}}
                          placeholder="criterion value (e.g., travel history, record_diagnosis, Dengue fever)"
                        />
                        <textarea
                          value={c.rationale || ""}
                          onChange={e => patchCriterionAt(i, { rationale: e.target.value })}
                          className="ta"
                          rows={2}
                          style={{width:"100%", fontSize:11.5, color:"var(--ink-dim)"}}
                          placeholder="Rationale — why this criterion?"
                        />
                        {c.related_disease_ruled_out && (
                          <div style={{fontSize:11, color:"var(--ink-mute)", fontFamily:"var(--font-mono)", marginTop:2}}>
                            rules out: {c.related_disease_ruled_out}
                          </div>
                        )}
                      </div>
                    );
                  })}
                </div>
              </>
            )}
          </div>
          {Array.isArray(llm.suggestedDbRows) && llm.suggestedDbRows.length > 0 && (
            <div style={{gridColumn:"1 / -1"}}>
              <div className="gen-section-label">Suggested DB rows ({llm.suggestedDbRows.length})</div>
              <pre className="gen-json-preview">{JSON.stringify(llm.suggestedDbRows, null, 2)}</pre>
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// ---- step 5: commit ----------------------------------------------------------

function GenStepCommit({ user, draft, onCommitted, onNotice, tr }) {
  const [cohorts, setCohorts] = React.useState([]);
  const [cohortsLoading, setCohortsLoading] = React.useState(true);
  const [cohortMode, setCohortMode] = React.useState("existing");
  const [cohortId, setCohortId] = React.useState("");
  const [newName, setNewName] = React.useState("Generator · " + new Date().toISOString().slice(0, 10));
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState(null);
  const [showJson, setShowJson] = React.useState(false);

  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        if (!window.tasksApi || window.tasksApi.OFFLINE) throw new Error("offline");
        const res = await window.tasksApi.listCohorts();
        const items = Array.isArray(res && res.items) ? res.items : [];
        if (cancelled) return;
        setCohorts(items);
        if (items[0]) setCohortId(items[0].id);
        else setCohortMode("new");
      } catch {
        if (!cancelled) setCohortMode("new");
      } finally {
        if (!cancelled) setCohortsLoading(false);
      }
    })();
    return () => { cancelled = true; };
  }, []);

  const resolvedCohort = React.useMemo(() => {
    if (cohortMode === "new") {
      const id = "coh_" + Date.now().toString(36);
      return { id, name: newName.trim() || id, source: "PrimeKG", generator: "task-generator · v1" };
    }
    const c = cohorts.find(x => x.id === cohortId);
    if (!c) return null;
    return { id: c.id, name: c.name, source: c.source || "PrimeKG", generator: c.generator || "task-generator · v1" };
  }, [cohortMode, cohortId, cohorts, newName]);

  const finalTask = React.useMemo(() => {
    const base = genBuildTask({ user, draft, cohortId: resolvedCohort && resolvedCohort.id });
    // If the annotator produced per-criterion reviews, rewrite the evaluation
    // fields from the accepted-only list so the committed task reflects exactly
    // what was approved — not the raw LLM draft and not the form inputs.
    const criteria = draft && draft.llm && Array.isArray(draft.llm.criteria) ? draft.llm.criteria : null;
    if (criteria && criteria.length && typeof window.applyCriteriaToTask === "function") {
      return window.applyCriteriaToTask(base, criteria);
    }
    return base;
  }, [user, draft, resolvedCohort]);
  const tau2Preview = React.useMemo(
    () => (typeof window.taskToTau2 === "function" ? window.taskToTau2(finalTask) : finalTask),
    [finalTask],
  );

  const commit = async ({ withDb }) => {
    if (!resolvedCohort) { setErr("Pick a cohort first."); return; }
    if (!window.tasksApi || window.tasksApi.OFFLINE) { setErr("Backend offline — commit requires a live D1."); return; }
    if (busy) return;
    setBusy(true);
    setErr(null);
    try {
      if (withDb && Array.isArray(draft.llm && draft.llm.suggestedDbRows)) {
        const dbApi = window.tasksApi.db;
        if (!dbApi || !dbApi.put) {
          onNotice("DB API not available — skipping suggested rows.");
        } else {
          for (const row of draft.llm.suggestedDbRows) {
            if (!row || !row.ns || !row.id) continue;
            const data = (row.data && typeof row.data === "object") ? row.data : {};
            try {
              await dbApi.put(row.ns, row.id, data, { cohort: "__shared" });
            } catch (e) {
              onNotice(`Failed to write ${row.ns}/${row.id}: ${(e && e.message) || e}`);
            }
          }
        }
      }
      await window.tasksApi.importCohort({ cohort: resolvedCohort, tasks: [finalTask] });
      if (cohortMode === "new") {
        try { window.tasksApi.invalidateCohorts && window.tasksApi.invalidateCohorts(); } catch {}
      }
      onCommitted(finalTask);
    } catch (e) {
      setErr((e && (e.body && e.body.error)) || (e && e.message) || String(e));
    } finally {
      setBusy(false);
    }
  };

  const suggestedCount = Array.isArray(draft.llm && draft.llm.suggestedDbRows) ? draft.llm.suggestedDbRows.length : 0;

  return (
    <div>
      <div className="gen-two-col">
        <div>
          <div className="gen-section-label">Cohort</div>
          <div style={{display:"flex", gap:6, marginBottom:8}}>
            <button className="filter-pill" data-active={cohortMode === "existing"} onClick={() => setCohortMode("existing")} disabled={cohorts.length === 0}>
              Existing
            </button>
            <button className="filter-pill" data-active={cohortMode === "new"} onClick={() => setCohortMode("new")}>
              Create new
            </button>
          </div>
          {cohortMode === "existing" ? (
            <select className="inp" value={cohortId} onChange={e => setCohortId(e.target.value)} style={{width:"100%"}}>
              <option value="" disabled>{cohortsLoading ? "Loading cohorts…" : "Select a cohort"}</option>
              {cohorts.map(c => (
                <option key={c.id} value={c.id}>{c.name} ({(c.total || c.task_count || 0).toLocaleString()})</option>
              ))}
            </select>
          ) : (
            <input
              className="inp"
              value={newName}
              onChange={e => setNewName(e.target.value)}
              placeholder="New cohort name"
              style={{width:"100%"}}
            />
          )}

          <div className="gen-section-label" style={{marginTop:16}}>Summary</div>
          <div style={{fontSize:12.5, color:"var(--ink-dim)", lineHeight:1.6}}>
            <div><b>Disease:</b> {(draft.disease && draft.disease.name) || "—"}</div>
            <div><b>Phenotypes:</b> {draft.selectedPhenos.length} selected</div>
            <div><b>Extras:</b> {draft.extraSymptoms.length}</div>
            <div><b>Diagnosis:</b> {(draft.llm && draft.llm.diagnosis) || (draft.disease && draft.disease.name) || "—"}</div>
            <div><b>Required tools:</b> {genAsArray(draft.llm && draft.llm.requiredTools).join(", ") || "—"}</div>
            <div><b>Suggested DB rows:</b> {suggestedCount}</div>
          </div>

          {err && <div className="gen-banner" style={{marginTop:12}}>{Ico.warn()} {err}</div>}

          <div style={{marginTop:16, display:"flex", gap:8, flexWrap:"wrap"}}>
            <button className="btn primary" onClick={() => commit({ withDb: false })} disabled={busy || !resolvedCohort}>
              {Ico.check()} {busy ? "Committing…" : tr("gen_commit_task")}
            </button>
            <button
              className="btn accent"
              onClick={() => commit({ withDb: true })}
              disabled={busy || !resolvedCohort || suggestedCount === 0}
              title={suggestedCount === 0 ? "No DB rows suggested by the LLM" : ""}
            >
              {Ico.upload()} {tr("gen_commit_with_db")}
            </button>
          </div>
        </div>

        <div>
          <div className="gen-section-label" style={{display:"flex", alignItems:"center", gap:8}}>
            tau2 preview
            <button className="btn ghost" style={{marginLeft:"auto", padding:"2px 8px", fontSize:11}} onClick={() => setShowJson(v => !v)}>
              {showJson ? "Hide" : "Show"}
            </button>
          </div>
          {showJson && (
            <pre className="gen-json-preview">{JSON.stringify(tau2Preview, null, 2)}</pre>
          )}
          {!showJson && (
            <div style={{padding:"12px 14px", fontSize:12, color:"var(--ink-mute)", border:"1px dashed var(--line)", borderRadius:6}}>
              Collapsed. Click <b>Show</b> to inspect the serialized tau2 task that will be committed.
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { GeneratorPage });
