// App shell — auxiliary components (split from app.jsx).
// UserChip, ParkedEditModal, NotificationBell, DownlineFocusPanel,
// BroadcastBanner/Modal, PartnerLinkClaimView, NoAccountView.
// MUST load BEFORE app.jsx (App renders these).

// User chip used at the bottom of both sidebars. Shows the primary's avatar
// + ABO; if a partner is linked, overlays a second avatar and joins the
// names so it's clear that this account represents two people sharing one
// ABO ("Shelldon & คุณวริศรา").
function UserChip({ user, onLogout }) {
  const hasPartner = !!(user?.partner_name || user?.partner_line_user_id || user?.partner_avatar);
  const partnerSeed = user?.partner_name || 'P';
  // Build an Avatar-compatible object that mirrors the primary user — uses
  // partner_avatar (data URL or external image) when uploaded, otherwise
  // falls back to the first letter of the partner name on a green tile.
  const partnerUser = {
    avatar_url: user?.partner_avatar || null,
    avatar: partnerSeed.charAt(0),
    color: '#10B981',
    name: partnerSeed,
  };
  return (
    <div className="flex items-center gap-2.5 px-3">
      {hasPartner ? (
        <div className="relative flex-shrink-0" style={{ width: 50, height: 34 }}>
          <div className="absolute top-0 left-0 ring-2 ring-white rounded-full">
            <Avatar user={user} size={30} />
          </div>
          <div className="absolute top-0 left-[20px] ring-2 ring-white rounded-full">
            <Avatar user={partnerUser} size={30} />
          </div>
        </div>
      ) : (
        <Avatar user={user} size={34} />
      )}
      <div className="flex-1 min-w-0">
        <div className="text-xs font-semibold text-ink-900 truncate">
          {user?.name}
          {hasPartner && <span className="text-ink-400 font-normal"> & </span>}
          {hasPartner && <span className="text-emerald-700">{user.partner_name || '—'}</span>}
        </div>
        <div className="text-[10px] text-ink-400 num truncate">
          {user?.abo_code || '— ABO ยังไม่ตั้ง —'}
          {hasPartner && <span className="ml-1 text-emerald-600 font-medium">· Joint ABO</span>}
        </div>
      </div>
      <button onClick={onLogout} title="ออกจากระบบ" className="text-ink-400 hover:text-rose-600 flex-shrink-0">
        <Icon name="arrowR" className="w-4 h-4" />
      </button>
    </div>
  );
}

