// shell.jsx — app shell + shared UI primitives. Exports to window.
const { useState, useEffect, useRef } = React;
const R = window.RAISE;

// ── helpers ────────────────────────────────────────────────────────
function money(k){ if(k==null) return '—'; if(k>=1000) return '$'+(k/1000).toFixed(k%1000===0?0:2)+'M'; return '$'+k+'K'; }
function initials(n){ const p=n.replace(/[()]/g,'').trim().split(/\s+/); return ((p[0]?.[0]||'')+(p[1]?.[0]||'')).toUpperCase(); }
function tint(hex,a){ return hex + (a||'22'); }

const NAV = [
  { id:'today',    label:"Today's Focus", d:'M8 5v3l2 1.5', ring:true },
  { id:'todos',    label:'To-Dos',        d:'M3 4.6l1.4 1.4L7 2.8M3 10.6l1.4 1.4L7 8.8M9.5 4.8h4M9.5 10.8h4' },
  { id:'outreach', label:'Outreach',      d:'M2.5 4.5h11v7h-11zM2.5 4.5l5.5 4 5.5-4' },
  { id:'pipeline', label:'Pipeline',      d:'M2.5 3h11v3h-11zM2.5 9.5h7.5v3h-7.5z', box:true },
  { id:'email',    label:'Email',         d:'M2 8.5l2-5h8l2 5M2 8.5v4h12v-4M2 8.5h3l1 1.5h4l1-1.5h3' },
  { id:'calendar', label:'Calendar',      d:'M2.5 4h11v9.5h-11zM2.5 6.7h11M5.3 2.6v2.8M10.7 2.6v2.8', box:false },
  { id:'plan',     label:'Master Plan',   d:'M3 13V3M3 13h10M6 11V7M9 11V5M12 11V8' },
  { id:'metrics',  label:'Metrics',       d:'M2.5 11l3.5-4 2.5 2 4.5-5' },
  { id:'settings', label:'Settings',      d:'M8 1.8v2M8 12.2v2M1.8 8h2M12.2 8h2', circ:true },
];

function NavIcon({ item }){
  return (
    <svg className="ic" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
      {item.ring && <circle cx="8" cy="8" r="6" />}
      {item.circ && <circle cx="8" cy="8" r="2.2" />}
      {item.box && <><rect x="2.5" y="3" width="11" height="3" rx="1"/><rect x="2.5" y="9.5" width="7.5" height="3" rx="1"/></>}
      {!item.box && <path d={item.d} />}
    </svg>
  );
}

function Sidebar({ active, onNav }){
  const [collapsed, setCollapsed] = useState(()=>{ try{ return localStorage.getItem('raiseos.side.collapsed')==='1'; }catch(e){ return false; } });
  const toggle = ()=> setCollapsed(c=>{ const n=!c; try{ localStorage.setItem('raiseos.side.collapsed', n?'1':'0'); }catch(e){} return n; });
  return (
    <aside className={'side'+(collapsed?' collapsed':'')}>
      <div className="brand">
        <div className="logo">R</div>
        <div className="bt"><b>Raise OS</b><span>FUND II · $10M</span></div>
        <button className="side-toggle" onClick={toggle} title={collapsed?'Expand sidebar':'Collapse sidebar'} aria-label="Toggle sidebar">{collapsed?'»':'«'}</button>
      </div>
      <nav className="nav">
        {NAV.map(it => (
          <button key={it.id} className={'navi'+(active===it.id?' on':'')} onClick={()=>onNav(it.id)} title={it.label}>
            <NavIcon item={it} /> <span className="navlbl">{it.label}</span>
          </button>
        ))}
      </nav>
      <div className="side-foot">
        <div className="engine">
          <span className="edot" />
          <small><b>Engine synced</b> 7:02 AM<br/>6 briefs · 4 scores written</small>
        </div>
      </div>
    </aside>
  );
}

// ── primitives ─────────────────────────────────────────────────────
function TopBar({ title, sub, children }){
  return (
    <header className="topbar">
      <div><h1>{title}</h1>{sub && <div className="topsub mono">{sub}</div>}</div>
      <div className="topactions">{children}</div>
    </header>
  );
}

