/* Ledger — Matter Activity Rail (Following, notifications & @-mention feed).
   Phase A of specs/trial-tracking-follow-feed-architecture-v1.md.

   Self-contained + modular (mirrors Feedback.jsx): registers a single global
   component, window.LedgerActivityRail, rendered as the right column of
   MatterDetail. It reads the shared matter_updates feed, lets a user post an
   update (with @-mentions of staff → they get followed/notified), and manages
   the user's own follow mode. No email is sent here — the notifier (Phase D)
   reads matter_follows + matter_updates and delivers. Data layer: window.LedgerAPI
   (listMatterUpdates / postMatterUpdate / getFollow / setFollow / unfollow /
   listMentionCandidates). Visual language from app/mocks/matter-feed-v1. */
(function () {
  const { useState, useEffect, useRef, useCallback } = React;

  const initialsOf = (nameOrEmail) => {
    const s = String(nameOrEmail || "").trim();
    if (!s) return "?";
    if (s.includes("@")) return s.slice(0, 2).toUpperCase();
    const p = s.split(/\s+/);
    return ((p[0][0] || "") + (p.length > 1 ? p[p.length - 1][0] : "")).toUpperCase();
  };

  function relTime(iso) {
    const d = new Date(iso), now = new Date();
    const sameDay = d.toDateString() === now.toDateString();
    if (sameDay) return d.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
    return d.toLocaleDateString([], { month: "short", day: "numeric" });
  }
  function dayLabel(iso) {
    const d = new Date(iso), now = new Date();
    const midnight = (x) => new Date(x.getFullYear(), x.getMonth(), x.getDate());
    const days = Math.round((midnight(now) - midnight(d)) / 86400000);
    if (days <= 0) return "Today · " + d.toLocaleDateString([], { month: "short", day: "numeric" });
    if (days === 1) return "Yesterday · " + d.toLocaleDateString([], { month: "short", day: "numeric" });
    return d.toLocaleDateString([], { weekday: "short", month: "short", day: "numeric" });
  }

  const SRC = {
    user:          { cls: "user",    icon: null,       tag: "Update" },
    trial_tracker: { cls: "tracker", icon: "gavel",    tag: "Trial Tracker" },
    iconik:        { cls: "iconik",  icon: "film",     tag: "Iconik" },
    system:        { cls: "system",  icon: "activity", tag: "System" },
  };

  const MODE_LABEL = { alerts: "Alerts", digest: "Daily digest", none: "Following" };

  // Suggested updates (0112) use INLINE SVG, not lucide. Cards leave the DOM when a
  // fact is decided, and lucide's createIcons() replaces the <i> node React owns —
  // removing one then makes React removeChild a non-child and unmount the tree (the
  // Search picker crash, 2026-09-11). Inline SVG is React's own node.
  const Svg = ({ d, size = 13 }) => (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor"
      strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      {d.map((p, i) => <path key={i} d={p} />)}
    </svg>
  );
  const IC = {
    spark: ["M12 3l1.9 5.8L20 11l-6.1 2.2L12 19l-1.9-5.8L4 11l6.1-2.2z"],
    check: ["M20 6 9 17l-5-5"],
    x: ["M18 6 6 18", "M6 6l12 12"],
    link: ["M15 3h6v6", "M10 14 21 3", "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"],
  };
  const KIND_LABEL = { case_event: "Court date", matter_field: "Case status", participant: "Person" };
  const hostLabel = (u) => { try { return new URL(u).hostname.replace(/^www\./, ""); } catch (e) { return "source"; } };

  function ActivityRail({ matterId, onMatterChanged }) {
    const [me, setMe] = useState({ email: "", initials: "?" });
    const [updates, setUpdates] = useState([]);
    const [proposals, setProposals] = useState([]);  // pending matter_fact_proposals
    const [deciding, setDeciding] = useState(null);  // proposal_id in flight
    // Deciding moved to Content Ops in 0130 (same predicate as the Metadata desk);
    // everyone with the workspace still sees what is pending. The RPC is the real gate.
    const canDecide = !!(window.LedgerCaps && window.LedgerCaps.isContentOps);
    const [follow, setFollow] = useState(null);      // matter_follows row | null
    const [loading, setLoading] = useState(true);
    const [err, setErr] = useState(null);
    const [menuOpen, setMenuOpen] = useState(false); // follow dropdown

    // composer
    const [expanded, setExpanded] = useState(false);
    const [text, setText] = useState("");
    const [posting, setPosting] = useState(false);
    const [mentions, setMentions] = useState([]);    // [{email,name}]
    const [candidates, setCandidates] = useState([]);
    const [mMenu, setMMenu] = useState(null);        // {query, items} | null
    const taRef = useRef(null);

    const load = useCallback(async () => {
      if (!matterId) return;
      setLoading(true); setErr(null);
      try {
        const [ups, fol, props] = await Promise.all([
          window.LedgerAPI.listMatterUpdates(matterId),
          window.LedgerAPI.getFollow(matterId),
          // Optional: a failure here must not blank the feed itself.
          window.LedgerAPI.listMatterProposals ? window.LedgerAPI.listMatterProposals(matterId).catch(() => []) : [],
        ]);
        setUpdates(ups); setFollow(fol); setProposals(props || []);
      } catch (e) { setErr(e.message || String(e)); }
      finally { setLoading(false); }
    }, [matterId]);

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

    useEffect(() => {
      (async () => {
        try {
          const { data: { user } } = await window.SB.auth.getUser();
          if (user) setMe({ email: user.email || "", initials: initialsOf(user.email) });
        } catch (e) { /* not signed in — rail still renders read-only-ish */ }
        try { setCandidates(await window.LedgerAPI.listMentionCandidates()); }
        catch (e) { /* mentions optional */ }
      })();
    }, []);

    // Re-render lucide glyphs after each paint.
    useEffect(() => { try { window.lucide && window.lucide.createIcons(); } catch (e) {} });

    // Close the follow menu on outside click.
    useEffect(() => {
      if (!menuOpen) return;
      const h = () => setMenuOpen(false);
      document.addEventListener("click", h);
      return () => document.removeEventListener("click", h);
    }, [menuOpen]);

    async function chooseMode(mode) {
      setMenuOpen(false);
      try {
        if (mode === "unfollow") { await window.LedgerAPI.unfollow(matterId); setFollow(null); }
        else { const row = await window.LedgerAPI.setFollow(matterId, mode); setFollow(row); }
      } catch (e) { setErr(e.message || String(e)); }
    }

    // ---- @-mention handling -------------------------------------------------
    function onTextChange(e) {
      const v = e.target.value; setText(v);
      const caret = e.target.selectionStart || v.length;
      const m = v.slice(0, caret).match(/@([\w.\-]*)$/);
      if (m) {
        const q = m[1].toLowerCase();
        const items = candidates
          .filter((c) => c.name.toLowerCase().includes(q) || c.email.toLowerCase().includes(q))
          .slice(0, 6);
        setMMenu(items.length ? { query: m[1], items } : null);
      } else setMMenu(null);
    }
    function pickMention(c) {
      const el = taRef.current; if (!el) return;
      const caret = el.selectionStart || text.length;
      const before = text.slice(0, caret).replace(/@([\w.\-]*)$/, "@" + c.name + " ");
      const after = text.slice(caret);
      const next = before + after;
      setText(next);
      setMentions((ms) => ms.some((x) => x.email === c.email) ? ms : [...ms, c]);
      setMMenu(null);
      setTimeout(() => { el.focus(); const p = before.length; el.setSelectionRange(p, p); }, 0);
    }
    function removeMention(email) { setMentions((ms) => ms.filter((m) => m.email !== email)); }

    function resetComposer() { setExpanded(false); setText(""); setMentions([]); setMMenu(null); }

    async function submit() {
      const body = text.trim();
      if (!body) return;
      setPosting(true); setErr(null);
      try {
        await window.LedgerAPI.postMatterUpdate({
          matter_id: matterId, title: null, body,
          mention_emails: mentions.map((m) => m.email),
        });
        resetComposer();
        await load();
        window.LedgerToast && window.LedgerToast("Update posted" + (mentions.length ? " · people notified" : ""));
      } catch (e) { setErr(e.message || String(e)); }
      finally { setPosting(false); }
    }

    async function decide(p, decision) {
      setDeciding(p.proposal_id); setErr(null);
      try {
        await window.LedgerAPI.decideMatterProposal(p.proposal_id, decision);
        setProposals((ps) => ps.filter((x) => x.proposal_id !== p.proposal_id));
        if (decision === "accept") {
          window.LedgerToast && window.LedgerToast("Added to the matter");
          await load();                         // the accept posts its own feed row
          onMatterChanged && onMatterChanged(); // status, events, people on the page
        }
      } catch (e) { setErr(e.message || String(e)); }
      finally { setDeciding(null); }
    }

    async function togglePin(u) {
      try { await window.LedgerAPI.pinMatterUpdate(u.id, !u.is_pinned); await load(); }
      catch (e) { setErr(e.message || String(e)); }
    }
    async function remove(u) {
      try { await window.LedgerAPI.deleteMatterUpdate(u.id); await load(); }
      catch (e) { setErr(e.message || String(e)); }
    }

    const following = !!follow;
    const followLabel = following ? (MODE_LABEL[follow.notify_mode] || "Following") : "Follow";

    // Group the feed: pinned first, then by day.
    const pinned = updates.filter((u) => u.is_pinned);
    const rest = updates.filter((u) => !u.is_pinned);
    const groups = [];
    let curKey = null;
    rest.forEach((u) => {
      const k = dayLabel(u.created_at);
      if (k !== curKey) { groups.push({ label: k, items: [] }); curKey = k; }
      groups[groups.length - 1].items.push(u);
    });

    function Item({ u }) {
      const meta = SRC[u.source] || SRC.user;
      const mine = u.source === "user" && u.author_email && me.email &&
        u.author_email.toLowerCase() === me.email.toLowerCase();
      return (
        <div className="af-item">
          <div className={"af-src af-src--" + meta.cls}>
            {meta.icon ? <span key={meta.icon} className="ic-wrap"><i data-lucide={meta.icon} /></span> : initialsOf(u.author_label || u.author_email)}
          </div>
          <div className="af-item-body">
            <div className="af-item-top">
              <span className="af-who">{u.author_label || u.author_email || "—"}</span>
              <span className={"af-stag af-stag--" + meta.cls}>{meta.tag}</span>
              <span className="af-ts" title={new Date(u.created_at).toLocaleString()}>{relTime(u.created_at)}</span>
            </div>
            {u.title && <div className="af-item-title">{u.title}</div>}
            {u.body && <div className="af-item-text">{u.body}</div>}
            {u.source === "trial_tracker" && u.payload && u.payload.read_depth === "headline" && (
              <div className="af-item-note">Read from the headline only. The publisher blocks automated reading.</div>
            )}
            {u.mention_emails && u.mention_emails.length > 0 && (
              <div className="af-item-ment"><i data-lucide="at-sign" /> {u.mention_emails.join(", ")}</div>
            )}
            {u.link_url && (
              <a className="af-item-link" href={u.link_url} target="_blank" rel="noopener noreferrer">
                <i data-lucide="external-link" /> {(() => { try { return new URL(u.link_url).hostname.replace(/^www\./, ""); } catch (e) { return "Open link"; } })()}
              </a>
            )}
            {/* A Trial Tracker development (0123) names every outlet that ran it; the link
                above is the primary, the rest are listed here as the email lists them. */}
            {(() => {
              const also = (u.source === "trial_tracker" && u.payload && Array.isArray(u.payload.sources))
                ? u.payload.sources.filter((s) => s && s.url && s.url !== u.link_url) : [];
              if (!also.length) return null;
              return (
                <div className="af-item-also">
                  Also: {also.map((s, i) => (
                    <span key={s.url}>{i > 0 && " · "}<a href={s.url} target="_blank" rel="noopener noreferrer" title={s.title || ""}>
                      {s.publisher || (() => { try { return new URL(s.url).hostname.replace(/^www\./, ""); } catch (e) { return "link"; } })()}
                    </a></span>
                  ))}
                </div>
              );
            })()}
          </div>
          {/* Pin + delete act on the row via UPDATE/soft-delete; RLS (0041 member_update_own)
              only permits a user's OWN posts, so both are shown on own items only in Phase A. */}
          {mine && (
            <div className="af-item-act">
              <button title={u.is_pinned ? "Unpin" : "Pin"} onClick={() => togglePin(u)}><i data-lucide="pin" /></button>
              <button title="Delete" onClick={() => remove(u)}><i data-lucide="trash-2" /></button>
            </div>
          )}
        </div>
      );
    }

    return (
      <aside className="af-rail">
        <div className="af-head">
          <div className="af-title">
            <h3><span className="af-yb" /> ACTIVITY</h3>
            <div className="af-follow-wrap" onClick={(e) => e.stopPropagation()}>
              <button type="button" className={"af-btn" + (following ? " af-btn--on" : "")} onClick={() => setMenuOpen((o) => !o)}>
                <i data-lucide={following ? "check" : "plus"} /> <span>{followLabel}</span> <i data-lucide="chevron-down" />
              </button>
              {menuOpen && (
                <div className="af-menu">
                  <div className="af-mhead">Notify me</div>
                  {[["alerts", "bell", "Email me on every update."],
                    ["digest", "mail", "One daily email across followed matters."],
                    ["none", "eye", "Follow in this feed only — no email."]].map(([mode, icon, desc]) => (
                    <div key={mode} className={"af-mitem" + (following && follow.notify_mode === mode ? " af-mitem--sel" : "")} onClick={() => chooseMode(mode)}>
                      <i data-lucide={icon} className="af-lead" />
                      <div className="af-mtxt"><b>{MODE_LABEL[mode]}</b><span>{desc}</span></div>
                      <i data-lucide="check" className="af-chk" />
                    </div>
                  ))}
                  {following && <React.Fragment>
                    <div className="af-div" />
                    <div className="af-mitem af-mitem--danger" onClick={() => chooseMode("unfollow")}>
                      <i data-lucide="x" className="af-lead" />
                      <div className="af-mtxt"><b>Unfollow</b></div>
                      <i data-lucide="check" className="af-chk" />
                    </div>
                  </React.Fragment>}
                </div>
              )}
            </div>
          </div>
          <div className="af-sub"><i data-lucide="users" /> Shared team feed <span className="af-dot">·</span> posts notify followers</div>
        </div>

        {/* Composer */}
        <div className={"af-composer" + (expanded ? " af-composer--exp" : "")}>
          <div className="af-comp-row">
            <div className="af-av">{me.initials}</div>
            {!expanded ? (
              <input type="text" placeholder="Post an update to this matter…" onFocus={() => setExpanded(true)} />
            ) : (
              <div className="af-comp-field">
                <textarea ref={taRef} value={text} onChange={onTextChange} autoFocus
                  placeholder="Post an update…  Type @ to notify someone. Your name and the time are stamped automatically." />
                {mMenu && (
                  <div className="af-ment-menu">
                    {mMenu.items.map((c) => (
                      <div key={c.email} className="af-ment-item" onMouseDown={(e) => { e.preventDefault(); pickMention(c); }}>
                        <span className="af-ment-av">{initialsOf(c.name)}</span>
                        <span className="af-ment-nm">{c.name}</span><span className="af-ment-em">{c.email}</span>
                      </div>
                    ))}
                  </div>
                )}
              </div>
            )}
          </div>
          {mentions.length > 0 && (
            <div className="af-ment-chips">
              {mentions.map((m) => (
                <span key={m.email} className="af-ment-chip"><i data-lucide="at-sign" />{m.name}
                  <button type="button" onClick={() => removeMention(m.email)}><i data-lucide="x" /></button>
                </span>
              ))}
            </div>
          )}
          {expanded && (
            <div className="af-comp-actions">
              <button type="button" className="af-bsm af-bsm--ghost" onClick={resetComposer} disabled={posting}>Cancel</button>
              <button type="button" className="af-bsm af-bsm--post" onClick={submit} disabled={posting || !text.trim()}>{posting ? "Posting…" : "Post update"}</button>
            </div>
          )}
        </div>

        {err && <div className="af-err">{err}</div>}

        {/* Suggested updates — facts the Trial Tracker's AI read in coverage (0112).
            Nothing changes on the matter until someone accepts. */}
        {proposals.length > 0 && (
          <div className="af-sugg">
            <div className="af-sugg-head">
              <Svg d={IC.spark} /> <span>Suggested from coverage</span>
              <span className="af-sugg-n">{proposals.length}</span>
            </div>
            <div className="af-sugg-sub">
              {canDecide
                ? "Read in news coverage by the Trial Tracker. Nothing changes on the matter until you accept."
                : "Read in news coverage by the Trial Tracker. Pending review in Content Operations: nothing changes on the matter until they accept."}
            </div>
            {proposals.map((p) => (
              <div key={p.proposal_id} className="af-sugg-card">
                <div className="af-sugg-top">
                  <span className="af-sugg-kind">{KIND_LABEL[p.kind] || p.kind}</span>
                  {p.confidence != null && <span className="af-sugg-conf" title="Model confidence">{Math.round(Number(p.confidence) * 100)}%</span>}
                </div>
                <div className="af-sugg-sum">{p.summary}</div>
                {p.evidence && <div className="af-sugg-ev">&ldquo;{p.evidence}&rdquo;</div>}
                {p.source_url && (
                  <a className="af-sugg-src" href={p.source_url} target="_blank" rel="noopener noreferrer" title={p.source_title || p.source_url}>
                    <Svg d={IC.link} size={12} /> {hostLabel(p.source_url)}
                  </a>
                )}
                {canDecide && <div className="af-sugg-act">
                  <button type="button" className="af-bsm af-bsm--ghost" disabled={!!deciding} onClick={() => decide(p, "dismiss")}>
                    <Svg d={IC.x} size={12} /> Dismiss
                  </button>
                  <button type="button" className="af-bsm af-bsm--post" disabled={!!deciding} onClick={() => decide(p, "accept")}>
                    <Svg d={IC.check} size={12} /> {deciding === p.proposal_id ? "Adding…" : "Accept"}
                  </button>
                </div>}
              </div>
            ))}
          </div>
        )}

        {/* Feed */}
        <div className="af-feed">
          {loading ? (
            <div className="af-empty">Loading activity…</div>
          ) : updates.length === 0 ? (
            <div className="af-empty"><i data-lucide="message-square-plus" /><div>No activity yet. Post the first update — it becomes the shared record for this matter.</div></div>
          ) : (
            <React.Fragment>
              {pinned.length > 0 && <div className="af-pinrow"><i data-lucide="pin" /> Pinned</div>}
              {pinned.map((u) => <Item key={u.id} u={u} />)}
              {groups.map((g) => (
                <React.Fragment key={g.label}>
                  <div className="af-daydiv"><span>{g.label}</span><div className="af-ln" /></div>
                  {g.items.map((u) => <Item key={u.id} u={u} />)}
                </React.Fragment>
              ))}
            </React.Fragment>
          )}
        </div>
      </aside>
    );
  }

  window.LedgerActivityRail = ActivityRail;
})();