// ============== Parked follow-up edit modal ==============
// Open a "รอติดตาม" item to view/edit its note + reminder date, jump to the
// related tracking page, mark done, or delete. Shared by the bell popup and
// the sidebar list.
function ParkedEditModal({ item, onClose, onSaved, onNavigate }) {
  const [note, setNote] = useState('');
  const [remindOn, setRemindOn] = useState('');
  const [saving, setSaving] = useState(false);
  const [lead, setLead] = useState(null);       // { source_url, ig_handle } from the prospect
  const [leadInput, setLeadInput] = useState(''); // editable Lead Source link

  useEffect(() => {
    if (!item) return;
    setNote(item.notes || '');
    setRemindOn(item.remind_on || '');
    setSaving(false);
    setLead(null);
    setLeadInput('');
    // Pull the prospect's Lead Source link so it can be opened / edited here.
    if (item.prospect_id && window.SB_READY && window.supabase) {
      window.supabase.from('prospects').select('source_url, ig_handle')
        .eq('id', item.prospect_id).limit(1).maybeSingle()
        .then(({ data }) => {
          if (data) {
            setLead(data);
            setLeadInput(data.source_url || (data.ig_handle ? `https://instagram.com/${data.ig_handle.replace(/^@/, '')}` : ''));
          }
        });
    }
  }, [item?.id]);

  if (!item) return null;
  const isBm = item.kind === 'bm';
  const leadUrl = lead && (lead.source_url || (lead.ig_handle ? `https://instagram.com/${lead.ig_handle}` : null));

  const save = async () => {
    setSaving(true);
    const patch = { notes: note.trim() || null, remind_on: remindOn || item.remind_on };
    if (window.SB_READY) {
      const { error } = await window.db.parkedFollowUps.update(item.id, patch);
      if (error) { console.warn('[Parked] update failed:', error.message); }
      // Save the Lead Source link back onto the prospect if it changed.
      const trimmed = leadInput.trim();
      if (item.prospect_id && trimmed !== (lead?.source_url || '')) {
        const { error: pErr } = await window.supabase.from('prospects')
          .update({ source_url: trimmed || null }).eq('id', item.prospect_id);
        if (pErr) console.warn('[Parked] lead url update failed:', pErr.message);
      }
    }
    setSaving(false);
    onSaved?.({ ...item, ...patch });
    onClose();
  };

  return (
    <Modal open={!!item} onClose={onClose} size="sm"
      title={
        <div className="flex items-center gap-2">
          <span className="w-8 h-8 rounded-lg flex items-center justify-center text-white text-[11px] font-bold"
                style={{ background: isBm ? '#0EA5E9' : '#7C3AED' }}>{isBm ? 'BM' : '6W'}</span>
          <div>
            <div className="text-sm font-semibold text-ink-900">{item.customer_name || '—'}</div>
            <div className="text-[11px] text-ink-400">{isBm ? 'BM · ยังไม่ได้นัดต่อ' : '6W · ของไว้รอติดตาม'}</div>
          </div>
        </div>
      }
      footer={
        <div className="flex items-center justify-between gap-2">
          <button onClick={() => { onNavigate?.(isBm ? 'abo' : '6w'); onClose(); }}
            className="text-xs font-medium text-brand-600 hover:text-brand-700">
            ไปที่ {isBm ? 'ABO' : '6W'} Tracking →
          </button>
          <div className="flex items-center gap-2">
            <Btn variant="ghost" size="sm" onClick={onClose}>ยกเลิก</Btn>
            <Btn variant="brand" size="sm" disabled={saving} onClick={save}>
              {saving ? 'กำลังบันทึก…' : 'บันทึก'}
            </Btn>
          </div>
        </div>
      }>
      <div className="space-y-3">
        {/* Lead Source — open the saved link, and edit/add it inline. */}
        {item.prospect_id && (
          <div>
            <label className="block text-[11px] font-semibold text-ink-600 uppercase tracking-wide mb-1.5">Lead Source (ลิงก์แหล่งที่มา)</label>
            {leadUrl && (
              <a href={leadUrl} target="_blank" rel="noopener noreferrer"
                className="flex items-center gap-2 p-2.5 mb-1.5 rounded-lg border border-pink-200 bg-pink-50/50 hover:bg-pink-50 transition-colors">
                <span className="w-7 h-7 rounded-md bg-pink-500 text-white flex items-center justify-center flex-shrink-0">
                  <Icon name="arrowR" className="w-4 h-4" />
                </span>
                <div className="flex-1 min-w-0">
                  <div className="text-[12px] font-semibold text-pink-700">เปิด Lead Source</div>
                  <div className="text-[10px] text-ink-400 truncate">{lead.ig_handle ? `@${lead.ig_handle}` : leadUrl}</div>
                </div>
              </a>
            )}
            <input value={leadInput} onChange={e => setLeadInput(e.target.value)} inputMode="url"
              placeholder="วางลิงก์ IG / โพสต์ / โปรไฟล์… (บันทึกกลับไปที่ ABO)"
              className="w-full h-10 px-3 text-sm border border-ink-200 rounded-lg outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-500/15" />
          </div>
        )}
        <div>
          <label className="block text-[11px] font-semibold text-ink-600 uppercase tracking-wide mb-1.5">วันเตือน</label>
          <input type="date" value={remindOn || ''} onChange={e => setRemindOn(e.target.value)}
            className="w-full h-10 px-3 text-sm border border-ink-200 rounded-lg outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-500/15 num" />
          <p className="text-[10px] text-ink-400 mt-1">LINE OA จะแจ้งเตือนตอน 07:00 น. ของวันนี้</p>
        </div>
        <div>
          <label className="block text-[11px] font-semibold text-ink-600 uppercase tracking-wide mb-1.5">โน้ต</label>
          <textarea value={note} onChange={e => setNote(e.target.value)} rows={4}
            placeholder="สิ่งที่ต้องตามต่อ, รายละเอียด…"
            className="w-full px-3 py-2 text-sm border border-ink-200 rounded-lg outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-500/15 resize-none" />
        </div>
      </div>
    </Modal>
  );
}

