/* chat.jsx — limited, email-gated "Ask Gigi" assistant.
   A self-mounting floating widget (its own React root, appended to <body>) so it
   doesn't collide with each page's #root app. Talks to the `chat` Cloud Function
   via /api/chat (override with window.GIGI_CHAT_ENDPOINT). Sessions persist in
   Firestore server-side and in localStorage on the device. */
(function () {
  const { Icon, Button, Input } = window.GigiMoneyDesignSystem_b54949 || {};
  if (!window.React || !window.ReactDOM || !Icon) return; // deps not present

  const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  const MSG_MAX_LEN = 2000;
  // The chat function runs in africa-south1, which Firebase Hosting can't serve
  // via a same-origin rewrite, so we call it directly (CORS is enabled).
  // Override with window.GIGI_CHAT_ENDPOINT for the local emulator.
  const ENDPOINT = () => window.GIGI_CHAT_ENDPOINT || 'https://africa-south1-gigi-money.cloudfunctions.net/chat';

  const LS = {
    email: 'gigi_chat_email',
    session: 'gigi_chat_session',
    msgs: 'gigi_chat_msgs',
  };
  const get = (k) => { try { return localStorage.getItem(k); } catch { return null; } };
  const set = (k, v) => { try { localStorage.setItem(k, v); } catch { /* private mode */ } };

  function newSessionId() {
    try { if (crypto?.randomUUID) return crypto.randomUUID(); } catch { /* fall through */ }
    return 'sxxxxxxxxxxxx'.replace(/x/g, () => Math.floor(Math.random() * 16).toString(16)) + Date.now().toString(16);
  }
  function sessionId() {
    let s = get(LS.session);
    if (!s || !/^[a-z0-9-]{8,64}$/.test(s)) { s = newSessionId(); set(LS.session, s); }
    return s;
  }
  function loadMsgs() {
    try { const m = JSON.parse(get(LS.msgs)); return Array.isArray(m) ? m : []; } catch { return []; }
  }
  function saveMsgs(msgs) { set(LS.msgs, JSON.stringify(msgs.slice(-50))); }

  /* POST a message to the Cloud Function. Throws Error(message) on failure. */
  async function gigiChat(email, session, message) {
    const headers = { 'Content-Type': 'application/json' };
    // App Check attestation (anti-bot / anti-replay); null until configured.
    const appCheck = window.gigiAppCheckToken ? await window.gigiAppCheckToken() : null;
    if (appCheck) headers['X-Firebase-AppCheck'] = appCheck;
    const res = await fetch(ENDPOINT(), {
      method: 'POST',
      headers,
      body: JSON.stringify({ email, sessionId: session, message }),
    });
    let data = {};
    try { data = await res.json(); } catch { /* non-JSON */ }
    if (!res.ok || !data.ok) throw new Error(data.error || 'Something went wrong. Please try again.');
    return data.reply;
  }

  const GREETING = "Hi! I'm Gigi. Ask me anything about rent advances, rent guarantee, the property HELOC or our credit card.";

  function EmailGate({ onDone }) {
    const [email, setEmail] = React.useState('');
    const [err, setErr] = React.useState('');
    const valid = EMAIL_RE.test(email.trim());
    const submit = () => {
      if (!valid) { setErr('Please enter a valid email address.'); return; }
      const e = email.trim().toLowerCase();
      // Fresh start: a new session keyed to this email, no stale transcript.
      set(LS.session, newSessionId());
      set(LS.msgs, '[]');
      set(LS.email, e);
      onDone(e);
    };
    return (
      <div style={{ padding: 18 }}>
        <p style={{ fontSize: 14, color: 'var(--ink-2)', lineHeight: 1.5, margin: '0 0 14px' }}>
          Enter your email to start a chat. We&rsquo;ll use it to follow up if you&rsquo;d like us to.
        </p>
        <Input
          label="Email"
          value={email}
          onChange={(v) => { setEmail(v); if (err) setErr(''); }}
          placeholder="you@gigi.money"
        />
        {err && (
          <div role="alert" style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 10, color: 'var(--warn, #b3261e)', fontSize: 13 }}>
            <Icon name="info" size={16} /> {err}
          </div>
        )}
        <div style={{ height: 14 }} />
        <Button kind="accent" icon="arrowUp" full onClick={submit} disabled={!valid}>Start chat</Button>
      </div>
    );
  }

  /* ── Minimal Markdown rendering for model replies ──────────────────────────
     The model returns Markdown (bold, bullet/numbered lists, links). We render
     it to React elements directly — never via dangerouslySetInnerHTML — so the
     model's (untrusted) output can't inject HTML/script. Supports the subset it
     actually emits: paragraphs, unordered/ordered lists, headings, bold,
     italic, inline code, and safe links. */
  const mdCodeStyle = {
    fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace', fontSize: '0.92em',
    background: 'var(--surface-3, rgba(0,0,0,.06))', padding: '1px 5px', borderRadius: 5,
  };
  const mdLinkStyle = { color: 'var(--accent-ink)', textDecoration: 'underline', fontWeight: 600 };

  /* Allow only safe link targets; reject javascript:/data: and other schemes. */
  function safeUrl(raw) {
    const u = (raw || '').trim();
    if (/^(https?:|mailto:)/i.test(u)) return u; // explicit safe schemes
    if (/^[/#]/.test(u)) return u;               // same-site relative / anchor
    return null;
  }

  /* Inline span parsing: code → bold → italic → links, leftmost match wins. */
  function parseInline(text, keyBase) {
    const re = /(`+)([^`]*?)\1|(\*\*|__)([\s\S]+?)\3|(\*|_)([\s\S]+?)\5|\[([^\]]*)\]\(([^)\s]+)\)/;
    const out = [];
    let rest = String(text);
    let i = 0;
    while (rest) {
      const m = re.exec(rest);
      if (!m) { out.push(rest); break; }
      if (m.index > 0) out.push(rest.slice(0, m.index));
      const key = `${keyBase}-${i++}`;
      if (m[1] !== undefined) {
        out.push(<code key={key} style={mdCodeStyle}>{m[2]}</code>);
      } else if (m[3] !== undefined) {
        out.push(<strong key={key}>{parseInline(m[4], key)}</strong>);
      } else if (m[5] !== undefined) {
        out.push(<em key={key}>{parseInline(m[6], key)}</em>);
      } else {
        const href = safeUrl(m[8]);
        out.push(href
          ? <a key={key} href={href} target="_blank" rel="noopener noreferrer" style={mdLinkStyle}>{parseInline(m[7], key)}</a>
          : `[${m[7]}](${m[8]})`);
      }
      rest = rest.slice(m.index + m[0].length);
    }
    return out;
  }

  /* Group lines into block-level nodes: headings, lists, and paragraphs. */
  function parseBlocks(text) {
    const lines = String(text).replace(/\r\n?/g, '\n').split('\n');
    const blocks = [];
    let para = [];
    let list = null;
    const flushPara = () => { if (para.length) { blocks.push({ type: 'p', lines: para }); para = []; } };
    const flushList = () => { if (list) { blocks.push(list); list = null; } };
    for (const line of lines) {
      const h = /^\s*(#{1,6})\s+(.*)$/.exec(line);
      const ul = /^\s*[-*•]\s+(.*)$/.exec(line);
      const ol = /^\s*\d+[.)]\s+(.*)$/.exec(line);
      if (h) {
        flushPara(); flushList();
        blocks.push({ type: 'h', text: h[2] });
      } else if (ul || ol) {
        flushPara();
        const ordered = !!ol;
        if (!list || list.ordered !== ordered) { flushList(); list = { type: 'list', ordered, items: [] }; }
        list.items.push(ul ? ul[1] : ol[1]);
      } else if (!line.trim()) {
        flushPara(); flushList();
      } else {
        flushList();
        para.push(line);
      }
    }
    flushPara(); flushList();
    return blocks;
  }

  function Markdown({ text }) {
    const blocks = React.useMemo(() => parseBlocks(text), [text]);
    return blocks.map((b, i) => {
      const key = `b${i}`;
      const topGap = i ? 8 : 0; // collapse leading margin so bubbles stay tight
      if (b.type === 'list') {
        const Tag = b.ordered ? 'ol' : 'ul';
        return (
          <Tag key={key} style={{ margin: `${topGap}px 0 0`, paddingLeft: 20 }}>
            {b.items.map((it, j) => (
              <li key={j} style={{ margin: '2px 0' }}>{parseInline(it, `${key}-${j}`)}</li>
            ))}
          </Tag>
        );
      }
      if (b.type === 'h') {
        return <div key={key} style={{ fontWeight: 700, margin: `${topGap}px 0 2px` }}>{parseInline(b.text, key)}</div>;
      }
      const children = [];
      b.lines.forEach((ln, j) => {
        if (j) children.push(<br key={`br${j}`} />);
        children.push(...parseInline(ln, `${key}-${j}`));
      });
      return <p key={key} style={{ margin: `${topGap}px 0 0` }}>{children}</p>;
    });
  }

  function Bubble({ role, text }) {
    const me = role === 'user';
    return (
      <div style={{ display: 'flex', justifyContent: me ? 'flex-end' : 'flex-start', marginBottom: 10 }}>
        <div style={{
          maxWidth: '82%', padding: '9px 13px', borderRadius: 14, fontSize: 14, lineHeight: 1.5,
          wordBreak: 'break-word',
          background: me ? 'var(--accent-ink)' : 'var(--surface-2)',
          color: me ? '#fff' : 'var(--ink)',
          borderBottomRightRadius: me ? 4 : 14, borderBottomLeftRadius: me ? 14 : 4,
        }}>
          {/* User text is shown verbatim; model replies are Markdown. */}
          {me ? <span style={{ whiteSpace: 'pre-wrap' }}>{text}</span> : <Markdown text={text} />}
        </div>
      </div>
    );
  }

  function Conversation({ email }) {
    const session = React.useRef(sessionId()).current;
    const [msgs, setMsgs] = React.useState(() => {
      const stored = loadMsgs();
      return stored.length ? stored : [{ role: 'model', text: GREETING }];
    });
    const [draft, setDraft] = React.useState('');
    const [busy, setBusy] = React.useState(false);
    const [err, setErr] = React.useState('');
    const scroller = React.useRef(null);

    React.useEffect(() => { saveMsgs(msgs); }, [msgs]);
    React.useEffect(() => {
      const el = scroller.current;
      if (el) el.scrollTop = el.scrollHeight;
    }, [msgs, busy]);

    const send = async () => {
      const text = draft.trim().slice(0, MSG_MAX_LEN);
      if (!text || busy) return;
      setErr('');
      setDraft('');
      setMsgs((m) => [...m, { role: 'user', text }]);
      setBusy(true);
      try {
        const reply = await gigiChat(email, session, text);
        setMsgs((m) => [...m, { role: 'model', text: reply }]);
      } catch (e) {
        setErr(e.message || 'Something went wrong. Please try again.');
      } finally {
        setBusy(false);
      }
    };

    const onKeyDown = (e) => {
      if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); }
    };

    return (
      <>
        <div ref={scroller} className="gchat-scroll" style={{ flex: 1, overflowY: 'auto', padding: '16px 16px 6px' }}>
          {msgs.map((m, i) => <Bubble key={i} role={m.role} text={m.text} />)}
          {busy && (
            <div style={{ display: 'flex', justifyContent: 'flex-start', marginBottom: 10 }}>
              <div style={{ padding: '9px 13px', borderRadius: 14, borderBottomLeftRadius: 4, background: 'var(--surface-2)', color: 'var(--ink-3)', fontSize: 13 }}>
                Gigi is typing&hellip;
              </div>
            </div>
          )}
        </div>
        {err && (
          <div role="alert" style={{ display: 'flex', alignItems: 'center', gap: 8, margin: '0 16px 8px', background: 'var(--warn-bg, #fdecec)', color: 'var(--warn, #b3261e)', borderRadius: 10, padding: '8px 12px', fontSize: 13 }}>
            <Icon name="info" size={16} /> {err}
          </div>
        )}
        <div style={{ display: 'flex', alignItems: 'flex-end', gap: 8, padding: 12, borderTop: '1px solid var(--line)' }}>
          <textarea
            className="gchat-input"
            rows={1}
            value={draft}
            maxLength={MSG_MAX_LEN}
            placeholder="Ask about Gigi Money…"
            onChange={(e) => setDraft(e.target.value)}
            onKeyDown={onKeyDown}
            style={{
              flex: 1, resize: 'none', border: '1px solid var(--line)', borderRadius: 12,
              padding: '10px 12px', fontSize: 14, lineHeight: 1.4, fontFamily: 'inherit',
              maxHeight: 120, background: 'var(--surface)', color: 'var(--ink)', outline: 'none',
            }}
          />
          <button
            aria-label="Send"
            onClick={send}
            disabled={busy || !draft.trim()}
            style={{
              flexShrink: 0, width: 40, height: 40, borderRadius: 12, border: 'none', cursor: 'pointer',
              background: 'var(--accent)', color: 'var(--accent-ink)', display: 'grid', placeItems: 'center',
              opacity: busy || !draft.trim() ? 0.5 : 1,
            }}
          >
            <Icon name="arrowUp" size={20} stroke={2.5} />
          </button>
        </div>
      </>
    );
  }

  function ChatWidget() {
    const [open, setOpen] = React.useState(false);
    const [email, setEmail] = React.useState(() => get(LS.email) || '');

    return (
      <>
        {open && (
          <div className="gchat-panel" role="dialog" aria-label="Ask Gigi">
            <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '14px 16px', background: 'var(--accent-ink)', color: '#fff' }}>
              <div style={{ width: 30, height: 30, borderRadius: 9, background: 'rgba(255,255,255,.12)', display: 'grid', placeItems: 'center', color: 'var(--accent)' }}>
                <Icon name="bolt" size={18} />
              </div>
              <div style={{ flex: 1 }}>
                <div style={{ fontWeight: 700, fontSize: 14.5 }}>Ask Gigi</div>
                <div style={{ fontSize: 11.5, color: 'rgba(255,255,255,.6)' }}>Answers about Gigi Money</div>
              </div>
              <button aria-label="Close chat" onClick={() => setOpen(false)} style={{ background: 'none', border: 'none', color: 'rgba(255,255,255,.8)', cursor: 'pointer', display: 'grid', placeItems: 'center', padding: 4 }}>
                <Icon name="x" size={20} />
              </button>
            </div>
            {email
              ? <Conversation email={email} />
              : <EmailGate onDone={setEmail} />}
          </div>
        )}
        <button
          className="gchat-fab"
          aria-label={open ? 'Close chat' : 'Open chat'}
          onClick={() => setOpen((o) => !o)}
        >
          <Icon name={open ? 'x' : 'bolt'} size={22} stroke={2.4} />
          {!open && <span className="gchat-fab-label">Ask Gigi</span>}
        </button>
      </>
    );
  }

  /* Inject the few styles that need pseudo-states / media queries / animation. */
  function injectStyles() {
    if (document.getElementById('gchat-styles')) return;
    const css = `
.gchat-fab{position:fixed;right:20px;bottom:20px;z-index:2147483000;display:flex;align-items:center;gap:8px;
  border:none;cursor:pointer;border-radius:999px;padding:12px 18px;font:inherit;font-weight:700;font-size:14px;
  background:var(--accent-ink,#1B1A38);color:#fff;box-shadow:0 8px 24px rgba(0,0,0,.22);transition:transform .15s ease}
.gchat-fab:hover{transform:translateY(-1px)}
.gchat-fab .gchat-fab-label{white-space:nowrap}
.gchat-panel{position:fixed;right:20px;bottom:84px;z-index:2147483000;width:380px;max-width:calc(100vw - 32px);
  height:560px;max-height:calc(100vh - 120px);display:flex;flex-direction:column;overflow:hidden;
  background:var(--surface,#fff);border:1px solid var(--line,#e7e7ee);border-radius:18px;
  box-shadow:0 20px 60px rgba(0,0,0,.28);animation:gchatIn .18s ease}
@keyframes gchatIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.gchat-input::placeholder{color:var(--ink-3,#9a9aae)}
.gchat-scroll::-webkit-scrollbar{width:8px}
.gchat-scroll::-webkit-scrollbar-thumb{background:var(--line,#e7e7ee);border-radius:8px}
@media (max-width:480px){
  .gchat-panel{right:8px;left:8px;bottom:78px;width:auto;height:calc(100vh - 96px)}
  .gchat-fab{right:14px;bottom:14px}
}`;
    const el = document.createElement('style');
    el.id = 'gchat-styles';
    el.textContent = css;
    document.head.appendChild(el);
  }

  function mount() {
    if (document.getElementById('gigi-chat-root')) return;
    injectStyles();
    const host = document.createElement('div');
    host.id = 'gigi-chat-root';
    document.body.appendChild(host);
    window.ReactDOM.createRoot(host).render(<ChatWidget />);
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', mount);
  } else {
    mount();
  }
})();
