// ── EditStillButton (v1020) ─────────────────────────────────────────────────
// "Pull the still for this shot out of the cut." One component, used in two
// places, so the two can never behave differently:
//   ShotDetailModal  — one shot, next to the FRAMES header
//   ShotsPanel       — the whole multi-select, in the batch action bar
//
// The work is two-staged on the server because a button cannot wait 15 minutes:
//   SCAN   reads the burned-in SH#### off a sample every 2s across the whole
//          edit (~700 OCR calls) and caches an index. Background, with progress.
//   FETCH  uses that index — extract, crop the letterbox off (which is where the
//          burned-in number lives, so the crop removes it), upload, stamp.
//          Seconds per shot. This is what a click normally runs.
//
// So the button has three faces: "Still from edit" (ready), "Scan the edit first"
// (no index yet), and a live progress readout while a scan runs. It never starts
// the 15-minute scan without being asked.

// Pass shotIds for specific shots, or `all` for "every blank shot in one go"
// (that one scans first if it has to — the same thing the automatic sweep does).
function EditStillButton({ shotIds, all, label, className, title, onDone }) {
  const fetcher = window.authFetch || fetch;
  const ids = (Array.isArray(shotIds) ? shotIds : [shotIds]).filter(Boolean);

  const [status, setStatus] = React.useState(null);   // the /status payload
  const [busy, setBusy] = React.useState(false);
  const [msg, setMsg] = React.useState("");           // inline result line
  const pollRef = React.useRef(null);

  const load = React.useCallback(() => {
    const q = ids.length && ids.length <= 40 ? "?shot_ids=" + encodeURIComponent(ids.join(",")) : "";
    return fetcher("/api/edit-stills/status" + q)
      .then(r => r.ok ? r.json() : null)
      .then(d => { setStatus(d || null); return d; })
      .catch(() => null);
  }, [ids.join(",")]);

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

  // While a scan is running, poll it. Cleared the moment it stops.
  React.useEffect(() => {
    const running = status && status.running && status.running.state === "running";
    if (!running) { if (pollRef.current) { clearInterval(pollRef.current); pollRef.current = null; } return; }
    if (pollRef.current) return;
    pollRef.current = setInterval(() => {
      load().then(d => {
        const st = d && d.running;
        if (st && st.state !== "running") {
          clearInterval(pollRef.current); pollRef.current = null;
          if (window.__toast) window.__toast(st.state === "done" ? "Edit scanned — stills are ready" : ("Scan " + st.state), st.state === "done");
        }
      });
    }, 3000);
    return () => { if (pollRef.current) { clearInterval(pollRef.current); pollRef.current = null; } };
  }, [status && status.running && status.running.state, load]);

  // "Do the lot": scans if needed, then fills every blank linked shot.
  const runAll = () => {
    setBusy(true); setMsg("");
    fetcher("/api/edit-stills/autofill", { method: "POST", headers: { "Content-Type": "application/json" }, body: "{}" })
      .then(r => r.json().then(j => r.ok ? j : Promise.reject(new Error(j.error || ("HTTP " + r.status)))))
      .then((j) => {
        if (j.scanning) {
          setMsg("Reading the edit…");
          if (window.__toast) window.__toast("Reading the edit — the stills appear as soon as it finishes");
        } else {
          const line = j.filled
            ? j.filled + " blank shot" + (j.filled === 1 ? "" : "s") + " filled from the edit"
            : "Nothing left to fill";
          setMsg(line);
          if (window.__toast) window.__toast(line, true);
          if (j.filled && window.reloadAppData) window.reloadAppData();
          if (onDone) onDone(j);
        }
        load();
      })
      .catch(e => { setMsg(e.message); if (window.__toast) window.__toast(e.message, false); })
      .then(() => setBusy(false));
  };

  const startScan = () => {
    setBusy(true); setMsg("");
    fetcher("/api/edit-stills/scan", { method: "POST", headers: { "Content-Type": "application/json" }, body: "{}" })
      .then(r => r.json().then(j => r.ok ? j : Promise.reject(new Error(j.error || ("HTTP " + r.status)))))
      .then(() => { load(); if (window.__toast) window.__toast("Reading the edit — this takes a few minutes"); })
      .catch(e => { setMsg(e.message); if (window.__toast) window.__toast(e.message, false); })
      .then(() => setBusy(false));
  };

  const pull = () => {
    if (!ids.length) return;
    setBusy(true); setMsg("");
    fetcher("/api/edit-stills/fetch", {
      method: "POST", headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ shot_ids: ids }),
    })
      .then(async (r) => {
        const j = await r.json().catch(() => ({}));
        if (r.status === 409 && j.code === "NO_INDEX") { const e = new Error("NO_INDEX"); e.noIndex = true; throw e; }
        if (!r.ok) throw new Error(j.error || ("HTTP " + r.status));
        return j;
      })
      .then((j) => {
        const ok = (j.results || []).filter(x => x.ok);
        const bad = (j.results || []).filter(x => !x.ok);
        const line = ok.length
          ? ok.length + " still" + (ok.length === 1 ? "" : "s") + " pulled from the edit" + (bad.length ? ", " + bad.length + " not found" : "")
          : "Not found in the edit" + (bad[0] && bad[0].error ? " — " + bad[0].error : "");
        setMsg(line);
        if (window.__toast) window.__toast(line, ok.length > 0);
        if (ok.length && window.reloadAppData) window.reloadAppData();
        if (onDone) onDone(j);
        load();
      })
      .catch((e) => {
        if (e.noIndex) { setMsg("This edit hasn't been read yet."); load(); return; }
        setMsg(e.message);
        if (window.__toast) window.__toast(e.message, false);
      })
      .then(() => setBusy(false));
  };

  if (!status) return null;                       // don't flash a button before we know
  if (!status.local) return null;                 // Railway / passive: the work happens locally
  const running = status.running && status.running.state === "running";
  const canFill = status.scanned && (!status.available || status.available.length > 0);

  let face, action, hint;
  if (running) {
    const st = status.running;
    const pct = st.total ? Math.round((st.done / st.total) * 100) : 0;
    face = "Reading the edit… " + pct + "%";
    action = null;
    hint = st.done + " of " + st.total + " samples · " + st.found + " shots found so far";
  } else if (all) {
    // One button that just does the whole job, whatever state it is in.
    face = label || "Fill blank shots from the edit";
    action = runAll;
    hint = status.scanned
      ? "Pull a still from the cut for every shot still missing one."
      : "Reads the edit (a few minutes, once), then fills every shot still missing an image.";
  } else if (!status.scanned) {
    face = "Read the edit first";
    action = startScan;
    hint = "Finds which shot is on screen at every moment. Takes a few minutes, once per edit.";
  } else if (!canFill) {
    face = label || "Still from edit";
    action = null;
    hint = ids.length === 1 ? "This shot doesn't appear in the edit." : "None of these shots appear in the edit.";
  } else {
    const n = status.available ? status.available.length : ids.length;
    face = (label || "Still from edit") + (ids.length > 1 ? " (" + n + ")" : "");
    action = pull;
    hint = ids.length > 1
      ? "Pull a frame from the cut for " + n + " shot" + (n === 1 ? "" : "s")
      : "Pull this shot's frame out of the cut";
  }

  return (
    <span className="esb-wrap">
      <button type="button"
        className={"esb-btn" + (className ? " " + className : "") + (running ? " is-running" : "")}
        disabled={busy || !action}
        title={title || hint}
        onClick={action || undefined}>
        <svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
          <rect x="2" y="6" width="20" height="12" rx="1.5"/><path d="M2 9h20M2 15h20"/>
        </svg>
        {busy ? "Working…" : face}
      </button>
      {/* min-height so the row never changes size when a result appears */}
      <span className="esb-msg">{msg}</span>
    </span>
  );
}

window.EditStillButton = EditStillButton;