function Card({ title, tag, children, style, className, pad }){
  return (
    <section className={'card'+(className?' '+className:'')} style={style}>
      {(title||tag) && <div className="ch"><h3>{title}</h3>{tag && <span className="tag mono">{tag}</span>}</div>}
      {children}
    </section>
  );
}

function StageBadge({ id, small }){
  const s = R.stage(id);
  return <span className={'sbadge mono'+(small?' sm':'')} style={{color:s.color, background:tint(s.color,'20')}}>{s.label}</span>;
}

function IcpTag({ icp }){
  const m = R.ICP[icp];
  return <span className="icp mono" style={{color:m.color, background:tint(m.color,'1e')}} title={m.label}>{m.short}</span>;
}

function Avatar({ name, anchor }){
  return <span className={'avatar mono'+(anchor?' anchor':'')}>{initials(name)}{anchor && <i className="astar" title="Anchor">★</i>}</span>;
}

function Progress({ pct, color }){
  return <span className="track"><i style={{width:Math.min(100,pct)+'%', background:color||'var(--primary)'}} /></span>;
}

function Score({ v, max }){
  const c = v>=8 ? 'var(--green)' : v>=6 ? 'var(--amber)' : 'var(--ink-3)';
  return <span className="scorepill mono" style={{color:c, borderColor:tint(c.replace('var(--green)','#3f9560').replace('var(--amber)','#c08a2a').replace('var(--ink-3)','#9b9285'),'55')}}>{v==null?'—':v}{max&&<small>/{max}</small>}</span>;
}

// ── confetti reward ───────────────────────────────────────────────
function celebrate(x, y, big){
  const colors=['#c96442','#e0a25e','#3f9560','#b06fa8','#4a78c8','#c08a2a','#d8743f'];
  const n = big ? 46 : 22;
  const layer=document.createElement('div');
  layer.style.cssText=`position:fixed;left:${x}px;top:${y}px;z-index:9999;pointer-events:none;`;
  document.body.appendChild(layer);
  for(let i=0;i<n;i++){
    const p=document.createElement('div');
    const a=Math.random()*Math.PI*2, d=(big?70:40)+Math.random()*(big?120:80);
    const dx=Math.cos(a)*d, dy=Math.sin(a)*d-20;
    const sz=5+Math.random()*(big?8:6);
    p.style.cssText=`position:absolute;width:${sz}px;height:${sz}px;border-radius:${Math.random()>.5?'50%':'2px'};background:${colors[i%colors.length]};transform:translate(0,0) scale(1);opacity:1;transition:transform .85s cubic-bezier(.15,.7,.35,1),opacity .85s ease-out;`;
    layer.appendChild(p);
    requestAnimationFrame(()=>{ p.style.transform=`translate(${dx}px,${dy+90}px) scale(.25) rotate(${Math.random()*360}deg)`; p.style.opacity='0'; });
  }
  setTimeout(()=>layer.remove(),950);
}
function toast(msg){
  const t=document.createElement('div');
  t.className='rtoast'; t.textContent=msg;
  document.body.appendChild(t);
  requestAnimationFrame(()=>t.classList.add('show'));
  setTimeout(()=>{ t.classList.remove('show'); setTimeout(()=>t.remove(),300); }, 1900);
}

// ── shared stores (todos · lists · notes · person drawer) ─────────
// Todos ⇄ used by Today (quick-adds, flagged) and the To-Dos tab.
const SEED_TODOS = [];
function loadLS(key, seed){ try{ const v=JSON.parse(localStorage.getItem(key)); return v||seed; }catch(e){ return seed; } }

