// tab-calendar.jsx — week/day/month/agenda · Google-Calendar-style drag/resize/create
//                     · pulls ALL to-dos onto the calendar (tray → drop to schedule)
const { useState: tcState, useRef: tcRef, useEffect: tcEffect } = React;
const { TopBar: TC_TopBar } = window.RaiseShell;
const U = window.CalUI;
const CST = window.RaiseStore;

const HOUR0 = 6, HOUR1 = 22, HOURH = 52;          // extended day, 6 AM – 10 PM
const HOURS = Array.from({length:HOUR1-HOUR0+1},(_,i)=>HOUR0+i);
const DAY_MIN0 = HOUR0*60, DAY_MIN1 = (HOUR1+1)*60; // grid spans 6:00 → 23:00
const SNAP = 15;                                    // 15-minute increments
const PXMIN = HOURH/60;

function hourLabel(h){ const ap=h>=12?'PM':'AM'; let x=h%12; if(x===0)x=12; return x+' '+ap; }
function minOf(d){ return d.getHours()*60+d.getMinutes(); }
function dateAtMin(day,min){ const d=new Date(day); d.setHours(0,0,0,0); d.setMinutes(Math.round(min)); return d; }
function snap(m,s){ return Math.round(m/s)*s; }
function fmtMin(min){ let h=Math.floor(min/60), m=Math.round(min%60); const ap=h>=12?'PM':'AM'; h=h%12; if(h===0)h=12; return m? `${h}:${String(m).padStart(2,'0')} ${ap}`:`${h} ${ap}`; }
// Google all-day events use a date-only string ("2026-06-15"). new Date() treats that
// as UTC midnight → wrong day in negative-offset zones. Parse date-only strings as local.
function asLocalDate(v){ if(typeof v==='string'){ const m=v.match(/^(\d{4})-(\d{2})-(\d{2})$/); if(m) return new Date(+m[1],+m[2]-1,+m[3]); } return new Date(v); }
function isAllDay(ev){ return !!ev.allDay || (typeof ev.start==='string' && /^\d{4}-\d{2}-\d{2}$/.test(ev.start)); }

// ── one event block in the time grid ───────────────────────────────
function EventBlock({ ev, col, onOpen, onBeginDrag, dragging, movedRef }){
  const s=new Date(ev.start), e=new Date(ev.end);
  const gridH=(HOUR1-HOUR0+1)*HOURH;
  let top=((s.getHours()+s.getMinutes()/60)-HOUR0)*HOURH;
  const dur=Math.max(15,Math.round((e-s)/60000));
  let h=Math.max(20,(dur/60)*HOURH-2);
  if(top<0){ h+=top; top=0; }              // starts before the grid window
  if(top+h>gridH){ h=gridH-top; }          // clamp so it never drags past the day
  h=Math.max(18,h);
  const lane=dragging?0:(ev._lane||0), lanes=dragging?1:(ev._lanes||1);
  const wf=100/lanes;
  const conflict=ev.syncState==='conflict', syncing=ev.syncState==='syncing';
  const tint=window.RaiseShell.tint;
  const tiny=h<34;
  return (
    <button className={'evblk'+(conflict?' conflict':'')+(syncing?' syncing':'')+(ev.source==='google'?' g':'')+(tiny?' tiny':'')+(dragging?' dragging':'')}
      style={{ top, height:h, left:`calc(${lane*wf}% + 2px)`, width:`calc(${wf}% - 4px)`, '--cc':col,
               background:tint(col,'22'), boxShadow:`inset 3px 0 0 ${col}`, color:col }}
      onPointerDown={e2=>onBeginDrag(e2,'move',ev,'event')}
      onClick={ev2=>{ ev2.stopPropagation(); if(movedRef.current) return; onOpen(ev, ev2.currentTarget.getBoundingClientRect()); }}>
      <span className="tg-rs top"  onPointerDown={e2=>onBeginDrag(e2,'resize-start',ev,'event')}/>
      <span className="tg-rs bot"  onPointerDown={e2=>onBeginDrag(e2,'resize-end',ev,'event')}/>
      <span className="evblk-t">{ev.title||'(busy)'}{tiny && <span className="evblk-time2">· {fmtMin(minOf(s))}{ev.zoom && <span className="evblk-z" title="Zoom"> ▶</span>}</span>}</span>
      {!tiny && <span className="evblk-time">{dragging? `${fmtMin(minOf(s))} – ${fmtMin(minOf(e))}` : U.fmtTime(s)}{ev.zoom && <span className="evblk-z" title="Zoom">▶</span>}</span>}
      {conflict && <span className="evblk-flag" title="Sync conflict">⚠</span>}
    </button>
  );
}

