/* =================== PRINT-SHOP OUTREACH =================== */
/* Manage prospects, read their Google profile via AI, attach a mockup,
   generate the outreach email, preview, edit, and send. */
const { useState: useStateO, useEffect: useEffectO, useRef: useRefO } = React;

const OUT_STATUS = {
  new:       { label: "New",       cls: "badge--grey" },
  previewed: { label: "Previewed", cls: "badge--blue" },
  sent:      { label: "Sent",      cls: "badge--green" },
};
// List order: new on top, previewed next, sent last (stable within each group).
const STATUS_RANK = { new: 0, previewed: 1, sent: 2 };

// Quick-pick products → Dynamic Mockups template + smart-object. Blank UUIDs
// mean "use the default" (the black tee). Add more products here as needed.
const MOCKUP_PRESETS = [
  { key: "tshirt",  label: "Black t-shirt",       icon: "shirt", mockupUuid: "50661cc6-7cbc-488d-bd73-c88144178f24", smartObjectUuid: "04902ff2-6311-4a70-a2d1-32ba0e7ec229" },
  { key: "apron",   label: "Black apron (men) 2", icon: "box",   mockupUuid: "2377dabf-0a8a-4797-a98c-d958d60a7a16", smartObjectUuid: "11116db4-051e-4208-9776-7c1d1ad04951" },
  { key: "cap",     label: "Men black cap",       icon: "box",   mockupUuid: "7db1302c-8f09-46ab-8458-efb29c99830e", smartObjectUuid: "d8659646-eba7-4bdd-8494-c895b20be4e5" },
  { key: "wcap",    label: "Women black cap",     icon: "box",   mockupUuid: "b18505f2-9af0-4a86-b9e5-f04c2d4e6651", smartObjectUuid: "bf763326-c822-434d-acc2-292d1ff92f4c" },
  { key: "apronBlkM", label: "Black apron (men)",    icon: "box", mockupUuid: "2de3f6f1-6784-481e-b9b3-dcd4a15ecc4d", smartObjectUuid: "e8dffa89-4745-4435-9efa-b3e7dcebfadf" },
  { key: "apronNatM", label: "Natural apron (men)",  icon: "box", mockupUuid: "9c928af8-a908-4c6a-943d-56d4efbbd278", smartObjectUuid: "fb2d2c87-af7b-4a1b-b884-5eeb76fe14c3" },
  { key: "apronWhtM", label: "White apron (men)",    icon: "box", mockupUuid: "cc2b9258-aba9-43e8-9237-36a799149c2a", smartObjectUuid: "01400c27-c724-44a1-b3a7-04c6e6cc5cb2" },
  { key: "apronWhtM2", label: "White apron (men) 2", icon: "box", mockupUuid: "ec1f12d0-b0a8-4e83-9a27-579808764316", smartObjectUuid: "fc5ee798-370a-4ac7-b2bb-9df9b728e77f" },
  { key: "tridriBlkM", label: "TriDri man black",   icon: "shirt", mockupUuid: "55594354-38b9-4eb5-ba22-b18c94afa093", smartObjectUuid: "47bb67df-ad18-4a0e-8d2d-e24b28e26c61" },
  { key: "tridriBluM", label: "TriDri man blue",    icon: "shirt", mockupUuid: "1536eca8-bb38-4212-925a-ad83c3a0c086", smartObjectUuid: "dbe0abb3-33f1-4c38-b9b8-d1de0fec7fa5" },
  { key: "tridriPrpM", label: "TriDri man purple",  icon: "shirt", mockupUuid: "071559bb-26da-4a93-a6e8-5994df794321", smartObjectUuid: "c585048d-7ac6-43c7-9594-fad809655697" },
  { key: "tridriGrnW", label: "TriDri women green", icon: "shirt", mockupUuid: "f942d99a-25cb-4609-a257-c8fd2dd0de75", smartObjectUuid: "fa371b42-2a81-4ecb-b833-1f0ce0234ecc" },
  { key: "tridriKid",  label: "TriDri kids",       icon: "shirt", mockupUuid: "395321ef-8c41-4bd6-9149-1565d9d5f588", smartObjectUuid: "583c28a5-6071-40e5-8b5f-3d757e0de5fe,a37aff3e-8465-481a-bd90-15c61916a3b4" },
  { key: "tridriKidB", label: "TriDri kids blue",  icon: "shirt", mockupUuid: "6f171db9-7ea1-4480-b130-cbdc61916580", smartObjectUuid: "8f95dba9-26bc-422b-99b1-012b097f7082" },
  { key: "sweatBlk",   label: "Sweatshirt black",  icon: "shirt", mockupUuid: "477ebb08-8870-4754-9222-c6d440c35709", smartObjectUuid: "76704c6e-ce38-4551-82d3-c4f9c33563ca" },
  { key: "hoodieBlk",  label: "Hoodie black",      icon: "shirt", mockupUuid: "55f15f5c-c802-42ea-964e-f23de0d1ab9b", smartObjectUuid: "4617c7cd-b5ef-4f61-a9f7-5e4ed7f7aa57" },
  { key: "hoodieZip",  label: "Hoodie full-zip",   icon: "shirt", mockupUuid: "534b12a0-60c8-4b4e-ab6c-04ef1f960247", smartObjectUuid: "3c9fea08-6171-4cdc-8646-5d953e256b2a" },
  { key: "sweatZip",   label: "Sweatshirt zip-neck", icon: "shirt", mockupUuid: "96be2268-eaa0-4cfe-9af1-7c849140793a", smartObjectUuid: "295f06ff-5ac4-4e7a-9340-770719ff3154" },
];
const DEFAULT_MOCKUP_UUID = "50661cc6-7cbc-488d-bd73-c88144178f24";
const FOLLOWUP_STEPS = [
  { step: 1, day: 3, label: "Soft bump" },
  { step: 2, day: 7, label: "Use-cases + offer" },
  { step: 3, day: 14, label: "Breakup" },
];
const fmtDate = (ts) => { try { return new Date(ts).toLocaleDateString(undefined, { day: "numeric", month: "short" }); } catch { return ""; } };
function activeProductKey(sel) {
  const mk = (sel.mockupUuid || "").trim();
  const m = MOCKUP_PRESETS.find(p => (p.mockupUuid || DEFAULT_MOCKUP_UUID) === (mk || DEFAULT_MOCKUP_UUID));
  return m ? m.key : null; // null = a custom UUID not in the preset list
}

