/* ===========================================================
   DTF.uk · Ops — Customers list + Customer profile
   =========================================================== */
const { useState, useEffect, useRef, useMemo } = React;

// --- behavioral-event display helpers ---
function fmtEventTime(ms) {
  if (!ms) return "—";
  const d = new Date(Number(ms));
  const M = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
  return `${d.getDate()} ${M[d.getMonth()]} ${String(d.getHours()).padStart(2,"0")}:${String(d.getMinutes()).padStart(2,"0")}`;
}
function eventLabel(e) {
  const d = e.data || {};
  switch (e.type) {
    case "product_viewed":        return `Viewed product · ${d.productTitle || d.productId || ""}`;
    case "collection_viewed":     return `Viewed collection · ${d.collectionTitle || d.collectionId || ""}`;
    case "search_submitted":      return `Searched "${d.query || ""}"${d.resultsCount != null ? ` · ${d.resultsCount} results` : ""}`;
    case "product_added_to_cart": return `Added to cart · ${d.productTitle || d.productId || ""}${d.quantity ? ` ×${d.quantity}` : ""}`;
    case "cart_viewed":           return `Viewed cart${d.itemCount != null ? ` · ${d.itemCount} items` : ""}`;
    case "checkout_started":      return `Started checkout${d.value != null ? ` · ${DTF.fmtGBP(d.value)}` : ""}`;
    case "checkout_completed":    return `Completed checkout${d.orderId ? ` · order ${d.orderId}` : ""}`;
    case "page_viewed":           return `Viewed ${e.pageType || "page"}${e.title ? ` · ${e.title}` : ""}`;
    case "email_opened":          return `Opened email${d.context ? ` · ${d.context}` : ""}`;
    case "email_link_clicked":    return `Clicked email link${d.url ? ` · ${String(d.url).replace(/^https?:\/\/[^/]+/, "").slice(0, 60) || d.url}` : ""}`;
    default:                      return `${e.type}${e.title ? ` · ${e.title}` : ""}`;
  }
}

// Aggregate product/collection views (one card) keeping each item's storefront URL.
function aggProducts(events) {
  const m = new Map();
  events.filter(e => e.type === "product_viewed" || e.type === "collection_viewed").forEach(e => {
    const d = e.data || {}; const isCol = e.type === "collection_viewed";
    const ref = isCol ? (d.collectionId || d.collectionTitle) : (d.productId || d.productTitle);
    if (!ref) return;
    const key = (isCol ? "c:" : "p:") + ref;
    const cur = m.get(key) || { kind: isCol ? "collection" : "product",
      title: isCol ? (d.collectionTitle || "Collection") : (d.productTitle || "Product"),
      type: d.productType || "", price: isCol ? null : d.price, productCount: isCol ? d.productCount : null,
      url: e.url || null, ts: e.ts, views: 0 };
    cur.views++; if (e.ts > cur.ts) { cur.ts = e.ts; if (e.url) cur.url = e.url; } m.set(key, cur);
  });
  return [...m.values()].sort((a, b) => b.ts - a.ts);
}
// Content pages only (page/article/blog) — excludes products & collections.
function aggPages(events) {
  const m = new Map();
  events.filter(e => ["page", "article", "blog"].includes(e.pageType)).forEach(e => {
    const key = e.path || e.title || e.pageType; if (!key) return;
    const cur = m.get(key) || { title: e.title || e.path || e.pageType, handle: e.path || e.pageType, pageType: e.pageType, url: e.url || null, ts: e.ts };
    if (e.ts > cur.ts) { cur.ts = e.ts; if (e.url) cur.url = e.url; } m.set(key, cur);
  });
  return [...m.values()].sort((a, b) => b.ts - a.ts);
}

