// screens-cr-outreach.jsx — Priority Custom Outreach.
//
// Moved out of the Action Dashboard's fourth tab (screens-actions.jsx), which
// is being retired, and re-seated as a sub-screen of the CR Report. It belongs
// next to the CR Universe: one screen is the pool of companies eligible for
// re-approach, the other is the queue of re-approaches actually assigned.
//
// Behaviour is carried over intact — Requested → Accepted → Done, Week/Status
// grouping, the "update Harvey" reminder on completion, and the + New CR modal.
//
// WHAT IS NEW: the calendar feed is surfaced.
// The Worker has always served GET /api/cr-calendar/<token>.ics — subscribe in
// Outlook or Google and your open CRs appear as calendar entries. But
// cr_calendar_feed was granted to the service role only, so there was no way to
// see your token, create one, or switch it off from the app. sql_86 scopes the
// table to its owner and this screen gives it a UI. Brian, 8/19: "that's a cool
// functionality we built before... don't want to lose it."
//
// NOT CARRIED: importCRsFromFile(). It existed in screens-actions.jsx with no
// call site anywhere — dead on arrival, so it is not being ported forward.

(function () {
const { useState, useEffect, useMemo, useCallback } = React;

// PO was a FOURTH local palette, identical in shape and values to CV in
// screens-crreport.jsx. It was missed in the first sweep because the worst-
// offenders list was ranked by literal count per FILE, and Custom Approach
// Actions is one screen split across two files: this is its "Priority Custom
// Outreach" sub-tab. Half the screen moved onto the standard and half did not.
//
// Same treatment as CV: the object shape stays so no call site changes, the
// values become tokens, and the panel goes flat per the standard.
const PO = {
  canvas: "var(--bg)", ink: "var(--ink)", body: "var(--ink-2)", sec: "var(--ink-3)",
  muted: "var(--muted)",
  accent: "var(--accent)", accentDeep: "var(--accent-2)", over: "var(--risk)", good: "var(--ok)",
  mono: "var(--f-mono)",
  panel: { background: "var(--surface)", border: "1px solid var(--line)",
           borderRadius: "var(--r-card)", boxShadow: "var(--shadow-none)" },
  kickerStyle: { fontSize: "var(--kicker-size)", letterSpacing: "var(--kicker-track)",
                 fontWeight: "var(--kicker-weight)",
                 textTransform: "uppercase", color: "var(--kicker-color)" },
};
const PO_GRAD = "var(--accent)";
// Owner avatars are an UNORDERED series -> the categorical ramp.
const AVATAR_BGS = ["var(--cat-2)", "var(--cat-4)", "var(--cat-3)", "var(--cat-5)", "var(--cat-6)", "var(--cat-7)"];

// Delegates to VaultAlpha for the same reason CV's hexA does: a hex parser
// cannot follow a theme, and every alpha it produced was pinned to light mode.
function poHexA(color, a) {
  return window.VaultAlpha ? window.VaultAlpha(color, a)
    : "color-mix(in srgb, " + color + " " + Math.round((a || 0) * 100) + "%, transparent)";
}
function poToday() {
  const d = new Date();
  return d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0");
}
const poInitials = (name) => String(name || "?").replace(/[^A-Za-z ]/g, "").split(/\s+/).filter(Boolean).slice(0, 2).map(w => w[0].toUpperCase()).join("") || "?";
const poMondayOf = (d) => { const dt = new Date(d + "T00:00:00Z"); const dow = dt.getUTCDay() || 7; dt.setUTCDate(dt.getUTCDate() - (dow - 1)); return dt.toISOString().slice(0, 10); };
const poMonDay = (d) => window.VaultDate(d, { month: "short", day: "numeric" }).toUpperCase();

const PO_ST = {
  requested: { label: "Requested", color: "var(--gold)" },
  accepted:  { label: "Accepted",  color: PO.accent },
  done:      { label: "Done",      color: PO.good },
  declined:  { label: "Declined",  color: PO.over },
};

// Hoisted to module scope. A component declared inside a render closure is a new
// type on every keystroke, which remounts its inputs and steals focus.
const poFieldInput = {
  width: "100%", boxSizing: "border-box", padding: "7px 10px", fontSize: 13,
  fontFamily: "inherit", border: "1px solid var(--line-strong)", borderRadius: "var(--r-ctl)",
  background: "var(--surface)", color: PO.ink,
};
function POField({ label, children }) {
  return (
    <label style={{ display: "block", marginBottom: 10 }}>
      <span style={{ display: "block", fontSize: 11.5, fontWeight: 600, color: PO.sec, marginBottom: 4 }}>{label}</span>
      {children}
    </label>
  );
}

// New-CR modal. Exported as VaultCRModal rather than CRModal on purpose: the
// Action Dashboard still defines a CRModal global, and two identical names in
// the shared global namespace is exactly the collision that silently broke the
// Badges summary row when screens-actions.jsx overwrote screens-badges.jsx's
// SummaryStat. This name cannot collide.
function VaultCRModal({ viewer, crPeople, onClose, onCreated }) {
  const initialAssignee = (crPeople && crPeople[0] && crPeople[0].id) || (viewer && viewer.id) || null;
  const [target, setTarget] = useState("");
  const [ownerId, setOwnerId] = useState(initialAssignee);
  const [dueDate, setDueDate] = useState(poToday());
  const [note, setNote] = useState("");
  const [saving, setSaving] = useState(false);

  const submit = async () => {
    if (!target.trim()) { window.VaultUI.toast("error", "Target / company is required."); return; }
    if (!ownerId) { window.VaultUI.toast("error", "Assignee is required."); return; }
    setSaving(true);
    try {
      const moduleHandle = viewer.team || (viewer.subteam ? String(viewer.subteam).replace(/^st-/, "") : null);
      await window.VaultAPI.createCR({
        target: target.trim(), ownerId: ownerId, requesterId: viewer.id, module: moduleHandle,
        dueDate: dueDate || null, note: note.trim() || null, status: "requested",
      });
      onCreated();
    } catch (err) { setSaving(false); window.VaultUI.toast("error", "Failed to create CR: " + (err.message || err)); }
  };

  return ReactDOM.createPortal(
    <div onClick={onClose} style={{ position: "fixed", inset: 0, background: "var(--scrim)", display: "flex",
      alignItems: "center", justifyContent: "center", padding: 16, zIndex: 1000 }}>
      <div onClick={e => e.stopPropagation()} style={{ background: "var(--surface)", padding: 20, borderRadius: "var(--r-card)", width: 480,
        maxWidth: "92vw", maxHeight: "90vh", overflowY: "auto", boxShadow: "var(--shadow-modal)" }}>
        <h3 style={{ margin: 0, marginBottom: 14, fontSize: 16, fontWeight: 600, color: PO.ink }}>New CR (Custom Re-approach)</h3>
        <POField label="Target / company *">
          <input type="text" value={target} onChange={e => setTarget(e.target.value)} placeholder="e.g. Bochi" autoFocus style={poFieldInput}/>
        </POField>
        <div className="kpi-grid one-col" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
          <POField label="Assign to *">
            <select value={ownerId || ""} onChange={e => setOwnerId(e.target.value)} style={poFieldInput}>
              {(crPeople || []).map(p => (<option key={p.id} value={p.id}>{p.name}</option>))}
            </select>
          </POField>
          <POField label="Due date">
            <input type="date" value={dueDate} onChange={e => setDueDate(e.target.value)} style={poFieldInput}/>
          </POField>
        </div>
        <POField label="Note">
          <textarea value={note} onChange={e => setNote(e.target.value)} rows={3} placeholder="Context for the re-approach"
            style={{ ...poFieldInput, resize: "vertical" }}/>
        </POField>
        <div className="row" style={{ justifyContent: "flex-end", gap: 8, marginTop: 14 }}>
          <button onClick={onClose} disabled={saving}
            style={{ padding: "8px 14px", borderRadius: "var(--r-ctl)", border: "1px solid var(--line-strong)", background: "var(--surface)",
              color: PO.sec, fontSize: 13, cursor: "pointer", fontFamily: "inherit" }}>Cancel</button>
          {/* Fully inline-styled: the app's .btn base is ghost/white, so a
              class-only primary renders as an invisible button. */}
          <button onClick={submit} disabled={saving}
            style={{ padding: "8px 16px", borderRadius: "var(--r-ctl)", border: "none", background: PO_GRAD, color: "var(--accent-ink)",
              fontSize: 13, fontWeight: 600, cursor: saving ? "default" : "pointer", fontFamily: "inherit",
              opacity: saving ? .6 : 1 }}>{saving ? "Saving\u2026" : "Create CR"}</button>
        </div>
      </div>
    </div>,
    document.body
  );
}
window.VaultCRModal = VaultCRModal;

// ---------------------------------------------------------------- feed card
function CRFeedCard({ personId }) {
  const [feed, setFeed] = useState(undefined); // undefined = loading, null = none
  const [busy, setBusy] = useState(false);
  const [copied, setCopied] = useState(false);

  const load = useCallback(async () => {
    if (!personId || !window.VaultAPI.getCrCalendarFeed) { setFeed(null); return; }
    try { setFeed(await window.VaultAPI.getCrCalendarFeed(personId)); }
    catch (e) {
      // A 401/403 here means sql_86 has not been run. Say so plainly rather
      // than rendering an empty card that looks like "you have no feed".
      console.warn("[cr-feed] read failed", e);
      setFeed({ error: e.message || String(e) });
    }
  }, [personId]);
  useEffect(() => { load(); }, [load]);

  const act = async (fn, msg) => {
    setBusy(true);
    try { await fn(); await load(); if (msg) window.VaultUI.toast("success", msg); }
    catch (e) { window.VaultUI.toast("error", e.message || String(e)); }
    finally { setBusy(false); }
  };

  const copy = () => {
    if (!feed || !feed.url) return;
    try {
      navigator.clipboard.writeText(feed.url);
      setCopied(true);
      setTimeout(() => setCopied(false), 1800);
    } catch (e) { window.VaultUI.toast("error", "Couldn't copy — select the link and copy manually."); }
  };

  if (feed === undefined) return null;

  return (
    <div style={{ ...PO.panel, padding: "16px 18px", marginBottom: 14 }}>
      <div style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }}>
        <span style={{ ...PO.kickerStyle }}>Calendar Feed</span>
        <span style={{ fontSize: 12.5, color: PO.sec, flex: 1, minWidth: 220 }}>
          Subscribe in Outlook or Google to see your open CRs as calendar entries.
        </span>
        {feed && feed.error ? null : feed ? (
          <span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 11, fontWeight: 600,
            padding: "3px 9px", borderRadius: "var(--r-pill)",
            background: feed.enabled ? poHexA(PO.good, .12) : "var(--line-2)",
            color: feed.enabled ? PO.good : PO.muted }}>
            <span style={{ width: 8, height: 8, borderRadius: "50%", background: feed.enabled ? PO.good : PO.muted }}/>
            {feed.enabled ? "Active" : "Paused"}
          </span>
        ) : null}
      </div>

      {feed && feed.error && (
        <div style={{ marginTop: 10, fontSize: 12, color: PO.over, lineHeight: 1.5 }}>
          Couldn't read your feed settings. If <span style={{ fontFamily: PO.mono }}>sql_86</span> hasn't been run,
          <span style={{ fontFamily: PO.mono }}> cr_calendar_feed</span> is still service-role only.
          <div style={{ fontFamily: PO.mono, fontSize: 11, color: PO.muted, marginTop: 4 }}>{feed.error}</div>
        </div>
      )}

      {feed === null && (
        <div style={{ marginTop: 12 }}>
          <button disabled={busy}
            onClick={() => act(() => window.VaultAPI.upsertCrCalendarFeed(personId, {}), "Calendar feed created.")}
            style={{ border: "none", cursor: busy ? "default" : "pointer", fontFamily: "inherit", fontSize: 13,
              fontWeight: 600, color: "var(--accent-ink)", padding: "9px 15px", borderRadius: "var(--r-card)", background: PO_GRAD,
              opacity: busy ? .6 : 1 }}>Create My Calendar Feed</button>
        </div>
      )}

      {feed && !feed.error && (
        <div style={{ marginTop: 12 }}>
          <div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
            <input readOnly value={feed.url} onFocus={e => e.target.select()}
              style={{ flex: "1 1 340px", minWidth: 0, padding: "8px 10px", borderRadius: "var(--r-ctl)",
                border: "1px solid var(--line-strong)", background: "var(--surface-2)", color: PO.sec,
                fontFamily: PO.mono, fontSize: 11.5 }}/>
            <button onClick={copy}
              style={{ border: "1px solid var(--line-strong)", background: "var(--surface)", color: PO.sec, borderRadius: "var(--r-ctl)",
                padding: "8px 12px", fontSize: 12, cursor: "pointer", fontFamily: "inherit", whiteSpace: "nowrap" }}>
              {copied ? "Copied" : "Copy link"}
            </button>
            <button disabled={busy}
              onClick={() => act(() => window.VaultAPI.upsertCrCalendarFeed(personId, { enabled: !feed.enabled }),
                feed.enabled ? "Feed paused." : "Feed resumed.")}
              style={{ border: "1px solid var(--line-strong)", background: "var(--surface)", color: PO.sec, borderRadius: "var(--r-ctl)",
                padding: "8px 12px", fontSize: 12, cursor: busy ? "default" : "pointer", fontFamily: "inherit", whiteSpace: "nowrap" }}>
              {feed.enabled ? "Pause" : "Resume"}
            </button>
            <button disabled={busy}
              onClick={async () => {
                const ok = await window.VaultUI.confirm({
                  message: "Rotate this feed URL? The current link stops working immediately and any calendar subscribed to it will need the new one.",
                  danger: true });
                if (ok) act(() => window.VaultAPI.upsertCrCalendarFeed(personId, { rotate: true }), "New feed URL generated.");
              }}
              style={{ border: "1px solid var(--line-strong)", background: "var(--surface)", color: PO.sec, borderRadius: "var(--r-ctl)",
                padding: "8px 12px", fontSize: 12, cursor: busy ? "default" : "pointer", fontFamily: "inherit", whiteSpace: "nowrap" }}>
              Rotate
            </button>
          </div>
          <div style={{ fontSize: 11, color: PO.muted, marginTop: 8, lineHeight: 1.5 }}>
            Anyone with this link can read your open CRs — calendar apps fetch it without signing in. Treat it like a password;
            rotate if it leaks. Shows CRs at <span style={{ fontFamily: PO.mono }}>requested</span> or <span style={{ fontFamily: PO.mono }}>accepted</span>.
          </div>
        </div>
      )}
    </div>
  );
}