// ── one scheduled to-do block in the time grid ─────────────────────
function TodoBlock({ td, onBeginDrag, dragging, onOpen }){
  const s=new Date(td.start), e=new Date(td.end);
  const gridH=(HOUR1-HOUR0+1)*HOURH;
  let top=((s.getHours()+s.getMinutes()/60)-HOUR0)*HOURH;
  const dur=Math.max(15,Math.round((e-s)/60000));
  let h=Math.max(20,(dur/60)*HOURH-2);
  if(top<0){ h+=top; top=0; }
  if(top+h>gridH){ h=gridH-top; }
  h=Math.max(18,h);
  const tiny=h<34;
  return (
    <button className={'tg-todoblk'+(tiny?' tiny':'')+(dragging?' dragging':'')}
      style={{top,height:h}}
      onPointerDown={e2=>onBeginDrag(e2,'move',td,'todo')}
      onClick={e2=>{ e2.stopPropagation(); CST.openPerson(td.personId||'self'); }}>
      <span className="tg-rs top" onPointerDown={e2=>onBeginDrag(e2,'resize-start',td,'todo')}/>
      <span className="tg-rs bot" onPointerDown={e2=>onBeginDrag(e2,'resize-end',td,'todo')}/>
      <span className="tg-todoblk-c"/>
      <span className="tg-todoblk-t">{td.text}{!tiny && <span className="tg-todoblk-time">{dragging?`${fmtMin(minOf(s))} – ${fmtMin(minOf(e))}`:fmtMin(minOf(s))}</span>}</span>
    </button>
  );
}

