(()=>{
 const box=document.querySelector('#practice');if(!box)return;const base=(window.EF_BASE||'').replace(/\/$/,'');let data,answers={},marked=new Set(),remaining=0,timer,incorrect=[];
 box.querySelector('[data-start-test]')?.addEventListener('click',start);
 async function start(){track('test_start');box.innerHTML='<p>Loading practice…</p>';data=await fetch(base+'/api/practice/'+box.dataset.slug).then(json);if(data.randomize_questions)shuffle(data.questions);data.questions.forEach(q=>{if(data.randomize_options&&q.options)shuffle(q.options)});remaining=Number(data.time_limit)||0;render();if(remaining)timer=setInterval(()=>{remaining--;document.querySelector('#timer').textContent=time(remaining);if(remaining<=0){clearInterval(timer);submit()}},1000)}
 function render(){box.innerHTML=`<div class="testbar"><strong id="timer">${remaining?time(remaining):'Untimed'}</strong><span>${data.questions.length} questions</span><span>${marked.size} marked</span></div><nav class="qnav" aria-label="Question navigation">${data.questions.map((q,i)=>`<button type="button" data-go="${i}">${i+1}</button>`).join('')}</nav><form id="test-form">${data.questions.map(question).join('')}<button class="button">Submit answers</button></form>`;box.querySelectorAll('[data-go]').forEach(button=>button.addEventListener('click',()=>box.querySelectorAll('fieldset')[Number(button.dataset.go)].scrollIntoView({behavior:'smooth'})));box.querySelectorAll('[data-mark]').forEach(button=>button.addEventListener('click',()=>{const id=button.dataset.mark;marked.has(id)?marked.delete(id):marked.add(id);button.classList.toggle('marked');button.textContent=marked.has(id)?'★ Marked':'☆ Mark for review'}));box.querySelector('form').addEventListener('submit',event=>{event.preventDefault();submit()})}
 function question(q,i){let input='';const name=escapeAttr(q.id);if(q.type==='matching'&&Array.isArray(q.pairs)){input=q.pairs.map((pair,index)=>`<label>${escapeHtml(pair.left)}<select name="${name}__${index}" data-match-key="${escapeAttr(pair.key??pair.left)}"><option value="">Choose…</option>${(pair.options||q.match_options||[]).map(option=>`<option value="${escapeAttr(option.value??option)}">${escapeHtml(option.label??option)}</option>`).join('')}</select></label>`).join('')}else{let options=q.options;if(q.type==='true_false'&&!options)options=[{value:'true',label:'True'},{value:'false',label:'False'}];if(options){const multiple=q.type==='multiple_choice';input=options.map(option=>`<label><input type="${multiple?'checkbox':'radio'}" name="${name}" value="${escapeAttr(option.value??option)}"> ${escapeHtml(option.label??option)}</label>`).join('')}else input=`<label>Answer<input name="${name}" autocomplete="off"></label>`}return`<fieldset data-q="${name}"><legend>${i+1}. ${escapeHtml(q.prompt)}</legend>${q.passage?`<blockquote>${escapeHtml(q.passage)}</blockquote>`:''}${input}<button type="button" class="mark" data-mark="${name}">☆ Mark for review</button></fieldset>`}
 async function submit(){clearInterval(timer);collect();const result=await fetch(base+'/api/practice/'+box.dataset.slug+'/submit',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({answers,csrf:data.csrf})}).then(json);incorrect=result.results.filter(x=>!x.correct).map(x=>x.id);const topics={};result.results.forEach(x=>{const topic=x.topic||'General';topics[topic]??={right:0,total:0};topics[topic].total++;if(x.correct)topics[topic].right++});box.innerHTML=`<div class="notice"><h2>Score: ${result.score}/${result.total}</h2><p>${Math.round(result.score/Math.max(1,result.total)*100)}% correct</p></div><h3>Topic score</h3><ul>${Object.entries(topics).map(([k,v])=>`<li>${escapeHtml(k)}: ${v.right}/${v.total}</li>`).join('')}</ul>${result.results.map(x=>`<div class="panel ${x.correct?'correct':'wrong'}"><strong>${x.correct?'Correct':'Needs review'}</strong><p>${escapeHtml(data.questions.find(q=>q.id===x.id)?.prompt||x.id)}</p><p>Correct answer: ${escapeHtml(formatAnswer(x.answer))}</p><p>${escapeHtml(x.explanation)}</p></div>`).join('')}<div class="result-actions"><button class="button" id="retry">Retry incorrect</button><button class="button" id="print-result">Print result</button></div>`;box.querySelector('#retry').addEventListener('click',retry);box.querySelector('#print-result').addEventListener('click',()=>window.print());localStorage.setItem('ef-progress-'+box.dataset.slug,JSON.stringify({score:result.score,total:result.total,incorrect,date:new Date().toISOString()}));track('test_complete',result.score)}
 function retry(){data.questions=data.questions.filter(q=>incorrect.includes(q.id));answers={};marked.clear();remaining=0;render()}
 function collect(){data.questions.forEach(q=>{if(q.type==='matching'){const pairs={};box.querySelectorAll(`[data-q="${CSS.escape(q.id)}"] [data-match-key]`).forEach(select=>pairs[select.dataset.matchKey]=select.value);answers[q.id]=pairs;return}const checked=[...box.querySelectorAll(`[name="${CSS.escape(q.id)}"]:checked`)];const textInput=box.querySelector(`[name="${CSS.escape(q.id)}"]:not([type=radio]):not([type=checkbox])`);answers[q.id]=textInput?textInput.value:(q.type==='multiple_choice'?checked.map(x=>x.value):checked[0]?.value??null)})}
 function track(event,value=1){window.examfitTrack?.(event,value,box.dataset.slug)}function json(response){if(!response.ok)throw Error(response.status===419?'Session expired. Reload the page.':'Practice request failed.');return response.json()}function time(seconds){return`${Math.floor(seconds/60)}:${String(seconds%60).padStart(2,'0')}`}function shuffle(items){for(let i=items.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1));[items[i],items[j]]=[items[j],items[i]]}}function escapeHtml(value){const node=document.createElement('div');node.textContent=String(value??'');return node.innerHTML}function escapeAttr(value){return escapeHtml(value).replace(/"/g,'&quot;')}function formatAnswer(value){if(Array.isArray(value))return value.join(', ');if(value&&typeof value==='object')return Object.entries(value).map(([k,v])=>`${k}: ${v}`).join('; ');return String(value??'')}
})();