// ============== Header bell → รอติดตาม popup ==============
// The notification bell opens a dropdown of the same parked follow-ups the
// sidebar shows. Red dot only when there's at least one pending item.
function NotificationBell({ currentUser, onNavigate }) {
  const [open, setOpen] = useState(false);
  const [items, setItems] = useState([]);
  const [loading, setLoading] = useState(true);
  const [editing, setEditing] = useState(null); // parked item being edited

  const load = useCallback(async () => {
    if (!window.SB_READY || !currentUser?.id) { setItems([]); setLoading(false); return; }
    setLoading(true);
    const { data, error } = await window.db.parkedFollowUps.listPendingForOwner(currentUser.id);
    if (!error) setItems(data || []);
    setLoading(false);
  }, [currentUser?.id]);

  useEffect(() => { load(); }, [load]);
  useEffect(() => {
    const h = () => load();
    window.addEventListener('emphasis:parked-follow-up-created', h);
    return () => window.removeEventListener('emphasis:parked-follow-up-created', h);
  }, [load]);

  const remove = async (id) => {
    setItems(prev => prev.filter(x => x.id !== id));
    if (window.SB_READY) {
      const { error } = await window.db.parkedFollowUps.delete(id);
      if (error) { console.warn('[Bell] delete failed:', error.message); load(); }
    }
  };
  const fmtDate = (d) => { try { return new Date(d).toLocaleDateString('th-TH', { day: 'numeric', month: 'short' }); } catch { return d; } };
  const todayKey = new Date(Date.now() + 7 * 3600 * 1000).toISOString().slice(0, 10);

  return (
    <div className="relative">
      <button onClick={() => setOpen(o => !o)}
        className="relative w-9 h-9 rounded-lg border border-ink-200 flex items-center justify-center text-ink-500 hover:bg-ink-50">
        <Icon name="bell" className="w-4 h-4" />
        {items.length > 0 && (
          <span className="absolute -top-1 -right-1 min-w-[16px] h-4 px-1 rounded-full bg-rose-500 text-white text-[9px] font-bold flex items-center justify-center num">
            {items.length}
          </span>
        )}
      </button>

      {open && (
        <>
          {/* click-away backdrop */}
          <div className="fixed inset-0 z-40" onClick={() => setOpen(false)} />
          <div className="absolute right-0 mt-2 w-80 max-w-[90vw] bg-white rounded-xl border border-ink-100 shadow-lift z-50 overflow-hidden">
            <div className="px-4 py-3 border-b border-ink-100 flex items-center gap-2">
              <span className="w-7 h-7 rounded-lg bg-purple-100 text-purple-600 flex items-center justify-center flex-shrink-0">
                <Icon name="bell" className="w-4 h-4" />
              </span>
              <div className="flex-1 min-w-0">
                <div className="text-sm font-semibold text-ink-900">รอติดตาม</div>
                <div className="text-[10px] text-ink-400">BM ยังไม่ได้นัดต่อ + 6W ของไว้รอติดตาม</div>
              </div>
              <span className="text-[11px] num text-ink-400">{items.length} คน</span>
            </div>
            <ul className="max-h-[360px] overflow-y-auto scroll-thin">
              {!loading && items.length === 0 && (
                <li className="px-4 py-6 text-center text-[12px] text-ink-400">ไม่มีรายการรอติดตาม 🎉</li>
              )}
              {items.map(it => {
                const isBm = it.kind === 'bm';
                const overdue = it.remind_on && it.remind_on < todayKey;
                return (
                  <li key={it.id} className="group border-b border-ink-50 last:border-0">
                    <div className="w-full flex items-center gap-2.5 px-4 py-2.5 hover:bg-ink-50/60">
                      <button onClick={() => setEditing(it)}
                        title="แก้ไข / ดูโน้ต"
                        className="flex items-center gap-2.5 flex-1 min-w-0 text-left">
                        <span className="w-8 h-8 rounded-lg flex items-center justify-center text-white text-[10px] font-bold flex-shrink-0"
                              style={{ background: isBm ? '#0EA5E9' : '#7C3AED' }}>{isBm ? 'BM' : '6W'}</span>
                        <div className="flex-1 min-w-0">
                          <div className="text-[13px] font-semibold text-ink-900 truncate">
                            {it.customer_name || '—'}
                            {it.prospect?.sponsor?.name && <span className="text-ink-400 font-normal"> (DL: {it.prospect.sponsor.name})</span>}
                          </div>
                          <div className="text-[10px] text-ink-400 num truncate">
                            {isBm ? 'ยังไม่ได้นัดต่อ' : `เตือน ${fmtDate(it.remind_on)}`}
                            {it.notes && <span className="text-ink-400"> · {it.notes}</span>}
                            {overdue && <span className="text-rose-500 font-semibold"> · เลย</span>}
                          </div>
                        </div>
                      </button>
                      <button onClick={() => remove(it.id)} title="ลบแจ้งเตือน"
                        className="w-7 h-7 rounded-md flex items-center justify-center text-ink-300 hover:text-rose-500 hover:bg-rose-50 flex-shrink-0">
                        <Icon name="x" className="w-3.5 h-3.5" strokeWidth={2.5} />
                      </button>
                    </div>
                  </li>
                );
              })}
            </ul>
          </div>
        </>
      )}

      <ParkedEditModal
        item={editing}
        onClose={() => setEditing(null)}
        onNavigate={onNavigate}
        onSaved={(updated) => setItems(prev => prev.map(x => x.id === updated.id ? { ...x, ...updated } : x))}
      />
    </div>
  );
}