// ================================================================== main
function PriorityCustomOutreach({ user, viewer, viewerScope, crPeople, onOpenNewCR, newCrTick }) {
  const F = window.VAULT_FIRM;
  const PEOPLE_BY_ID = (F && F.PEOPLE_BY_ID) || {};
  const today = poToday();

  const [crs, setCrs] = useState([]);
  const [refresh, setRefresh] = useState(0);
  const [sort, setSort] = useState("week");         // "week" | "status"
  const [doneReminder, setDoneReminder] = useState(null);
  const [outcomeNote, setOutcomeNote] = useState("");
  const [loading, setLoading] = useState(true);

  const ownerIds = useMemo(() => {
    if (!viewer) return [];
    if (viewerScope === "module") {
      const my = viewer.team || (viewer.subteam ? String(viewer.subteam).replace(/^st-/, "") : null);
      const list = ((F && F.PEOPLE) || []).filter(p => {
        const t = p.team || (p.subteam ? String(p.subteam).replace(/^st-/, "") : null);
        return t === my;
      }).map(p => p.id);
      return list.length ? list : [viewer.id];
    }
    return [viewer.id];
  }, [viewer, viewerScope]);

  useEffect(() => {
    if (!viewer || ownerIds.length === 0) { setCrs([]); setLoading(false); return; }
    let alive = true;
    setLoading(true);
    window.VaultAPI.listCRs(ownerIds, viewer.id)
      .then(r => { if (alive) { setCrs(r || []); setLoading(false); } })
      .catch(() => { if (alive) setLoading(false); });
    return () => { alive = false; };
  }, [ownerIds.join(","), viewer, refresh, newCrTick]);

  const bump = () => setRefresh(x => x + 1);
  const fail = (e) => window.VaultUI.toast("error", "Failed: " + (e.message || e));
  async function acceptCR(cr)   { try { await window.VaultAPI.updateCR(cr.id, { status: "accepted", acceptedAt: new Date().toISOString() }); bump(); } catch (e) { fail(e); } }
  async function declineCR(cr)  { try { await window.VaultAPI.updateCR(cr.id, { status: "declined" }); bump(); } catch (e) { fail(e); } }
  async function reopenCR(cr)   { try { await window.VaultAPI.updateCR(cr.id, { status: "requested", acceptedAt: null, completedBy: null, completedAt: null }); bump(); } catch (e) { fail(e); } }
  async function completeCR(cr) {
    try {
      await window.VaultAPI.updateCR(cr.id, { status: "done", completedBy: viewer.id, completedAt: new Date().toISOString() });
      bump(); setDoneReminder(cr);
    } catch (e) { fail(e); }
  }
  async function saveOutcome() {
    const cr = doneReminder, txt = outcomeNote.trim();
    setDoneReminder(null); setOutcomeNote("");
    if (!cr || !txt) return;
    try {
      const merged = (cr.note ? cr.note + "\n" : "") + "Outcome: " + txt;
      await window.VaultAPI.updateCR(cr.id, { note: merged });
      bump();
    } catch (e) { window.VaultUI.toast("error", "Note not saved: " + (e.message || e)); }
  }

  // Done CRs are kept in the DB but never shown in the active queue.
  const visible = crs.filter(c => c.status !== "done");
  const hiddenDone = crs.length - visible.length;
  const stOf = (cr) => PO_ST[cr.status] || PO_ST.requested;

  const groups = useMemo(() => {
    if (sort === "status") {
      return ["requested", "accepted", "declined"].map(s => ({
        key: s, label: PO_ST[s].label, color: PO_ST[s].color,
        items: visible.filter(cr => (cr.status || "requested") === s),
      })).filter(g => g.items.length);
    }
    const byWeek = {};
    visible.forEach(cr => {
      const wk = cr.dueDate ? poMondayOf(cr.dueDate) : "zz-none";
      (byWeek[wk] = byWeek[wk] || []).push(cr);
    });
    return Object.keys(byWeek).sort().map(wk => ({
      key: wk,
      label: wk === "zz-none" ? "No due date"
        : "Week of " + window.VaultDate(wk, { month: "short", day: "numeric" }),
      color: PO.accent,
      items: byWeek[wk].sort((a, b) => String(a.dueDate || "9999").localeCompare(String(b.dueDate || "9999"))),
    }));
  }, [visible, sort]);

  const sortBtn = (active) => active
    ? { border: "none", cursor: "pointer", fontFamily: PO.mono, fontSize: 11, letterSpacing: "var(--kicker-track)",
        color: "var(--accent-ink)", padding: "6px 14px", borderRadius: "var(--r-ctl)", background: PO_GRAD, boxShadow: "none" }
    : { border: "none", cursor: "pointer", fontFamily: PO.mono, fontSize: 11, letterSpacing: "var(--kicker-track)",
        color: PO.sec, padding: "6px 14px", borderRadius: "var(--r-ctl)", background: "transparent" };

  return (
    <div>
      <CRFeedCard personId={viewer && viewer.id}/>

      <div style={{ padding: "22px 24px", ...PO.panel }}>
        <div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 16, marginBottom: 16, flexWrap: "wrap" }}>
          <div>
            <div style={{ ...PO.kickerStyle, marginBottom: 5 }}>Priority Custom Outreach</div>
            <h2 style={{ margin: 0, fontSize: 18, fontWeight: 600, color: PO.ink }}>
              Custom Outreach Queue{" "}
              <span style={{ fontFamily: PO.mono, fontSize: 13, fontWeight: 500, color: PO.muted }}>{"\u00B7 " + visible.length}</span>
            </h2>
            <div style={{ fontFamily: PO.mono, fontSize: 11, color: PO.sec, marginTop: 4 }}>
              {"Requested \u2192 Accepted" + (hiddenDone ? " \u00B7 " + hiddenDone + " done (hidden)" : "")}
            </div>
          </div>
          <div style={{ display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }}>
            <span style={{ ...PO.kickerStyle, letterSpacing: "var(--kicker-track)" }}>Sort By</span>
            <div style={{ display: "flex", padding: 3, borderRadius: "var(--r-ctl)", background: "var(--line-2)", border: "1px solid var(--line)" }}>
              <button onClick={() => setSort("week")} style={sortBtn(sort === "week")}>Week</button>
              <button onClick={() => setSort("status")} style={sortBtn(sort === "status")}>Status</button>
            </div>
            {/* The shared primary button. This is the SECOND "+ New CR" on the
                screen -- the page header carries one too. Worth a ruling on
                whether both are wanted; leaving both for now, but identical. */}
            <button className="btn primary" onClick={onOpenNewCR}>+ New CR</button>
          </div>
        </div>

        {loading ? (
          <window.VaultLoader/>
        ) : visible.length === 0 ? (
          <div style={{ padding: "34px 20px", textAlign: "center" }}>
            <div style={{ fontSize: 15, fontWeight: 600, color: PO.ink }}>No CRs</div>
            <div style={{ fontFamily: PO.mono, fontSize: 11, color: PO.muted, marginTop: 6 }}>
              {"Use \u201C+ New CR\u201D to assign a custom re-approach."}
            </div>
          </div>
        ) : (
          <div style={{ maxHeight: 600, overflow: "auto", paddingRight: 6, marginRight: -6 }}>
            {groups.map(g => (
              <div key={g.key}>
                <div style={{ position: "sticky", top: 0, zIndex: 2, display: "flex", alignItems: "center", gap: 9, padding: "9px 2px", background: "var(--surface)" }}>
                  <span style={{ width: 8, height: 8, borderRadius: "50%", background: g.color }}/>
                  <span style={{ fontSize: 10, letterSpacing: "var(--kicker-track)", textTransform: "uppercase", color: PO.sec }}>{g.label}</span>
                  <span style={{ fontFamily: PO.mono, fontSize: 10, color: PO.muted, background: "var(--line-2)", padding: "1px 6px", borderRadius: "var(--r-ctl)" }}>{g.items.length}</span>
                  <span style={{ flex: 1, height: 1, background: "var(--line-2)" }}/>
                </div>
                <div style={{ display: "flex", flexDirection: "column", gap: 8, marginBottom: 10 }}>
                  {g.items.map((cr, i) => {
                    const assignee = PEOPLE_BY_ID[cr.ownerId];
                    const requester = PEOPLE_BY_ID[cr.requesterId];
                    const st = stOf(cr);
                    const isOpen = cr.status !== "done" && cr.status !== "declined";
                    const overdue = isOpen && cr.dueDate && cr.dueDate < today;
                    const mine = !!viewer && viewer.id === cr.ownerId;
                    const canManage = mine || viewerScope === "module";
                    return (
                      <div key={cr.id} style={{ display: "flex", alignItems: "center", gap: 14, padding: "12px 15px", borderRadius: "var(--r-card)",
                        background: "var(--surface)", border: "1px solid " + (overdue ? poHexA(PO.over, .3) : "var(--line)") }}>
                        {/* A COMPANY initials circle, not a person -- so it is not
                            window.Avatar. 38px read oversized in a list row; 26 is the
                            standard diameter (Brian, 2026-08-20). */}
                        <div style={{ width: 26, height: 26, borderRadius: "50%", flexShrink: 0,
                          background: AVATAR_BGS[i % AVATAR_BGS.length], display: "flex", alignItems: "center", justifyContent: "center",
                          fontFamily: PO.mono, fontSize: 10, fontWeight: 600, color: "var(--accent-ink)" }}>{poInitials(cr.target)}</div>
                        <div style={{ flex: 1, minWidth: 0 }}>
                          <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
                            <span style={{ fontSize: 14, fontWeight: 600, color: PO.ink, textDecoration: cr.status === "declined" ? "line-through" : "none" }}>
                              {cr.harveyTargetId && window.HarveyLink
                                ? <window.HarveyLink companyId={cr.harveyTargetId} companyName={cr.target} style={{ color: PO.accent }}>{cr.target}</window.HarveyLink>
                                : cr.target}
                            </span>
                            <span style={{ fontFamily: PO.mono, fontSize: 9, letterSpacing: "var(--kicker-track)", padding: "2px 6px", borderRadius: "var(--r-ctl)",
                              color: "var(--cat-6)", background: "color-mix(in srgb, var(--cat-6) 12%, transparent)", border: "1px solid color-mix(in srgb, var(--cat-6) 30%, transparent)" }}>CR</span>
                            <span style={{ display: "inline-flex", alignItems: "center", gap: 5 }}>
                              <span style={{ width: 8, height: 8, borderRadius: "50%", background: st.color }}/>
                              <span style={{ fontSize: 9, letterSpacing: "var(--kicker-track)", textTransform: "uppercase", color: st.color }}>{st.label}</span>
                            </span>
                          </div>
                          <div style={{ fontSize: 12, color: PO.sec, marginTop: 3, lineHeight: 1.4, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                            {cr.note || ((requester ? requester.name : cr.requesterId) + " \u2192 " + (assignee ? assignee.name : cr.ownerId))}
                          </div>
                        </div>
                        <div style={{ display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 3, flexShrink: 0 }}>
                          <span style={{ fontFamily: PO.mono, fontSize: 10, color: PO.muted }}>
                            {assignee ? assignee.name.split(" ")[0].toUpperCase() : String(cr.ownerId).toUpperCase()}
                          </span>
                          <span style={{ fontFamily: PO.mono, fontSize: 10, color: overdue ? PO.over : PO.sec, fontWeight: overdue ? 700 : 400 }}>
                            {cr.dueDate ? "DUE " + poMonDay(cr.dueDate) + (overdue ? " \u00B7 OVERDUE" : "") : "NO DUE DATE"}
                          </span>
                        </div>
                        <div className="row" style={{ gap: 6, flexShrink: 0 }}>
                          {mine && cr.status === "requested" && (<>
                            <button onClick={() => acceptCR(cr)}
                              style={{ fontSize: 11, padding: "4px 10px", borderRadius: "var(--r-ctl)", border: "none",
                                background: PO_GRAD, color: "var(--accent-ink)", fontWeight: 600, cursor: "pointer", fontFamily: "inherit" }}>Accept</button>
                            <button onClick={() => declineCR(cr)}
                              style={{ fontSize: 11, padding: "4px 10px", borderRadius: "var(--r-ctl)", border: "1px solid var(--line-strong)",
                                background: "var(--surface)", color: PO.sec, cursor: "pointer", fontFamily: "inherit" }}>Decline</button>
                          </>)}
                          {mine && cr.status === "accepted" && (
                            <button onClick={() => completeCR(cr)}
                              style={{ fontSize: 11, padding: "4px 10px", borderRadius: "var(--r-ctl)", border: "none",
                                background: PO_GRAD, color: "var(--accent-ink)", fontWeight: 600, cursor: "pointer", fontFamily: "inherit" }}>Mark Done</button>
                          )}
                          {canManage && (cr.status === "done" || cr.status === "declined") && (
                            <button onClick={() => reopenCR(cr)}
                              style={{ fontSize: 11, padding: "4px 10px", borderRadius: "var(--r-ctl)", border: "1px solid var(--line-strong)",
                                background: "var(--surface)", color: PO.sec, cursor: "pointer", fontFamily: "inherit" }}>Reopen</button>
                          )}
                        </div>
                      </div>
                    );
                  })}
                </div>
              </div>
            ))}
          </div>
        )}
      </div>

      {/* Completing a CR in Vault does not update Harvey — Harvey stays the
          source of truth, so the reminder is the handoff. */}
      {doneReminder && ReactDOM.createPortal(
        <div onClick={() => setDoneReminder(null)} style={{ position: "fixed", inset: 0, background: "var(--scrim)", zIndex: 1000,
          display: "flex", alignItems: "center", justifyContent: "center", padding: 16 }}>
          <div onClick={e => e.stopPropagation()} style={{ background: "var(--surface)", padding: 20, borderRadius: "var(--r-card)", width: 420,
            maxWidth: "92vw", boxShadow: "var(--shadow-modal)" }}>
            <h3 style={{ margin: 0, marginBottom: 6, fontSize: 16, fontWeight: 600, color: PO.ink }}>Marked Done — Update the Harvey Database</h3>
            <div style={{ marginBottom: 14, fontSize: 12.5, color: PO.sec, lineHeight: 1.5 }}>
              Log the outcome of this re-approach for <strong>{doneReminder.target}</strong> in Harvey so the database stays the source of truth.
            </div>
            <textarea placeholder="Outcome note (optional) — saved onto this CR in Vault"
              value={outcomeNote} onChange={e => setOutcomeNote(e.target.value)} rows={2}
              style={{ ...poFieldInput, resize: "vertical", marginBottom: 12 }}/>
            <div className="row" style={{ justifyContent: "space-between", gap: 8, alignItems: "center" }}>
              {doneReminder.harveyTargetId
                ? <a href={"https://db.harveyllc.com/company/" + doneReminder.harveyTargetId + "/target"} target="_blank" rel="noopener noreferrer"
                    style={{ fontSize: 12.5, color: PO.accent, textDecoration: "none" }}>
                    {"Open " + doneReminder.target + " profile \u2197"}
                  </a>
                : <span className="v-empty" style={{ padding: 0 }}>No linked Harvey profile.</span>}
              <button onClick={saveOutcome}
                style={{ padding: "8px 16px", borderRadius: "var(--r-ctl)", border: "none", background: PO_GRAD, color: "var(--accent-ink)",
                  fontSize: 13, fontWeight: 600, cursor: "pointer", fontFamily: "inherit" }}>Got It</button>
            </div>
          </div>
        </div>,
        document.body
      )}
    </div>
  );
}

window.PriorityCustomOutreach = PriorityCustomOutreach;
})();
