/* Jovana Medical — NotificationBell
 * Loaded as a Babel-standalone <script type="text/babel"> ahead of
 * landing-sections.jsx on every page that has a site header. Exposes
 * window.NotificationBell; AvatarMenu (in landing-sections.jsx) renders
 * it next to the avatar so we only mount in one place.
 *
 * The bell only renders when currentUser.notifyEnabled is true — which
 * is now driven by users.messaging_enabled (admin-controlled per user)
 * + is_admin (admins are always on so they receive DM replies).
 *
 * The panel has two tabs: 通知 (comment replies, the original feature)
 * and 對話 (admin↔user direct messages). Admins see a list of threads
 * on 對話; non-admin users see a single thread with admin.
 */
(function () {
  const { useState, useEffect, useRef, useCallback } = React;

  // 30 minutes — DMs are deliberative, not chatroom turns. The focus +
  // visibility listeners cover the "just came back to the tab" case.
  const POLL_MS = 30 * 60 * 1000;

  async function api(path, opts = {}) {
    const init = { credentials: 'include', ...opts };
    init.headers = { 'Content-Type': 'application/json', ...(opts.headers || {}) };
    if (init.body && typeof init.body !== 'string') init.body = JSON.stringify(init.body);
    const res = await fetch(path, init);
    let data = null;
    try { data = await res.json(); } catch {}
    if (!res.ok) {
      const err = new Error((data && data.error) || `http_${res.status}`);
      err.status = res.status;
      throw err;
    }
    return data;
  }

  function formatRelative(iso) {
    if (!iso) return '';
    const t = new Date(iso).getTime();
    const diff = Math.floor((Date.now() - t) / 1000);
    if (diff < 60) return '剛剛';
    if (diff < 3600) return `${Math.floor(diff / 60)} 分鐘前`;
    if (diff < 86400) return `${Math.floor(diff / 3600)} 小時前`;
    if (diff < 604800) return `${Math.floor(diff / 86400)} 天前`;
    return new Date(iso).toLocaleDateString();
  }

  // ---- DM thread (shared between admin & non-admin views) ---------------
  // adminMode=true means we're rendering admin's view: GETs go to
  // /api/admin/messages/:userId and POSTs to /api/admin/messages.
  // adminMode=false means non-admin: GETs go to /api/messages and POSTs
  // to /api/messages (their own thread, no userId).
  function DmThread({ adminMode, userId, peerName, onBack, onAfterChange }) {
    const [thread, setThread] = useState([]);
    const [draft, setDraft] = useState('');
    const [loading, setLoading] = useState(true);
    const [sending, setSending] = useState(false);
    const [error, setError] = useState('');
    const scrollRef = useRef(null);

    const fetchThread = useCallback(async () => {
      try {
        const path = adminMode ? `/api/admin/messages/${encodeURIComponent(userId)}` : '/api/messages';
        const data = await api(path);
        setThread(data.thread || []);
        setError('');
      } catch (e) {
        setError(e.message || 'failed');
      } finally {
        setLoading(false);
      }
    }, [adminMode, userId]);

    // Initial load + mark as read so the bell badge clears.
    useEffect(() => {
      let cancelled = false;
      (async () => {
        await fetchThread();
        if (cancelled) return;
        try {
          const path = adminMode
            ? `/api/admin/messages/${encodeURIComponent(userId)}/read-thread`
            : '/api/messages/read-thread';
          await api(path, { method: 'PATCH' });
          if (onAfterChange) onAfterChange();
        } catch {}
      })();
      return () => { cancelled = true; };
    }, [fetchThread, adminMode, userId, onAfterChange]);

    // Auto-scroll to bottom on new messages.
    useEffect(() => {
      const el = scrollRef.current;
      if (el) el.scrollTop = el.scrollHeight;
    }, [thread.length]);

    const send = async () => {
      const text = draft.trim();
      if (!text || sending) return;
      setSending(true);
      setError('');
      try {
        const path = adminMode ? '/api/admin/messages' : '/api/messages';
        const body = adminMode ? { userId, body: text } : { body: text };
        const data = await api(path, { method: 'POST', body });
        // Append optimistically — server already echoes the canonical row.
        setThread((prev) => prev.concat(data.message));
        setDraft('');
        if (onAfterChange) onAfterChange();
      } catch (e) {
        setError(e.message || 'failed');
      } finally {
        setSending(false);
      }
    };

    const onKeyDown = (e) => {
      // Cmd/Ctrl+Enter sends; plain Enter inserts a newline (so multi-line
      // composition still works).
      if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
        e.preventDefault();
        send();
      }
    };

    return (
      <div className="dm-thread">
        {adminMode && (
          <div className="dm-thread-head">
            <button type="button" className="dm-back" onClick={onBack} aria-label="返回對話列表">
              ← {peerName || '返回'}
            </button>
          </div>
        )}

        <div className="dm-msgs" ref={scrollRef}>
          {loading && <div className="dm-loading">載入中…</div>}
          {!loading && thread.length === 0 && (
            <div className="dm-empty">
              {adminMode ? '尚無訊息，從下方開始對話。' : '尚無訊息。'}
            </div>
          )}
          {thread.map((m) => {
            // For non-admin user: admin's msgs on the left, mine on the right.
            // For admin: user's msgs on the left, my admin-replies on the right.
            const isMine = adminMode ? m.senderIsAdmin : !m.senderIsAdmin;
            return (
              <div
                key={m.id}
                className={'dm-msg ' + (isMine ? 'dm-msg--mine' : 'dm-msg--peer')}
              >
                <div className="dm-msg-body">{m.body}</div>
                <div className="dm-msg-time">{formatRelative(m.createdAt)}</div>
              </div>
            );
          })}
        </div>

        <div className="dm-compose">
          <textarea
            className="dm-compose-input"
            placeholder={adminMode ? `傳送訊息給 ${peerName || '此用戶'}…` : '回覆 admin…（⌘/Ctrl + Enter 送出）'}
            value={draft}
            onChange={(e) => setDraft(e.target.value)}
            onKeyDown={onKeyDown}
            rows={2}
            maxLength={4000}
          />
          <button
            type="button"
            className="dm-compose-send"
            onClick={send}
            disabled={sending || !draft.trim()}
          >
            {sending ? '傳送中…' : '送出'}
          </button>
        </div>
        {error && <div className="dm-error">{error}</div>}
      </div>
    );
  }

  // ---- Admin's list of threads -----------------------------------------
  function DmAdminList({ onPick, refreshKey }) {
    const [threads, setThreads] = useState([]);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState('');

    const load = useCallback(async () => {
      try {
        const data = await api('/api/admin/messages');
        setThreads(data.threads || []);
        setError('');
      } catch (e) {
        setError(e.message || 'failed');
      } finally {
        setLoading(false);
      }
    }, []);

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

    if (loading) return <div className="dm-loading">載入中…</div>;
    if (error) return <div className="dm-error">{error}</div>;
    if (threads.length === 0) {
      return <div className="dm-empty">尚無啟用 DM 的用戶，請先到 /admin/users.html 開啟對應帳號的 DM。</div>;
    }

    return (
      <div className="dm-thread-list">
        {threads.map((t) => (
          <button
            key={t.user.id}
            type="button"
            className={'dm-thread-row' + (t.unreadCount > 0 ? ' dm-thread-row--unread' : '')}
            onClick={() => onPick(t.user)}
          >
            <div className="dm-thread-row-head">
              <span className="dm-thread-row-name">
                {t.user.name || t.user.email || '匿名'}
              </span>
              <span className="dm-thread-row-time">
                {t.updatedAt ? formatRelative(t.updatedAt) : '尚無訊息'}
              </span>
            </div>
            <div className="dm-thread-row-preview">
              {t.lastMessage
                ? (t.lastMessage.senderIsAdmin ? '你：' : '') + t.lastMessage.body.slice(0, 80)
                : <span className="dm-thread-row-empty">點擊開始對話</span>}
            </div>
            {t.unreadCount > 0 && (
              <span className="dm-thread-row-badge">{t.unreadCount > 9 ? '9+' : t.unreadCount}</span>
            )}
          </button>
        ))}
      </div>
    );
  }

  // ---- Main bell --------------------------------------------------------
  function NotificationBell({ currentUser }) {
    const [open, setOpen] = useState(false);
    const [tab, setTab] = useState('notif');
    const [unreadReply, setUnreadReply] = useState(0);
    const [unreadDm, setUnreadDm] = useState(0);
    const [items, setItems] = useState([]);
    const [busy, setBusy] = useState(false);
    const [error, setError] = useState('');
    const [adminPicked, setAdminPicked] = useState(null); // {id, name, email} when admin drills into a thread
    const [refreshKey, setRefreshKey] = useState(0);
    const ref = useRef(null);

    const enabled = !!(currentUser && currentUser.notifyEnabled);
    const isAdmin = !!(currentUser && currentUser.isAdmin);

    const fetchUnread = useCallback(async () => {
      if (!enabled) return;
      try {
        const data = await api('/api/notifications?unread_only=1&limit=10');
        setUnreadReply(data.unreadReplyCount || 0);
        setUnreadDm(data.unreadDmCount || 0);
        setItems(data.notifications || []);
        setError('');
      } catch (e) {
        // Silent — bell is non-essential UI. Keep previous count visible.
        setError(e.message || 'failed');
      }
    }, [enabled]);

    // Initial load + 30-min polling + re-poll on focus / visibility change.
    useEffect(() => {
      if (!enabled) return;
      let cancelled = false;
      fetchUnread();
      const interval = setInterval(() => {
        if (cancelled) return;
        if (typeof document !== 'undefined' && document.hidden) return;
        fetchUnread();
      }, POLL_MS);
      const onFocus = () => { if (!cancelled) fetchUnread(); };
      const onVisible = () => {
        if (!cancelled && typeof document !== 'undefined' && !document.hidden) fetchUnread();
      };
      window.addEventListener('focus', onFocus);
      document.addEventListener('visibilitychange', onVisible);
      return () => {
        cancelled = true;
        clearInterval(interval);
        window.removeEventListener('focus', onFocus);
        document.removeEventListener('visibilitychange', onVisible);
      };
    }, [fetchUnread, enabled]);

    // Click-out + Escape close the dropdown. When admin is inside a
    // thread, Escape pops out to the thread list instead.
    useEffect(() => {
      if (!open) return;
      const onDown = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
      const onKey = (e) => {
        if (e.key !== 'Escape') return;
        if (isAdmin && adminPicked) { setAdminPicked(null); return; }
        setOpen(false);
      };
      document.addEventListener('mousedown', onDown);
      document.addEventListener('touchstart', onDown);
      document.addEventListener('keydown', onKey);
      return () => {
        document.removeEventListener('mousedown', onDown);
        document.removeEventListener('touchstart', onDown);
        document.removeEventListener('keydown', onKey);
      };
    }, [open, isAdmin, adminPicked]);

    const onItemClick = (n) => {
      api(`/api/notifications/${encodeURIComponent(n.id)}/read`, { method: 'PATCH' }).catch(() => {});
      setItems((prev) => prev.filter((x) => x.id !== n.id));
      setUnreadReply((c) => Math.max(0, c - (n.readAt ? 0 : 1)));
      const url = `/archive.html#e=${encodeURIComponent(n.examSlug)}&q=${n.questionNum}` +
                  `&highlight=${encodeURIComponent(n.commentId)}`;
      window.location.href = url;
    };

    const markAllRead = async () => {
      if (busy) return;
      setBusy(true);
      try {
        await api('/api/notifications/read-all', { method: 'PATCH' });
        setItems([]);
        setUnreadReply(0);
      } catch (e) {
        setError(e.message || 'failed');
      } finally {
        setBusy(false);
      }
    };

    if (!enabled) return null;

    const totalUnread = unreadReply + unreadDm;
    const badgeText = totalUnread > 9 ? '9+' : String(totalUnread);

    // Triggered by thread send / read so badge + list refresh.
    const afterDmChange = () => {
      fetchUnread();
      setRefreshKey((k) => k + 1);
    };

    return (
      <div className="notif-bell" ref={ref}>
        <button
          type="button"
          className="notif-bell-btn"
          onClick={() => setOpen((o) => !o)}
          aria-label={`通知 ${totalUnread > 0 ? `(${totalUnread} 則未讀)` : ''}`}
          aria-haspopup="menu"
          aria-expanded={open}
        >
          <svg className="notif-bell-icon" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
            <path d="M12 2a1.5 1.5 0 0 0-1.5 1.5v.69a7 7 0 0 0-5.5 6.81v3l-2 3v1h18v-1l-2-3v-3a7 7 0 0 0-5.5-6.81V3.5A1.5 1.5 0 0 0 12 2Zm0 20a3 3 0 0 0 3-3H9a3 3 0 0 0 3 3Z" fill="currentColor"/>
          </svg>
          {totalUnread > 0 && <span className="notif-badge" aria-hidden>{badgeText}</span>}
        </button>

        {open && (
          <div className="notif-panel" role="menu">
            <div className="notif-tabs" role="tablist">
              <button
                type="button"
                role="tab"
                aria-selected={tab === 'notif'}
                className={'notif-tab' + (tab === 'notif' ? ' notif-tab--active' : '')}
                onClick={() => setTab('notif')}
              >
                通知{unreadReply > 0 && <span className="notif-tab-badge"> · {unreadReply}</span>}
              </button>
              <button
                type="button"
                role="tab"
                aria-selected={tab === 'dm'}
                className={'notif-tab' + (tab === 'dm' ? ' notif-tab--active' : '')}
                onClick={() => { setTab('dm'); setAdminPicked(null); }}
              >
                對話{unreadDm > 0 && <span className="notif-tab-badge"> · {unreadDm}</span>}
              </button>
            </div>

            {tab === 'notif' && (
              <>
                <div className="notif-panel-head">
                  <span className="notif-panel-title">通知 · Notifications</span>
                  {unreadReply > 0 && (
                    <button type="button" className="notif-mark-all" onClick={markAllRead} disabled={busy}>
                      全部標為已讀
                    </button>
                  )}
                </div>

                {items.length === 0 && (
                  <div className="notif-empty">目前沒有新通知 · No new notifications</div>
                )}

                {items.map((n) => (
                  <button
                    key={n.id}
                    type="button"
                    className={'notif-item' + (n.readAt ? '' : ' notif-item--unread')}
                    onClick={() => onItemClick(n)}
                    role="menuitem"
                  >
                    <div className="notif-item-meta">
                      <span className="notif-item-actor">
                        {n.actor?.isAdmin ? 'yulu' : (n.actor?.name || 'Admin')}
                        {n.actor?.isAdmin && <span className="notif-item-badge"> · admin</span>}
                      </span>
                      <span className="notif-item-time">{formatRelative(n.createdAt)}</span>
                    </div>
                    <div className="notif-item-body">{n.bodyPreview}</div>
                    <div className="notif-item-loc">
                      <span className="notif-item-slug">{n.examSlug}</span>
                      <span className="notif-item-qnum">#{n.questionNum}</span>
                    </div>
                  </button>
                ))}

                {error && <div className="notif-error">{error}</div>}
              </>
            )}

            {tab === 'dm' && (
              <div className="notif-dm-pane">
                {isAdmin && !adminPicked && (
                  <DmAdminList onPick={(u) => setAdminPicked(u)} refreshKey={refreshKey} />
                )}
                {isAdmin && adminPicked && (
                  <DmThread
                    adminMode
                    userId={adminPicked.id}
                    peerName={adminPicked.name || adminPicked.email}
                    onBack={() => setAdminPicked(null)}
                    onAfterChange={afterDmChange}
                  />
                )}
                {!isAdmin && (
                  <DmThread
                    adminMode={false}
                    userId={null}
                    onAfterChange={afterDmChange}
                  />
                )}
              </div>
            )}
          </div>
        )}
      </div>
    );
  }

  window.NotificationBell = NotificationBell;
})();