// ============== Sidebar widget: รอติดตาม ==============
// Unified "waiting to follow up" list. Combines two sources, both stored in
// parked_follow_ups:
//   - BM appts whose outcome was "ยังไม่ได้นัดต่อ · รอติดตาม"  (kind='bm')
//   - 6W appts whose outcome was "ของไว้รอติดตาม"            (kind='6w')
// Each row can be dismissed (deletes the reminder). Clicking a row jumps to
// the relevant tracking page.
function DownlineFocusPanel({ currentUser, onNavigate }) {
  const [items, setItems] = useState([]);
  const [loading, setLoading] = useState(true);
  const [editing, setEditing] = useState(null);

  const load = useCallback(async () => {
    if (!window.SB_READY || !currentUser?.id) { setItems([]); setLoading(false); return; }
    setLoading(true);
    const { data, error } = await window.db.parkedFollowUps.listPendingForOwner(currentUser.id);
    if (!error) setItems(data || []);
    setLoading(false);
  }, [currentUser?.id]);

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

  // Refetch when a new parked follow-up is created anywhere in the app.
  useEffect(() => {
    const h = () => load();
    window.addEventListener('emphasis:parked-follow-up-created', h);
    return () => window.removeEventListener('emphasis:parked-follow-up-created', h);
  }, [load]);

  const remove = async (id) => {
    setItems(prev => prev.filter(x => x.id !== id)); // optimistic
    if (window.SB_READY) {
      const { error } = await window.db.parkedFollowUps.delete(id);
      if (error) { console.warn('[Focus] delete failed:', error.message); load(); }
    }
  };

  const fmtDate = (d) => {
    try { return new Date(d).toLocaleDateString('th-TH', { day: 'numeric', month: 'short' }); }
    catch { return d; }
  };
  const todayKey = new Date(Date.now() + 7 * 3600 * 1000).toISOString().slice(0, 10);

  return (
    <div className="mt-5">
      <div className="text-[10px] uppercase tracking-wider text-ink-400 px-3 pb-2 font-semibold flex items-center justify-between">
        <span>รอติดตาม</span>
        <span className="text-ink-400 text-[10px] font-medium num">{items.length} คน</span>
      </div>
      <ul className="space-y-0.5">
        {!loading && items.length === 0 && (
          <li className="px-3 py-2 text-[11px] text-ink-400 italic leading-relaxed">
            ยังไม่มีคนรอติดตาม — กด "ยังไม่ได้นัดต่อ" ใน BM หรือ "ของไว้รอติดตาม" ใน 6W
          </li>
        )}
        {items.map(it => {
          const isBm = it.kind === 'bm';
          const overdue = it.remind_on && it.remind_on < todayKey;
          return (
            <li key={it.id} className="group">
              <div className="w-full flex items-center gap-2 px-3 py-1.5 hover:bg-ink-50 rounded-md">
                <button onClick={() => setEditing(it)}
                  title="แก้ไข / ดูโน้ต"
                  className="flex items-center gap-2 flex-1 min-w-0 text-left">
                  <span className="w-7 h-7 rounded-md flex items-center justify-center text-white text-[9px] font-bold flex-shrink-0"
                        style={{ background: isBm ? '#0EA5E9' : '#7C3AED' }}>
                    {isBm ? 'BM' : '6W'}
                  </span>
                  <div className="flex-1 min-w-0">
                    <div className="text-xs text-ink-800 font-medium truncate">
                      {it.customer_name || '—'}
                      {it.prospect?.sponsor?.name && <span className="text-ink-400 font-normal"> (DL: {it.prospect.sponsor.name})</span>}
                    </div>
                    <div className="text-[9px] text-ink-400 num truncate">
                      {isBm ? 'ยังไม่ได้นัดต่อ' : `เตือน ${fmtDate(it.remind_on)}`}
                      {it.notes && <span> · {it.notes}</span>}
                      {overdue && <span className="text-rose-500 font-semibold"> · เลย</span>}
                    </div>
                  </div>
                </button>
                <button onClick={() => remove(it.id)} title="ลบแจ้งเตือน"
                  className="w-6 h-6 rounded-md flex items-center justify-center text-ink-300 hover:text-rose-500 hover:bg-rose-50 opacity-0 group-hover:opacity-100 transition-all flex-shrink-0">
                  <Icon name="x" className="w-3.5 h-3.5" strokeWidth={2.5} />
                </button>
              </div>
            </li>
          );
        })}
      </ul>

      <ParkedEditModal
        item={editing}
        onClose={() => setEditing(null)}
        onNavigate={onNavigate}
        onSaved={(updated) => setItems(prev => prev.map(x => x.id === updated.id ? { ...x, ...updated } : x))}
      />
    </div>
  );
}

