// The validation gate. Nothing reaches Jira until someone approves it here.
//
// Each row shows what the agent captured, the severity it graded, and where the
// routing rule wants to send it. The admin can override the severity (which
// re-runs the rule) or force the destination outright, then approve — that click
// is what creates the Jira issue and drops it on the board.
const { useState: useStatePQ, useEffect: useEffectPQ } = React;

const SEVS = ["SEV1", "SEV2", "SEV3", "SEV4"];

function DestPicker({ value, onChange, suggested }) {
  return (
    <div className="seg" role="group" aria-label="Destination">
      {["sprint", "backlog"].map((d) => (
        <button key={d} type="button"
          className={value === d ? "is-active" : ""}
          onClick={() => onChange(d)}>
          {d === "sprint" ? "Current sprint" : "Backlog"}
          {suggested === d && <span className="seg-hint" title="What the severity rule suggests">rule</span>}
        </button>
      ))}
    </div>
  );
}

function PendingRow({ row, onDone, busyKey, setBusyKey, routing }) {
  const [sev, setSev] = useStatePQ(row.severity || "SEV3");
  const [dest, setDest] = useStatePQ(row.suggestedDestination || routeOf(routing, row.severity));
  const [forcedDest, setForcedDest] = useStatePQ(false);
  const [open, setOpen] = useStatePQ(false);
  const [rejecting, setRejecting] = useStatePQ(false);
  const [reason, setReason] = useStatePQ("");
  const [err, setErr] = useStatePQ("");

  const id = row.id || row.pendingId || row.key;
  const busy = busyKey === id;
  // The rule follows the CURRENT severity and the admin's configured routing —
  // not the severity the agent originally graded. Otherwise overriding SEV2→SEV4
  // would leave the "rule" hint pointing at the sprint.
  const ruleDest = routeOf(routing, sev);

  // Changing severity re-runs the rule — unless the admin already forced a
  // destination, in which case an explicit choice wins.
  function changeSev(next) {
    setSev(next);
    if (!forcedDest) setDest(routeOf(routing, next));
  }
  function changeDest(next) { setDest(next); setForcedDest(true); }

  async function approve() {
    setErr(""); setBusyKey(id);
    const res = await window.NS.admin.decide(id, "approve", { severity: sev, destination: dest, validatedBy: "admin" });
    setBusyKey(null);
    if (!res || !res.ok) return setErr(errText(res));
    onDone(res, `${res.key || row.ref || id} → ${res.placement && res.placement.placed === "sprint" ? "sprint" : res.destination}`);
  }
  async function reject() {
    setErr(""); setBusyKey(id);
    const res = await window.NS.admin.decide(id, "reject", { reason, validatedBy: "admin" });
    setBusyKey(null);
    if (!res || !res.ok) return setErr(errText(res));
    onDone(res, "Rejected — no Jira issue created");
  }

  const s = window.NS.sev(sev);
  const changed = sev !== row.severity;

  return (
    <div className="pq-row">
      <div className="pq-main">
        <button className="pq-title" onClick={() => setOpen((v) => !v)} aria-expanded={open}>
          <span className={"sev-chip sev-" + s.rank}><span className="sev-dot" />{sev}</span>
          <span className="pq-text">{row.title}</span>
          <span className={"ticket-chev" + (open ? " is-open" : "")} aria-hidden="true">⌄</span>
        </button>
        <div className="pq-meta">
          <span className="ticket-key">{row.ref || row.key}</span>
          {row.account && <span>· {row.account}</span>}
          {row.reporter && <span>· {row.reporter}</span>}
          {row.component && <span>· {row.component}</span>}
          <span>· {fmtAgo(row.createdAt)}</span>
        </div>
        {open && (
          <dl className="td-dl pq-detail">
            {[["actual", "What happened"], ["reproSteps", "What led to it"], ["expected", "Expected"],
              ["blastRadius", "Who's affected"], ["errorText", "Error / console"], ["environment", "Environment"]]
              .map(([k, label]) => (
                <div className="td-row" key={k}>
                  <dt>{label}</dt>
                  <dd className={row[k] ? "" : "is-empty"}>{row[k] || "Not provided"}</dd>
                </div>
              ))}
            {row.severityRationale && (
              <div className="td-row"><dt>Why this severity</dt><dd>{row.severityRationale}</dd></div>
            )}
            {row.kbCitation && (
              <div className="td-row"><dt>Knowledge base</dt>
                <dd>{window.NS.kbVerdictLabel(row.kbVerdict)} — {row.kbCitation}</dd></div>
            )}
          </dl>
        )}
      </div>

      <div className="pq-controls">
        <label className="pq-field">
          <span>Severity</span>
          <select value={sev} onChange={(e) => changeSev(e.target.value)} disabled={busy}>
            {SEVS.map((c) => <option key={c} value={c}>{c} · {window.NS.sev(c).label}</option>)}
          </select>
          {changed && <em className="pq-note">overridden from {row.severity}</em>}
        </label>
        <label className="pq-field">
          <span>Destination</span>
          <DestPicker value={dest} onChange={changeDest} suggested={ruleDest} />
          {forcedDest && dest !== ruleDest && <em className="pq-note">forced — rule says {ruleDest}</em>}
        </label>
        <div className="pq-actions">
          <button className="btn btn-primary btn-sm" onClick={approve} disabled={busy}>
            {busy ? "Filing…" : "Approve → create in Jira"}
          </button>
          <button className="btn btn-ghost btn-sm" onClick={() => setRejecting((v) => !v)} disabled={busy}>Reject</button>
        </div>
        {rejecting && (
          <div className="pq-reject">
            <input placeholder="Why? (optional)" value={reason} onChange={(e) => setReason(e.target.value)} />
            <button className="btn btn-danger btn-sm" onClick={reject} disabled={busy}>Confirm reject</button>
          </div>
        )}
        {err && <div className="gate-err pq-err">{err}</div>}
      </div>
    </div>
  );
}

