// discreet.jsx — Ryoko · "Browse off-book" mode (anonymous / discretionary)
//
// Exposes:
//   window.RyokoDiscreet.toggle()      — flip on/off (4-hour session)
//   window.RyokoDiscreet.activate(h?)  — turn on for h hours (default 4)
//   window.RyokoDiscreet.deactivate()  — turn off
//   window.RyokoDiscreet.isOn()        — boolean
//   window.RyokoDiscreet.subscribe(fn) — change listener; returns unsubscribe
//
// Behaviour:
//   - Adds class `rk-discreet` to <body> when active.
//   - Self-mounts a floating pill (top-right, below topbar) showing time left.
//   - Self-mounts a one-time onboarding sheet the first time it's activated.
//   - Persists across pages via sessionStorage (key: rk.discreet.until).
//   - Auto-expires when the timer runs out; re-checks on every page load.
//
// Trust-builder: this is a UX statement, not encryption. Saves & views during
// a discreet session are visually separated and not added to the avatar
// menu's "Today, at a glance" stats. The on-screen pill makes the mode
// unambiguous so principals never accidentally browse off-book.

(function () {
  const { useState, useEffect, useRef } = React;
  const KEY = "rk.discreet.until";
  const ONBOARDED = "rk.discreet.onboarded";

  let listeners = new Set();

  function readUntil() {
    try {
      const raw = sessionStorage.getItem(KEY);
      if (!raw) return 0;
      const n = parseInt(raw, 10);
      if (!Number.isFinite(n)) return 0;
      return n > Date.now() ? n : 0;
    } catch (e) { return 0; }
  }

  function writeUntil(ts) {
    try {
      if (ts > 0) sessionStorage.setItem(KEY, String(ts));
      else sessionStorage.removeItem(KEY);
    } catch (e) {}
  }

  function applyClass(on) {
    if (typeof document === "undefined") return;
    const b = document.body;
    if (!b) return;
    if (on) b.classList.add("rk-discreet");
    else b.classList.remove("rk-discreet");
  }

  function emit() { listeners.forEach((fn) => { try { fn(isOn()); } catch (e) {} }); }
  function isOn() { return readUntil() > 0; }
  function untilTs() { return readUntil(); }

  function activate(hours) {
    const h = Number.isFinite(hours) && hours > 0 ? hours : 4;
    const ts = Date.now() + h * 3600 * 1000;
    writeUntil(ts);
    applyClass(true);
    emit();
    if (!sessionStorage.getItem(ONBOARDED)) {
      try { sessionStorage.setItem(ONBOARDED, "1"); } catch (e) {}
      // Onboarding sheet is rendered by the Host once isOn flips
    }
  }

  function deactivate() {
    writeUntil(0);
    applyClass(false);
    emit();
  }

  function toggle() { isOn() ? deactivate() : activate(); }

  function subscribe(fn) {
    listeners.add(fn);
    return () => listeners.delete(fn);
  }

  // Off-book ledger — purely illustrative for the prototype. Returns the
  // fake activity log for the current/most recent off-book session, so the
  // avatar menu can show the principal exactly what was logged where.
  function getLedger() {
    if (!sessionStorage.getItem(ONBOARDED)) return [];
    return [
      { when: "now",         what: "viewed: Aman Venice · Carnival week",  surface: "Discover" },
      { when: "12 min ago",  what: "saved: Asador Etxebarri",               surface: "Discover" },
      { when: "28 min ago",  what: "opened: trace · Annabel's",             surface: "Discover" },
      { when: "1 h ago",     what: "session opened",                        surface: "Avatar menu" },
    ];
  }
  function fmtRemaining(ms) {
    if (ms <= 0) return "expired";
    const total = Math.floor(ms / 1000);
    const h = Math.floor(total / 3600);
    const m = Math.floor((total % 3600) / 60);
    if (h > 0) return `${h}h ${String(m).padStart(2, "0")}m`;
    const s = total % 60;
    return `${m}m ${String(s).padStart(2, "0")}s`;
  }

  // ── Floating pill ────────────────────────────────────────────────────────

  // Detect topbar height once at mount so the pill can offset reliably
  // without depending on :has() CSS support.
  function getTopbarOffset() {
    if (typeof document === "undefined") return 14;
    const tb = document.querySelector(".rk-topbar");
    if (!tb) return 14;
    const r = tb.getBoundingClientRect();
    return Math.max(14, Math.round(r.bottom + 8));
  }

  function DiscreetPill({ onShare }) {
    const [until, setUntil] = useState(untilTs());
    const [now, setNow] = useState(Date.now());
    const [topOffset, setTopOffset] = useState(14);

    useEffect(() => subscribe(() => setUntil(untilTs())), []);
    useEffect(() => {
      if (!until) return;
      const t = setInterval(() => {
        const n = Date.now();
        setNow(n);
        if (n >= until) deactivate();
      }, 1000);
      return () => clearInterval(t);
    }, [until]);
    useEffect(() => {
      if (!until) return;
      // Re-measure after a tick (topbar may render slightly after).
      const measure = () => setTopOffset(getTopbarOffset());
      measure();
      const t = setTimeout(measure, 250);
      window.addEventListener("resize", measure);
      return () => { clearTimeout(t); window.removeEventListener("resize", measure); };
    }, [until]);

    if (!until) return null;
    const remaining = until - now;

    return (
      <div className="rk-discreet-pill"
           data-screen-label="Discreet mode pill"
           role="status"
           aria-live="polite"
           style={{ top: topOffset + "px" }}>
        <span className="rk-discreet-dot" aria-hidden />
        <span className="rk-discreet-label">Off-book</span>
        <span className="rk-discreet-sep">·</span>
        <span className="rk-discreet-clock">{fmtRemaining(remaining)} left</span>
        <button className="rk-discreet-share"
                onClick={() => onShare && onShare()}
                aria-label="Send a link in confidence">
          <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
            <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
            <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
          </svg>
          Send link
        </button>
        <button className="rk-discreet-end" onClick={() => deactivate()} aria-label="End off-book session">
          End now
        </button>
      </div>
    );
  }

  // ── Send-to-concierge expiring-link sheet ────────────────────────────────
  function ShareSheet({ open, onClose }) {
    const [copied, setCopied] = useState(false);
    if (!open) return null;
    // Synthesised expiring link · purely illustrative for the prototype.
    const link = `https://ryoko.travel/co/x/${Math.random().toString(36).slice(2, 9)}/${Math.random().toString(36).slice(2, 7)}`;
    const expires = new Date(Date.now() + 4 * 3600 * 1000);
    const expiresLabel = expires.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" });
    const onCopy = () => {
      navigator.clipboard && navigator.clipboard.writeText(link).then(() => {
        setCopied(true);
        setTimeout(() => setCopied(false), 1600);
      });
    };
    return (
      <div className="rk-discreet-veil" onClick={(e) => e.target === e.currentTarget && onClose()}>
        <div className="rk-discreet-sheet" data-screen-label="Send-to-concierge link" role="dialog">
          <div className="rk-discreet-sheet-eyebrow">Off-book · send in confidence</div>
          <h2 className="rk-discreet-sheet-h">A link your concierge can open. Once.</h2>
          <p className="rk-discreet-sheet-lede">
            One-use link to your current view, addressed to Camille at the desk.
            It carries no header, no avatar, no member number.
            It expires at {expiresLabel} and self-destructs after first open.
          </p>
          <div className="rk-discreet-share-link">
            <input type="text" value={link} readOnly aria-label="Off-book share link" />
            <button onClick={onCopy} className="rk-discreet-share-copy">
              {copied ? "Copied" : "Copy"}
            </button>
          </div>
          <ul className="rk-discreet-sheet-list">
            <li><span className="k">Audience</span><span className="v">your architect at the desk · no other staff cc'd</span></li>
            <li><span className="k">Header</span><span className="v">stripped · "from a member" · no name on the link</span></li>
            <li><span className="k">Lifespan</span><span className="v">expires {expiresLabel} · single open · no re-share</span></li>
            <li><span className="k">Audit</span><span className="v">writes only to your off-book ledger</span></li>
          </ul>
          <div className="rk-discreet-sheet-foot">
            <button className="rk-discreet-sheet-end" onClick={onClose}>Cancel</button>
            <button className="rk-discreet-sheet-go" onClick={onClose}>Send to Camille</button>
          </div>
        </div>
      </div>
    );
  }

  // ── Onboarding sheet · shown once per session when first activated ───────

  function DiscreetOnboard() {
    const [open, setOpen] = useState(false);
    const seenRef = useRef(false);

    useEffect(() => {
      const onChange = (on) => {
        if (on && !seenRef.current && !sessionStorage.getItem(ONBOARDED + ".shown")) {
          seenRef.current = true;
          try { sessionStorage.setItem(ONBOARDED + ".shown", "1"); } catch (e) {}
          setOpen(true);
        }
      };
      const unsub = subscribe(onChange);
      // Catch the initial activation too
      if (isOn() && !sessionStorage.getItem(ONBOARDED + ".shown")) {
        try { sessionStorage.setItem(ONBOARDED + ".shown", "1"); } catch (e) {}
        setOpen(true);
      }
      return unsub;
    }, []);

    if (!open) return null;
    return (
      <div className="rk-discreet-veil" onClick={(e) => e.target === e.currentTarget && setOpen(false)}>
        <div className="rk-discreet-sheet" data-screen-label="Off-book briefing" role="dialog" aria-modal="true">
          <div className="rk-discreet-sheet-eyebrow">Off-book · what changes</div>
          <h2 className="rk-discreet-sheet-h">You are now browsing in confidence.</h2>
          <p className="rk-discreet-sheet-lede">
            For the next four hours, this session is held separately from your file.
            Saves, views, and notes do not appear in your principal's activity.
            Your architect is not paged. Nothing surfaces in the household feed.
          </p>
          <ul className="rk-discreet-sheet-list">
            <li><span className="k">Activity log</span><span className="v">writes to the off-book ledger only · purges in 30 days</span></li>
            <li><span className="k">Architect</span><span className="v">not notified · you may share manually if you choose</span></li>
            <li><span className="k">Household</span><span className="v">not visible to spouse, EA, family office, or staff views</span></li>
            <li><span className="k">Vault writes</span><span className="v">paused · uploads queue privately until you end the session</span></li>
            <li><span className="k">Session</span><span className="v">expires in 4h, or when you tap "End now" in the corner</span></li>
          </ul>
          <p className="rk-discreet-sheet-note">
            This is a UX statement, not encryption. The mode exists because some scouting
            should not become household record. If you need a one-way handoff to your
            concierge, ask via the Send-in-confidence link in the avatar menu.
          </p>
          <div className="rk-discreet-sheet-foot">
            <button className="rk-discreet-sheet-end" onClick={() => { deactivate(); setOpen(false); }}>
              End off-book now
            </button>
            <button className="rk-discreet-sheet-go" onClick={() => setOpen(false)}>
              Understood — continue
            </button>
          </div>
        </div>
      </div>
    );
  }

  function Host() {
    const [shareOpen, setShareOpen] = useState(false);
    return (
      <>
        <DiscreetPill onShare={() => setShareOpen(true)} />
        <DiscreetOnboard />
        <ShareSheet open={shareOpen} onClose={() => setShareOpen(false)} />
      </>
    );
  }

  // Initial sync on load
  function syncOnLoad() {
    applyClass(isOn());
  }

  function mount() {
    if (document.getElementById("ryoko-discreet-host")) return;
    const div = document.createElement("div");
    div.id = "ryoko-discreet-host";
    document.body.appendChild(div);
    syncOnLoad();
    ReactDOM.createRoot(div).render(<Host />);
  }
  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", mount);
  } else {
    mount();
  }

  window.RyokoDiscreet = {
    toggle, activate, deactivate, isOn, untilTs, subscribe, fmtRemaining, getLedger,
  };
})();
