// Trajectories blade — manager-facing browser of submitted simulation runs.
// Lists every saved trajectory (D1 `trajectories` table) with filters, an
// inline preview, and per-row + bulk download buttons. Auth gated to
// managers since it can show every annotator's submissions.
//
// Data flow:
//   GET /api/trajectories?task_id=...&mine=1&limit=N  → list
//   GET /api/trajectories/:id                         → full body (messages,
//                                                        scores, env_state)
//
// Each row carries: id, task_id, annotator_email, submitted_at, scores
// summary, comments. The full message log is only fetched on row-click /
// download to keep the list page snappy.

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

  const [items, setItems] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [error, setError] = React.useState(null);
  const [q, setQ] = React.useState("");
  const [taskFilter, setTaskFilter] = React.useState("");
  const [mineOnly, setMineOnly] = React.useState(false);
  const [activeId, setActiveId] = React.useState(null);
  const [activeBody, setActiveBody] = React.useState(null);
  const [activeLoading, setActiveLoading] = React.useState(false);
  const [selected, setSelected] = React.useState(new Set());

  const load = React.useCallback(async () => {
    setLoading(true);
    setError(null);
    try {
      const params = new URLSearchParams();
      if (taskFilter.trim()) params.set("task_id", taskFilter.trim());
      if (mineOnly) params.set("mine", "1");
      params.set("limit", "500");
      const res = await fetch("/api/trajectories?" + params.toString(), { credentials: "same-origin" });
      if (!res.ok) {
        let msg = `HTTP ${res.status}`;
        try { const j = await res.json(); if (j && j.error) msg = j.error; } catch {}
        throw new Error(msg);
      }
      const j = await res.json();
      setItems(Array.isArray(j && j.items) ? j.items : []);
    } catch (e) {
      setError((e && e.message) || String(e));
      setItems([]);
    } finally {
      setLoading(false);
    }
  }, [taskFilter, mineOnly]);

  React.useEffect(() => { load(); }, [load]);

  // Lazy-load the full body when a row is clicked. Cached per-id in a ref so
  // re-clicking doesn't re-fetch.
  const bodyCache = React.useRef(new Map());
  const openRow = async (id) => {
    setActiveId(id);
    if (bodyCache.current.has(id)) {
      setActiveBody(bodyCache.current.get(id));
      return;
    }
    setActiveLoading(true);
    setActiveBody(null);
    try {
      const res = await fetch(`/api/trajectories/${encodeURIComponent(id)}`, { credentials: "same-origin" });
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      const j = await res.json();
      bodyCache.current.set(id, j);
      setActiveBody(j);
    } catch (e) {
      setActiveBody({ _error: (e && e.message) || String(e) });
    } finally {
      setActiveLoading(false);
    }
  };

  const downloadOne = async (id) => {
    let body = bodyCache.current.get(id);
    if (!body) {
      const res = await fetch(`/api/trajectories/${encodeURIComponent(id)}`, { credentials: "same-origin" });
      if (!res.ok) { alert(`Failed to fetch ${id}: HTTP ${res.status}`); return; }
      body = await res.json();
      bodyCache.current.set(id, body);
    }
    if (typeof window.downloadBlob === "function") {
      window.downloadBlob(`${id}.json`, JSON.stringify(body, null, 2));
    }
  };

  const downloadSelected = async () => {
    if (selected.size === 0) return;
    const ids = [...selected];
    const out = [];
    for (const id of ids) {
      let body = bodyCache.current.get(id);
      if (!body) {
        try {
          const res = await fetch(`/api/trajectories/${encodeURIComponent(id)}`, { credentials: "same-origin" });
          if (res.ok) body = await res.json();
        } catch {}
      }
      if (body) { out.push(body); bodyCache.current.set(id, body); }
    }
    if (typeof window.downloadBlob === "function") {
      window.downloadBlob(
        `trajectories_${new Date().toISOString().slice(0,10)}_n${out.length}.json`,
        JSON.stringify(out, null, 2),
      );
    }
  };

  const toggleSelect = (id) => {
    setSelected(prev => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id); else next.add(id);
      return next;
    });
  };

  const filtered = (items || []).filter(it => {
    if (!q) return true;
    const hay = `${it.id} ${it.task_id} ${it.annotator_email || ""} ${it.comments || ""}`.toLowerCase();
    return hay.includes(q.toLowerCase());
  });

  const fmtScores = (it) => {
    const s = it.reward_summary;
    if (s && typeof s.reward_sum === "number") {
      const m = typeof s.reward_mean === "number" ? s.reward_mean.toFixed(2) : "—";
      return `Σ${s.reward_sum} · μ${m} · n${s.turns_scored || 0}`;
    }
    return "—";
  };

  const fmtPass = (it) => {
    const fr = it.final_reward;
    if (!fr || typeof fr.binary_pass !== "boolean") return "—";
    return fr.binary_pass ? "PASS" : "FAIL";
  };

  return (
    <div className="page" style={{display:"flex", height:"100%", overflow:"hidden"}}>
      <div style={{flex:"1 1 auto", display:"flex", flexDirection:"column", borderRight:"1px solid var(--line)", overflow:"hidden"}}>
        <div style={{padding:"14px 22px 10px", borderBottom:"1px solid var(--line)", display:"flex", gap:8, alignItems:"center", flexWrap:"wrap"}}>
          <div style={{fontFamily:"var(--font-serif)", fontSize:18, marginRight:"auto"}}>Trajectories</div>
          <input
            className="inp"
            placeholder="search id / task / annotator / comment"
            value={q}
            onChange={e => setQ(e.target.value)}
            style={{minWidth:240}}
          />
          <input
            className="inp"
            placeholder="task_id filter"
            value={taskFilter}
            onChange={e => setTaskFilter(e.target.value)}
            style={{minWidth:180, fontFamily:"var(--font-mono)", fontSize:12}}
          />
          <label style={{display:"flex", gap:4, alignItems:"center", fontSize:12, color:"var(--ink-mute)"}}>
            <input type="checkbox" checked={mineOnly} onChange={e => setMineOnly(e.target.checked)}/>
            mine only
          </label>
          <button className="btn ghost" onClick={load} title="Refresh">⟳</button>
          {selected.size > 0 && (
            <button className="btn primary" onClick={downloadSelected}>
              Download {selected.size} selected
            </button>
          )}
        </div>
        <div style={{flex:"1 1 auto", overflow:"auto"}}>
          {loading && <div style={{padding:22, color:"var(--ink-mute)"}}>Loading…</div>}
          {error && <div style={{padding:22, color:"var(--red, #b91c1c)"}}>Error: {error}</div>}
          {!loading && !error && filtered.length === 0 && (
            <div style={{padding:22, color:"var(--ink-mute)", fontStyle:"italic"}}>No trajectories yet. Submit a simulation run from the Simulator to populate this list.</div>
          )}
          {!loading && !error && filtered.length > 0 && (
            <table style={{width:"100%", borderCollapse:"collapse", fontSize:13}}>
              <thead>
                <tr style={{background:"var(--bg-sunken)", color:"var(--ink-mute)", fontFamily:"var(--font-mono)", fontSize:11, textTransform:"uppercase", letterSpacing:"0.05em"}}>
                  <th style={{padding:"8px 12px", textAlign:"left", width:32}}>
                    <input
                      type="checkbox"
                      checked={selected.size > 0 && filtered.every(it => selected.has(it.id))}
                      onChange={(e) => {
                        if (e.target.checked) setSelected(new Set(filtered.map(it => it.id)));
                        else setSelected(new Set());
                      }}
                    />
                  </th>
                  <th style={{padding:"8px 12px", textAlign:"left"}}>id</th>
                  <th style={{padding:"8px 12px", textAlign:"left"}}>task_id</th>
                  <th style={{padding:"8px 12px", textAlign:"left"}}>annotator</th>
                  <th style={{padding:"8px 12px", textAlign:"left"}}>submitted</th>
                  <th style={{padding:"8px 12px", textAlign:"left"}}>scores</th>
                  <th style={{padding:"8px 12px", textAlign:"left"}}>final</th>
                  <th style={{padding:"8px 12px", textAlign:"right"}}></th>
                </tr>
              </thead>
              <tbody>
                {filtered.map(it => (
                  <tr
                    key={it.id}
                    data-active={activeId === it.id}
                    onClick={() => openRow(it.id)}
                    style={{
                      borderBottom:"1px solid var(--line)",
                      cursor:"pointer",
                      background: activeId === it.id ? "var(--bg-sunken)" : undefined,
                    }}
                  >
                    <td style={{padding:"8px 12px"}} onClick={(e) => e.stopPropagation()}>
                      <input type="checkbox" checked={selected.has(it.id)} onChange={() => toggleSelect(it.id)}/>
                    </td>
                    <td style={{padding:"8px 12px", fontFamily:"var(--font-mono)", fontSize:11.5}}>{String(it.id).slice(0, 32)}</td>
                    <td style={{padding:"8px 12px", fontFamily:"var(--font-mono)", fontSize:11.5}}>{it.task_id}</td>
                    <td style={{padding:"8px 12px", color:"var(--ink-dim)"}}>{it.annotator_email || it.annotator_name || "—"}</td>
                    <td style={{padding:"8px 12px", color:"var(--ink-mute)"}}>{(it.submitted_at || "").slice(0, 19).replace("T", " ")}</td>
                    <td style={{padding:"8px 12px", fontFamily:"var(--font-mono)", fontSize:11.5}}>{fmtScores(it)}</td>
                    <td style={{padding:"8px 12px", fontFamily:"var(--font-mono)", fontSize:11.5, color: fmtPass(it) === "PASS" ? "var(--green)" : (fmtPass(it) === "FAIL" ? "var(--red, #b91c1c)" : "var(--ink-mute)")}}>{fmtPass(it)}</td>
                    <td style={{padding:"8px 12px", textAlign:"right"}}>
                      <button
                        className="btn ghost"
                        onClick={(e) => { e.stopPropagation(); downloadOne(it.id); }}
                        title="Download this trajectory as JSON"
                      >Download</button>
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          )}
        </div>
      </div>
      <div style={{flex:"0 0 480px", display:"flex", flexDirection:"column", overflow:"hidden"}}>
        <div style={{padding:"14px 18px 10px", borderBottom:"1px solid var(--line)", fontFamily:"var(--font-mono)", fontSize:11, color:"var(--ink-mute)", textTransform:"uppercase", letterSpacing:"0.05em"}}>Preview</div>
        <div style={{flex:"1 1 auto", overflow:"auto", padding:"14px 18px"}}>
          {!activeId && <div style={{color:"var(--ink-mute)", fontStyle:"italic"}}>Click a row on the left to preview the full trajectory.</div>}
          {activeId && activeLoading && <div style={{color:"var(--ink-mute)"}}>Loading…</div>}
          {activeId && !activeLoading && activeBody && activeBody._error && (
            <div style={{color:"var(--red, #b91c1c)"}}>{activeBody._error}</div>
          )}
          {activeId && !activeLoading && activeBody && !activeBody._error && (
            <div>
              <div style={{display:"flex", gap:8, marginBottom:10}}>
                <button className="btn primary" onClick={() => downloadOne(activeId)}>Download JSON</button>
                <button className="btn ghost" onClick={() => { setActiveId(null); setActiveBody(null); }}>Close</button>
              </div>
              <pre style={{
                background:"var(--bg-sunken)",
                padding:"10px 12px",
                borderRadius:4,
                fontSize:11,
                lineHeight:1.4,
                whiteSpace:"pre-wrap",
                wordBreak:"break-word",
                margin:0,
              }}>{JSON.stringify(activeBody, null, 2)}</pre>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

window.TrajectoriesPage = TrajectoriesPage;