// ============================================================================
// Broadcast banner — slim sticky strip below the app header.
// ----------------------------------------------------------------------------
// Pulls from admin_broadcasts. Shows the newest undismissed item with a
// short preview; clicking opens a modal with the full body. Dismiss (✕)
// remembers the broadcast id in localStorage so it doesn't reappear.
// Listens to `emphasis:broadcasts-changed` so admin sends in another tab /
// the Admin view itself immediately surface the new banner.
// ============================================================================
const BROADCAST_DISMISSED_KEY = 'emphasis.broadcasts.dismissed.v1';

function _broadcastsDismissed() {
  try { return new Set(JSON.parse(localStorage.getItem(BROADCAST_DISMISSED_KEY) || '[]')); }
  catch { return new Set(); }
}
function _saveBroadcastsDismissed(set) {
  try { localStorage.setItem(BROADCAST_DISMISSED_KEY, JSON.stringify([...set])); } catch {}
}
function _relTime(iso) {
  if (!iso) return 'เพิ่งส่ง';
  const ms = Date.now() - new Date(iso).getTime();
  const min = Math.floor(ms / 60000);
  if (min < 1) return 'เพิ่งส่ง';
  if (min < 60) return `${min} นาทีที่แล้ว`;
  const hr = Math.floor(min / 60);
  if (hr < 24) return `${hr} ชม.ที่แล้ว`;
  const d = Math.floor(hr / 24);
  if (d < 7) return `${d} วันที่แล้ว`;
  return new Date(iso).toLocaleDateString('th-TH', { day: 'numeric', month: 'short' });
}

