// Capacity planning UI. Original packets are opened through the existing editors.
function DesignStudyPlanner({ state, post, today, onOpen, onOpenDaily, onSandbox, refresh, calendarOnly = false }) {
  const plan = state.planner;
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState('');
  const [pauseDays, setPauseDays] = React.useState(1);
  const [pauseFrom, setPauseFrom] = React.useState(today);
  const [dayDate, setDayDate] = React.useState(today);
  const [dayCapacity, setDayCapacity] = React.useState('1');
  const [openDay, setOpenDay] = React.useState('');
  const [openQueue, setOpenQueue] = React.useState('');
  const [queuePage, setQueuePage] = React.useState(1);
  const [repair, setRepair] = React.useState(null);
  const [recallOpen, setRecallOpen] = React.useState(false);
  const [recallTopic, setRecallTopic] = React.useState('');
  const [recallText, setRecallText] = React.useState('');
  const [recallMinutes, setRecallMinutes] = React.useState(15);
  const [actualMinutes, setActualMinutes] = React.useState('');
  const [closing, setClosing] = React.useState(false);
  const [deferring, setDeferring] = React.useState('');
  const [deferDate, setDeferDate] = React.useState('');
  if (!plan?.current) return <div className="card">Loading the study plan…</div>;
  const current = plan.current;
  const cfg = plan.config;
  const completedTopics = state.topics.filter(t => state.occurrences.some(o => o.topicId === t.id && o.completedAt));
  const isToday = current.date === today;
  async function act(action, values = {}) {
    setBusy(true); setError('');
    try { return await post('/api/design-srs/plan', { action, expectedRevision: plan.revision, ...values }); }
    catch (e) { setError(e.message); return null; } finally { setBusy(false); }
  }
  async function openBlock(task) {
    const data = await act('start_block', { key: task.key });
    if (!data) return;
    if (task.type === 'occurrence') onOpen(data.occurrences.find(o => o.id === task.sourceId));
    else if (task.type === 'drill') onOpenDaily(data.dailyDrills.find(d => d.id === task.sourceId));
    else if (task.type === 'repair') setRepair(task.sourceId);
    else onSandbox();
  }
  const groupKey = t => t.blocked === 'Awaiting curated packet' ? 'curation' : t.blocked ? 'later' : t.dueDate > today ? 'future' : t.type === 'occurrence' ? t.stage === 'D0' ? 'new' : 'reviews' : 'support';
  const labels = { reviews: 'SRS reviews', new: 'New topics', support: 'Repairs & daily drills', curation: 'Awaiting curation', later: 'Waiting for an earlier stage', future: 'Future due dates' };
  const groups = Object.entries(labels).map(([key, title]) => ({ key, title, items: plan.queue.filter(t => groupKey(t) === key).sort((a, b) => a.dueDate.localeCompare(b.dueDate) || a.key.localeCompare(b.key)) }));
  const showTask = task => <div className="design-plan-task" key={task.key}>
    <div><strong>{task.title}</strong><div className="muted">{task.stage || task.type} · SRS/source due {task.dueDate || '—'}</div><div className="muted">Work date: {task.plannedDate || 'Awaiting capacity'}{task.delayDays ? ` · ${task.delayDays} day(s) overdue` : ''}</div><small>{task.queueReason || task.reason}{task.deferrals ? ` · deferred ${task.deferrals} time(s)` : ''}</small></div>
    {task.type === 'occurrence' && <button className="btn tiny" onClick={() => onOpen(state.occurrences.find(o => o.id === task.sourceId))}>View packet</button>}
  </div>;
  const forecast = <section className="card design-plan-forecast"><div className="card-title">Next 14 study days</div><p className="muted">Projected work dates assume each allocated block is finished. SRS due dates remain separate. Paused dates do not consume a study day.</p>
    <div className="design-plan-days">{plan.forecast.map(day => <button className={'design-plan-day ' + (openDay === day.date ? 'selected' : '')} key={day.date} onClick={() => setOpenDay(openDay === day.date ? '' : day.date)} aria-expanded={openDay === day.date}><time>{day.date}</time><strong>Day {day.studyIndex}</strong><span>{day.extension ? 'Extra review' : `${day.phase === 'learning' ? 'Learn' : 'Review'} · ${day.phaseDay}/14`}</span><small>{day.slots.length}/{day.capacity} blocks{day.capacity === 1 ? ' · light day' : ''}</small></button>)}</div>
    {openDay && <div className="design-plan-day-details">{plan.forecast.find(d => d.date === openDay)?.slots.map(t => showTask({ ...t, plannedDate: openDay }))}<p className="muted">{plan.forecast.find(d => d.date === openDay)?.missingNew ? 'No eligible new packet available. Existing obligations remain queued.' : 'No questions are removed to make this forecast fit.'}</p></div>}
  </section>;
  return <div className="col design-plan">
    {error && <div className="card design-plan-error" role="alert">{error}<button className="btn tiny" onClick={refresh}>Refresh plan</button></div>}
    {!calendarOnly && <>
      <section className="card design-plan-header">
        <div><div className="kicker">Study day {current.studyIndex} · cycle {current.cycle}</div><h2>{current.extension ? 'Extra recovery time' : current.phase === 'learning' ? 'Learn, then retrieve.' : 'Consolidate what you know.'}</h2><p>{current.extension ? 'Review extension' : `Cycle day ${current.phaseDay} of 14`} · {current.phase === 'learning' ? 'One new topic + available review capacity' : 'Review and recall only · no new topics'}</p></div>
        <div className="design-plan-date"><strong>{current.date}</strong><span>{current.capacity} block{current.capacity === 1 ? '' : 's'} maximum</span><span>{isToday ? 'Current study day' : 'Next available study day'}</span></div>
      </section>
      <div className="design-plan-stats">{[['Planned blocks',`${current.slots.length}/${current.capacity}`],['Remaining estimate',`${Math.round(current.estimatedMinutes)} min`],['Items overdue',plan.stats.overdue],['Oldest delay',`${plan.stats.oldestDelay} days`]].map(([label,value]) => <div className="card" key={label}><span>{label}</span><strong>{value}</strong></div>)}</div>
      <p className="muted design-plan-caption">Estimates start at {current.estimates.fresh} min per new packet and {current.estimates.review} min per review. {current.recallMinutes} min of brief recall is included separately. Term brushes stay supplemental and unmetered.</p>
      <details className="card design-plan-pressure"><summary>Review debt & capacity · {plan.stats.outsideForecast} due items outside the forecast</summary>{plan.warnings.map(w => <p key={w}>{w}</p>)}<p>{plan.stats.plannedNew} new packets and {plan.stats.fullReviewSlots} full SRS reviews currently fit in the forecast. Capacity also serves repairs, drills and consolidation.</p><button className="btn" disabled={busy} onClick={() => act('extend_review',{days:4})}>Add 4 recovery days after this cycle</button><span className="muted"> {cfg.extraReviewDays} additional recovery days queued</span></details>
      <DesignPlanCuration state={state} post={post} today={today}/>
      {current.missingNew && <div className="card design-plan-error">No eligible new-topic packet is available for this learning day. Check New topics and Awaiting curation below. The scheduler will not invent or skip a packet.</div>}
      <section className="design-plan-blocks">{current.slots.map((task,i) => <article className={'card design-plan-block '+(task.completedAt?'done':'')} key={task.key}>
        <div className="design-plan-block-meta"><span className="kicker">Block {i+1} · {task.stage || task.type}</span><span className="mono">{task.completedAt?'Completed':`${task.minutes} min`}</span></div>
        <h3>{task.title}</h3><p className="muted">{task.reason || 'Kept in the current study day'}{task.started && !task.completedAt ? ' · in progress' : ''}</p>
        {task.type !== 'consolidation' && <div className="design-plan-dates"><span>SRS/source due <b>{task.dueDate}</b></span><span>Planned work <b>{current.date}</b></span></div>}
        {task.type === 'consolidation' && <p className="muted">Reattempt original questions from completed material. Only questions actually submitted count as recalled; no SRS occurrence advances.</p>}
        <div className="design-plan-actions">{!task.completedAt && <button className="btn primary" disabled={busy || !isToday} onClick={() => openBlock(task)}>{task.started?'Resume':'Start block'}</button>}
          {task.type === 'consolidation' && !task.completedAt && <button className="btn" disabled={busy || !isToday || !task.started} onClick={() => act('consolidation_complete')}>Finish consolidation</button>}
          {!task.completedAt && !task.started && task.type !== 'consolidation' && <button className="btn" disabled={busy} onClick={() => {setDeferring(task.key);setDeferDate('');}}>Defer work date</button>}
        </div>
        {deferring === task.key && <form className="design-plan-actions" onSubmit={async e=>{e.preventDefault();if(await act('defer',{key:task.key,date:deferDate}))setDeferring('');}}><label>Earliest work date<input className="input" type="date" required value={deferDate} onChange={e=>setDeferDate(e.target.value)}/></label><button className="btn" disabled={busy}>Save deferral</button><span className="muted">SRS due date stays unchanged.</span></form>}
      </article>)}</section>
      {!current.slots.length && <div className="card">No eligible work can be allocated. All pending and uncurated stages remain visible below.</div>}
      {!!completedTopics.length && <details className="card design-plan-recall" open={recallOpen} onToggle={e=>setRecallOpen(e.currentTarget.open)}><summary>Brief closed-source recall · {cfg.shortRecalls.length ? 'recorded for this study day' : `${cfg.recallMinutes} minutes planned`}</summary><p className="muted">Before opening your notes, reconstruct an earlier topic's data flow, decisions, calculation and failure mechanism. This is separate practice; it does not complete any question or SRS packet. Review your notebook after saving.</p><form onSubmit={async e=>{e.preventDefault();if(await act('short_recall',{topicId:recallTopic,response:recallText,minutes:Number(recallMinutes)})){setRecallText('');setRecallOpen(false);}}}>
        <label>Previously completed topic<select className="select" required value={recallTopic} onChange={e=>setRecallTopic(e.target.value)}><option value="">Choose a topic</option>{completedTopics.map(t=><option value={t.id} key={t.id}>{t.title}</option>)}</select></label><label>Your reconstruction<textarea className="textarea" required value={recallText} onChange={e=>setRecallText(e.target.value)} maxLength={20000}/></label><label>Actual recall minutes<input className="input" type="number" required min="1" max="180" value={recallMinutes} onChange={e=>setRecallMinutes(e.target.value)}/></label><button className="btn primary" disabled={busy || !isToday}>Save recall</button>
      </form></details>}
      <section className="card design-plan-controls"><div className="card-title">Make room for real life</div><p className="muted">A pause moves work allocations and freezes the cycle counter. Thursday remains limited to one block wherever it falls in the cycle. Completed work and SRS due dates stay fixed.</p>
        <div className="design-plan-control-grid"><form onSubmit={e=>{e.preventDefault();act('pause',{days:Number(pauseDays),fromDate:pauseFrom});}}><label>Pause from<input className="input" type="date" min={today} value={pauseFrom} onChange={e=>setPauseFrom(e.target.value)} required/></label><label>Calendar days<input className="input" type="number" min="1" max="365" value={pauseDays} onChange={e=>setPauseDays(e.target.value)} required/></label><button className="btn" disabled={busy}>Pause & move work</button></form>
        <form onSubmit={e=>{e.preventDefault();act('capacity',{date:dayDate,capacity:Number(dayCapacity)});}}><label>Capacity exception date<input className="input" type="date" min={today} value={dayDate} onChange={e=>setDayDate(e.target.value)} required/></label><label>Available blocks<select className="select" value={dayCapacity} onChange={e=>setDayCapacity(e.target.value)}><option value="0">0 · pause this day</option><option value="1">1 block</option><option value="2">2 blocks (Thursday stays 1)</option></select></label><button className="btn" disabled={busy}>Set capacity</button></form></div>
        <div className="design-plan-end"><button className="btn primary" disabled={busy || !isToday} onClick={()=>setClosing(true)}>Finish study day {current.studyIndex}</button><p className="muted">Advances the counter once. Unfinished work carries forward, and the next study day cannot start before tomorrow. Days without this action do not advance automatically.</p></div>
        {closing && <form className="design-plan-confirm" onSubmit={async e=>{e.preventDefault();if(await act('close_day',{actualMinutes:actualMinutes === '' ? null : Number(actualMinutes)}))setClosing(false);}}><p>{current.slots.filter(t=>!t.completedAt).length} block(s) unfinished. They will remain queued. Finishing the day is not packet completion.</p><label>Actual Design study minutes, excluding term brushes (optional)<input className="input" type="number" min="0" max="1440" value={actualMinutes} onChange={e=>setActualMinutes(e.target.value)}/></label><button className="btn primary" disabled={busy}>Confirm day finished</button><button className="btn" type="button" onClick={()=>setClosing(false)}>Keep studying</button></form>}
      </section>
    </>}
    {forecast}
    {!calendarOnly && <section className="card design-plan-queue"><div className="card-title">Every obligation stays accounted for</div><p className="muted">{plan.stats.pending} pending items across every approved batch. Dates shown below are current D1 dates; future allocations are provisional.</p><div className="design-plan-queue-tabs">{groups.map(g=><button key={g.key} className={'btn '+(openQueue===g.key?'primary':'')} onClick={()=>{setOpenQueue(openQueue===g.key?'':g.key);setQueuePage(1);}} aria-expanded={openQueue===g.key}>{g.title} · {g.items.length}</button>)}</div>{openQueue && <div>{groups.find(g=>g.key===openQueue)?.items.slice((queuePage-1)*12,queuePage*12).map(showTask)}<div className="design-plan-actions"><button className="btn tiny" disabled={queuePage<=1} onClick={()=>setQueuePage(p=>p-1)}>Previous</button><span>Page {queuePage}</span><button className="btn tiny" disabled={queuePage*12 >= (groups.find(g=>g.key===openQueue)?.items.length||0)} onClick={()=>setQueuePage(p=>p+1)}>Next</button></div></div>}</section>}
    {!calendarOnly && <details className="card"><summary>Allocation history · last 20 changes</summary>{plan.events.map(e=><details className="design-plan-event" key={e.id}><summary>Study day {e.study_index} · {e.action.replace(/_/g,' ')} · {e.created_at.slice(0,10)}</summary>{e.payload.moved?.map((m,i)=><p className="muted" key={i}>{m.key}: {m.from || 'unallocated'} → {m.to || 'awaiting capacity'} · SRS due {m.dueDate}</p>)}{e.payload.closedDay && <p className="muted">{e.payload.closedDay.slots.filter(t=>t.completedAt).length} completed blocks · {e.payload.unfinished.length} carried forward · {e.payload.actualMinutes || 'No'} actual minutes recorded</p>}</details>)}</details>}
    {repair && <DesignV2Repair card={state.repairCards.find(r=>r.id===repair)} topic={state.topics.find(t=>t.id===state.repairCards.find(r=>r.id===repair)?.topicId)} sourceAttempt={state.attempts.find(a=>a.id===state.repairCards.find(r=>r.id===repair)?.sourceAttemptId)} onComplete={async(id,response)=>{await post('/api/design-srs/attempt',{action:'complete_repair',repairId:id,response});setRepair(null);}}/>}
    <p className="muted design-plan-caption">All dates use Asia/Calcutta. New topics: cycle days 1–10. Review only: days 11–14 plus requested extensions. Content and the SRS ladder are unchanged.</p>
  </div>;
}

