/* CUET (UG) page: three pages from one dropdown (?view=full | sectional | topic):
   Full mock tests (one whole subject paper), Sectional tests (one section of a subject), Topic tests.
   Subjects come from the CUET catalog (Admin → CUET tests → Subjects & sections, GET /api/cuet), grouped as
   Languages · General Test · Domain subjects; the tests are the ones placed there (page 'cuet', groups
   "cuet:<subject>:full" / "cuet:<subject>:<sectional|topic>:<section id>"). Styles: the Practice page's fpq-* classes.
   Plan rights: CUET Topic Tests / CUET Sectional & Mock Tests (server/src/lib/plans.js).
   Live Tests (GET /api/live, exam CUET or placed here): window open now, and upcoming ones to register for — each has its
   own page /live/<slug>. A placed test that is live is hidden from its tab until its window ends; after it, it is a
   normal mock (with the student's live score). Ended live mocks not placed under a subject are listed in "Past live mocks". */
const h=React.createElement;
const CUET_VIEWS=[{key:'full',label:'Full Mock Tests'},{key:'sectional',label:'Sectional Tests'},{key:'topic',label:'Topic Tests'}];
function cuetParam(n){return (new RegExp('[?&]'+n+'=([^&#]+)').exec(location.search)||[])[1];}
function cuetMins(m){return window.SBListings?window.SBListings.mins(m):m+' mins';}
function cuetLeft(ms){var s=Math.max(0,Math.floor(ms/1000)),d=Math.floor(s/86400),hh=Math.floor(s%86400/3600),mm=Math.floor(s%3600/60),ss=s%60;
return d?d+'d '+hh+'h':hh?hh+'h '+mm+'m':mm+'m '+(ss<10?'0':'')+ss+'s';}