function BroadcastBanner({ currentUser, onNavigate }) {
  const [broadcasts, setBroadcasts] = useState([]);
  const [dismissed, setDismissed] = useState(() => _broadcastsDismissed());
  const [open, setOpen] = useState(null); // the broadcast being expanded

  const load = React.useCallback(async () => {
    if (!window.SB_READY) return;
    const { data, error } = await window.db.broadcasts.list();
    if (!error) setBroadcasts(data || []);
  }, []);

  useEffect(() => {
    load();
    const onChanged = () => load();
    window.addEventListener('emphasis:broadcasts-changed', onChanged);
    window.addEventListener('emphasis:data-ready', onChanged);
    // Background refresh every 2 minutes
    const t = setInterval(load, 120000);
    return () => {
      window.removeEventListener('emphasis:broadcasts-changed', onChanged);
      window.removeEventListener('emphasis:data-ready', onChanged);
      clearInterval(t);
    };
  }, [load]);

  // Only display broadcasts with the 'banner' channel (admin can opt out by
  // sending LINE-only). Newest first, filter out dismissed.
  const visible = useMemo(() => {
    return broadcasts
      .filter(b => Array.isArray(b.channels) ? b.channels.includes('banner') : true)
      .filter(b => !dismissed.has(b.id));
  }, [broadcasts, dismissed]);

  if (visible.length === 0) return null;

  const head = visible[0];
  const more = visible.length - 1;

  const dismiss = (id) => {
    const next = new Set(dismissed);
    next.add(id);
    setDismissed(next);
    _saveBroadcastsDismissed(next);
  };

  return (
    <>
      <div className="border-b border-amber-200 bg-gradient-to-r from-amber-50 via-amber-50/60 to-white">
        <div className="px-6 lg:px-8 py-2.5 flex items-center gap-3">
          <div className="w-7 h-7 rounded-md bg-amber-400 text-white flex items-center justify-center flex-shrink-0">
            <Icon name="megaphone" className="w-3.5 h-3.5" />
          </div>
          <button onClick={() => setOpen(head)} className="flex-1 min-w-0 text-left group">
            <div className="flex items-baseline gap-2 flex-wrap">
              <span className="text-xs font-bold text-amber-900 truncate">{head.subject}</span>
              {head.body && (
                <span className="text-[11px] text-ink-600 truncate group-hover:text-ink-900 transition-colors">
                  · {head.body.split('\n')[0]}
                </span>
              )}
            </div>
          </button>
          <div className="flex items-center gap-2 flex-shrink-0">
            <span className="text-[10px] text-amber-800/70 num hidden sm:inline">{_relTime(head.sent_at)}</span>
            {more > 0 && (
              <Pill color="#B87C00" tint="#FFECB8" size="xs">+{more} ใหม่</Pill>
            )}
            <button onClick={() => setOpen(head)}
              className="text-[11px] font-semibold text-amber-700 hover:text-amber-900 hover:underline">
              อ่านต่อ
            </button>
            <button onClick={() => dismiss(head.id)}
              title="ปิดประกาศนี้"
              className="w-6 h-6 rounded-md text-amber-700 hover:bg-amber-100 flex items-center justify-center">
              <Icon name="x" className="w-3.5 h-3.5" />
            </button>
          </div>
        </div>
      </div>

      {open && (
        <BroadcastModal
          broadcast={open}
          all={visible}
          onClose={() => setOpen(null)}
          onDismiss={(id) => { dismiss(id); setOpen(null); }}
          onOpen={(b) => setOpen(b)}
          onGoAdmin={() => { setOpen(null); onNavigate?.(); }}
        />
      )}
    </>
  );
}