// ── calendar seed (events anchored to the CURRENT week so they always show) ──
function _mondayOf(d){ const m=new Date(d); m.setHours(0,0,0,0); m.setDate(d.getDate()-((d.getDay()+6)%7)); return m; }
function makeZoom(){
  const a=Math.floor(100+Math.random()*900), b=Math.floor(1000+Math.random()*9000), c=Math.floor(1000+Math.random()*9000);
  const pass=Math.random().toString(36).slice(2,8);
  return { url:'https://zoom.us/j/'+`${a}${b}${c}`, meetingId:`${a} ${b} ${c}`, passcode:pass, host:'AJ · Fund II' };
}
function buildCalSeed(){
  let _eid=0; const eid=()=>'ev'+Date.now().toString(36)+(_eid++);
  const now=new Date(); const mon=_mondayOf(now); const today=(now.getDay()+6)%7;
  const at=(off,h,m,dur)=>{ const s=new Date(mon); s.setDate(mon.getDate()+off); s.setHours(h,m,0,0); const e=new Date(s.getTime()+dur*60000); return {start:s.toISOString(),end:e.toISOString()}; };
  const ev=(o)=>({ id:eid(), calId:'cal-raise', personId:null, location:'', notes:'', zoom:null, allDay:false, source:'raise', syncState:'local-only', conflict:null, ...o });
  return {
    calendars:[
      { id:'cal-raise', name:'Calendar', color:'#c96442', kind:'raise', visible:true },
    ],
    google:{ connected:false, account:null, syncing:false, lastSync:null,
      accounts:[],
      remotes:[
        { gid:'g-primary',  name:'Primary calendar',   color:'#c96442', mapTo:'cal-raise',    twoWay:true },
      ] },
    zoom:{ connected:false, account:null },
    events:[],
  };
}
const _state = {
  todos: loadLS('raiseos.todos.v2', SEED_TODOS),
  lists: loadLS('raiseos.lists.v2', R.lists),
  notes: loadLS('raiseos.notes.v4', R.notesSeed),
  cal:   loadLS('raiseos.cal.v1', null),
  person: null,        // active person id for the profile drawer
};
if(!_state.cal || !_state.cal.events) _state.cal = buildCalSeed();
// migrate: every to-do / note has an owner — unassigned → you (AJ)
_state.todos = _state.todos.map(t=>({ ...t, personId: t.personId || 'self' }));
_state.notes = _state.notes.map(n=>({ parentId:null, ...n, personId: n.personId || 'self' }));
// ensure the AJ workspace has its starter notes-to-self (non-destructive)
if(!_state.notes.some(n=>n.personId==='self')){
  _state.notes = [..._state.notes, ...R.notesSeed.filter(n=>n.personId==='self')];
}
// migrate: notes get a type (contact → meeting, self → quick) + actions array
_state.notes = _state.notes.map(n=>({ type: (n.personId&&n.personId!=='self')?'meeting':'quick', actions:[], pinned:false, ...n }));
const _subs = new Set();
const _persist = ()=>{ try{
  localStorage.setItem('raiseos.todos.v2', JSON.stringify(_state.todos));
  localStorage.setItem('raiseos.lists.v2', JSON.stringify(_state.lists));
  localStorage.setItem('raiseos.notes.v4', JSON.stringify(_state.notes));
  localStorage.setItem('raiseos.cal.v1', JSON.stringify(_state.cal));
}catch(e){} };
const _notify = ()=> _subs.forEach(s=>s());
const uid = (p)=> p+Date.now().toString(36)+Math.floor(Math.random()*1e3);