// ── to-do tray (right rail) ────────────────────────────────────────
function TodoTray({ items, lists, trayRef, onBeginTrayDrag, dropping }){
  const byList={}; items.forEach(t=>{ (byList[t.listId]=byList[t.listId]||[]).push(t); });
  return (
    <div className={'cal-tray'+(dropping?' dropping':'')} ref={trayRef}>
      <div className="ctray-head">
        <span className="ctray-title">To-dos</span>
        <span className="ctray-count">{items.length}</span>
      </div>
      <div className="ctray-hint">Drag onto the calendar to schedule</div>
      <div className="ctray-list scroll">
        {items.length===0 && <div className="ctray-empty">Everything's scheduled 🎉</div>}
        {lists.filter(l=>byList[l.id]&&byList[l.id].length).map(l=>(
          <div className="ctray-group" key={l.id}>
            <div className="ctray-glabel"><span className="ctray-gdot" style={{background:l.color}}/>{l.name}</div>
            {byList[l.id].map(t=>(
              <div key={t.id} className="ctray-item" style={{'--lc':l.color}} onPointerDown={e=>onBeginTrayDrag(e,t)} title={t.text}>
                <span className="ctray-grip"><i/><i/></span>
                <span className="ctray-c"/>
                <span className="ctray-txt">{t.text}</span>
                {t.flagged && <span className="ctray-flag">●</span>}
              </div>
            ))}
          </div>
        ))}
      </div>
    </div>
  );
}

// ── time grid (week or day) with full drag/resize/create ───────────
function TimeGrid({ days, cal, visEvents, allDayEvents, gridTodos, allDayTodos, trayTodos, lists, showTodos, onOpen, onCreate }){
  const bodyRef=tcRef(null), trayRef=tcRef(null);
  const [drag,setDrag]=tcState(null);
  const dragRef=tcRef(null); dragRef.current=drag;
  const ptRef=tcRef({x:0,y:0});
  const movedRef=tcRef(false);
  const cols=days.length;
  const gridCols=`62px repeat(${cols}, minmax(0,1fr))`;

  function pointToDayMin(cx,cy){
    const r=bodyRef.current.getBoundingClientRect();
    const colW=(r.width-62)/cols;
    let di=Math.floor((cx-r.left-62)/colW);
    di=Math.max(0,Math.min(cols-1,di));
    let min=(cy-r.top)/PXMIN + DAY_MIN0;
    const inGrid = cx>=r.left+62 && cx<=r.right && cy>=r.top && cy<=r.bottom;
    return {di,min,inGrid};
  }
  function inTray(cx,cy){ const t=trayRef.current; if(!t) return false; const r=t.getBoundingClientRect(); return cx>=r.left&&cx<=r.right&&cy>=r.top&&cy<=r.bottom; }

  // ── begin gestures ──
  function beginDrag(e,kind,item,type){
    if(e.button!==undefined && e.button!==0) return;
    e.stopPropagation(); e.preventDefault();
    const s=new Date(item.start), en=new Date(item.end);
    const startMin=minOf(s), endMin=minOf(en);
    const di0=days.findIndex(d=>U.sameDay(d,s));
    const {min}=pointToDayMin(e.clientX,e.clientY);
    movedRef.current=false;
    ptRef.current={x:e.clientX,y:e.clientY};
    setDrag({ kind,id:item.id,type, di:di0<0?0:di0, startMin,endMin, grab:min-startMin, dur:endMin-startMin });
  }
  function beginCreate(e,di){
    if(e.button!==0) return;
    if(e.target.closest('.evblk,.tg-todoblk,.tg-rs')) return;
    e.preventDefault();
    const {min}=pointToDayMin(e.clientX,e.clientY);
    const m=Math.max(DAY_MIN0,Math.min(DAY_MIN1-SNAP,snap(min,SNAP)));
    movedRef.current=false;
    ptRef.current={x:e.clientX,y:e.clientY};
    setDrag({ kind:'create', di, startMin:m, endMin:m+SNAP, anchorMin:m });
  }
  function beginTrayDrag(e,td){
    if(e.button!==undefined && e.button!==0) return;
    e.preventDefault();
    movedRef.current=false;
    ptRef.current={x:e.clientX,y:e.clientY};
    setDrag({ kind:'tray', id:td.id, td, x:e.clientX, y:e.clientY, over:null });
  }

  function computeNext(prev,e){
    const {di,min,inGrid}=pointToDayMin(e.clientX,e.clientY);
    if(prev.kind==='move'){
      let ns=snap(min-prev.grab,SNAP), ne=ns+prev.dur;
      if(ns<DAY_MIN0){ ne+=DAY_MIN0-ns; ns=DAY_MIN0; }
      if(ne>DAY_MIN1){ ns-=ne-DAY_MIN1; ne=DAY_MIN1; }
      if(ns!==prev.startMin||di!==prev.di) movedRef.current=true;
      return {...prev,di,startMin:ns,endMin:ne};
    }
    if(prev.kind==='resize-end'){
      let ne=Math.max(prev.startMin+SNAP,Math.min(DAY_MIN1,snap(min,SNAP)));
      if(ne!==prev.endMin) movedRef.current=true;
      return {...prev,endMin:ne};
    }
    if(prev.kind==='resize-start'){
      let ns=Math.min(prev.endMin-SNAP,Math.max(DAY_MIN0,snap(min,SNAP)));
      if(ns!==prev.startMin) movedRef.current=true;
      return {...prev,startMin:ns};
    }
    if(prev.kind==='create'){
      let m=Math.max(DAY_MIN0,Math.min(DAY_MIN1,snap(min,SNAP)));
      let s=Math.min(prev.anchorMin,m), en=Math.max(prev.anchorMin+SNAP,m);
      if(en-s>SNAP) movedRef.current=true;
      return {...prev,startMin:s,endMin:en};
    }
    if(prev.kind==='tray'){
      movedRef.current=true;
      return {...prev,x:e.clientX,y:e.clientY, over: inGrid?{di,min:Math.max(DAY_MIN0,Math.min(DAY_MIN1-30,snap(min,SNAP)))}:null};
    }
    return prev;
  }

  function commit(d){
    if(!d) return;
    const {x,y}=ptRef.current;
    if(d.kind==='move'||d.kind==='resize-start'||d.kind==='resize-end'){
      // dropped over tray → unschedule a to-do
      if(d.type==='todo' && inTray(x,y)){ CST.patch(d.id,{schedStart:null,schedEnd:null}); return; }
      if(!movedRef.current) return;
      const day=days[d.di]||days[0];
      const s=dateAtMin(day,d.startMin).toISOString(), en=dateAtMin(day,d.endMin).toISOString();
      if(d.type==='event') CST.patchEvent(d.id,{start:s,end:en});
      else CST.patch(d.id,{schedStart:s,schedEnd:en,reminder:''});
      return;
    }
    if(d.kind==='create'){
      const day=days[d.di]||days[0];
      const endMin = movedRef.current ? d.endMin : d.startMin+30;
      const s=dateAtMin(day,d.startMin).toISOString();
      const en=dateAtMin(day,Math.min(DAY_MIN1,endMin)).toISOString();
      onCreate({start:s,end:en},{left:x,right:x,top:Math.max(80,y-40)});
      return;
    }
    if(d.kind==='tray'){
      if(d.over){
        const day=days[d.over.di];
        const s=dateAtMin(day,d.over.min).toISOString();
        const en=dateAtMin(day,Math.min(DAY_MIN1,d.over.min+30)).toISOString();
        CST.patch(d.id,{schedStart:s,schedEnd:en,reminder:''});
        window.RaiseShell.toast&&window.RaiseShell.toast('To-do scheduled');
      }
      return;
    }
  }

  // ── one window-level pointer session per drag ──
  const active=!!drag;
  tcEffect(()=>{
    if(!active) return;
    document.body.classList.add('cal-dragging');
    const move=(e)=>{ ptRef.current={x:e.clientX,y:e.clientY}; setDrag(prev=>prev&&computeNext(prev,e)); };
    const up=()=>{ commit(dragRef.current); setDrag(null); setTimeout(()=>{movedRef.current=false;},0); };
    window.addEventListener('pointermove',move,{passive:false});
    window.addEventListener('pointerup',up);
    return ()=>{ document.body.classList.remove('cal-dragging'); window.removeEventListener('pointermove',move); window.removeEventListener('pointerup',up); };
  },[active]); // eslint-disable-line

  // ── apply ghost positions for the item under drag ──
  function effEvent(ev){
    if(drag&&drag.type==='event'&&drag.id===ev.id&&drag.kind!=='tray'){
      const day=days[drag.di]||days[0];
      return {...ev,start:dateAtMin(day,drag.startMin).toISOString(),end:dateAtMin(day,drag.endMin).toISOString(),_drag:true};
    }
    return ev;
  }
  function effTodo(td){
    if(drag&&drag.type==='todo'&&drag.id===td.id&&drag.kind!=='tray'){
      const day=days[drag.di]||days[0];
      return {...td,start:dateAtMin(day,drag.startMin).toISOString(),end:dateAtMin(day,drag.endMin).toISOString(),_drag:true};
    }
    return td;
  }

  const now=new Date(); const nowTop=((now.getHours()+now.getMinutes()/60)-HOUR0)*HOURH;

  return (
    <div className="tgwrap">
      <div className="tgrid">
        <div className="tg-scroll scroll" ref={r=>{ if(r&&!r._init){ r._init=true; r.scrollTop=Math.max(0,(8-HOUR0)*HOURH); } }}>
          {/* sticky header + all-day row */}
          <div className="tg-sticky">
            <div className="tg-head" style={{gridTemplateColumns:gridCols}}>
              <div className="tg-corner"/>
              {days.map((d,i)=>{ const today=U.sameDay(d,now); return (
                <div key={i} className={'tg-dh'+(today?' today':'')}>
                  <span className="tg-dn">{U.DAYNAMES[(d.getDay()+6)%7]}</span>
                  <span className="tg-dnum">{d.getDate()}</span>
                </div>); })}
            </div>
            <div className="tg-allday" style={{gridTemplateColumns:gridCols}}>
              <div className="tg-allday-lab">all-day</div>
              {days.map((d,i)=>{
                const chips=allDayTodos.filter(t=>U.sameDay(t.date,d));
                // all-day events cover [start, end) where Google's end date is exclusive
                const evChips=(allDayEvents||[]).filter(ev=>{ const s=asLocalDate(ev.start); s.setHours(0,0,0,0); let en=asLocalDate(ev.end); en.setHours(0,0,0,0); if(en<=s) en=new Date(s.getTime()+86400000); return d>=s && d<en; });
                return <div key={i} className="tg-allday-cell">
                  {evChips.map(ev=>{ const c=cal.calendars.find(c=>c.id===ev.calId); const col=c?c.color:'#c96442'; return (
                    <span key={ev.id} className="todochip evchip" title={ev.title} style={{color:col,background:window.RaiseShell.tint(col,'22'),borderColor:window.RaiseShell.tint(col,'55')}}
                      onClick={e2=>{ e2.stopPropagation(); onOpen(ev, e2.currentTarget.getBoundingClientRect()); }}>
                      <span className="tc-dot" style={{background:col}}/>{ev.title||'(busy)'}{ev.zoom&&<span className="evblk-z"> ▶</span>}
                    </span>); })}
                  {chips.map(t=><span key={t.id} className="todochip" title={t.text} onPointerDown={e=>beginTrayDrag(e,t)} onClick={()=>CST.openPerson(t.personId||'self')}><span className="tc-dot"/>{t.text}</span>)}
                </div>;
              })}
            </div>
          </div>
          {/* time body */}
          <div className="tg-body" ref={bodyRef} style={{height:(HOUR1-HOUR0+1)*HOURH, gridTemplateColumns:gridCols}}>
            <div className="tg-gutter">
              {HOURS.map(h=><div key={h} className="tg-hr" style={{height:HOURH}}><span>{hourLabel(h)}</span></div>)}
            </div>
            {days.map((d,i)=>{
              const evs=U.layoutDay(visEvents.map(effEvent).filter(ev=>U.sameDay(new Date(ev.start),d)).map(ev=>({...ev,start:new Date(ev.start),end:new Date(ev.end)})));
              const tds=gridTodos.map(effTodo).filter(t=>U.sameDay(new Date(t.start),d));
              const today=U.sameDay(d,now);
              const showCreate=drag&&drag.kind==='create'&&drag.di===i;
              const showDrop=drag&&drag.kind==='tray'&&drag.over&&drag.over.di===i;
              return (
                <div key={i} className={'tg-col'+(today?' today':'')} onPointerDown={e=>beginCreate(e,i)}>
                  {HOURS.map(h=><div key={h} className="tg-cell" style={{height:HOURH}}/>)}
                  {evs.map(ev=>{ const c=cal.calendars.find(c=>c.id===ev.calId); return <EventBlock key={ev.id} ev={ev} col={c?c.color:'#c96442'} onOpen={onOpen} onBeginDrag={beginDrag} dragging={!!ev._drag} movedRef={movedRef}/>; })}
                  {tds.map(t=><TodoBlock key={t.id} td={t} onBeginDrag={beginDrag} dragging={!!t._drag}/>)}
                  {showCreate && <div className="tg-prev create" style={{top:(drag.startMin-DAY_MIN0)*PXMIN, height:(drag.endMin-drag.startMin)*PXMIN}}><span>{fmtMin(drag.startMin)} – {fmtMin(drag.endMin)}</span></div>}
                  {showDrop && <div className="tg-prev drop" style={{top:(drag.over.min-DAY_MIN0)*PXMIN, height:30*PXMIN}}><span>{drag.td.text}</span></div>}
                  {today && nowTop>=0 && nowTop<=(HOUR1-HOUR0+1)*HOURH && <div className="tg-now" style={{top:nowTop}}><span/></div>}
                </div>
              );
            })}
          </div>
        </div>
      </div>
      {showTodos && <TodoTray items={trayTodos} lists={lists} trayRef={trayRef} onBeginTrayDrag={beginTrayDrag} dropping={drag&&drag.kind!=='tray'&&drag.type==='todo'}/>}
      {/* floating ghost while dragging from the tray */}
      {drag&&drag.kind==='tray' && <div className="tray-ghost" style={{left:drag.x+12,top:drag.y+8}}><span className="ctray-c"/>{drag.td.text}</div>}
    </div>
  );
}

// ── month view ──────────────────────────────────────────────────────
function MonthGrid({ anchor, cal, visEvents, todoItems, onOpen, onPickDay }){
  const first=new Date(anchor.getFullYear(),anchor.getMonth(),1);
  const start=U.mondayOf(first);
  const weeks=[]; let cur=new Date(start);
  for(let w=0;w<6;w++){ const row=[]; for(let d=0;d<7;d++){ row.push(new Date(cur)); cur=U.addDays(cur,1); } weeks.push(row); }
  const now=new Date();
  return (
    <div className="mgrid">
      <div className="mg-head">{U.DAYNAMES.map(d=><div key={d} className="mg-hd">{d}</div>)}</div>
      <div className="mg-body">
        {weeks.map((row,wi)=>(
          <div className="mg-row" key={wi}>
            {row.map((d,di)=>{
              const inMonth=d.getMonth()===anchor.getMonth();
              const today=U.sameDay(d,now);
              const evs=visEvents.filter(ev=>U.sameDay(asLocalDate(ev.start),d)).sort((a,b)=>asLocalDate(a.start)-asLocalDate(b.start));
              const tds=todoItems.filter(t=>U.sameDay(t.date,d));
              return (
                <div key={di} className={'mg-cell'+(inMonth?'':' out')+(today?' today':'')} onClick={()=>onPickDay(d)}>
                  <div className="mg-dnum">{d.getDate()}</div>
                  <div className="mg-evs">
                    {evs.slice(0,3).map(ev=>{ const c=cal.calendars.find(c=>c.id===ev.calId); return (
                      <button key={ev.id} className={'mg-ev'+(ev.syncState==='conflict'?' cf':'')} style={{'--cc':c?c.color:'#c96442'}}
                        onClick={e=>{e.stopPropagation(); onOpen(ev,e.currentTarget.getBoundingClientRect());}}>
                        <span className="mg-ev-dot"/><span className="mg-ev-t">{U.fmtTime(new Date(ev.start))} {ev.title}</span>{ev.zoom&&<span className="mg-ev-z">▶</span>}
                      </button>); })}
                    {(evs.length>3) && <span className="mg-more">+{evs.length-3} more</span>}
                    {tds.slice(0,2).map(t=><span key={t.id} className="mg-todo"><span className="mg-todo-c"/>{t.text}</span>)}
                  </div>
                </div>
              );
            })}
          </div>
        ))}
      </div>
    </div>
  );
}

// ── agenda view ─────────────────────────────────────────────────────
function AgendaView({ anchor, cal, visEvents, todoItems, onOpen }){
  const start=U.mondayOf(anchor);
  const days=Array.from({length:14},(_,i)=>U.addDays(start,i));
  const now=new Date();
  return (
    <div className="agenda scroll">
      {days.map((d,i)=>{
        const evs=visEvents.filter(ev=>U.sameDay(asLocalDate(ev.start),d)).sort((a,b)=>asLocalDate(a.start)-asLocalDate(b.start));
        const tds=todoItems.filter(t=>U.sameDay(t.date,d));
        if(!evs.length && !tds.length) return null;
        return (
          <div className="ag-day" key={i}>
            <div className={'ag-date'+(U.sameDay(d,now)?' today':'')}>
              <span className="ag-dn">{U.DAYNAMES[(d.getDay()+6)%7]}</span>
              <span className="ag-num">{d.getDate()}</span>
              <span className="ag-mo">{U.MONTHS[d.getMonth()].slice(0,3)}</span>
            </div>
            <div className="ag-items">
              {evs.map(ev=>{ const c=cal.calendars.find(c=>c.id===ev.calId); const s=new Date(ev.start),e=new Date(ev.end); return (
                <button key={ev.id} className="ag-ev" onClick={e2=>onOpen(ev,e2.currentTarget.getBoundingClientRect())}>
                  <span className="ag-ev-time mono">{isAllDay(ev)?'all-day':U.fmtTime(s)}</span>
                  <span className="ag-ev-bar" style={{background:c?c.color:'#c96442'}}/>
                  <span className="ag-ev-t">{ev.title}</span>
                  {ev.zoom && <span className="ag-ev-z">▶ Zoom</span>}
                  <U.SyncDot state={ev.syncState}/>
                </button>); })}
              {tds.map(t=>(
                <div key={t.id} className="ag-todo" onClick={()=>CST.openPerson(t.personId||'self')}>
                  <span className="ag-ev-time mono">{t.timed?U.fmtTime(t.date):'to-do'}</span>
                  <span className="ag-todo-c"/><span className="ag-ev-t">{t.text}</span>
                </div>
              ))}
            </div>
          </div>
        );
      })}
    </div>
  );
}

// ── classify a to-do for the calendar ──────────────────────────────
function classifyTodo(t){
  if(t.schedStart){ const s=new Date(t.schedStart); const e=t.schedEnd?new Date(t.schedEnd):new Date(s.getTime()+30*60000); return {kind:'grid',start:s,end:e}; }
  const r=U.reminderDate(t.reminder);
  if(r&&r.timed){ return {kind:'grid',start:r.date,end:new Date(r.date.getTime()+30*60000)}; }
  if(r&&!r.timed){ return {kind:'allday',date:r.date}; }
  return {kind:'tray'};
}

// ── main tab ────────────────────────────────────────────────────────
function TabCalendar(){
  const cal=window.RaiseShell.useCal();
  const todos=window.RaiseShell.useTodos();
  const [view,setView]=tcState(()=>localStorage.getItem('raiseos.calview')||'week');
  const [anchor,setAnchor]=tcState(()=>new Date());
  const [showTodos,setShowTodos]=tcState(true);
  const [pop,setPop]=tcState(null);
  const [conflict,setConflict]=tcState(null);
  const [connectOpen,setConnectOpen]=tcState(false);
  const [settingsOpen,setSettingsOpen]=tcState(false);

  React.useEffect(()=>{ const h=()=>setConnectOpen(true); window.addEventListener('cal:connect',h); return ()=>window.removeEventListener('cal:connect',h); },[]);
  const setV=(v)=>{ setView(v); localStorage.setItem('raiseos.calview',v); };

  const visEvents=cal.events.filter(ev=>{ const c=cal.calendars.find(c=>c.id===ev.calId); return c&&c.visible; });
  const timedEvents=visEvents.filter(ev=>!isAllDay(ev));   // placed in the time grid
  const allDayEvents=visEvents.filter(isAllDay);            // shown in the all-day row
  const conflictCount=cal.events.filter(e=>e.syncState==='conflict').length;
  const lists=CST.getLists();

  // pull ALL to-dos onto the calendar
  const live = showTodos ? todos.filter(t=>!t.done) : [];
  const gridTodos=[], allDayTodos=[], trayTodos=[], monthTodos=[];
  live.forEach(t=>{
    const c=classifyTodo(t); const base={id:t.id,text:t.text,personId:t.personId,listId:t.listId||lists[0]?.id,flagged:t.flagged};
    if(c.kind==='grid'){ gridTodos.push({...base,start:c.start.toISOString(),end:c.end.toISOString()}); monthTodos.push({...base,date:c.start,timed:true}); }
    else if(c.kind==='allday'){ allDayTodos.push({...base,date:c.date}); monthTodos.push({...base,date:c.date,timed:false}); }
    else trayTodos.push(base);
  });

  const monday=U.mondayOf(anchor);
  const weekDays=Array.from({length:7},(_,i)=>U.addDays(monday,i));
  const step=(dir)=>{ const d=new Date(anchor); if(view==='month') d.setMonth(d.getMonth()+dir); else if(view==='day') d.setDate(d.getDate()+dir); else d.setDate(d.getDate()+dir*(view==='agenda'?14:7)); setAnchor(d); };
  const title=()=>{ if(view==='month') return U.MONTHS[anchor.getMonth()]+' '+anchor.getFullYear();
    if(view==='day') return U.DAYNAMES[(anchor.getDay()+6)%7]+', '+U.MONTHS[anchor.getMonth()]+' '+anchor.getDate();
    const a=weekDays[0], b=weekDays[6]; const sm=U.MONTHS[a.getMonth()].slice(0,3), em=U.MONTHS[b.getMonth()].slice(0,3);
    return sm===em? `${sm} ${a.getDate()}–${b.getDate()}` : `${sm} ${a.getDate()} – ${em} ${b.getDate()}`; };

  const openEvent=(ev,rect)=> setPop({ ev, rect, isNew:false });
  const createEvent=(times,rect)=>{ const def={ title:'', calId:cal.calendars[0].id, personId:null, notes:'', location:'', zoom:null, allDay:false, source:'raise', ...times };
    setPop({ ev:def, rect, isNew:true }); };

  const g=cal.google;

  return (
    <>
      <TC_TopBar title="Calendar" sub={`${visEvents.length} events · ${gridTodos.length+allDayTodos.length+trayTodos.length} to-dos`}>
        <div className="cal-views seg">
          {['day','week','month','agenda'].map(v=><button key={v} className={'segb'+(view===v?' on':'')} onClick={()=>setV(v)}>{v[0].toUpperCase()+v.slice(1)}</button>)}
        </div>
      </TC_TopBar>

      {/* toolbar: nav + todo toggle */}
      <div className="caltool">
        <div className="ct-nav">
          <button className="ct-today" onClick={()=>setAnchor(new Date())}>Today</button>
          <button className="ct-arrow" onClick={()=>step(-1)} aria-label="Previous">‹</button>
          <button className="ct-arrow" onClick={()=>step(1)} aria-label="Next">›</button>
          <h2 className="ct-title">{title()}</h2>
        </div>
        <div className="ct-right">
          <button className={'ct-todobtn'+(showTodos?' on':'')} onClick={()=>setShowTodos(s=>!s)}><span className="ct-todobtn-c"/>To-dos</button>
          <button className="ct-new" onClick={()=>{ const s=U.roundToHalf(new Date()); createEvent({start:s.toISOString(), end:new Date(s.getTime()+30*60000).toISOString()}, {right:window.innerWidth-360,left:window.innerWidth-360,top:120}); }}>+ Event</button>
        </div>
      </div>

      {/* body */}
      <div className="calbody">
        {view==='week' && <TimeGrid days={weekDays} cal={cal} visEvents={timedEvents} allDayEvents={allDayEvents} gridTodos={gridTodos} allDayTodos={allDayTodos} trayTodos={trayTodos} lists={lists} showTodos={showTodos} onOpen={openEvent} onCreate={createEvent}/>}
        {view==='day' && <TimeGrid days={[anchor]} cal={cal} visEvents={timedEvents} allDayEvents={allDayEvents} gridTodos={gridTodos} allDayTodos={allDayTodos} trayTodos={trayTodos} lists={lists} showTodos={showTodos} onOpen={openEvent} onCreate={createEvent}/>}
        {view==='month' && <MonthGrid anchor={anchor} cal={cal} visEvents={visEvents} todoItems={monthTodos} onOpen={openEvent} onPickDay={d=>{ setAnchor(d); setV('day'); }}/>}
        {view==='agenda' && <AgendaView anchor={anchor} cal={cal} visEvents={visEvents} todoItems={monthTodos} onOpen={openEvent}/>}
      </div>

      {pop && <U.EventPopover ev={pop.ev} anchorRect={pop.rect} isNew={pop.isNew} onConflict={ev=>{ setPop(null); setConflict(ev); }} onClose={()=>setPop(null)}/>}
      {conflict && <U.ConflictModal ev={cal.events.find(e=>e.id===conflict.id)||conflict} onClose={()=>setConflict(null)}/>}
      {connectOpen && <U.GoogleConnectModal cal={cal} onClose={()=>setConnectOpen(false)}/>}
      {settingsOpen && <U.SyncSettings cal={cal} onClose={()=>setSettingsOpen(false)}/>}
    </>
  );
}
window.TabCalendar = TabCalendar;