function BroadcastModal({ broadcast: b, all, onClose, onDismiss, onOpen, onGoAdmin }) {
  return (
    <Modal open={true} onClose={onClose} size="md" title={
      <div className="flex items-center gap-2">
        <div className="w-7 h-7 rounded-md bg-amber-400 text-white flex items-center justify-center">
          <Icon name="megaphone" className="w-4 h-4" />
        </div>
        <div>
          <div className="text-sm font-semibold text-ink-900">ประกาศจาก Admin</div>
          <div className="text-[10px] text-ink-400 num">ส่งเมื่อ {_relTime(b.sent_at)} · {b.audience || 'all'}</div>
        </div>
      </div>
    } footer={
      <div className="flex items-center justify-between gap-2">
        <button onClick={onGoAdmin}
          className="text-[11px] font-medium text-brand-600 hover:text-brand-800 hover:underline">
          ดูประกาศทั้งหมด →
        </button>
        <div className="flex items-center gap-2">
          <button onClick={onClose}
            className="h-9 px-3 text-sm font-medium rounded-lg bg-ink-100 text-ink-700 hover:bg-ink-200">
            ปิด
          </button>
          <button onClick={() => onDismiss(b.id)}
            className="h-9 px-3 text-sm font-medium rounded-lg bg-amber-500 text-white hover:bg-amber-600 inline-flex items-center gap-1.5">
            <Icon name="check" className="w-3.5 h-3.5" strokeWidth={2.5} /> รับทราบ · ปิดประกาศนี้
          </button>
        </div>
      </div>
    }>
      <div className="space-y-4">
        <div>
          <h2 className="text-lg font-bold text-ink-900">{b.subject}</h2>
          {b.body ? (
            <p className="text-sm text-ink-700 mt-2 whitespace-pre-wrap leading-relaxed">{b.body}</p>
          ) : (
            <p className="text-xs text-ink-400 italic mt-2">— ไม่มีเนื้อหาเพิ่มเติม —</p>
          )}
        </div>

        {all.length > 1 && (
          <div className="pt-3 border-t border-ink-100">
            <div className="text-[10px] uppercase tracking-wider font-bold text-ink-500 mb-2">
              ประกาศอื่นๆ ที่ยังไม่อ่าน · {all.length - 1}
            </div>
            <ul className="space-y-1.5 max-h-48 overflow-y-auto scroll-thin">
              {all.filter(x => x.id !== b.id).map(x => (
                <li key={x.id}>
                  <button onClick={() => onOpen(x)}
                    className="w-full text-left px-3 py-2 rounded-lg hover:bg-amber-50 border border-ink-100 hover:border-amber-200 transition-all">
                    <div className="text-xs font-semibold text-ink-900 truncate">{x.subject}</div>
                    <div className="text-[10px] text-ink-500 num mt-0.5">{_relTime(x.sent_at)}</div>
                  </button>
                </li>
              ))}
            </ul>
          </div>
        )}
      </div>
    </Modal>
  );
}

// Shown when a Supabase session exists but no `users` row is attached to it
// (e.g. the LINE userid is unknown or the password-account has no profile
// row yet). Self-registration is disabled — the user must contact an admin.
// Standalone view shown to the partner when they open the QR link on their
// phone. Initialises LIFF (LINE-only — no Supabase session), gets the
// partner's profile + idToken, and POSTs to the partner-link-claim edge
// function. The owner's open popup polls and detects completion.
function PartnerLinkClaimView({ token }) {
  const [phase, setPhase] = useState('init'); // init | login | claiming | done | error
  const [msg, setMsg]   = useState('');
  const [partner, setPartner] = useState(null);

  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        setPhase('init');
        if (!window.liff) throw new Error('LIFF SDK ไม่ได้โหลด');
        const liffId = window.EMPHASIS_ENV?.liffId;
        if (!liffId || liffId.startsWith('REPLACE')) throw new Error('LIFF ID ยังไม่ได้ตั้งค่า');
        await window.liff.init({ liffId });
        if (cancelled) return;
        if (!window.liff.isLoggedIn()) {
          setPhase('login');
          // Send them through LINE login; we'll come back to the same URL
          // with the partner_link param intact.
          window.liff.login({ redirectUri: window.location.href });
          return;
        }
        const idToken = window.liff.getIDToken && window.liff.getIDToken();
        const profile = await window.liff.getProfile();
        if (!idToken) throw new Error('ไม่ได้รับ id token จาก LINE');

        setPhase('claiming');
        const url = `${window.EMPHASIS_ENV?.supabaseUrl || ''}/functions/v1/partner-link-claim`;
        const apikey = window.EMPHASIS_ENV?.supabaseAnonKey || '';
        // Supabase edge functions reject requests without Authorization
        // even when JWT verification is off; the apikey header alone
        // returns 401. The partner has no Supabase session yet (they
        // haven't logged into our app), so pass the anon key as Bearer.
        const res = await fetch(url, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            apikey,
            Authorization: `Bearer ${apikey}`,
          },
          body: JSON.stringify({ token, idToken }),
        });
        // Some failures return non-JSON (e.g. raw 401 from the gateway);
        // parse defensively so we surface a useful message.
        const raw = await res.text();
        let body;
        try { body = JSON.parse(raw); } catch { body = { error: raw || `HTTP ${res.status}` }; }
        if (cancelled) return;
        if (!res.ok && !body.error) body.error = `HTTP ${res.status}`;
        if (body.error) { setPhase('error'); setMsg(body.error); return; }
        setPartner({ name: body.partnerDisplayName || profile?.displayName || '—', userId: body.partnerLineUserId });
        setPhase('done');
      } catch (e) {
        if (cancelled) return;
        setPhase('error');
        setMsg(e.message);
      }
    })();
    return () => { cancelled = true; };
  }, [token]);

  return (
    <div className="min-h-screen flex items-center justify-center p-6 bg-gradient-to-br from-emerald-50 to-white">
      <div className="bg-white rounded-2xl shadow-lift border border-ink-100 p-6 max-w-sm w-full text-center">
        <div className="w-16 h-16 rounded-2xl mx-auto mb-4 flex items-center justify-center text-white"
             style={{ background: phase === 'error' ? '#EF4444' : '#06C755' }}>
          {phase === 'done' ? <Icon name="check" className="w-8 h-8" strokeWidth={3} />
           : phase === 'error' ? <Icon name="x" className="w-8 h-8" strokeWidth={3} />
           : <svg className="w-8 h-8 animate-spin" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M21 12a9 9 0 1 1-6.2-8.55" strokeLinecap="round" /></svg>}
        </div>

        {phase === 'done' ? (
          <>
            <div className="text-base font-bold text-ink-900">เชื่อม LINE สำเร็จ 🎉</div>
            <div className="text-sm text-ink-600 mt-1">{partner?.name}</div>
            <div className="text-[11px] text-ink-400 mt-3 leading-relaxed">
              คุณสามารถปิดหน้านี้ได้แล้ว · บัญชีคู่ร่วมธุรกิจของเจ้าของได้รับการอัปเดตเรียบร้อย
            </div>
          </>
        ) : phase === 'error' ? (
          <>
            <div className="text-base font-bold text-rose-700">เชื่อม LINE ไม่สำเร็จ</div>
            <div className="text-sm text-ink-600 mt-1">{msg}</div>
            <div className="text-[11px] text-ink-400 mt-3">กรุณาให้เจ้าของบัญชีสร้าง QR ใหม่</div>
          </>
        ) : (
          <>
            <div className="text-base font-bold text-ink-900">
              {phase === 'claiming' ? 'กำลังเชื่อม LINE...' : 'กำลังเตรียม LINE login...'}
            </div>
            <div className="text-sm text-ink-500 mt-1">รอสักครู่</div>
          </>
        )}
      </div>
    </div>
  );
}