const Store = {
  // todos
  get:()=>_state.todos,
  mutate:(fn)=>{ _state.todos = fn(_state.todos.slice()); _persist(); _notify(); },
  add:(item)=> Store.mutate(t=>[{ id:uid('t'), done:false, flagged:false, reminder:'', recurring:null, subtasks:[], note:'', personId:'self', listId:_state.lists[0]?.id, ...item }, ...t]),
  patch:(id,p)=> Store.mutate(t=>t.map(x=>x.id===id?{...x,...p}:x)),
  remove:(id)=> Store.mutate(t=>t.filter(x=>x.id!==id)),
  // complete a recurring todo → re-spawn the next occurrence
  complete:(id)=> Store.mutate(t=>{
    const it = t.find(x=>x.id===id); if(!it) return t;
    if(!it.done && it.recurring){
      const fresh = {...it, id:uid('t'), done:false, subtasks:it.subtasks.map(s=>({...s,done:false}))};
      return t.map(x=>x.id===id?{...x,done:true}:x).concat([fresh]);
    }
    return t.map(x=>x.id===id?{...x,done:!x.done}:x);
  }),
  // sub-tasks (one level)
  addSub:(id,text)=> Store.mutate(t=>t.map(x=>x.id===id?{...x,subtasks:[...x.subtasks,{id:uid('s'),text,done:false}]}:x)),
  patchSub:(id,sid,p)=> Store.mutate(t=>t.map(x=>x.id===id?{...x,subtasks:x.subtasks.map(s=>s.id===sid?{...s,...p}:s)}:x)),
  removeSub:(id,sid)=> Store.mutate(t=>t.map(x=>x.id===id?{...x,subtasks:x.subtasks.filter(s=>s.id!==sid)}:x)),
  // drag-reorder: move `id` into `listId`, placed before item `beforeId` (null = end of that list)
  moveBefore:(id, listId, beforeId)=> Store.mutate(t=>{
    const moving = t.find(x=>x.id===id); if(!moving) return t;
    const without = t.filter(x=>x.id!==id);
    const updated = {...moving, listId};
    if(beforeId==null) return [...without, updated];
    const out=[]; for(const x of without){ if(x.id===beforeId) out.push(updated); out.push(x); } return out;
  }),
  // lists
  getLists:()=>_state.lists,
  addList:(name)=>{ const palette=['#c96442','#4a78c8','#c08a2a','#3f9560','#8159bf','#b06fa8','#2f8f86']; _state.lists=[..._state.lists,{id:uid('l'),name:name||'New List',color:palette[_state.lists.length%palette.length]}]; _persist(); _notify(); },
  patchList:(id,p)=>{ _state.lists=_state.lists.map(l=>l.id===id?{...l,...p}:l); _persist(); _notify(); },
  removeList:(id)=>{ _state.lists=_state.lists.filter(l=>l.id!==id); _state.todos=_state.todos.filter(t=>t.listId!==id); _persist(); _notify(); },
  // notes
  getNotes:()=>_state.notes,
  addNote:(n)=>{ _state.notes=[{ id:uid('n'), title:'Untitled note', body:'', date:new Date().toLocaleDateString('en-US',{month:'short',day:'numeric',year:'numeric'}), personId:'self', type:'quick', actions:[], pinned:false, parentId:null, ...n }, ..._state.notes]; _persist(); _notify(); return _state.notes[0].id; },
  patchNote:(id,p)=>{ _state.notes=_state.notes.map(n=>n.id===id?{...n,...p}:n); _persist(); _notify(); },
  removeNote:(id)=>{ const n=_state.notes.find(x=>x.id===id); const newParent=n?n.parentId:null;
    _state.notes=_state.notes.filter(x=>x.id!==id).map(x=>x.parentId===id?{...x,parentId:newParent}:x); _persist(); _notify(); },
  // drag-to-nest / reorder: move `id` under `newParentId`, inserted before `beforeId` (null = end of group)
  moveNote:(id,newParentId=null,beforeId=null)=>{
    if(id===newParentId) return false;
    const byId=x=>_state.notes.find(n=>n.id===x);
    // block dropping a page into its own descendant (would orphan the subtree)
    let cur=newParentId; while(cur){ if(cur===id) return false; cur=byId(cur)?.parentId; }
    const moving=byId(id); if(!moving) return false;
    const updated={...moving, parentId:newParentId||null};
    let rest=_state.notes.filter(n=>n.id!==id);
    if(beforeId && beforeId!==id){ const idx=rest.findIndex(n=>n.id===beforeId); if(idx>=0){ rest.splice(idx,0,updated); } else { rest.push(updated); } }
    else { rest.push(updated); }
    _state.notes=rest; _persist(); _notify(); return true;
  },
  // note action-items (meeting/plan follow-ups)
  addNoteAction:(id,text)=>{ _state.notes=_state.notes.map(n=>n.id===id?{...n,actions:[...(n.actions||[]),{id:uid('a'),text,done:false}]}:n); _persist(); _notify(); },
  patchNoteAction:(id,aid,p)=>{ _state.notes=_state.notes.map(n=>n.id===id?{...n,actions:(n.actions||[]).map(a=>a.id===aid?{...a,...p}:a)}:n); _persist(); _notify(); },
  removeNoteAction:(id,aid)=>{ _state.notes=_state.notes.map(n=>n.id===id?{...n,actions:(n.actions||[]).filter(a=>a.id!==aid)}:n); _persist(); _notify(); },
  // push an action item out as a real, flagged to-do
  pushNoteAction:(id,aid)=>{ const n=_state.notes.find(x=>x.id===id); const a=n&&(n.actions||[]).find(x=>x.id===aid); if(!a) return;
    Store.add({ text:a.text, personId:n.personId||'self', listId:_state.lists[0]?.id, flagged:true });
    Store.patchNote(id,{ actions:n.actions.map(x=>x.id===aid?{...x,done:true,pushed:true}:x) }); },
  // move a less-urgent to-do into Notes (becomes a quick note)
  todoToNote:(id)=>{ const t=_state.todos.find(x=>x.id===id); if(!t) return;
    const nid=Store.addNote({ title:t.text, body:t.note||'', personId:t.personId||'self', type:'quick',
      actions:(t.subtasks||[]).map(s=>({id:uid('a'),text:s.text,done:s.done})) });
    Store.remove(id); return nid; },
  // ── calendar: events · calendars · Google sync · Zoom ──────────────
  getCal:()=>_state.cal,
  _calMut:(fn)=>{ fn(_state.cal); _persist(); _notify(); },
  addEvent:(e)=>{ const conn=_state.cal.google.connected; const cal=_state.cal.calendars.find(c=>c.id===(e.calId||'cal-raise'));
    const id=uid('ev');
    const ne={ id, calId:'cal-raise', personId:null, location:'', notes:'', zoom:null, allDay:false, source:'raise',
      syncState:(conn && cal && cal.kind==='google')?'syncing':'local-only', conflict:null, ...e };
    Store._calMut(c=>{ c.events=[...c.events, ne]; });
    // simulate the push to Google completing
    if(ne.syncState==='syncing') setTimeout(()=>{ Store.patchEvent(id,{syncState:'synced'}); }, 1100);
    return id; },
  patchEvent:(id,p)=> Store._calMut(c=>{ c.events=c.events.map(e=>e.id===id?{...e,...p}:e); }),
  removeEvent:(id)=> Store._calMut(c=>{ c.events=c.events.filter(e=>e.id!==id); }),
  toggleCalVisible:(id)=> Store._calMut(c=>{ c.calendars=c.calendars.map(x=>x.id===id?{...x,visible:!x.visible}:x); }),
  // zoom
  addZoom:(id)=> Store.patchEvent(id,{ zoom:makeZoom() }),
  removeZoom:(id)=> Store.patchEvent(id,{ zoom:null }),
  connectZoom:(account)=> Store._calMut(c=>{ c.zoom={connected:true, account:account||c.zoom.account||'aj@allstarcap.vc'}; }),
  disconnectZoom:()=> Store._calMut(c=>{ c.zoom={connected:false, account:null}; }),
  // google two-way sync (simulated)
  connectGoogle:(account)=>{ Store._calMut(c=>{ c.google={...c.google, connected:true, account, syncing:true, lastSync:null}; });
    setTimeout(()=>{ Store._calMut(c=>{
      const mappedCals=new Set(c.google.remotes.filter(r=>r.mapTo).map(r=>r.mapTo));
      c.events=c.events.map(e=> e.syncState==='conflict' ? e : (mappedCals.has(e.calId)?{...e,syncState:'synced'}:e));
      c.google.syncing=false; c.google.lastSync=Store._now();
    }); }, 1500); },
  disconnectGoogle:()=> Store._calMut(c=>{ c.google={...c.google, connected:false, account:null, syncing:false, lastSync:null};
    c.events=c.events.map(e=> e.syncState==='synced'?{...e,syncState:'local-only'}:e); }),
  syncNow:()=>{ if(!_state.cal.google.connected||_state.cal.google.syncing) return;
    Store._calMut(c=>{ c.google={...c.google,syncing:true};
      const mapped=new Set(c.google.remotes.filter(r=>r.mapTo).map(r=>r.mapTo));
      c.events=c.events.map(e=> (mapped.has(e.calId)&&e.syncState!=='conflict')?{...e,syncState:'syncing'}:e); });
    setTimeout(()=>{ Store._calMut(c=>{ c.google={...c.google,syncing:false,lastSync:Store._now()};
      c.events=c.events.map(e=> e.syncState==='syncing'?{...e,syncState:'synced'}:e); }); }, 1500); },
  setMapping:(gid,mapTo)=> Store._calMut(c=>{ c.google.remotes=c.google.remotes.map(r=>r.gid===gid?{...r,mapTo}:r); }),
  toggleTwoWay:(gid)=> Store._calMut(c=>{ c.google.remotes=c.google.remotes.map(r=>r.gid===gid?{...r,twoWay:!r.twoWay}:r); }),
  // conflict resolution: choose mine / theirs / keep both
  resolveConflict:(id,choice)=> Store._calMut(c=>{
    const e=c.events.find(x=>x.id===id); if(!e||!e.conflict) return;
    if(choice==='theirs'){ c.events=c.events.map(x=>x.id===id?{...x,start:e.conflict.remote.start,end:e.conflict.remote.end,syncState:'synced',conflict:null}:x); }
    else if(choice==='mine'){ c.events=c.events.map(x=>x.id===id?{...x,syncState:'syncing',conflict:null}:x); setTimeout(()=>Store.patchEvent(id,{syncState:'synced'}),1000); }
    else if(choice==='both'){ const clone={...e,id:uid('ev'),start:e.conflict.remote.start,end:e.conflict.remote.end,syncState:'synced',conflict:null,title:e.title+' (Google)'};
      c.events=c.events.map(x=>x.id===id?{...x,syncState:'syncing',conflict:null}:x).concat([clone]); setTimeout(()=>Store.patchEvent(id,{syncState:'synced'}),1000); }
  }),
  _now:()=> new Date().toLocaleTimeString('en-US',{hour:'numeric',minute:'2-digit'}),
  // ── live Google events (real-time read; not persisted, tagged source:'google-live') ──
  ingestLive:(items)=>{
    const conf=(e)=>{ const l=e.location||''; if(/zoom\.us/.test(l)) return l; if(e.hangoutLink) return e.hangoutLink;
      const m=(e.description||'').match(/https?:\/\/[^\s"<]+zoom\.us\/j\/[^\s"<]+/); return m?m[0]:''; };
    const mapped=(items||[]).map(e=>{ const cu=conf(e); return {
      id:'glive-'+e.id, gcalId:e.id, calId:'cal-raise',
      title:e.summary||'(no title)',
      start:(e.start&&(e.start.dateTime||e.start.date))||null,
      end:(e.end&&(e.end.dateTime||e.end.date))||null,
      allDay:!!(e.start&&e.start.date&&!e.start.dateTime),
      location:e.location||'', notes:e.description||'',
      zoom: cu?{url:cu,meetingId:'',passcode:'',host:'Google Calendar'}:null,
      personId:null, source:'google-live', syncState:'synced', conflict:null,
      attendees:(e.attendees||[]).map(a=>a.email).filter(Boolean),
    };}).filter(e=>e.start&&e.end);
    _state.cal.events=_state.cal.events.filter(x=>x.source!=='google-live').concat(mapped); _notify();
  },
  clearLive:()=>{ _state.cal.events=_state.cal.events.filter(x=>x.source!=='google-live'); _notify(); },
  // person drawer
  openPerson:(id)=>{ _state.person=id; _notify(); },
  closePerson:()=>{ _state.person=null; _notify(); },
  getPerson:()=>_state.person,
  subscribe:(fn)=>{ _subs.add(fn); return ()=>_subs.delete(fn); },
  // public re-render nudge (used by RaiseLiveData after a live fetch resolves)
  bump:()=>_notify(),
};
function useStore(sel){ const [,f]=useState(0); useEffect(()=>Store.subscribe(()=>f(x=>x+1)),[]); return sel?sel():undefined; }
function useTodos(){ useStore(); return Store.get(); }
function useLists(){ useStore(); return Store.getLists(); }
function useNotes(){ useStore(); return Store.getNotes(); }
function useCal(){ useStore(); return Store.getCal(); }
function useActivePerson(){ useStore(); return Store.getPerson(); }