function CuetPage(){
const LS=window.SBListings;
const P=LS?LS.use('cuet'):[];
const [cat,setCat]=React.useState(null);           /* { groups, subjects } */
const [tests,setTests]=React.useState([]);         /* the student's attempts (GET /api/mocks) */
const [view,setViewS]=React.useState(()=>{const v=cuetParam('view');return CUET_VIEWS.some(x=>x.key===v)?v:'full';});
const [subj,setSubjS]=React.useState(()=>decodeURIComponent(cuetParam('subject')||''));
const [grp,setGrp]=React.useState('');
const [sec,setSec]=React.useState('');
React.useEffect(()=>{let on=true;
fetch('/api/cuet',{credentials:'same-origin'}).then(r=>r.ok?r.json():null).then(d=>{if(on&&d)setCat(d);}).catch(()=>{if(on)setCat({groups:[],subjects:[]});});
(window.SBAuth?window.SBAuth.ready.then(()=>window.SBAuth.api('GET','/mocks')):Promise.reject()).then(l=>{if(on)setTests(Array.isArray(l)?l:[]);},()=>{});
return ()=>{on=false;};},[]);
/* live tests: refreshed every minute; countdowns tick every second */
const [lives,setLives]=React.useState({now:[],upcoming:[],past:[]});
const [,setTick]=React.useState(0);
const skew=React.useRef(0);
React.useEffect(()=>{let on=true;
const load=()=>fetch('/api/live',{credentials:'same-origin'}).then(r=>r.ok?r.json():null).then(l=>{if(!on||!l||!l.now)return;skew.current=l.serverNow-Date.now();setLives(l);}).catch(()=>{});
load();const a=setInterval(load,60000),b=setInterval(()=>setTick(x=>x+1),1000);
return ()=>{on=false;clearInterval(a);clearInterval(b);};},[]);
const remember=(v,s)=>{try{history.replaceState(null,'',location.pathname+'?view='+v+(s?'&subject='+encodeURIComponent(s):''));}catch(e){}};
const setView=v=>{setViewS(v);setSec('');remember(v,subj);};
const setSubj=s=>{setSubjS(s);setSec('');remember(view,s);};
React.useEffect(()=>{document.title=(CUET_VIEWS.find(x=>x.key===view)||{}).label+' | CUET (UG) | Scorebuddy';},[view]);

if(!cat)return h('div',{className:'fpq'},h('div',{className:'fpq-wrap fpq-main'},h('div',{className:'fpq-empty'},h('p',null,'Loading CUET tests…'))));
const subjects=cat.subjects||[];
const nowMs=Date.now()+skew.current;
const placedIds=(P||[]).map(o=>o.test&&(/id=(\d+)/.exec(o.test.href||'')||[])[1]).filter(Boolean);
const mine=L=>/cuet/i.test(L.exam||'')||placedIds.indexOf(String(L.mockId))>=0;
const allLive=lives.now.concat(lives.upcoming).filter(mine);
const liveNow=allLive.filter(L=>L.startsAt<=nowMs&&L.endsAt>nowMs);
const liveSoon=allLive.filter(L=>L.startsAt>nowMs);
const liveHide=allLive.filter(L=>L.endsAt>nowMs).map(L=>String(L.mockId));
/* ended live mocks that are now normal mocks (window over, set to move to practice) */
const livePast=(lives.past||[]).concat(allLive.filter(L=>L.endsAt<=nowMs&&L.after==='practice')).filter(mine);
const pastOf=id=>livePast.find(L=>String(L.mockId)===String(id));
/* rows of one group, newest placement order first by position */
const rowsOf=g=>(LS?LS.added(P,g):[]).filter(o=>o.test&&liveHide.indexOf((/id=(\d+)/.exec(o.test.href||'')||[])[1])<0).map(o=>{const t=o.test,m=/id=(\d+)/.exec(t.href||''),a=m&&tests.find(x=>String(x.id)===m[1]);
const was=m&&pastOf(m[1]);
return {title:o.title||t.title,desc:o.subtitle||'',href:t.href,q:t.questions,dur:cuetMins(t.durationMin),marks:t.maxMarks,soon:!!t.comingSoon,opensAt:t.opensAt||null,wasLive:!!was,liveMe:was&&was.me&&was.me.attemptId?was.me:null,
attempts:a?a.myAttempts||0:0,best:a?a.myBestPct:null,lastId:a?a.myLastAttemptId:null,tries:a?a.maxAttempts||null:null};});
/* sectional tests: the subject's sections; topic tests: its areas (its sections while it has none) */
const partsOf=(s,kind)=>kind==='topic'&&s.areas&&s.areas.length?s.areas:s.sections;
const count=(s,kind)=>kind==='full'?rowsOf('cuet:'+s.key+':full').length:partsOf(s,kind).reduce((n,x)=>n+rowsOf('cuet:'+s.key+':'+kind+':'+x.id).length,0);
/* groups with subjects; the chosen group / subject (defaults: the first subject that has tests of this kind) */
const groups=(cat.groups||[]).filter(g=>subjects.some(s=>s.group===g.key));
const curSubj=subjects.find(s=>s.key===subj)||subjects.find(s=>count(s,view)>0)||subjects[0];
const curGrp=grp&&groups.some(g=>g.key===grp)?(curSubj&&curSubj.group===grp?grp:grp):(curSubj?curSubj.group:(groups[0]||{}).key);
const inGrp=subjects.filter(s=>s.group===curGrp);
const S=inGrp.find(s=>curSubj&&s.key===curSubj.key)||inGrp[0];
const secs=S&&view!=='full'?partsOf(S,view):[];
const curSec=secs.find(x=>x.id===sec)||secs.find(x=>rowsOf('cuet:'+S.key+':'+view+':'+x.id).length)||secs[0];
const rows=!S?[]:view==='full'?rowsOf('cuet:'+S.key+':full'):curSec?rowsOf('cuet:'+S.key+':'+view+':'+curSec.id):[];

function tabs(list,cur,set,cls,label){
return h('div',{className:'fpq-tabs '+(cls||''),role:'tablist','aria-label':label},list.map(t=>h('button',{key:t.key,type:'button',role:'tab','aria-selected':cur===t.key,className:'fpq-tab'+(cur===t.key?' on':''),onClick:()=>set(t.key)},
h('span',null,t.label),t.count!=null?h('small',null,t.count):null)));
}
function opens(at){
if(!(at&&at>Date.now()))return h('div',{className:'fpq-opens'},h('span',{className:'ic','aria-hidden':true},'⏳'),h('div',null,h('small',null,'Coming soon'),h('b',null,'Questions are being added')));
const d=new Date(at),o={timeZone:'Asia/Kolkata'};
return h('div',{className:'fpq-opens'},h('span',{className:'ic','aria-hidden':true},'📅'),h('div',null,h('small',null,'Opens on'),
h('b',null,d.toLocaleDateString('en-IN',Object.assign({weekday:'short',day:'numeric',month:'short',year:'numeric'},o))),
h('span',{className:'t'},d.toLocaleTimeString('en-IN',Object.assign({hour:'numeric',minute:'2-digit',hour12:true},o)).toUpperCase()+' IST')));
}
/* Start / Retake with the attempts the admin allows (SBListings.tries), and the card's attempts note */
function cuStart(r,retake,start){
const T=window.SBListings?window.SBListings.tries(r):{suffix:''};
if(T.done)return h('span',{className:'fpq-btn ghost',style:{cursor:'default'},title:'You have used every attempt this test allows'},T.used);
return h('a',{className:'fpq-btn',href:r.href},(r.attempts?retake:start)+T.suffix+' →');
}
function cuTries(r){
const T=!r.soon&&window.SBListings?window.SBListings.tries(r):{};
return T.note?h('p',{className:'fpq-when',style:{margin:'10px 0 0'}},T.note):null;
}
function card(r,i){
const meta=[r.q?r.q+' Qs':null,r.dur||null,r.marks?r.marks+' marks':null].filter(Boolean);
return h('div',{key:(r.href||'')+i,className:'fpq-card'+(r.soon?' soon':'')},
h('div',{className:'fpq-card-top'},r.soon?h('span',{className:'fpq-soon-tag'},'COMING SOON'):null,h('span',{className:'fpq-badge'},'CUET'),r.wasLive?h('span',{className:'fpq-badge was'},'Was a live test'):null,
r.attempts&&!(window.SBListings&&window.SBListings.tries(r).done&&r.best==null)?h('span',{className:'fpq-done'},window.SBListings&&window.SBListings.tries(r).done?'Best '+r.best+'%':'✓ Attempted'+(r.best!=null?' · best '+r.best+'%':'')):null),
h('h3',null,r.title),r.desc?h('p',null,r.desc):null,
h('div',{className:'fpq-meta'},meta.map(m=>h('span',{key:m},m))),
h('div',{className:'fpq-acts'},r.soon?opens(r.opensAt):cuStart(r,'Retake test','Start test'),
r.liveMe?h('a',{className:'fpq-btn ghost',href:'analysis.html?id='+r.liveMe.attemptId},'Live rank & analysis'):r.lastId?h('a',{className:'fpq-btn ghost',href:'analysis.html?id='+r.lastId},'Last analysis'):null,
window.SBShare?window.SBShare.button(h,r.href,r.title):null),
r.liveMe?h('p',{className:'fpq-when',style:{margin:'10px 0 0'}},'🏆 Your live score: '+r.liveMe.score+' / '+r.liveMe.total):null,
cuTries(r));
}
// a live test the student has taken: their score, and (in the window) when rank and solutions open
function cuetTaken(L,me){
const held=L.submitBy>nowMs,at=new Date(L.submitBy).toLocaleString('en-IN',{timeZone:'Asia/Kolkata',day:'numeric',month:'short',hour:'numeric',minute:'2-digit',hour12:true})+' IST';
return h('a',{className:'fpq-taken',href:'analysis.html?id='+me.attemptId},
h('span',{className:'fpq-taken-k'},'✓ You took this test'),
h('b',null,'Score ',me.score,h('small',null,' / '+me.total)),
h('span',{className:'fpq-taken-n'},held?'🔒 Rank, percentile & solutions open '+at:'See your rank & solutions →'));
}
function liveCard(L){
const me=L.me||{},soon=L.startsAt>nowMs,href='live/'+encodeURIComponent(L.slug),meta=[L.questions?L.questions+' Qs':null,L.durationMin?cuetMins(L.durationMin):null,L.maxMarks?L.maxMarks+' marks':null].filter(Boolean);
const when=new Date(L.startsAt).toLocaleString('en-IN',{timeZone:'Asia/Kolkata',weekday:'short',day:'numeric',month:'short',hour:'numeric',minute:'2-digit',hour12:true})+' IST';
return h('div',{key:'live'+L.slug,className:'fpq-card live'+(soon?' soon':'')},
h('div',{className:'fpq-card-top'},soon?h('span',{className:'fpq-live soon'},'UPCOMING'):h('span',{className:'fpq-live'},h('i',null),'LIVE NOW'),
h('span',{className:'fpq-left'},(soon?'Starts in ':'Closes in ')+cuetLeft((soon?L.startsAt:L.endsAt)-nowMs)),h('span',{className:'fpq-badge'},'CUET')),
h('h3',null,L.title),L.description?h('p',null,L.description):null,
h('div',{className:'fpq-meta'},meta.map(m=>h('span',{key:m},m)),me.attemptId?null:L.free?h('span',{className:'price'},'Free'):window.SB_APP?null:h('span',{className:'price'},'Entry ₹'+L.priceINR)),
soon?h('p',{className:'fpq-when'},'🗓 '+when):null,
h('div',{className:'fpq-acts'},me.attemptId?cuetTaken(L,me):
soon?h('a',{className:'fpq-btn'+(me.registered?' ghost':''),href},me.registered?'✓ Registered · details':(L.free?'Register free →':window.SB_APP?'Details →':'Register · ₹'+L.priceINR+' →')):
h('a',{className:'fpq-btn',href},(me.registered||L.free?'Take the live test':'Register & take test')+' →')));
}
const liveSection=allLive.length?h('section',{className:'fpq-sec',id:'live-tests'},
h('div',{className:'fpq-head'},h('div',null,h('h2',null,'CUET Live Tests ',liveNow.length?h('span',{className:'fpq-live big'},h('i',null),liveNow.length+' LIVE NOW'):null),
h('p',null,'Take a CUET mock with everyone at the same time and see your All-India rank. One attempt each, only during the window.'))),
liveNow.length?h('div',{className:'fpq-grid'},liveNow.map(liveCard)):null,
liveSoon.length?h('div',{className:'fpq-group'},h('div',{className:'fpq-group-h'},h('h4',{style:{color:'var(--brand-primary)'}},'UPCOMING — REGISTER NOW'),h('span',null,liveSoon.length+' test'+(liveSoon.length===1?'':'s'))),
h('div',{className:'fpq-grid'},liveSoon.map(liveCard))):null):null;
const banner=liveNow.length?h('a',{className:'fpq-banner',href:'#live-tests'},h('span',{className:'fpq-live'},h('i',null),'LIVE NOW'),
h('b',null,liveNow[0].title+(liveNow.length>1?' + '+(liveNow.length-1)+' more':'')),h('span',{className:'fpq-left'},'Closes in '+cuetLeft(liveNow[0].endsAt-nowMs)),h('em',null,liveNow[0].me&&liveNow[0].me.attemptId?'✓ Taken · score '+liveNow[0].me.score+' / '+liveNow[0].me.total:'Take the test →')):null;
const pastRows=livePast.filter(L=>placedIds.indexOf(String(L.mockId))<0).map(L=>{const a=tests.find(x=>String(x.id)===String(L.mockId));
return {title:L.title,desc:L.description||'',href:'mock.html?id='+L.mockId,q:L.questions,dur:cuetMins(L.durationMin),marks:L.maxMarks,wasLive:true,liveMe:L.me&&L.me.attemptId?L.me:null,
attempts:a?a.myAttempts||0:0,best:a?a.myBestPct:null,lastId:a?a.myLastAttemptId:null,tries:a?a.maxAttempts||null:null};});
const pastSection=view==='full'&&pastRows.length?h('section',{className:'fpq-sec',id:'past-live'},h('div',{className:'fpq-head'},h('div',null,h('h2',null,'Past live mocks'),
h('p',null,'All-India CUET live mocks whose window is over — now normal mock tests. Took it live? See your score, rank and analysis.'))),h('div',{className:'fpq-grid'},pastRows.map(card))):null;
const V=CUET_VIEWS.find(x=>x.key===view);
const intro={full:'Complete subject papers, timed and marked like the real CUET — with your score, rank and a detailed analysis.',
sectional:'One section of a subject at a time — build speed and accuracy before you take the full paper.',
topic:'Short tests on one topic — find a weak area and fix it.'}[view];
const empty=S?(S.name+(view==='full'?' full mock tests':curSec?' · '+curSec.name+' '+(view==='topic'?'topic':'sectional')+' tests':' tests')+' are coming soon.'):'CUET tests are coming soon.';
return h('div',{className:'fpq'},
h('section',{className:'fpq-hero'},h('div',{className:'fpq-wrap'},
h('div',{className:'fpq-hero-top'},h('span',{className:'fpq-eyebrow'},'CUET (UG)'),
h('label',{className:'fpq-view'},h('span',null,'Show'),h('select',{value:view,onChange:e=>setView(e.target.value),'aria-label':'CUET tests'},CUET_VIEWS.map(v=>h('option',{key:v.key,value:v.key},v.label))))),
h('h1',null,'CUET ',h('span',null,V.label)),
h('p',null,'English, General Aptitude Test (GAT) and domain subjects like Accountancy, Economics and Mathematics — '+intro),
h('div',{className:'fpq-jump'},CUET_VIEWS.map(v=>h('a',{key:v.key,href:'?view='+v.key,className:view===v.key?'':'',onClick:e=>{e.preventDefault();setView(v.key);}},v.label))))),
h('div',{className:'fpq-wrap fpq-main'},banner,liveSection,
h('section',{className:'fpq-sec',id:'cuet-tests'},
h('div',{className:'fpq-head'},h('div',null,h('h2',null,V.label),h('p',null,'Pick a subject'+(view==='full'?'.':view==='topic'?', then an area.':', then a section.')))),
groups.length>1?tabs(groups.map(g=>({key:g.key,label:g.label,count:subjects.filter(s=>s.group===g.key).reduce((n,s)=>n+count(s,view),0)})),curGrp,v=>{setGrp(v);const f=subjects.find(s=>s.group===v);if(f)setSubj(f.key);},'fpq-exam','Subject group'):null,
inGrp.length?h('div',{style:{marginTop:12}},tabs(inGrp.map(s=>({key:s.key,label:s.name,count:count(s,view)})),S&&S.key,setSubj,'fpq-under','Subject')):null,
secs.length?h('div',{style:{marginTop:12}},tabs(secs.map(x=>({key:x.id,label:x.name,count:rowsOf('cuet:'+S.key+':'+view+':'+x.id).length})),curSec&&curSec.id,setSec,'fpq-area',view==='topic'?'Area':'Section')):null,
h('div',{className:'fpq-panel',role:'tabpanel'},
S?h('div',{className:'fpq-sec-title'},h('b',null,S.name+(curSec&&view!=='full'?' · '+curSec.name:'')),h('span',null,view==='full'?'Full subject paper':V.label)):null,
rows.length?h('div',{className:'fpq-grid'},rows.map(card)):h('div',{className:'fpq-empty'},h('div',{className:'fpq-empty-ic','aria-hidden':true},'🗂️'),h('p',null,empty)))),
pastSection,
h('section',{className:'fpq-sec'},h('div',{className:'fpq-head'},h('div',null,h('h2',null,'How to practise for CUET (UG)'),
h('p',null,'Each subject you choose — a language, the General Aptitude Test or a domain subject — is its own test, so train for each one separately. Full papers build stamina, sectional tests build speed, and topic tests close the gaps the others reveal.')))))
);
}
window.CuetPage=CuetPage;