function BrowseRow({ icon, title, meta, ts, url, mono, compact }) {
  const inner = (<>
    <div className="viewed__ic"><Icon name={icon} /></div>
    <div style={{ minWidth: 0, flex: 1 }}>
      <div className="viewed__t" style={{ whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{title}</div>
      <div className={"viewed__m" + (mono ? " mono" : "")} style={{ whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{meta}</div>
      {/* In the narrow (inbox) panel the right-aligned time gets squeezed off, so
          show it on its own line under the meta instead. */}
      {compact && <div className="viewed__m" style={{ display: "flex", alignItems: "center", gap: 4, marginTop: 2 }}>{url ? <Icon name="external" /> : null}{fmtEventTime(ts)}</div>}
    </div>
    {!compact && <div className="viewed__time" style={{ display: "flex", alignItems: "center", gap: 4 }}>{url ? <Icon name="external" /> : null}{fmtEventTime(ts)}</div>}
  </>);
  return url
    ? <a className="viewed__row" href={url} target="_blank" rel="noopener noreferrer" title={"Open " + url}>{inner}</a>
    : <div className="viewed__row">{inner}</div>;
}

// Self-contained browsing panel for an email: summary + recently viewed
// products/collections + pages, last 10 with infinite-scroll. Renders nothing if no data.
function BrowsingSection({ email, compact }) {
  const EV_PAGE = 10;
  const [summary, setSummary] = useState(null);
  const [events, setEvents] = useState([]);
  const [more, setMore] = useState(false);
  const [loading, setLoading] = useState(false);
  const moreRef = useRef(null);
  useEffect(() => {
    let alive = true; setSummary(null); setEvents([]); setMore(false);
    if (!email) return;
    const e = encodeURIComponent(email);
    fetch("/api/events/summary?email=" + e).then(r => r.json()).then(s => { if (alive) setSummary(s); }).catch(() => {});
    fetch("/api/events/customer/" + e + "?limit=" + EV_PAGE).then(r => r.json()).then(j => {
      if (!alive) return; const ev = (j && j.events) || []; setEvents(ev); setMore(ev.length >= EV_PAGE);
    }).catch(() => {});
    return () => { alive = false; };
  }, [email]);
  async function loadMore() {
    if (loading || !more || !email || !events.length) return;
    setLoading(true);
    const before = events[events.length - 1].ts;
    try {
      const j = await fetch("/api/events/customer/" + encodeURIComponent(email) + "?limit=" + EV_PAGE + "&before=" + before).then(r => r.json());
      const ev = (j && j.events) || []; setEvents(prev => [...prev, ...ev]); setMore(ev.length >= EV_PAGE);
    } catch (e) {} finally { setLoading(false); }
  }
  useEffect(() => {
    const el = moreRef.current; if (!el || !more) return;
    const io = new IntersectionObserver(es => { if (es[0].isIntersecting) loadMore(); }, { rootMargin: "240px" });
    io.observe(el); return () => io.disconnect();
  }, [more, loading, events.length, email]);

  const products = aggProducts(events);
  const pages = aggPages(events);
  if (!(summary && summary.lastSeen) && products.length === 0 && pages.length === 0) return null;

  return (
    <>
      {summary && summary.lastSeen && (
        <div className="card">
          <div className="card__head"><Icon name="spark" /><h3>Browsing</h3>
            <span className="right kick">last seen {fmtEventTime(summary.lastSeen)}</span></div>
          <div className="card__body">
            <div style={{ display: "flex", gap: 18, flexWrap: "wrap", fontSize: 12.5 }}>
              {Object.entries(summary.counts || {}).map(([k, n]) => <span key={k}><b>{n}</b> <span className="dim">{k.replace(/_/g, " ")}</span></span>)}
            </div>
            {summary.recentSearches && summary.recentSearches.length > 0 && (
              <div style={{ marginTop: 12 }}>
                <div className="kick" style={{ marginBottom: 4 }}>Recent searches</div>
                <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>{summary.recentSearches.map((q, i) => <span key={i} className="tag">{q}</span>)}</div>
              </div>
            )}
          </div>
        </div>
      )}
      {(products.length > 0 || pages.length > 0) && (
        <div style={{ display: "grid", gridTemplateColumns: compact ? "1fr" : "repeat(auto-fit, minmax(260px, 1fr))", gap: 16 }}>
          {products.length > 0 && (
            <div className="card" style={{ minWidth: 0 }}>
              <div className="card__head"><Icon name="shirt" /><h3>Recently viewed Products &amp; Collections</h3><span className="right kick">live</span></div>
              <div className="card__body--flush viewed">
                {products.map((row, i) => <BrowseRow key={i} compact={compact} icon={row.kind === "collection" ? "tag" : "shirt"} title={row.title} ts={row.ts} url={row.url}
                  meta={(row.kind === "collection"
                    ? ["Collection", row.productCount != null ? `${row.productCount} products` : null, row.views > 1 ? `${row.views} views` : null]
                    : [row.type, row.price != null ? DTF.fmtGBP(row.price) : null, row.views > 1 ? `${row.views} views` : null]).filter(Boolean).join(" · ")} />)}
              </div>
            </div>
          )}
          {pages.length > 0 && (
            <div className="card" style={{ minWidth: 0 }}>
              <div className="card__head"><Icon name="doc" /><h3>Recently viewed pages</h3><span className="right kick">live</span></div>
              <div className="card__body--flush viewed">
                {pages.map((row, i) => <BrowseRow key={i} compact={compact} icon="doc" mono title={row.title} ts={row.ts} url={row.url}
                  meta={(row.pageType ? row.pageType + " · " : "") + row.handle} />)}
              </div>
            </div>
          )}
        </div>
      )}
      {more && <div ref={moreRef} className="browse-sentinel">{loading ? <span className="spin spin--dark" /> : <span className="kick">scroll for more</span>}</div>}
    </>
  );
}
window.BrowsingSection = BrowsingSection;
window.fmtEventTime = fmtEventTime;
window.eventLabel = eventLabel;

const CUST_GETTERS = {
  name: c => (c.name||"").toLowerCase(),
  subscribed: c => c.subscribed ? 1 : 0,
  location: c => (c.location||"").toLowerCase(),
  channel: c => c.channel || "",
  orders: c => c.orders || 0,
  aov: c => c.orders ? (c.spent || 0) / c.orders : 0,
  spent: c => c.spent || 0,
  since: c => c.since || "",
};

function CustomersList({ go }) {
  const [sortBy, setSortBy] = useState({ key:"spent", dir:-1 }); // dir: 1 asc, -1 desc
  const [seg, setSeg] = useState("all");     // all | subscribed | trade
  const [query, setQuery] = useState("");
  const [ver, setVer] = useState(0); // bump so memos recompute after a reload
  const [loading, setLoading] = useState(DTF.customers.length === 0);
  // Reload data when the section opens so customers are never stale.
  useEffect(() => {
    let alive = true;
    if (DTF.reloadData) DTF.reloadData().then(() => { if (alive) { setLoading(false); setVer(v => v + 1); } }).catch(() => alive && setLoading(false));
    else setLoading(false);
    return () => { alive = false; };
  }, []);
  const [reloading, setReloading] = useState(false);
  const doReload = () => { if (reloading || !DTF.reloadData) return; setReloading(true); DTF.reloadData().then(() => { setReloading(false); setVer(v => v + 1); }).catch(() => setReloading(false)); };
  // Live-sync pushed fresh data → recompute the list.
  useEffect(() => { const h = () => setVer(v => v + 1); window.addEventListener("dtf:refreshed", h); return () => window.removeEventListener("dtf:refreshed", h); }, []);

  const q = query.trim().toLowerCase();
  const terms = q ? q.split(/\s+/).filter(Boolean) : [];

  // Instant search by name, email, phone and city/location. All terms must match (AND).
  function matchCustomer(c){
    const hay = [c.name, c.email, c.phone, c.location]
      .filter(Boolean).join("  ").toLowerCase();
    return terms.every(t => hay.includes(t));
  }

  const rows = useMemo(() => {
    let list = DTF.customers.slice();
    if (seg === "subscribed") list = list.filter(c => c.subscribed);
    if (seg === "trade") list = list.filter(c => c.channel === "Trade");
    if (seg === "lapsed") list = list.filter(c => DTF.lapsedInfo(c).lapsed);
    if (seg === "vip-risk") list = list.filter(c => DTF.isVip(c) && DTF.lapsedInfo(c).atRisk);
    if (seg === "hival-lapsed") list = list.filter(c => DTF.lapsedInfo(c).lapsed && (c.spent || 0) >= 500);
    if (terms.length) list = list.filter(matchCustomer);
    const g = CUST_GETTERS[sortBy.key] || CUST_GETTERS.spent;
    list.sort((a,b) => { const av=g(a), bv=g(b); if (av<bv) return -1*sortBy.dir; if (av>bv) return 1*sortBy.dir; return 0; });
    return list;
  }, [sortBy, seg, q, ver]);

  function sortCol(key){
    setSortBy(s => s.key===key ? { key, dir:-s.dir } : { key, dir:(key==="name"||key==="location"||key==="channel") ? 1 : -1 });
  }
  const Th = ({ k, label, num }) => (
    <th className={num?"num":""} onClick={()=>sortCol(k)} style={{cursor:"pointer", userSelect:"none", whiteSpace:"nowrap"}} title="Click to sort">
      {label}{sortBy.key===k ? <span style={{marginLeft:4, color:"var(--mint-ink)"}}>{sortBy.dir<0 ? "▾" : "▴"}</span> : ""}
    </th>
  );

  const totalSpent = DTF.customers.reduce((s,c)=>s+c.spent,0);
  const totalOrders = DTF.customers.reduce((s,c)=>s+c.orders,0);
  const aov = totalOrders ? totalSpent/totalOrders : 0;   // store-wide average order value
  const subs = DTF.customers.filter(c=>c.subscribed).length;
  const trade = DTF.customers.filter(c=>c.channel==="Trade").length;
  const lapsed = DTF.customers.filter(c=>DTF.lapsedInfo(c).lapsed).length;
  const vipRisk = DTF.customers.filter(c=>DTF.isVip(c) && DTF.lapsedInfo(c).atRisk).length;
  const hivalLapsed = DTF.customers.filter(c=>{ const l=DTF.lapsedInfo(c); return l.lapsed && (c.spent||0) >= 500; }).length;
  const n = DTF.customers.length || 1;

  return (
    <div className="board">
      <div className="stats">
        <div className="stat"><div className="stat__k">Customers</div><div className="stat__v">{DTF.customers.length}</div><div className="stat__d">{trade} trade accounts</div></div>
        <div className="stat"><div className="stat__k">Subscribed</div><div className="stat__v">{subs}</div><div className="stat__d">{Math.round(subs/n*100)}% of list</div></div>
        <div className="stat"><div className="stat__k">Lifetime revenue</div><div className="stat__v">{DTF.fmtGBP(totalSpent)}</div><div className="stat__d">all customers</div></div>
        <div className="stat"><div className="stat__k">Avg. order value</div><div className="stat__v">{DTF.fmtGBP(aov)}</div><div className="stat__d">{totalOrders} orders · AOV</div></div>
        <div className="stat stat--clickable" onClick={()=>setSeg(seg==="lapsed"?"all":"lapsed")} title="Regulars (4+ orders) who've gone quiet past their usual cadence — click to filter" style={{cursor:"pointer"}}>
          <div className="stat__k">Lapsed regulars</div><div className="stat__v" style={lapsed?{color:"#b45309"}:null}>{lapsed}</div><div className="stat__d">used to order often, now quiet</div></div>
      </div>

      <div className="toolbar">
        <div className="seg">
          <button className={seg==="all"?"is-active":""} onClick={()=>setSeg("all")}>All</button>
          <button className={seg==="subscribed"?"is-active":""} onClick={()=>setSeg("subscribed")}>Subscribed</button>
          <button className={seg==="trade"?"is-active":""} onClick={()=>setSeg("trade")}>Trade</button>
          <button className={seg==="lapsed"?"is-active":""} onClick={()=>setSeg("lapsed")} title="Regulars who've gone quiet — 4+ orders, silent past their usual cadence">Lapsed{lapsed?` · ${lapsed}`:""}</button>
          <button className={seg==="vip-risk"?"is-active":""} onClick={()=>setSeg("vip-risk")} title="VIPs (£1k+ spend or tagged) who are overdue for their next order — catch them before they fully lapse">VIP at-risk{vipRisk?` · ${vipRisk}`:""}</button>
          <button className={seg==="hival-lapsed"?"is-active":""} onClick={()=>setSeg("hival-lapsed")} title="Lapsed regulars with £500+ lifetime spend — highest-value win-backs">High-value lapsed{hivalLapsed?` · ${hivalLapsed}`:""}</button>
        </div>
        <div className="ord-search">
          <Icon name="search" />
          <input type="search" value={query} onChange={e=>setQuery(e.target.value)}
                 placeholder="Search name, email, phone, city…" />
          {query && <button className="ord-search__clear" title="Clear" onClick={()=>setQuery("")}>×</button>}
        </div>
        <span className="kick right">
          {terms.length ? `${rows.length} ${rows.length===1?"match":"matches"} for “${query.trim()}”` : "Click a column to sort"}
        </span>
        <button className="btn btn--ghost btn--sm" onClick={doReload} disabled={reloading} title="Reload customers"><Icon name="refresh" /> {reloading ? "Reloading…" : "Reload"}</button>
      </div>

      <div className="tbl-wrap">
      <table className="tbl">
        <thead>
          <tr>
            <Th k="name" label="Customer" />
            <Th k="subscribed" label="Subscribed" />
            <Th k="location" label="Location" />
            <Th k="channel" label="Channel" />
            <Th k="orders" label="Orders" num />
            <Th k="aov" label="AOV" num />
            <Th k="spent" label="LTV" num />
            <th>Tags</th>
            <Th k="since" label="Since" />
          </tr>
        </thead>
        <tbody>
          {(reloading || (loading && DTF.customers.length === 0))
            ? Array.from({ length: 8 }).map((_, i) => (
                <tr key={"sk" + i}><td colSpan={9} style={{ padding: "10px 12px" }}><div className="skel skel-bar" style={{ height: 18, opacity: Math.max(0.35, 1 - i * 0.08) }} /></td></tr>
              ))
            : <>
          {rows.map(c => (
            <tr key={c.id} onClick={()=>go({view:"customer",id:c.id})}>
              <td>
                <div style={{display:"flex",alignItems:"center",gap:10}}>
                  <span className="av" style={{width:30,height:30,borderRadius:"50%",background:"var(--ink)",color:"var(--mint)",display:"grid",placeItems:"center",fontFamily:"var(--font-mono)",fontWeight:700,fontSize:11,flex:"none"}}>{DTF.initials(c.name)}</span>
                  <div>
                    <div className="cust">{c.name}{(() => { const l = DTF.lapsedInfo(c);
                      if (l.lapsed) return <span className="badge badge--amber" style={{marginLeft:6}} title={`Ordered ${c.orders}× (usually ~every ${l.avgInterval||"?"} days) but nothing for ${l.daysSince} days`}>lapsed · {l.daysSince}d</span>;
                      if (l.atRisk) return <span className="badge badge--ghost" style={{marginLeft:6}} title={`Overdue — usually orders ~every ${l.avgInterval||"?"} days, nothing for ${l.daysSince} days`}>at risk · {l.daysSince}d</span>;
                      return null; })()}</div>
                    <div className="muted" style={{fontFamily:"var(--font-mono)",fontSize:10.5}}>{c.email}</div>
                  </div>
                </div>
              </td>
              <td>{c.subscribed ? <span className="badge badge--mint">Subscribed</span> : <span className="badge badge--ghost">No</span>}</td>
              <td>{c.location}</td>
              <td><ChannelBadge channel={c.channel} /></td>
              <td className="num" style={{fontWeight:600}}>{c.orders}</td>
              <td className="num">{c.orders ? DTF.fmtGBP(c.spent / c.orders) : "—"}</td>
              <td className="num" style={{fontWeight:600}}>{DTF.fmtGBP(c.spent)}</td>
              <td><Tags tags={c.tags} /></td>
              <td className="muted" style={{fontFamily:"var(--font-mono)",fontSize:11}}>{c.since}</td>
            </tr>
          ))}
          {rows.length === 0 && (
            <tr><td colSpan={9} className="empty-state">No customers match “{query.trim()}”. <button className="linklike" onClick={()=>setQuery("")}>Clear search</button></td></tr>
          )}
            </>}
        </tbody>
      </table>
      </div>
    </div>
  );
}

// WhatsApp click-to-chat link for a phone number. Strips formatting; a UK local
// "0…" number is converted to international "44…". Returns null if no digits.
function waLink(phone) {
  let wa = String(phone || "").replace(/[^\d]/g, "");
  if (!wa) return null;
  if (wa.startsWith("0")) wa = "44" + wa.slice(1);
  return (
    <a href={"https://wa.me/" + wa} target="_blank" rel="noopener noreferrer" title="Message on WhatsApp"
       onClick={e => e.stopPropagation()} style={{ display: "inline-flex", alignItems: "center", color: "#25D366" }}>
      <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" aria-hidden="true"><path d="M17.5 14.4c-.3-.1-1.7-.8-2-.9s-.4-.1-.6.2-.7.9-.9 1.1-.3.2-.6.1a8.2 8.2 0 0 1-2.4-1.5 9 9 0 0 1-1.7-2.1c-.2-.3 0-.5.1-.6l.5-.5a2 2 0 0 0 .3-.5.5.5 0 0 0 0-.5c0-.1-.6-1.6-.9-2.2s-.5-.5-.6-.5H7.7a1 1 0 0 0-.8.4A3.1 3.1 0 0 0 6 8.9a5.4 5.4 0 0 0 1.1 2.9 12.3 12.3 0 0 0 4.8 4.2 5.3 5.3 0 0 0 3.3.8 2.9 2.9 0 0 0 1.9-1.4 2.4 2.4 0 0 0 .2-1.4c-.1-.1-.3-.2-.6-.3ZM12 2a10 10 0 0 0-8.6 15L2 22l5.1-1.3A10 10 0 1 0 12 2Zm0 18.3a8.3 8.3 0 0 1-4.2-1.2l-.3-.2-3 .8.8-3-.2-.3A8.3 8.3 0 1 1 12 20.3Z"/></svg>
    </a>
  );
}

/* =================== CUSTOMER PROFILE =================== */
function CustomerProfile({ id, go }) {
  const c = DTF.getCustomer(id);
  const custEmail = c && c.email;
  const [mailThreads, setMailThreads] = useState(null);
  const [reachOut, setReachOut] = useState(false);   // re-activation email modal
  const lapse = c ? DTF.lapsedInfo(c) : { lapsed: false };
  const ReachModal = window.BrowseEmailModal;
  // Editable relationship note — background used when composing a re-activation email.
  const custKey = c ? String(c.id).split("/").pop() : "";
  const [relNote, setRelNote] = useState("");
  const [relSaved, setRelSaved] = useState("");
  useEffect(() => {
    if (!custKey) return; let alive = true;
    fetch("/api/customers/" + encodeURIComponent(custKey) + "/note").then(r => r.json()).then(j => { if (alive) { setRelNote(j.note || ""); setRelSaved(j.note || ""); } }).catch(() => {});
    return () => { alive = false; };
  }, [custKey]);
  async function saveRelNote() {
    if (relNote === relSaved) return;
    try { await fetch("/api/customers/" + encodeURIComponent(custKey) + "/note", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ note: relNote }) }); setRelSaved(relNote); } catch (e) {}
  }
  async function deleteRelNote() {
    setRelNote("");
    try { await fetch("/api/customers/" + encodeURIComponent(custKey) + "/note", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ note: "" }) }); setRelSaved(""); } catch (e) {}
  }
  // Background handed to the re-activation AI: the relationship note + any Shopify note.
  const reactivationContext = [relNote, c && c.note].filter(Boolean).join("\n\n");
  useEffect(() => {
    let alive = true; setMailThreads(null);
    if (custEmail) DTF.searchMail(custEmail).then(t => alive && setMailThreads(t)).catch(() => alive && setMailThreads([]));
    else setMailThreads([]);
    return () => { alive = false; };
  }, [id, custEmail]);

  // Pull the customer's full order history (not just the loaded page).
  const [ordersLive, setOrdersLive] = useState(null);
  useEffect(() => {
    let alive = true; setOrdersLive(null);
    DTF.fetchCustomerOrders(id).then(o => { if (alive) setOrdersLive(o); }).catch(() => { if (alive) setOrdersLive([]); });
    return () => { alive = false; };
  }, [id]);

  // Store-credit balance (Shopify store-credit account).
  const [credit, setCredit] = useState(null); // null = loading
  const isMail = typeof id === "string" && id.indexOf("mail:") === 0;
  useEffect(() => {
    let alive = true; setCredit(null);
    if (isMail) { setCredit({ credit: 0 }); return; }
    DTF.fetchCustomerCredit(id).then(r => { if (alive) setCredit(r || { credit: 0 }); }).catch(() => { if (alive) setCredit({ credit: 0 }); });
    return () => { alive = false; };
  }, [id]);


  // Manual link for email-only / merged contacts (id like "mail:someone@x.com").
  const isUnlinked = typeof id === "string" && id.indexOf("mail:") === 0;
  const [linkQ, setLinkQ] = useState("");
  const [linkResults, setLinkResults] = useState(null);
  const [linking, setLinking] = useState(false);
  useEffect(() => {
    if (!isUnlinked || !linkQ.trim()) { setLinkResults(null); return; }
    let alive = true;
    const t = setTimeout(() => {
      DTF.searchShopifyCustomers(linkQ).then(r => alive && setLinkResults(r)).catch(() => alive && setLinkResults([]));
    }, 300);
    return () => { alive = false; clearTimeout(t); };
  }, [linkQ, isUnlinked]);
  async function doLink(cust){
    setLinking(true);
    try { await DTF.linkCustomerAlias(c.email, cust); go({ view:"customer", id: cust.id }); }
    catch (e) { alert("Link failed: " + e.message); }
    finally { setLinking(false); }
  }

  if (!c) return <div className="empty"><div className="t">Customer not found</div></div>;
  const orders = (ordersLive && ordersLive.length)
    ? ordersLive
    : DTF.ordersForCustomer(id).slice().sort((a,b)=>a.date<b.date?1:-1);
  const _seen = new Set();
  const threads = [...DTF.threadsForCustomer(id), ...(mailThreads || [])]
    .filter(t => _seen.has(t.id) ? false : (_seen.add(t.id), true))
    .sort((a,b) => a.updated < b.updated ? 1 : -1);
  const v = DTF.viewedFor(id);
  const avgOrder = c.orders ? c.spent / c.orders : 0;
  // Shopify often leaves the customer record's phone empty — the number the
  // customer entered at checkout lives on the order shipping address. Fall back
  // to the most recent order that carries one.
  const contactPhone = c.phone || ((orders.find(o => o.ship && o.ship.phone) || {}).ship || {}).phone || "";


  return (
    <div className="prof">
      {reachOut && ReachModal &&
        <ReachModal group={{ email: custEmail, name: c.name }} events={[]} ordered7d={new Set()} initialNotes={reactivationContext} defaultTpl="reactivation" onClose={() => setReachOut(false)} />}

      {/* Internal notes — pinned to the top, prominent. Shown on this customer's
          orders too, and used as background for re-activation emails. */}
      <div className={"card cust-notes" + (relSaved ? " cust-notes--has" : "")} style={{ gridColumn: "1 / -1" }}>
        <div className="card__head"><Icon name="doc" /><h3>Internal notes</h3>
          <span className="right" style={{ display: "flex", gap: 8, alignItems: "center" }}>
            {relNote !== relSaved && <span className="kick" style={{ color: "var(--sub)" }}>unsaved · saves on blur</span>}
            {relSaved && <button className="btn btn--ghost btn--sm" onClick={deleteRelNote} title="Delete this note"><Icon name="x" /> Delete</button>}
          </span></div>
        <div className="card__body">
          <div className="dim" style={{ fontSize: 12, marginBottom: 6 }}>Private notes about this customer — what they do, what they order, how to help. Shown on their orders and used as background for re-activation emails.</div>
          <textarea className="rm-in" style={{ width: "100%", minHeight: 64, resize: "vertical", fontSize: 13.5 }}
            value={relNote} onChange={e => setRelNote(e.target.value)} onBlur={saveRelNote}
            placeholder="e.g. Runs an embroidery business, bought two heat presses. Prefers Friday cutoffs." />
        </div>
      </div>

      {/* side */}
      <div className="prof__side">
        <div className="card">
          <div className="card__body">
            <div className="crumbs" style={{marginBottom:14}}>
              <a onClick={()=>go({view:"customers"})}>Customers</a><span className="sep">/</span><span style={{color:"var(--txt)"}}>{c.name}</span>
            </div>
            <div style={{display:"flex",gap:13,alignItems:"center",marginBottom:14}}>
              <Avatar name={c.name} lg />
              <div style={{minWidth:0}}>
                <div style={{fontFamily:"var(--font-display)",fontWeight:800,fontSize:18,letterSpacing:"-.01em",whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis"}}>{c.name}</div>
                {c.email && <div className="mono dim" style={{fontSize:11,display:"flex",alignItems:"center",gap:4}}>
                  <span title={c.email} style={{minWidth:0,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}}>{c.email}</span>
                  <CopyBtn text={c.email} title="Copy email" />
                </div>}
              </div>
            </div>
            <div style={{display:"flex",gap:6,flexWrap:"wrap",marginBottom:16}}>
              {c.subscribed && <span className="badge badge--mint">Subscribed</span>}
              {(c.tags||[]).map(t=><span key={t} className="tag">{t}</span>)}
            </div>
            {isUnlinked && (
              <div className="link-box">
                <div className="link-box__hd"><Icon name="spark" /> Not a Shopify customer</div>
                <div className="link-box__sub">Email-only contact. If this person exists in Shopify (e.g. after a merge), link them so their orders &amp; future emails attach correctly.</div>
                <input className="link-box__in" placeholder="Search Shopify by name or email…" value={linkQ} onChange={e=>setLinkQ(e.target.value)} />
                {linkResults && (
                  <div className="link-box__results">
                    {linkResults.length === 0
                      ? <div className="dim" style={{fontSize:12,padding:"6px 2px"}}>No matches</div>
                      : linkResults.map(r => (
                          <button key={r.id} className="link-box__row" disabled={linking} onClick={()=>doLink(r)}>
                            <span className="link-box__name">{r.name}</span>
                            <span className="link-box__email mono">{r.email}{r.orders ? ` · ${r.orders} orders` : ""}</span>
                          </button>
                        ))}
                  </div>
                )}
              </div>
            )}
            <div className="statline">
              <div><div className="k">Orders</div><div className="v">{c.orders}</div></div>
              <div><div className="k">Lifetime</div><div className="v">{DTF.fmtGBP(c.spent)}</div></div>
              <div><div className="k">Avg. order</div><div className="v">{DTF.fmtGBP(avgOrder)}</div></div>
              <div title={credit && credit.unavailable ? "Add the read_store_credit_account_transactions scope to show this" : ""}>
                <div className="k">Store credit</div>
                <div className="v" style={credit && credit.credit > 0 ? { color: "var(--mint-ink)" } : null}>
                  {credit == null ? "…" : credit.unavailable ? "—" : DTF.fmtGBP(credit.credit || 0)}
                </div>
              </div>
              <div><div className="k">Customer since</div><div className="v" style={{fontSize:14}}>{c.since}</div></div>
            </div>
            {lapse.lapsed && <div className="reactivate-note" title={`Ordered ${c.orders}× (usually ~every ${lapse.avgInterval||"?"} days) — nothing for ${lapse.daysSince} days`}>
              <Icon name="clock" /> Lapsed regular — {c.orders} orders, quiet for {lapse.daysSince} days</div>}
            <div style={{display:"flex",gap:8,marginTop:16,flexWrap:"wrap"}}>
              <button className="btn btn--primary btn--sm btn--block" onClick={()=>{ const t=threads[0]; go(t?{view:"inbox",id:t.id}:{view:"inbox"}); }}><Icon name="mail" /> Email</button>
              {custEmail && window.BrowseEmailModal &&
                <button className="btn btn--ghost btn--sm btn--block" onClick={()=>setReachOut(true)} title="AI-write a warm re-activation email (references their history, offers help)"><Icon name="spark" /> Re-activation email</button>}
              {!isMail && DTF.shop && DTF.shop.adminUrl &&
                <a className="btn btn--ghost btn--sm" href={DTF.shop.adminUrl + "/customers/" + String(c.id).split("/").pop()} target="_blank" rel="noopener noreferrer" title="Open in Shopify admin"><Icon name="external" /></a>}
            </div>
          </div>
        </div>

        <div className="card">
          <div className="card__head"><h3>Details</h3></div>
          <div className="card__body">
            <dl className="kv">
              <dt>Location</dt><dd>{c.location}</dd>
              <dt>Email</dt><dd>{c.email ? <a href={"mailto:"+c.email}>{c.email}</a> : "—"}</dd>
              <dt>Phone</dt><dd>{contactPhone ? <span style={{display:"inline-flex",alignItems:"center",gap:8}}><a href={"tel:"+contactPhone.replace(/\s+/g,"")}>{contactPhone}</a>{waLink(contactPhone)}</span> : "—"}</dd>
              <dt>Channel</dt><dd><ChannelBadge channel={c.channel} /></dd>
              <dt>Marketing</dt><dd>{c.subscribed?"Opted in":"Not subscribed"}</dd>
            </dl>
          </div>
        </div>

        {/* how this customer was acquired + channels touched */}
        {custEmail && <CustomerAcquisitionCard email={custEmail} />}

        {c.note && (
          <div className="card">
            <div className="card__head"><h3>Internal note</h3><span className="right flag flag--note">✎</span></div>
            <div className="card__body" style={{fontSize:13,lineHeight:1.55,color:"var(--txt-2)"}}>{c.note}</div>
          </div>
        )}
      </div>

      {/* main */}
      <div className="prof__main">
        {/* browsing summary — only the bits without a dedicated section (searches, last seen).
            Products & pages feed the "Recently viewed" cards below. */}
        <BrowsingSection email={custEmail} />
        {/* orders */}
        <div className="card">
          <div className="card__head"><h3>Orders</h3><span className="right kick">{orders.length} total</span></div>
          <div className="card__body--flush tbl-wrap">
            <table className="tbl" style={{border:0,borderRadius:0}}>
              <thead>
                <tr><th>Order</th><th>Date</th><th>Items</th><th>Method</th><th className="num">Total</th><th>Payment</th><th>Status</th></tr>
              </thead>
              <tbody>
                {orders.map(o => {
                  const sum = o.items.reduce((s,i)=>s+i.qty,0);
                  return (
                    <tr key={o.id} onClick={()=>{ DTF.setOrderNav(orders.map(x=>x.id), { source: "Customer · " + c.name, route: { view:"customer", id: c.id } }); go({view:"order",id:o.id}); }}>
                      <td><span className="ordno">{o.id}</span></td>
                      <td className="muted" style={{fontFamily:"var(--font-mono)",fontSize:11.5}}>{o.date.slice(0,10)}</td>
                      <td>{sum} units</td>
                      <td><MethodCell method={o.method} /></td>
                      <td className="num" style={{fontWeight:600}}>{DTF.fmtGBP(o.total)}</td>
                      <td><MetaBadge map={DTF.payMeta} value={o.pay} /></td>
                      <td><StatusBadge status={o.status} /></td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        </div>


        {/* attachments across this customer's email history */}
        {(() => {
          const atts = DTF.collectAttachments(threads);
          if (!atts.length) return null;
          return (
            <div className="card">
              <div className="card__head"><Icon name="paperclip" /><h3>Attachments</h3><span className="right kick">{atts.length}</span></div>
              <div className="card__body"><div className="att-grid">{atts.map((a,i)=><AttachmentTile key={i} att={a} meta={a.time} />)}</div></div>
            </div>
          );
        })()}

        {/* email threads */}
        <div className="card">
          <div className="card__head"><Icon name="mail" /><h3>Email threads</h3><span className="right kick">{threads.length}</span></div>
          <div className="card__body--flush viewed">
            {threads.length ? threads.map(t => (
              <div className="viewed__row" key={t.id} style={{cursor:"pointer"}} onClick={()=>go({view:"inbox",id:t.id})}>
                <div className="viewed__ic"><Icon name="mail" /></div>
                <div style={{minWidth:0}}>
                  <div className="viewed__t">{t.subject}</div>
                  <div className="viewed__m">{t.messages.length} message{t.messages.length>1?"s":""} · {t.labels.join(", ")}</div>
                </div>
                <div className="viewed__time">{t.status==="open"?<span className="badge badge--mint">Open</span>:<span className="badge badge--grey">Closed</span>}</div>
              </div>
            )) : <div style={{padding:16}} className="dim">{mailThreads===null ? "Searching mailbox…" : "No email threads yet."}</div>}
          </div>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { CustomersList, CustomerProfile });