// item owner helper: 'self' (AJ) also matches legacy null/undefined owners
function ownedBy(item, id){ const owner = item.personId || 'self'; return id==='self' ? (owner==='self') : owner===id; }

// small clickable person chip used across tabs
function PersonChip({ id, sub }){
  const pid = id || 'self';
  const c = R.contactById(pid); if(!c) return null;
  const self = pid==='self';
  return (
    <button className={'pchip'+(self?' self':'')} onClick={(e)=>{ e.stopPropagation(); Store.openPerson(pid); }} title={self?'You':c.firm}>
      <span className={'pchip-av mono'+(c.anchor?' anchor':'')}>{initials(c.name)}</span>
      <span className="pchip-nm">{self?'Me (AJ)':c.name}{sub && !self && <span className="pchip-sub"> · {c.firm}</span>}{sub && self && <span className="pchip-sub"> · note to self</span>}</span>
    </button>
  );
}

// searchable person picker (combo box) — never clipped (fixed-positioned menu)
function PersonCombo({ value, onChange, allowSelf=true }){
  const [open,setOpen]=useState(false);
  const [q,setQ]=useState('');
  const [rect,setRect]=useState(null);
  const ref=useRef(null);
  const cur=R.contactById(value||'self');
  const opts=[...(allowSelf?[R.self]:[]), ...R.contacts];
  const ql=q.trim().toLowerCase();
  const filtered=ql?opts.filter(c=>(c.name+' '+c.firm).toLowerCase().includes(ql)):opts;
  const openIt=(e)=>{ e&&e.stopPropagation(); const r=ref.current.getBoundingClientRect(); setRect(r); setQ(''); setOpen(true); };
  const pick=(id)=>{ onChange(id); setOpen(false); };
  const av=(c)=> <span className={'combo-av mono'+(c.isSelf?' self':(c.anchor?' anchor':''))}>{initials(c.name)}</span>;
  const openUp = rect && (window.innerHeight - rect.bottom) < 300;
  const VW = typeof window!=='undefined' ? window.innerWidth : 1280;
  let menuW = Math.max(rect ? rect.width : 248, 248);
  menuW = Math.min(menuW, VW - 16);
  let menuL = rect ? rect.left : 8;
  if(menuL + menuW > VW - 8) menuL = Math.max(8, VW - 8 - menuW);
  const menuStyle = rect ? (openUp
    ? { position:'fixed', left:menuL, bottom:(window.innerHeight-rect.top)+4, width:menuW }
    : { position:'fixed', left:menuL, top:rect.bottom+4, width:menuW }) : {};
  return (
    <div className="combo" ref={ref}>
      <button type="button" className="combo-field" onClick={openIt}>
        {av(cur)}
        <span className="combo-name">{cur.isSelf?'Me (AJ)':cur.name}</span>
        <svg className="combo-chev" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"><path d="M2.5 4.5L6 8l3.5-3.5"/></svg>
      </button>
      {open && rect && (
        <>
          <div className="combo-back" onClick={()=>setOpen(false)} />
          <div className="combo-menu" style={menuStyle} onClick={e=>e.stopPropagation()}>
            <input className="combo-search" autoFocus placeholder="Search people…" value={q} onChange={e=>setQ(e.target.value)} />
            <div className="combo-list">
              {filtered.map(c=>(
                <button type="button" key={c.id} className={'combo-opt'+(c.id===(value||'self')?' on':'')} onClick={()=>pick(c.id)}>
                  {av(c)}
                  <span className="combo-opt-nm">{c.isSelf?'Me (AJ)':c.name}<small>{c.isSelf?'note to self':c.firm}</small></span>
                </button>
              ))}
              {filtered.length===0 && <div className="combo-empty">No people match “{q}”</div>}
            </div>
          </div>
        </>
      )}
    </div>
  );
}

window.RaiseStore = Store;
window.RaiseShell = { Sidebar, TopBar, Card, StageBadge, IcpTag, Avatar, Progress, Score,
  money, initials, tint, celebrate, toast,
  useTodos, useLists, useNotes, useCal, useActivePerson, PersonChip, PersonCombo, ownedBy };