function DesignStudyCalendar(props) {
  const [mode,setMode] = React.useState('work');
  return <div className="col"><div className="design-plan-actions"><button className={'btn '+(mode==='work'?'primary':'')} onClick={()=>setMode('work')}>Planned work dates</button><button className={'btn '+(mode==='due'?'primary':'')} onClick={()=>setMode('due')}>SRS due dates & history</button></div>{mode==='work'?<DesignStudyPlanner {...props} today={designSrsTodayString()} calendarOnly/>:<DesignV2Calendar {...props}/>}</div>;
}

function DesignPlanCuration({ state, post, today }) {
  const [topic,setTopic] = React.useState('');
  const [open,setOpen] = React.useState(false);
  const reminders = state.occurrences.filter(o=>!o.completedAt&&!o.packetId&&o.reminderDate&&o.reminderDate<=today&&o.snoozedOn!==today);
  if (!reminders.length) return null;
  const topicIds = [...new Set(reminders.map(o=>o.topicId))];
  return <details className="card design-plan-curation" open={open} onToggle={e=>setOpen(e.currentTarget.open)}><summary>{reminders.length} curated packets need attention · reminders start two days before due</summary>{open && <div><label>Choose a topic<select className="select" value={topic} onChange={e=>setTopic(e.target.value)}><option value="">Select a topic to view its curation reminders</option>{topicIds.map(id=><option value={id} key={id}>{state.topics.find(t=>t.id===id)?.title || id}</option>)}</select></label>{topic && <DesignV2Curation reminders={reminders.filter(o=>o.topicId===topic).map(o=>({...o,context:designV2CurationContext(o,state.attempts)}))} post={post}/>}</div>}</details>;
}