// Normalise a typed website into an openable URL (prepend https:// if missing).
function webHref(u) {
  const s = String(u || "").trim();
  if (!s) return "";
  return /^https?:\/\//i.test(s) ? s : "https://" + s;
}

// Parse a response safely. A proxy (e.g. Cloudflare) can replace an origin 5xx
// with an HTML error page; reading that as JSON throws a cryptic
// "Unexpected token '<'". This surfaces the real server message or a clean status.
async function outJson(r, fallback) {
  const text = await r.text();
  let j = null; try { j = text ? JSON.parse(text) : null; } catch {}
  if (!r.ok) throw new Error((j && j.error) || ((fallback || "Request failed") + " (" + r.status + ")"));
  if (j == null) throw new Error((fallback || "Unexpected response") + " (" + r.status + ")");
  return j;
}

function OutreachView({ go, id }) {
  const [list, setList] = useStateO(null);
  const [sel, setSel] = useStateO(null);
  const [busy, setBusy] = useStateO("");      // action in progress
  const [err, setErr] = useStateO("");
  const [editHtml, setEditHtml] = useStateO(false);
  const [importOpen, setImportOpen] = useStateO(false);
  const [importText, setImportText] = useStateO("");
  const [importing, setImporting] = useStateO(false);
  const [importProg, setImportProg] = useStateO("");
  const [importMsg, setImportMsg] = useStateO("");   // post-import summary (imported / skipped dupes)
  const [copied, setCopied] = useStateO(false);
  const [recolorHex, setRecolorHex] = useStateO("#ffffff");
  const [q, setQ] = useStateO("");
  const [nicheFilter, setNicheFilter] = useStateO("");   // "" = all niches
  const [importNiche, setImportNiche] = useStateO("");   // tag applied to the batch being imported
  const fileRef = useRefO(null);
  const logoRef = useRefO(null);

  function load() {
    return fetch("/api/outreach").then(r => r.json()).then(j => setList(j.prospects || [])).catch(e => setErr(e.message));
  }
  useEffectO(() => { load(); }, []);

  // Open the prospect named in the URL (shareable link) once the list is loaded.
  useEffectO(() => {
    if (!id || !list) return;
    const p = list.find(x => x.id === id);
    if (p && (!sel || sel.id !== id)) { setSel(p); setEditHtml(false); }
  }, [id, list]);

  function select(p) { setSel(p); setEditHtml(false); setCopied(false); if (go) go({ view: "outreach", id: p.id }); }
  function copyLink() {
    if (!sel) return;
    navigator.clipboard.writeText(location.origin + "/#/outreach/" + sel.id).then(() => { setCopied(true); setTimeout(() => setCopied(false), 1600); });
  }

  // merge a server prospect into both list and selection
  function apply(p) { setSel(p); setList(l => (l || []).map(x => x.id === p.id ? p : x)); }

  async function addProspect() {
    const p = await fetch("/api/outreach", { method: "POST", headers: { "Content-Type": "application/json" }, body: "{}" }).then(r => r.json());
    setList(l => [p, ...(l || [])]); select(p);
  }
  async function importLeads() {
    const text = importText.trim(); if (!text) return;
    setImporting(true); setErr(""); setImportProg(""); setImportMsg("");
    try {
      // Split into lead blocks: numbered entries ("12. Name") or blank-line separated.
      let blocks = /^\s*\d+\.\s+\S/m.test(text) ? text.split(/\n(?=\s*\d+\.\s+\S)/) : text.split(/\n\s*\n/);
      blocks = blocks.map(b => b.replace(/^[=\s]+/, "").trim()).filter(b => /(website|phone|google\s*maps|email|address|rating)\s*:/i.test(b));
      if (!blocks.length) throw new Error("No leads found — paste the numbered list");
      let first = null, ok = 0, dupes = 0, failed = 0;
      for (let i = 0; i < blocks.length; i++) {
        setImportProg((i + 1) + "/" + blocks.length);
        try {
          const p = await fetch("/api/outreach/import", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text: blocks[i], niche: importNiche.trim().toLowerCase() }) })
            .then(async r => { if (!r.ok) throw new Error((await r.json()).error || "import failed"); return r.json(); });
          if (p && p.duplicate) { dupes++; continue; }          // already exists — skip
          setList(l => [p, ...(l || [])]); if (!first) first = p; ok++;
        } catch (e) { failed++; /* skip a bad block, keep going */ }
      }
      if (first) select(first);
      setImportText("");
      const parts = [`Imported ${ok}`];
      if (dupes) parts.push(`skipped ${dupes} duplicate${dupes > 1 ? "s" : ""}`);
      if (failed) parts.push(`${failed} failed`);
      setImportMsg(parts.join(" · "));
      if (!ok && !dupes) setErr("Couldn't import any leads from that text");
      else if (ok && !dupes && !failed) { setImportOpen(false); setImportMsg(""); } // clean run: close panel
    } catch (e) { setErr(e.message); } finally { setImporting(false); setImportProg(""); }
  }
  async function del(pid) {
    await fetch("/api/outreach/" + pid, { method: "DELETE" });
    setList(l => (l || []).filter(x => x.id !== pid));
    if (sel && sel.id === pid) { setSel(null); if (go) go({ view: "outreach" }); }
  }
  async function put(patch) {
    if (!sel) return;
    const p = await outJson(await fetch("/api/outreach/" + sel.id, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(patch) }), "Save failed");
    apply(p);
  }
  const setField = (k, v) => setSel(s => ({ ...s, [k]: v }));

  async function run(name, fn) { setErr(""); setBusy(name); try { await fn(); } catch (e) { setErr(e.message || String(e)); } finally { setBusy(""); } }

  const readMaps = () => run("maps", async () => {
    const p = await outJson(await fetch("/api/outreach/" + sel.id + "/read-maps", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ mapsUrl: sel.mapsUrl }) }), "Read failed");
    apply(p);
  });
  const generate = () => run("gen", async () => {
    // save current field edits first, then generate
    await put({ name: sel.name, company: sel.company, email: sel.email, mapsUrl: sel.mapsUrl });
    const r = await outJson(await fetch("/api/outreach/" + sel.id + "/generate", { method: "POST" }), "Generate failed");
    setSel(s => ({ ...s, subject: s.subject || r.subject, body: r.html, status: s.status === "sent" ? "sent" : "previewed" }));
    setList(l => (l || []).map(x => x.id === sel.id ? { ...x, status: x.status === "sent" ? "sent" : "previewed" } : x));
  });
  const uploadMockup = (file) => run("mockup", async () => {
    const p = await outJson(await fetch("/api/outreach/" + sel.id + "/mockup?filename=" + encodeURIComponent(file.name || "mockup.png"), { method: "POST", headers: { "Content-Type": file.type || "image/png" }, body: file }), "Upload failed");
    apply({ ...p });
  });
  const removeMockup = () => run("mockup", async () => { const p = await outJson(await fetch("/api/outreach/" + sel.id + "/mockup", { method: "DELETE" }), "Remove failed"); apply(p); });
  const uploadLogo = (file) => run("logo", async () => {
    const p = await outJson(await fetch("/api/outreach/" + sel.id + "/logo?filename=" + encodeURIComponent(file.name || "logo.png"), { method: "POST", headers: { "Content-Type": file.type || "image/png" }, body: file }), "Upload failed");
    apply({ ...p });
  });
  const removeLogo = () => run("logo", async () => { const p = await outJson(await fetch("/api/outreach/" + sel.id + "/logo", { method: "DELETE" }), "Remove failed"); apply(p); });
  const buildMockup = () => run("build", async () => {
    const p = await outJson(await fetch("/api/outreach/" + sel.id + "/build-mockup", { method: "POST" }), "Mockup failed");
    apply(p);
  });
  // Pick a product (template), save it, then (re)build the mockup in one go.
  const pickProduct = (preset) => run("build", async () => {
    await outJson(await fetch("/api/outreach/" + sel.id, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ mockupUuid: preset.mockupUuid, smartObjectUuid: preset.smartObjectUuid }) }), "Save failed");
    setField("mockupUuid", preset.mockupUuid); setField("smartObjectUuid", preset.smartObjectUuid);
    const p = await outJson(await fetch("/api/outreach/" + sel.id + "/build-mockup", { method: "POST" }), "Mockup failed");
    apply(p);
  });
  const readWebsite = () => run("web", async () => {
    const r = await outJson(await fetch("/api/outreach/" + sel.id + "/read-website", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ website: sel.website }) }), "Read failed");
    apply(r.prospect);
  });
  const removeBg = () => run("bg", async () => {
    const p = await outJson(await fetch("/api/outreach/" + sel.id + "/remove-bg", { method: "POST" }), "Background removal failed");
    apply(p);
  });
  const removeBgRembg = () => run("bg2", async () => {
    const p = await outJson(await fetch("/api/outreach/" + sel.id + "/remove-bg-rembg", { method: "POST" }), "Background removal failed");
    apply(p);
  });
  const removeBgBria = () => run("bg3", async () => {
    const p = await outJson(await fetch("/api/outreach/" + sel.id + "/remove-bg-bria", { method: "POST" }), "Background removal failed");
    apply(p);
  });
  const recolorLogo = (color) => run("recolor", async () => {
    const p = await outJson(await fetch("/api/outreach/" + sel.id + "/recolor-logo", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ color }) }), "Recolour failed");
    apply(p);
  });
  const send = () => run("send", async () => {
    if (!sel.email) throw new Error("Add the recipient email first");
    const p = await outJson(await fetch("/api/outreach/" + sel.id + "/send", { method: "POST" }), "Send failed");
    apply(p); // server records sentAt + sets status "sent"; reflect it in the UI
  });
  const sendFollowup = (step) => run("fu" + step, async () => {
    const p = await outJson(await fetch("/api/outreach/" + sel.id + "/followup", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ step }) }), "Follow-up failed");
    apply(p);
  });
  const refreshTracking = () => run("track", async () => {
    const j = await fetch("/api/outreach").then(r => r.json());
    setList(j.prospects || []);
    const fresh = (j.prospects || []).find(x => x.id === sel.id);
    if (fresh) setSel(fresh);
  });
  const toggleFollowups = () => run("fustop", async () => {
    const cur = (sel.followups && sel.followups.stopped) || false;
    const p = await outJson(await fetch("/api/outreach/" + sel.id, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ followups: { ...(sel.followups || { history: [] }), stopped: !cur } }) }), "Save failed");
    apply(p);
  });

  const ctx = sel && sel.context;
  const mockupSrc = sel && sel.mockup ? ("/api/outreach/" + sel.id + "/mockup?ts=" + (sel.mockup.id || "")) : null;
  const qq = q.trim().toLowerCase();
  // Distinct niches present in the list (for the filter chips), + counts.
  const niches = list ? [...new Set(list.map(p => (p.niche || "").trim()).filter(Boolean))].sort() : [];
  const nicheCount = (n) => (list || []).filter(p => (p.niche || "").trim() === n).length;
  const base = !list ? null : (nicheFilter ? list.filter(p => (p.niche || "").trim() === nicheFilter) : list);
  const filtered = base == null ? null : [...(qq ? base.filter(p => [p.company, p.name, p.email, p.website, p.phone].some(v => String(v || "").toLowerCase().includes(qq))) : base)]
    .sort((a, b) => (STATUS_RANK[a.status] ?? 0) - (STATUS_RANK[b.status] ?? 0)); // new → previewed → sent (stable)

  return (
    <div className="outreach">
      {/* prospect list */}
      <div className="out-list">
        <div className="out-list__head">
          <span className="kick">Prospects {list ? ((qq || nicheFilter) ? `· ${filtered.length}/${list.length}` : `· ${list.length}`) : ""}</span>
          <button className="btn btn--ghost btn--sm" onClick={() => setImportOpen(o => !o)} title="Paste a lead">Import</button>
          <button className="btn btn--primary btn--sm" onClick={addProspect}><Icon name="plus" /> New</button>
        </div>
        <div className="out-search">
          <Icon name="search" />
          <input className="rm-in" value={q} onChange={e => setQ(e.target.value)} placeholder="Search prospects…" />
          {q && <button className="out-search__clear" onClick={() => setQ("")} title="Clear">×</button>}
        </div>
        {niches.length > 0 && (
          <div className="out-niches">
            <button className={"out-niche" + (nicheFilter === "" ? " is-on" : "")} onClick={() => setNicheFilter("")}>All <span className="out-niche__n">{list.length}</span></button>
            {niches.map(n => (
              <button key={n} className={"out-niche" + (nicheFilter === n ? " is-on" : "")} onClick={() => setNicheFilter(nicheFilter === n ? "" : n)} title={"Show only " + n}>
                {n} <span className="out-niche__n">{nicheCount(n)}</span>
              </button>
            ))}
          </div>
        )}
        {importOpen && (
          <div className="out-import">
            <input className="rm-in" list="out-niche-list" value={importNiche} onChange={e => setImportNiche(e.target.value)} placeholder="Niche for this batch (e.g. badminton, restaurant)" />
            <datalist id="out-niche-list">{niches.map(n => <option key={n} value={n} />)}</datalist>
            <textarea className="out-import__ta" style={{ marginTop: 8 }} placeholder={"Paste a lead block here…\n1. Business Name\nWebsite: …\nGoogle Maps: …\nRating: …\nReview: “…”\n\n(blank line between multiple leads)"} value={importText} onChange={e => setImportText(e.target.value)} />
            <div style={{ display: "flex", gap: 8, marginTop: 8 }}>
              <button className="btn btn--primary btn--sm" disabled={importing || !importText.trim()} onClick={importLeads}>{importing ? ("Importing… " + importProg) : "Import + read website"}</button>
              <button className="btn btn--ghost btn--sm" onClick={() => { setImportOpen(false); setImportText(""); }}>Cancel</button>
            </div>
            <div className="dim" style={{ fontSize: 11, marginTop: 6 }}>Tag the batch with a niche, then paste leads. Duplicates (same email, website, phone, Maps URL or company) are skipped automatically.</div>
            {!importing && importMsg && <div style={{ fontSize: 11.5, marginTop: 6, color: "var(--mint-deep)" }}>{importMsg}</div>}
          </div>
        )}
        <div className="out-list__scroll">
          {list == null ? <div className="dim" style={{ padding: 16, fontSize: 12.5 }}>Loading…</div>
            : list.length === 0 ? <div className="dim" style={{ padding: 16, fontSize: 12.5 }}>No prospects yet. Add one to start.</div>
            : filtered.length === 0 ? <div className="dim" style={{ padding: 16, fontSize: 12.5 }}>No matches for “{q}”.</div>
            : filtered.map(p => (
              <div key={p.id} className={"out-row" + (sel && sel.id === p.id ? " is-active" : "")} onClick={() => select(p)}>
                <div style={{ minWidth: 0, flex: 1 }}>
                  <div className="out-row__co">{p.company || "Untitled prospect"}</div>
                  <div className="mono dim" style={{ fontSize: 10.5 }}>{p.name || "—"}{p.email ? " · " + p.email : ""}{p.niche ? " · " + p.niche : ""}</div>
                </div>
                {p.tracking && (p.tracking.clicks || p.tracking.opens) ? <span className="out-track-dot" style={{ background: p.tracking.clicks ? "var(--mint-deep)" : "var(--txt-3)" }} title={(p.tracking.clicks ? "clicked " + p.tracking.clicks + "×" : "opened " + p.tracking.opens + "×")} /> : null}
                <span className={"badge " + (OUT_STATUS[p.status] || OUT_STATUS.new).cls}>{(OUT_STATUS[p.status] || OUT_STATUS.new).label}</span>
              </div>
            ))}
        </div>
      </div>

      {/* editor */}
      <div className="out-editor">
        {!sel ? <div className="empty"><div className="t">Select or add a prospect</div><div className="m">Add a prospect, read their Google profile, attach a mockup, then generate & send.</div></div> : (
          <div className="out-cols">
            {/* left: details + context + mockup */}
            <div className="out-detail">
              <div className="card">
                <div className="card__head"><Icon name="customers" /><h3>Prospect</h3>
                  <span className="right" style={{ display: "flex", gap: 6 }}>
                    <button className="btn btn--ghost btn--sm" onClick={copyLink} title="Copy a shareable link to this prospect">{copied ? "Copied ✓" : "Copy link"}</button>
                    <button className="btn btn--ghost btn--sm" onClick={() => del(sel.id)}>Delete</button>
                  </span></div>
                <div className="card__body">
                  <label className="out-field"><span>Niche</span><input className="rm-in" list="out-niche-list" value={sel.niche || ""} onChange={e => setField("niche", e.target.value)} onBlur={() => put({ niche: (sel.niche || "").trim().toLowerCase() })} placeholder="e.g. badminton, restaurant" /></label>
                  <label className="out-field"><span>Contact name</span><input className="rm-in" value={sel.name || ""} onChange={e => setField("name", e.target.value)} onBlur={() => put({ name: sel.name })} placeholder="First name" /></label>
                  <label className="out-field"><span>Company</span><input className="rm-in" value={sel.company || ""} onChange={e => setField("company", e.target.value)} onBlur={() => put({ company: sel.company })} placeholder="Organisation" /></label>
                  <label className="out-field"><span>Email</span><input className="rm-in" type="email" value={sel.email || ""} onChange={e => setField("email", e.target.value)} onBlur={() => put({ email: sel.email })} placeholder="name@business.com" /></label>
                  <label className="out-field"><span>Website</span>
                    <span style={{ display: "flex", gap: 6, alignItems: "center" }}>
                      <input className="rm-in" style={{ flex: 1, minWidth: 0 }} value={sel.website || ""} onChange={e => setField("website", e.target.value)} onBlur={() => put({ website: sel.website })} placeholder="example.co.uk" />
                      {sel.website && <a className="btn btn--ghost btn--sm" style={{ flex: "0 0 auto", padding: "0 9px" }} href={webHref(sel.website)} target="_blank" rel="noopener noreferrer" title="Open website in a new tab"><Icon name="external" /></a>}
                    </span>
                  </label>
                  <button className="btn btn--ghost btn--sm btn--block" style={{ marginBottom: 10 }} disabled={busy === "web" || !sel.website} onClick={readWebsite}><Icon name="spark" /> {busy === "web" ? "Reading website…" : "Read website data (fill gaps)"}</button>
                  <label className="out-field"><span>Phone</span><input className="rm-in" value={sel.phone || ""} onChange={e => setField("phone", e.target.value)} onBlur={() => put({ phone: sel.phone })} placeholder="01908 …" /></label>
                  <label className="out-field"><span>Google Maps URL</span><input className="rm-in" value={sel.mapsUrl || ""} onChange={e => setField("mapsUrl", e.target.value)} onBlur={() => put({ mapsUrl: sel.mapsUrl })} placeholder="https://maps.app.goo.gl/…" /></label>
                  <label className="out-field"><span>Logo URL</span><input className="rm-in" value={sel.logoUrl || ""} onChange={e => setField("logoUrl", e.target.value)} onBlur={() => put({ logoUrl: sel.logoUrl })} placeholder="auto-found from website" /></label>
                  {sel.logoUrl && <div className="out-logo"><img src={sel.logoUrl} alt="logo" /></div>}
                  <div className="out-logo-drop" onClick={() => logoRef.current && logoRef.current.click()}
                    onDragOver={e => e.preventDefault()} onDrop={e => { e.preventDefault(); const f = e.dataTransfer.files[0]; if (f) uploadLogo(f); }}>
                    {busy === "logo" ? "Uploading…" : <><Icon name="plus" /> {sel.logoUrl ? "Upload your own logo (replace)" : "Upload your own logo"}</>}
                  </div>
                  <input ref={logoRef} type="file" accept="image/*" style={{ display: "none" }} onChange={e => { const f = e.target.files[0]; e.target.value = ""; if (f) uploadLogo(f); }} />
                  {sel.logoUrl && (
                    <div style={{ display: "flex", gap: 8, marginTop: 8, flexWrap: "wrap" }}>
                      <a className="btn btn--ghost btn--sm" href={"/api/outreach/" + sel.id + "/logo"}><Icon name="download" /> Download logo</a>
                      <button className="btn btn--ghost btn--sm" disabled={busy === "bg"} onClick={removeBg}><Icon name="spark" /> {busy === "bg" ? "Removing bg…" : "Remove background"}</button>
                      <button className="btn btn--ghost btn--sm" disabled={busy === "bg2"} onClick={removeBgRembg} title="Background removal via the rembg model"><Icon name="spark" /> {busy === "bg2" ? "Removing bg…" : "Remove bg (rembg)"}</button>
                      <button className="btn btn--ghost btn--sm" disabled={busy === "bg3"} onClick={removeBgBria} title="Background removal via the bria model"><Icon name="spark" /> {busy === "bg3" ? "Removing bg…" : "Remove bg (bria)"}</button>
                      <span className="out-recolor" title="Recolour the logo to a solid colour (keeps transparency) — e.g. make it white for dark garments">
                        <span className="kick">{busy === "recolor" ? "Recolouring…" : "Recolour"}</span>
                        <button className="out-swatch" style={{ background: "#fff" }} disabled={busy === "recolor"} onClick={() => recolorLogo("#ffffff")} title="White" />
                        <button className="out-swatch" style={{ background: "#000" }} disabled={busy === "recolor"} onClick={() => recolorLogo("#000000")} title="Black" />
                        <input type="color" value={recolorHex} onChange={e => setRecolorHex(e.target.value)} className="out-colorpick" title="Custom colour" />
                        <button className="btn btn--ghost btn--sm" disabled={busy === "recolor"} onClick={() => recolorLogo(recolorHex)}>Apply</button>
                      </span>
                      <button className="btn btn--ghost btn--sm" disabled={busy === "build"} onClick={buildMockup}><Icon name="image" /> {busy === "build" ? "Building mockup…" : "Build mockup"}</button>
                      {sel.logo && <button className="btn btn--ghost btn--sm" disabled={busy === "logo"} onClick={removeLogo}><Icon name="close" /> Remove logo</button>}
                    </div>
                  )}
                  <button className="btn btn--ghost btn--sm btn--block" style={{ marginTop: 8 }} disabled={busy === "maps" || !sel.mapsUrl} onClick={readMaps}>
                    <Icon name="spark" /> {busy === "maps" ? "Reading profile…" : "Read Google profile (AI)"}</button>
                </div>
              </div>

              {ctx && (
                <div className="card">
                  <div className="card__head"><Icon name="spark" /><h3>AI context</h3>{ctx.rating ? <span className="right kick">★ {ctx.rating}</span> : null}</div>
                  <div className="card__body">
                    <div className="kick" style={{ marginBottom: 4 }}>Review insight</div>
                    <div style={{ fontSize: 13, marginBottom: 12, color: "var(--mint-ink)", fontWeight: 600 }}>“{ctx.reviewInsight || "—"}”</div>
                    {ctx.reviews && ctx.reviews.length > 0 && <>
                      <div className="kick" style={{ marginBottom: 4 }}>Review highlights</div>
                      <ul style={{ margin: 0, paddingLeft: 16, fontSize: 12.5, color: "var(--txt-2)" }}>{ctx.reviews.map((r, i) => <li key={i} style={{ marginBottom: 4 }}>{r}</li>)}</ul>
                    </>}
                    {ctx.summary && <div className="dim" style={{ fontSize: 12, marginTop: 10 }}>{ctx.summary}</div>}
                  </div>
                </div>
              )}

              <div className="card">
                <div className="card__head"><Icon name="image" /><h3>Mockup</h3></div>
                <div className="card__body">
                  {sel.logoUrl && (
                    <div className="out-products">
                      {MOCKUP_PRESETS.map(pr => {
                        const on = activeProductKey(sel) === pr.key;
                        return <button key={pr.key} className={"btn btn--sm " + (on ? "btn--primary" : "btn--ghost")} disabled={busy === "build"} onClick={() => pickProduct(pr)} title={mockupSrc ? "Rebuild on this product" : "Build mockup on this product"}>
                          <Icon name={pr.icon || "shirt"} /> {busy === "build" && on ? "Building…" : pr.label}
                        </button>;
                      })}
                    </div>
                  )}
                  {mockupSrc ? (
                    <div>
                      <img src={mockupSrc} alt="mockup" className="out-mockup" />
                      <button className="btn btn--ghost btn--sm" style={{ marginTop: 8 }} onClick={removeMockup} disabled={busy === "mockup"}>Remove</button>
                    </div>
                  ) : (
                    <div className="out-drop" onClick={() => fileRef.current && fileRef.current.click()}
                      onDragOver={e => e.preventDefault()} onDrop={e => { e.preventDefault(); const f = e.dataTransfer.files[0]; if (f) uploadMockup(f); }}>
                      {busy === "mockup" ? "Uploading…" : <><Icon name="plus" /> or drop/click to upload your own PNG</>}
                    </div>
                  )}
                  <input ref={fileRef} type="file" accept="image/*" style={{ display: "none" }} onChange={e => { const f = e.target.files[0]; e.target.value = ""; if (f) uploadMockup(f); }} />
                  <details className="out-tmpl" open={activeProductKey(sel) === null}>
                    <summary>Custom template {activeProductKey(sel) === null ? "· in use" : "· advanced"}</summary>
                    <label className="out-field" style={{ marginTop: 8 }}><span>Mockup UUID</span><input className="rm-in mono" value={sel.mockupUuid || ""} onChange={e => setField("mockupUuid", e.target.value)} onBlur={() => put({ mockupUuid: (sel.mockupUuid || "").trim() })} placeholder="50661cc6-… (default tee)" /></label>
                    <label className="out-field"><span>Smart-object UUID</span><input className="rm-in mono" value={sel.smartObjectUuid || ""} onChange={e => setField("smartObjectUuid", e.target.value)} onBlur={() => put({ smartObjectUuid: (sel.smartObjectUuid || "").trim() })} placeholder="04902ff2-… (default tee)" /></label>
                    <div className="dim" style={{ fontSize: 11 }}>Use the product buttons above for the usual items. To render on something else, paste a Dynamic Mockups mockup + smart-object UUID here, then click a product button or Build to render.</div>
                  </details>
                </div>
              </div>
            </div>

            {/* right: email preview / edit / send */}
            <div className="out-email">
              <div className="card">
                <div className="card__head"><Icon name="mail" /><h3>Outreach email</h3>
                  <span className="right" style={{ display: "flex", gap: 8, alignItems: "center" }}>
                    <span className={"badge " + (OUT_STATUS[sel.status] || OUT_STATUS.new).cls}>{(OUT_STATUS[sel.status] || OUT_STATUS.new).label}</span>
                    <button className="btn btn--ghost btn--sm" disabled={busy === "gen"} onClick={generate}><Icon name="spark" /> {busy === "gen" ? "Generating…" : (sel.body ? "Regenerate" : "Generate preview")}</button>
                  </span>
                </div>
                <div className="card__body">
                  <label className="out-field"><span>Subject</span><input className="rm-in" value={sel.subject || ""} onChange={e => setField("subject", e.target.value)} onBlur={() => put({ subject: sel.subject })} placeholder="A free mockup for …" /></label>

                  {sel.body ? (
                    <>
                      <div style={{ display: "flex", alignItems: "center", gap: 8, margin: "6px 0 8px" }}>
                        <div className="seg">
                          <button className={!editHtml ? "is-active" : ""} onClick={() => setEditHtml(false)}>Preview</button>
                          <button className={editHtml ? "is-active" : ""} onClick={() => setEditHtml(true)}>Edit HTML</button>
                        </div>
                        <span className="kick" style={{ marginLeft: "auto", color: sel.email ? undefined : "var(--c-red)", fontWeight: sel.email ? undefined : 700 }}>{sel.email || "⚠ no recipient email"}</span>
                      </div>
                      {editHtml
                        ? <textarea className="out-html" value={sel.body} onChange={e => setField("body", e.target.value)} onBlur={() => put({ body: sel.body })} />
                        : <iframe className="out-frame" title="preview" srcDoc={sel.body} />}
                    </>
                  ) : <div className="dim" style={{ fontSize: 12.5, padding: "10px 0" }}>No preview yet — fill in the details and click <b>Generate preview</b>.</div>}

                  {err && <div style={{ color: "var(--c-red)", fontSize: 12, marginTop: 8 }}>{err}</div>}
                  {(() => {
                    const blockReason = !sel.body ? "Generate a preview first" : !sel.email ? "Add a recipient email above — this prospect has none" : "";
                    return blockReason && busy !== "send" ? (
                      <div style={{ display: "flex", alignItems: "center", gap: 6, marginTop: 10, color: "var(--c-red)", fontSize: 12.5, fontWeight: 600 }}>
                        <span aria-hidden="true">⚠</span> Can’t send: {blockReason}.
                      </div>
                    ) : null;
                  })()}
                  <div style={{ display: "flex", gap: 8, marginTop: 12, alignItems: "center" }}>
                    <button className="btn btn--primary btn--sm" disabled={busy === "send" || !sel.body || !sel.email} title={!sel.body ? "Generate a preview first" : !sel.email ? "Add a recipient email to send" : "Send the email"} onClick={send}>
                      <Icon name="send" /> {busy === "send" ? "Sending…" : sel.status === "sent" ? "Send again" : "Send email"}</button>
                    {sel.status === "sent" && sel.sentAt && <span className="kick">sent {window.fmtEventTime ? window.fmtEventTime(sel.sentAt) : ""}</span>}
                    {sel.status === "sent" && (() => {
                      const t = sel.tracking || {}; const op = t.opens || 0, cl = t.clicks || 0;
                      return <>
                        <span className="kick" style={{ color: cl ? "var(--mint-ink)" : op ? "var(--ink)" : "var(--txt-3)" }}
                          title={[op && ("opened " + op + "× · first " + fmtDate(t.firstOpenAt) + ", last " + fmtDate(t.lastOpenAt)), cl && ("clicked " + cl + "× · last " + fmtDate(t.lastClickAt))].filter(Boolean).join("  ·  ")}>
                          · {cl ? `clicked ${cl}×` : op ? `opened ${op}×` : "not opened yet"}
                        </span>
                        <button className="btn btn--ghost btn--sm" disabled={busy === "track"} onClick={refreshTracking} title="Refresh open/click stats"><Icon name="refresh" /></button>
                      </>;
                    })()}
                    {!sel.mockup && <span className="kick" style={{ color: "var(--c-amber)" }}>no mockup attached</span>}
                  </div>
                </div>
              </div>

              {/* follow-up sequence */}
              <div className="card">
                <div className="card__head"><Icon name="clock" /><h3>Follow-ups</h3>
                  {sel.sentAt && <button className="btn btn--ghost btn--sm right" disabled={busy === "fustop"} onClick={toggleFollowups}>{(sel.followups && sel.followups.stopped) ? "Resume" : "Stop"}</button>}
                </div>
                <div className="card__body">
                  {!sel.sentAt ? (
                    <div className="dim" style={{ fontSize: 12.5 }}>Send the first email to start the Day 3 / 7 / 14 sequence. Follow-ups go out automatically, in the same thread.</div>
                  ) : (sel.followups && sel.followups.stopped) ? (
                    <div className="kick" style={{ color: "var(--c-amber)", marginBottom: 10 }}>Follow-ups paused {sel.replied ? "(replied)" : ""}</div>
                  ) : (
                    <div className="dim" style={{ fontSize: 11.5, marginBottom: 10 }}>Auto-sends in the same thread. Hit <b>Stop</b> the moment they reply.</div>
                  )}
                  {sel.sentAt && FOLLOWUP_STEPS.map(fs => {
                    const hist = (sel.followups && sel.followups.history) || [];
                    const done = hist.find(h => h.step === fs.step);
                    const when = sel.sentAt + fs.day * 86400000;
                    const due = Date.now() >= when;
                    return (
                      <div key={fs.step} className="out-fu">
                        <span className={"out-fu__dot" + (done ? " is-done" : due ? " is-due" : "")} />
                        <div style={{ flex: 1, minWidth: 0 }}>
                          <div style={{ fontSize: 12.5, fontWeight: 600 }}>Day {fs.day} · {fs.label}</div>
                          <div className="mono dim" style={{ fontSize: 10.5 }}>
                            {done ? "sent " + fmtDate(done.at) : (due ? "due now" : "scheduled " + fmtDate(when))}
                          </div>
                        </div>
                        {!done && <button className="btn btn--ghost btn--sm" disabled={busy === "fu" + fs.step} onClick={() => sendFollowup(fs.step)}>{busy === "fu" + fs.step ? "Sending…" : "Send now"}</button>}
                      </div>
                    );
                  })}
                </div>
              </div>
            </div>
          </div>
        )}
      </div>
    </div>
  );
}
window.OutreachView = OutreachView;