// Mirrors api/_lib/store.js routeFor(). Falls back to the shipped default when
// settings haven't loaded (or there's no store).
function routeOf(routing, sev) {
  const r = routing && routing[sev];
  if (r === "sprint" || r === "backlog") return r;
  return ["SEV1", "SEV2"].indexOf(sev) >= 0 ? "sprint" : "backlog";
}

function errText(res) {
  if (!res) return "Something went wrong.";
  if (res.error === "jira_not_configured")
    return `Jira isn't configured — set ${(res.missing || []).join(", ")} before approving.`;
  if (res.error === "no_store") return "No datastore configured — the queue isn't shared.";
  if (res.error === "admin_required") return "Your admin session expired. Sign in again.";
  return res.error || "Something went wrong.";
}

function PendingQueue({ focusId }) {
  const [state, setState] = useStatePQ({ loading: true, live: false, pending: [] });
  const [busyKey, setBusyKey] = useStatePQ(null);
  const [routing, setRouting] = useStatePQ(null);

  async function load() {
    const r = await window.NS.admin.listPending();
    setState({ loading: false, live: !!r.live, pending: r.pending || [], error: r.error });
  }
  useEffectPQ(() => {
    load();
    // The severity->destination rule is admin-configurable, so read it rather
    // than assuming SEV1/2 -> sprint.
    window.NS.admin.getSettings().then((r) => setRouting((r.settings && r.settings.routing) || null));
  }, []);

  function onDone(res, msg) {
    if (window.fireToast) window.fireToast(msg);
    load();
  }

  if (state.loading) return <div className="pq-empty">Loading the queue…</div>;

  const rows = state.pending;
  const focused = focusId ? rows.filter((r) => (r.id === focusId || r.ref === focusId || r.key === focusId)) : rows;
  const list = focused.length ? focused : rows;

  return (
    <div>
      <div className="page-head">
        <div className="eyebrow">Validation</div>
        <h1 className="page-title">Waiting on you</h1>
        <p className="page-sub">
          The agent qualified these, but <strong>nothing is in Jira yet</strong>. Approving a ticket creates the
          Jira issue and drops it into the current sprint or the backlog — SEV1/SEV2 default to the sprint.
        </p>
      </div>

      {!state.live && (
        <div className="admin-note">
          <strong>Demo queue.</strong> No datastore is configured, so this queue lives in <em>this browser only</em> and
          approving won't create a Jira issue. Set <code>UPSTASH_REDIS_REST_URL</code> + <code>UPSTASH_REDIS_REST_TOKEN</code> to share it.
        </div>
      )}
      {focusId && focused.length > 0 && (
        <div className="admin-note admin-note-soft">Focused on <code>{focusId}</code> from Slack. <a href="?">Show all</a></div>
      )}

      {!list.length
        ? <div className="pq-empty">Nothing waiting. Every reported bug has been triaged. 🎉</div>
        : <div className="pq">{list.map((r) => (
            <PendingRow key={r.id || r.key} row={r} onDone={onDone} busyKey={busyKey} setBusyKey={setBusyKey} routing={routing} />
          ))}</div>}
    </div>
  );
}

window.PendingQueue = PendingQueue;