function NoAccountView({ session, onSignOut }) {
  const [signingOut, setSigningOut] = useState(false);
  const ident = session?.lineDisplayName || session?.email || session?.lineUserId || 'บัญชีนี้';
  const handleSignOut = async () => {
    setSigningOut(true);
    try { await onSignOut(); } finally { setSigningOut(false); }
  };
  return (
    <div className="min-h-screen flex items-center justify-center grid-bg px-4">
      <Card className="max-w-md w-full text-center shadow-lift">
        <div className="w-14 h-14 mx-auto rounded-full bg-amber-100 flex items-center justify-center mb-3">
          <Icon name="info" className="w-7 h-7 text-amber-600" />
        </div>
        <h2 className="text-base font-semibold text-ink-900">ยังไม่มีบัญชี ABO ในระบบ</h2>
        <p className="text-xs text-ink-500 mt-2 leading-relaxed">
          <span className="font-medium text-ink-700">{ident}</span> ผ่านการ login กับ Supabase แล้ว
          แต่ยังไม่มี ABO ที่ผูกอยู่กับบัญชีนี้ในระบบ Emphasis
        </p>
        <p className="text-[11px] text-ink-500 mt-3 leading-relaxed bg-ink-50/60 rounded-lg px-3 py-2 border border-ink-100">
          ติดต่อ upline / admin เพื่อขอให้สร้างบัญชี ABO + password<br />
          แล้วกลับมาเข้าระบบด้วย ABO 10 หลัก
        </p>
        <button onClick={handleSignOut} disabled={signingOut}
          className="mt-5 w-full h-10 rounded-lg font-medium text-white bg-ink-900 hover:bg-ink-800 disabled:opacity-60">
          {signingOut ? 'กำลังออกจากระบบ...' : 'ออกจากระบบ'}
        </button>
      </Card>
    </div>
  );
}