function DesignPlanSettings({ state, post }) {
  const cfg = state.planner?.config;
  const [draft,setDraft] = React.useState(cfg || {});
  const [busy,setBusy] = React.useState(false);
  const [error,setError] = React.useState('');
  if (!cfg) return null;
  return <div className="card design-plan"><div className="card-title">Study-cycle planning estimates</div><p className="muted">Ten learning study days, then four review-only study days. Thursday is one block; all other days allow two. Finish study day in Today to advance the counter. Personal pauses affect allocations, not SRS due dates.</p><form className="design-plan-control-grid" onSubmit={async e=>{e.preventDefault();setBusy(true);setError('');try{await post('/api/design-srs/plan',{action:'estimates',expectedRevision:state.planner.revision,...Object.fromEntries(Object.entries(draft).filter(([k])=>['newMinutes','reviewMinutes','recallMinutes','consolidationMinutes'].includes(k)).map(([k,v])=>[k,Number(v)]))});}catch(e){setError(e.message);}finally{setBusy(false);}}}>
  {[['newMinutes','New packet minutes',60,600],['reviewMinutes','Review packet minutes',30,300],['recallMinutes','Brief recall minutes',0,60],['consolidationMinutes','Consolidation block minutes',30,180]].map(([key,label,min,max])=><label key={key}>{label}<input className="input" type="number" required min={min} max={max} value={draft[key]??cfg[key]} onChange={e=>setDraft(p=>({...p,[key]:e.target.value}))}/></label>)}<button className="btn primary" disabled={busy}>Save planning estimates</button></form><p className="muted">These change planning only. Questions, packet versions, question counts and term-brush accounting stay unchanged. Complete-packet time records can raise the estimates.</p>{error&&<p role="alert">{error}</p>}</div>;
}
