import * as Q from "./quantum.js";
const $=(r,s)=>r.querySelector(s), $$=(r,s)=>[...r.querySelectorAll(s)];
const esc=s=>String(s).replace(/[&<>"']/g,c=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[c]));
const f=(x,n=5)=>Math.abs(x)<1e-10?"0":Number(x.toFixed(n)).toString();
const zstr=z=>Math.abs(z[1])<1e-10?f(z[0]):Math.abs(z[0])<1e-10?f(z[1])+"i":f(z[0])+(z[1]<0?" − ":" + ")+f(Math.abs(z[1]))+"i";
const pct=x=>f(100*x,2)+"%";
const fld=(name,label,value,min=-100,max=100,step="any")=>'<label>'+label+'<input name="'+name+'" type="number" value="'+value+'" min="'+min+'" max="'+max+'" step="'+step+'"></label>';
const range=(name,label,value,min,max,step=.01)=>'<label>'+label+' <output data-for="'+name+'">'+value+'</output><input name="'+name+'" type="range" value="'+value+'" min="'+min+'" max="'+max+'" step="'+step+'"></label>';
const sel=(name,label,opts,value)=>'<label>'+label+'<select name="'+name+'">'+opts.map(o=>{const a=Array.isArray(o)?o:[o,o];return '<option value="'+a[0]+'"'+(String(a[0])===String(value)?" selected":"")+'>'+a[1]+'</option>';}).join("")+'</select></label>';
const btn=(action,label,cls="")=>'<button type="button" data-action="'+action+'" class="'+cls+'">'+label+'</button>';
const table=(heads,rows)=>'<div class="table-scroll"><table><thead><tr>'+heads.map(h=>'<th>'+h+'</th>').join("")+'</tr></thead><tbody>'+rows.map(row=>'<tr>'+row.map(c=>'<td>'+c+'</td>').join("")+'</tr>').join("")+'</tbody></table></div>';
const matrix=r=>'<div class="matrix" role="img" aria-label="'+esc(r.map(row=>row.map(zstr).join(", ")).join("; "))+'">'+r.map(row=>'<div>'+row.map(z=>'<span>'+zstr(z)+'</span>').join("")+'</div>').join("")+'</div>';
const stats=items=>'<div class="stats">'+items.map(([l,v])=>'<div><span>'+l+'</span><strong>'+v+'</strong></div>').join("")+'</div>';
function bars(p,labels=null,counts=null,limit=64){
 let indices=p.map((_,i)=>i);if(p.length>limit)indices=indices.sort((a,b)=>p[b]-p[a]||a-b).slice(0,limit).sort((a,b)=>a-b);
 const total=counts?.reduce((a,b)=>a+b,0)||1;
 return '<div class="bars" role="list" aria-label="Outcome probabilities">'+indices.map(i=>'<div class="bar-row" role="listitem"><span>'+esc(labels?labels[i]:String(i))+'</span><div class="bar-track"><i style="width:'+Math.max(0,p[i]*100)+'%"></i>'+(counts?'<b style="left:'+counts[i]/total*100+'%"></b>':"")+'</div><span>'+pct(p[i])+(counts?' <small>('+counts[i]+')</small>':"")+'</span></div>').join("")+'</div>'+(p.length>limit?'<p class="fine">Showing '+limit+' largest probabilities, ordered by outcome. All outcomes remain in the calculation.</p>':"");
}
function amplitudes(v,n,max=32){let ids=v.map((_,i)=>i).filter(i=>Q.abs2(v[i])>1e-12);if(!ids.length)ids=[0];let truncated=ids.length>max;ids=ids.slice(0,max);return table(["Basis","Amplitude","Probability"],ids.map(i=>["|"+Q.bits(i,n)+"⟩",zstr(v[i]),pct(Q.abs2(v[i]))]))+(truncated?'<p class="fine">First '+max+' nonzero amplitudes shown.</p>':"");}
function complexPlot(v){
 const pts=v.slice(0,16);return '<svg class="complex-plot" viewBox="0 0 300 260" role="img" aria-label="Complex amplitudes: horizontal real axis, vertical imaginary axis"><path d="M20 130H280M150 10V250" stroke="#91a4bd"/><circle cx="150" cy="130" r="104" fill="none" stroke="#d4deeb"/><text x="264" y="150">Re</text><text x="157" y="22">Im</text>'+pts.map((z,i)=>{const x=150+104*z[0],y=130-104*z[1];return '<line x1="150" y1="130" x2="'+x+'" y2="'+y+'" stroke="hsl('+((i*83+210)%360)+' 70% 43%)" stroke-width="3"/><circle cx="'+x+'" cy="'+y+'" r="4" fill="hsl('+((i*83+210)%360)+' 70% 43%)"/><text x="'+(x+6)+'" y="'+(y-5)+'">'+i+'</text>';}).join("")+'</svg>';
}
let sphereCount=0;
function sphere(r,az=.7){
 const fillID="bloch-fill-"+(++sphereCount);
 const el=.4,c=Math.cos(az),s=Math.sin(az),ce=Math.cos(el),se=Math.sin(el);
 const pr=([x,y,z])=>[175+116*(c*x-s*y),150+116*(se*(s*x+c*y)-ce*z)];
 const curve=fn=>Array.from({length:97},(_,j)=>pr(fn(2*Math.PI*j/96))).map((p,j)=>(j?"L":"M")+p.map(x=>f(x,2)).join(",")).join(" ");
 const origin=pr([0,0,0]),tip=pr(r);
 const axes=[[[1,0,0],"X / |+⟩"],[[0,1,0],"Y / |+i⟩"],[[0,0,1],"Z / |0⟩"]];
 return '<svg class="sphere" viewBox="0 0 350 310" role="img" aria-label="Bloch vector x '+f(r[0])+' y '+f(r[1])+' z '+f(r[2])+'"><defs><radialGradient id="'+fillID+'"><stop stop-color="#ffffff"/><stop offset="1" stop-color="#e6f0fc"/></radialGradient></defs><circle cx="175" cy="150" r="116" fill="url(#'+fillID+')" stroke="#b6c9e1"/><g fill="none" stroke="#b6c9e1"><path d="'+curve(t=>[Math.cos(t),Math.sin(t),0])+'"/><path d="'+curve(t=>[Math.cos(t),0,Math.sin(t)])+'"/><path d="'+curve(t=>[0,Math.cos(t),Math.sin(t)])+'"/></g>'+axes.map(([v,l])=>{const a=pr(v),b=pr(v.map(x=>-x));return '<path d="M'+b.join(",")+"L"+a.join(",")+'" stroke="#8295b0"/><text x="'+(a[0]+4)+'" y="'+(a[1]-7)+'">'+l+'</text>';}).join("")+'<line x1="'+origin[0]+'" y1="'+origin[1]+'" x2="'+tip[0]+'" y2="'+tip[1]+'" stroke="#1760dc" stroke-width="4"/><circle cx="'+tip[0]+'" cy="'+tip[1]+'" r="6" fill="#1760dc"/><text x="157" y="297">|1⟩</text></svg>';
}
function plot(values,{min=0,max=1,labels=null,title="Value by step"}={}){
 const w=620,h=230,pad=36,N=values.length,xy=(v,i)=>[pad+i*(w-2*pad)/Math.max(1,N-1),h-pad-(v-min)/(max-min)*(h-2*pad)];
 return '<svg class="line-plot" viewBox="0 0 '+w+" "+h+'" role="img" aria-label="'+esc(title)+'"><path d="M36 20V194H600" fill="none" stroke="#a8b8cf"/><text x="3" y="28">'+f(max,2)+'</text><text x="3" y="195">'+f(min,2)+'</text><polyline points="'+values.map((v,i)=>xy(v,i).join(",")).join(" ")+'" fill="none" stroke="#1760dc" stroke-width="3"/>'+values.map((v,i)=>{const p=xy(v,i);return '<circle cx="'+p[0]+'" cy="'+p[1]+'" r="3" fill="#1760dc">'+(labels?'<title>'+esc(labels[i])+": "+f(v)+'</title>':"")+'</circle>'+(N<=24?'<text x="'+p[0]+'" y="217" text-anchor="middle">'+(labels?esc(labels[i]):i)+'</text>':"");}).join("")+'</svg>';
}
function shell(root,controls,initial,render,actions={}){
 root.innerHTML='<div class="lab-controls">'+controls+'</div><div class="lab-error" role="alert" hidden></div><div class="lab-result" aria-live="polite"></div>';
 const state={...initial};
 function read(){for(const e of $$(root,"input,select")){if(e.type==="checkbox")state[e.name]=e.checked;else if(e.type==="number"||e.type==="range"){let x=Number(e.value);if(!Number.isFinite(x)||e.value==="")throw Error("Enter a finite number for "+e.name);if(e.min!==""&&x<Number(e.min)||e.max!==""&&x>Number(e.max))throw Error("Value outside the allowed range for "+e.name);if(e.step==="1"&&!Number.isInteger(x))throw Error("Enter a whole number for "+e.name);state[e.name]=x;}else state[e.name]=e.value;}
 for(const o of $$(root,"output[data-for]"))o.textContent=f(Number(state[o.dataset.for]),3);}
 function update(){try{read();const out=render(state);$(root,".lab-result").innerHTML=out;$(root,".lab-error").hidden=true;}catch(e){$(root,".lab-result").innerHTML="";$(root,".lab-error").textContent=e.message;$(root,".lab-error").hidden=false;}}
 root.addEventListener("input",e=>{if(e.target.matches("input,select"))update();});
 root.addEventListener("change",e=>{if(e.target.matches("input,select"))update();});
 root.addEventListener("click",e=>{const b=e.target.closest("[data-action]");if(!b||!root.contains(b))return;try{read();actions[b.dataset.action]?.(state,b,root);update();}catch(err){$(root,".lab-result").innerHTML="";$(root,".lab-error").textContent=err.message;$(root,".lab-error").hidden=false;}});
 update();return {state,update};
}
const val=(root,name,value)=>{const e=$(root,'[name="'+name+'"]');if(e)e.value=value;};
const ampInputs=(prefix,a,b)=>fld(prefix+"ar","α real",a[0])+fld(prefix+"ai","α imaginary",a[1])+fld(prefix+"br","β real",b[0])+fld(prefix+"bi","β imaginary",b[1]);
const raw=s=>[Q.C(s.ar,s.ai),Q.C(s.br,s.bi)];
const sharedShots=()=>fld("shots","Shots",1024,1,10000,1)+fld("seed","Random seed",7,0,4294967295,1)+btn("resample","New sample");
function derivation(v,basis){
 const n2=v.reduce((s,z)=>s+Q.abs2(z),0),labels=basis==="Z"?["0","1"]:basis==="X"?["+","−"]:["+i","−i"],factor=basis==="Z"?1:2;
 const bras={Z:[[Q.C(1),Q.C()],[Q.C(),Q.C(1)]],X:[[Q.C(1),Q.C(1)],[Q.C(1),Q.C(-1)]],Y:[[Q.C(1),Q.C(0,-1)],[Q.C(1),Q.C(0,1)]]}[basis];
 return labels.map((label,j)=>{
 const bra=bras[j],terms=bra.map((z,k)=>Q.mul(z,v[k])),sum=terms.reduce(Q.add,Q.C()),den=n2*factor,amp=Q.scale(sum,1/Math.sqrt(den)),num=Q.abs2(sum);
 const integer=Number.isSafeInteger(num)&&Number.isSafeInteger(den),g=integer?Q.gcd(num,den):1,exact=integer?String(num/g)+(den/g===1?"":"/"+String(den/g)):f(num/den,8);
 return '<div class="derivation"><h4>Outcome |'+label+'⟩</h4><ol><li>Bra: ⟨'+label+'| = [('+zstr(bra[0])+')⟨0| + ('+zstr(bra[1])+')⟨1|]'+(factor===2?' / √2':'')+'. The state normalization is √'+f(n2)+'.</li><li>Projection: [('+zstr(bra[0])+')('+zstr(v[0])+')⟨0|0⟩ + ('+zstr(bra[0])+')('+zstr(v[1])+')⟨0|1⟩ + ('+zstr(bra[1])+')('+zstr(v[0])+')⟨1|0⟩ + ('+zstr(bra[1])+')('+zstr(v[1])+')⟨1|1⟩] / √'+f(den)+'.</li><li>Use ⟨0|0⟩ = ⟨1|1⟩ = 1 and ⟨0|1⟩ = ⟨1|0⟩ = 0. The two cross terms vanish. The surviving terms are ('+zstr(terms[0])+') + ('+zstr(terms[1])+').</li><li>Complex amplitude: ('+zstr(sum)+') / √'+f(den)+' ≈ '+zstr(amp)+'. In the Y basis, check the conjugated ±i and i² = −1 before adding.</li><li>Modulus squared: [('+zstr(Q.conj(sum))+')('+zstr(sum)+')] / '+f(den)+' = [('+f(sum[0])+')² + ('+f(sum[1])+')²] / '+f(den)+' = '+f(num)+'/'+f(den)+' = <strong>'+exact+'</strong>'+(integer?' ≈ '+f(num/den,8):' (rounded)')+'.</li></ol></div>';
 }).join("");
}
function stateLab(root,blochMode=false){
 const controls=ampInputs("",[2,0],[0,3])+sel("basis","Measurement basis",["Z","X","Y"],"Z")+sharedShots()+range("view","Sphere viewing angle",.7,-3.14,3.14)+
 (blochMode?range("theta","Preparation θ (radians)",1.2,0,Math.PI)+range("phi","Preparation φ (radians)",1.57,-Math.PI,Math.PI)+btn("prepare","Prepare from angles"):"")+
 '<div class="button-row">'+["H","X","Y","Z","S","Sdg"].map(g=>btn(g,g)).join("")+btn("example","2|0⟩ + 3i|1⟩")+'</div>'+btn("derive","Show / hide derivation");
 shell(root,controls,{show:false},s=>{
 const p=Q.projection(raw(s),s.basis),r=Q.bloch(p.state),counts=Q.sample(p.probabilities,s.shots,s.seed),labels=s.basis==="Z"?["|0⟩","|1⟩"]:s.basis==="X"?["|+⟩","|−⟩"]:["|+i⟩","|−i⟩"];
 return stats([["Raw norm²",f(p.norm**2)],["Normalized α",zstr(p.state[0])],["Normalized β",zstr(p.state[1])]])+
 '<div class="visual-pair">'+sphere(r,s.view)+complexPlot(p.state)+'</div>'+stats([["⟨X⟩",f(r[0])],["⟨Y⟩",f(r[1])],["⟨Z⟩",f(r[2])]])+
 '<p class="fine">Bars: exact probabilities. Dark markers and counts: sampled frequencies. Complex arrows label amplitudes 0 and 1.</p>'+bars(p.probabilities,labels,counts)+
 (s.show?derivation(raw(s),s.basis):"");
 },Object.fromEntries([
 ["resample",(s,b,r)=>val(r,"seed",(s.seed+1)>>>0)],["derive",s=>s.show=!s.show],["example",(s,b,r)=>{for(const[k,v]of Object.entries({ar:2,ai:0,br:0,bi:3}))val(r,k,v);}],
 ["prepare",(s,b,r)=>{const v=Q.blochState(s.theta,s.phi);["ar","ai","br","bi"].forEach((k,i)=>val(r,k,v[Math.floor(i/2)][i%2]));}],
 ...["H","X","Y","Z","S","Sdg"].map(g=>[g,(s,b,r)=>{const v=Q.apply1(Q.normalize(raw(s)),1,0,Q.gate(g));["ar","ai","br","bi"].forEach((k,i)=>val(r,k,v[Math.floor(i/2)][i%2]));}])
 ]));
}
function practice(root){
 let item=null,stage=0,message="";
 function generate(seed){const r=Q.rng(seed);let a=Math.floor(r()*7)+1,b=Math.floor(r()*7)+1,sgn=r()<.5?-1:1,imag=r()<.5;const v=[Q.C(a),imag?Q.C(0,sgn*b):Q.C(sgn*b)],basis=["Z","X","Y"][Math.floor(r()*3)],out=r()<.5?0:1;return{v,basis,out,p:Q.projection(v,basis).probabilities[out]};}
 shell(root,fld("seed","Problem seed",31,0,1e9,1)+btn("new","New problem")+'<label>Your probability<input name="answer" type="text" autocomplete="off" inputmode="decimal" placeholder="e.g. 25/26"></label>'+btn("check","Check answer")+btn("hint","Hint")+btn("step","Next step")+btn("all","Full derivation"),{},s=>{
 if(!item||item.seed!==s.seed){item={...generate(s.seed),seed:s.seed};stage=0;message="";}
 const l=item.basis==="Z"?["0","1"]:item.basis==="X"?["+","−"]:["+i","−i"];
 const html=derivation(item.v,item.basis),tmp=document.createElement("div");tmp.innerHTML=html;
 const target=tmp.children[item.out];[...target.querySelectorAll("li")].forEach((li,i)=>li.hidden=i>=stage);
 return '<p class="problem">For |ψ⟩ = [('+zstr(item.v[0])+')|0⟩ + ('+zstr(item.v[1])+')|1⟩] / √'+f(Q.norm(item.v)**2)+', find P('+l[item.out]+') in the '+item.basis+' basis.</p>'+(message?'<p class="feedback">'+esc(message)+'</p>':"")+(stage?target.outerHTML:"");
 },{new:(s,b,r)=>val(r,"seed",s.seed+1),hint:()=>{message="Conjugate the basis ket to form its bra. Project first, then multiply the amplitude by its conjugate.";},step:()=>stage=Math.min(5,stage+1),all:()=>stage=5,check:s=>{
 const t=s.answer.trim(),m=t.match(/^([+-]?(?:\d+(?:\.\d*)?|\.\d+))(?:\s*\/\s*([+-]?(?:\d+(?:\.\d*)?|\.\d+)))?$/);
 if(!m){message="Enter a decimal or a fraction such as 25/26.";return;}const v=Number(m[1])/(m[2]===undefined?1:Number(m[2]));
 message=Number.isFinite(v)&&Math.abs(v-item.p)<1e-5?"Correct. Explain which inner products vanished before moving on.":"That value does not match the projection. Check normalization, the bra's conjugation, and modulus squared.";
 }});
}
function bitsLab(root){shell(root,sel("gate","Reversible gate",["CNOT","Toffoli"],"Toffoli")+range("p","Independent bit-flip probability",.1,0,1),{},s=>{
 const n=s.gate==="CNOT"?2:3,rows=Array.from({length:2**n},(_,x)=>{const b=Q.bits(x,n).split("").map(Number);const out=b.slice();out[n-1]^=n===2?b[0]:b[0]&b[1];return [b.join(""),out.join("")];});
 return table(["Input","Output"],rows)+stats([["One-bit failure",pct(s.p)],["Three-bit majority failure",pct(Q.repetition(s.p,3))]])+plot(Array.from({length:21},(_,j)=>Q.repetition(j/20,3)),{labels:Array.from({length:21},(_,j)=>f(j/20,2)),title:"Three-bit failure versus independent physical error"})+'<p class="fine">Encoding and recovery are assumed perfect. The repetition code protects one classical bit against independent flips.</p>';
 });}
function entanglement(root){
 let c="";for(let j=0;j<4;j++)c+=fld("r"+j,"Re |"+Q.bits(j,2)+"⟩",j===0||j===3?1:0)+fld("i"+j,"Im |"+Q.bits(j,2)+"⟩",0);
 shell(root,c+btn("bell","Bell state")+btn("product","Product |++⟩")+btn("example","Partial-trace example"),{},s=>{
 const v=Q.normalize(Array.from({length:4},(_,j)=>Q.C(s["r"+j],s["i"+j]))),a=Q.reduced(v,2,[0]),b=Q.reduced(v,2,[1]),conc=2*Math.sqrt(Q.abs2(Q.sub(Q.mul(v[0],v[3]),Q.mul(v[1],v[2]))));
 return amplitudes(v,2)+'<div class="visual-pair"><div><h4>ρA = TrB ρ</h4>'+matrix(a)+sphere(Q.rhoBloch(a))+'</div><div><h4>ρB = TrA ρ</h4>'+matrix(b)+sphere(Q.rhoBloch(b))+'</div></div>'+stats([["Purity A",f(Q.purity(a))],["Entropy A (bits)",f(Q.entropyQubit(a))],["Concurrence (pure joint state)",f(conc)]])+'<p class="fine">(ρA)aa′ = Σb cab c*a′b. These are reduced states of the entered pure joint state; local mixedness alone would not certify entanglement for an unknown mixed joint state.</p>';
 },Object.fromEntries([["bell",[[1,0],[0,0],[0,0],[1,0]]],["product",[[1,0],[1,0],[1,0],[1,0]]],["example",[[1,0],[0,2],[2,0],[0,0]]]].map(([name,v])=>[name,(s,b,r)=>v.forEach((z,j)=>{val(r,"r"+j,z[0]);val(r,"i"+j,z[1]);})])));
}
function noiseLab(root){shell(root,range("theta","Input θ",Math.PI/2,0,Math.PI)+range("phi","Input φ",0,-Math.PI,Math.PI)+range("mix","Initial mixing with I/2",0,0,1)+sel("channel","Channel",[["amplitude","Amplitude damping γ"],["depolarizing","Depolarization p"],["phase","Phase flip p"],["bit","Bit flip p"]],"amplitude")+range("p","Channel parameter",.25,0,1),{},s=>{
 let rho=Q.outer(Q.blochState(s.theta,s.phi));rho=rho.map((row,j)=>row.map((z,k)=>Q.add(Q.scale(z,1-s.mix),Q.C(j===k?s.mix/2:0))));const out=Q.channel(rho,s.channel,s.p),r=Q.rhoBloch(out),len=Math.hypot(...r);
 return '<div class="visual-pair"><div><h4>Input density matrix</h4>'+matrix(rho)+'</div><div><h4>Output density matrix</h4>'+matrix(out)+'</div></div>'+sphere(r)+stats([["Trace",f(Q.trace(out)[0])],["Purity",f(Q.purity(out))],["Eigenvalues",f((1+len)/2)+", "+f((1-len)/2)]])+'<p class="fine">'+({amplitude:"Excited population is multiplied by 1−γ; coherences by √(1−γ).",depolarizing:"Convention: E(ρ) = (1−p)ρ + p I/2. The total nonidentity Pauli probability is 3p/4.",phase:"Convention: E(ρ) = (1−p)ρ + p ZρZ. Complete dephasing is p=1/2; p=1 is a deterministic Z gate.",bit:"Convention: E(ρ) = (1−p)ρ + p XρX."}[s.channel])+'</p>';
 });}
function chshLab(root){shell(root,range("a","Alice a",0,-Math.PI,Math.PI)+range("ap","Alice a′",Math.PI/2,-Math.PI,Math.PI)+range("b","Bob b",Math.PI/4,-Math.PI,Math.PI)+range("bp","Bob b′",-Math.PI/4,-Math.PI,Math.PI)+range("v","Bell visibility",1,0,1)+sharedShots(),{},s=>{
 const r=Q.chsh([s.a,s.ap,s.b,s.bp],s.v,s.shots,s.seed);return stats([["Exact S",f(r.S)],["Sampled S",f(r.observed)],["Shot-noise standard error",f(r.standardError)]])+table(["Setting","Exact correlation","Sampled","Counts ++,+−,−+,−−"],r.correlations.map((e,j)=>[["a,b","a,b′","a′,b","a′,b′"][j],f(e),f(r.estimates[j]),r.counts[j].join(", ")]))+'<p>Local hidden-variable bound: |S| ≤ 2. Quantum maximum: 2√2 ≈ 2.82843.</p><p class="fine">Shots are per setting. This is a classical sampler of specified quantum probabilities, not a loophole-free Bell test. Marginals stay uniform.</p>';
 },{resample:(s,b,r)=>val(r,"seed",s.seed+1)});}
function teleportLab(root){shell(root,ampInputs("",[2,0],[0,3])+sel("branch","Alice measurement bits",["00","01","10","11"],"11")+'<label class="check"><input name="correctX" type="checkbox" checked>Apply X correction when b=1</label><label class="check"><input name="correctZ" type="checkbox" checked>Apply Z correction when a=1</label>',{},s=>{
 const v=Q.normalize(raw(s)),r=Q.teleport(v),b=r.branches[parseInt(s.branch,2)];let out=b.before;if(b.b&&s.correctX)out=Q.apply1(out,1,0,Q.X);if(b.a&&s.correctZ)out=Q.apply1(out,1,0,Q.Z);
 return stats([["Branch probability",pct(b.p)],["Output fidelity",f(Q.abs2(Q.inner(v,out)),8)]])+table(["Alice ab","Bob amplitude 0 before","Bob amplitude 1 before","Branch probability"],r.branches.map(b=>[""+b.a+b.b,zstr(b.before[0]),zstr(b.before[1]),pct(b.p)]))+'<h4>Selected output after enabled corrections</h4>'+amplitudes(out,1)+'<p class="fine">Correction order: X when b=1, then Z when a=1. Without Alice’s two classical bits, Bob’s averaged state is I/2.</p>';
 });}
function denseLab(root){shell(root,sel("message","Two-bit message",["00","01","10","11"],"11"),{},s=>{const a=Number(s.message[0]),b=Number(s.message[1]),r=Q.dense(a,b);return '<h4>Encoded shared state</h4>'+amplitudes(r.encoded,2)+'<h4>Alice’s reduced state before transmission</h4>'+matrix(Q.reduced(r.encoded,2,[0]))+'<h4>Decoded computational distribution</h4>'+bars(r.probabilities,["00","01","10","11"])+ '<p class="fine">Alice sends her qubit after Xᵇ then Zᵃ. Bob uses CNOT then H. One previously shared Bell pair is consumed.</p>';});}
function circuitSVG(n,ops,active=ops.length){
 const width=Math.max(440,110+ops.length*55),height=n*56+30;
 return '<div class="circuit-scroll"><svg class="circuit-svg" viewBox="0 0 '+width+" "+height+'" style="min-width:'+width+'px" role="img" aria-label="Circuit from left to right">'+Array.from({length:n},(_,q)=>'<text x="4" y="'+(q*56+38)+'">q'+q+'</text><path d="M38 '+(q*56+32)+' H'+(width-15)+'" stroke="#7184a0"/>').join("")+ops.map((o,j)=>{
 const x=70+j*55,y=o.q*56+32,cs=o.name==="CCX"?[o.control,o.control2]:["CX","CZ","CP","SWAP"].includes(o.name)?[o.control]:[];
 if(o.name==="SWAP")return '<g opacity="'+(j<active?1:.32)+'"><line x1="'+x+'" x2="'+x+'" y1="'+y+'" y2="'+(o.control*56+32)+'" stroke="#1760dc" stroke-width="2"/>'+[o.q,o.control].map(q=>{const y=q*56+32;return '<path d="M'+(x-7)+' '+(y-7)+'L'+(x+7)+' '+(y+7)+'M'+(x-7)+' '+(y+7)+'L'+(x+7)+' '+(y-7)+'" stroke="#1760dc" stroke-width="2"/>';}).join('')+'</g>';
 return '<g opacity="'+(j<active?1:.32)+'">'+cs.map(c=>'<line x1="'+x+'" x2="'+x+'" y1="'+(c*56+32)+'" y2="'+y+'" stroke="#1760dc" stroke-width="2"/><circle cx="'+x+'" cy="'+(c*56+32)+'" r="4" fill="#1760dc"/>').join("")+'<rect x="'+(x-18)+'" y="'+(y-18)+'" width="36" height="36" rx="5" fill="#edf4ff" stroke="#1760dc"/><text x="'+x+'" y="'+(y+5)+'" text-anchor="middle">'+esc(["CX","CCX"].includes(o.name)?"X":o.name==="CZ"?"Z":o.name==="CP"?"P":o.name)+'</text>'+(o.condition?'<text x="'+x+'" y="'+(y+31)+'" text-anchor="middle" style="font-size:9px">q'+o.condition.q+'=1</text>':'')+'</g>';
 }).join("")+'</svg></div>';
}
function circuitLab(root){
 let ops=[{name:"H",q:0},{name:"CX",control:0,q:1}],currentN=2,step=2;
 shell(root,sel("n","Qubits",[1,2,3,4,5,6,7,8],2)+sel("gate","Gate",["H","X","Y","Z","S","Sdg","T","Rx","Ry","Rz","P","CX","CZ","CP","CCX","SWAP","M"],"H")+fld("target","Target q",0,0,7,1)+fld("control","Control / swap q",1,0,7,1)+fld("control2","Second control q",2,0,7,1)+fld("angle","Rotation angle (radians)",Math.PI/2,-100,100)+sel("cond","Classical condition",[["none","Unconditional"],["0","If measured q0=1"],["1","If measured q1=1"],["2","If measured q2=1"]],"none")+btn("append","Append gate")+btn("clear","Clear circuit")+btn("bell","Bell preset")+btn("ghz","GHZ preset")+btn("prev","Previous step")+btn("next","Next step")+btn("end","Final step")+sharedShots()+btn("export","Export circuit JSON"),{},s=>{
 const n=Number(s.n);if(currentN!==n){ops=[];step=0;currentN=n;}const r=Q.circuit(n,ops,s.seed),view=r.history[Math.min(step,ops.length)],counts=Q.circuitShots(n,ops,s.shots,s.seed),p=Q.probabilities(view.state);
 return circuitSVG(n,ops,step)+stats([["Step",Math.min(step,ops.length)+" / "+ops.length],["Operation",esc(view.label)],["State norm²",f(Q.norm(view.state)**2)]])+
 '<div class="op-list">'+ops.map((o,j)=>'<div><span>'+j+": "+esc(o.name)+" q"+o.q+(o.control!==undefined?" ← q"+o.control:"")+(o.angle!==undefined?" ("+f(o.angle)+" rad)":"")+(o.condition?" if q"+o.condition.q+"=1":"")+'</span>'+btn("up","↑","small") .replace('data-action="up"','data-action="up" data-index="'+j+'"')+btn("remove","Remove","small").replace('data-action="remove"','data-action="remove" data-index="'+j+'"')+'</div>').join("")+'</div>'+
 '<h4>State at selected step</h4>'+amplitudes(view.state,n)+'<h4>Final Z measurement samples</h4>'+bars(counts.map(c=>c/s.shots),Array.from({length:2**n},(_,x)=>Q.bits(x,n)))+
 '<p class="fine">Histogram bars are sampled final frequencies. The state table follows one seeded trajectory when mid-circuit measurements are present. Each histogram shot reruns the complete circuit. At most 40 operations.</p>';
 },{append:(s)=>{
 const n=Number(s.n),q=s.target,g=s.gate;if(!Number.isInteger(q)||q>=n)throw Error("Target wire is outside the register.");if(ops.length>=40)throw Error("The teaching workbench supports 40 operations.");
 const o={name:g,q};if(["CX","CZ","CP","CCX","SWAP"].includes(g)){if(s.control>=n||s.control===q)throw Error("Choose a different valid control wire.");o.control=s.control;}
 if(g==="CCX"){if(s.control2>=n||s.control2===q||s.control2===s.control)throw Error("Toffoli needs three distinct valid wires.");o.control2=s.control2;}
 if(["Rx","Ry","Rz","P","CP"].includes(g))o.angle=s.angle;
 if(s.cond!=="none"){const c=Number(s.cond);if(!ops.some(o=>o.name==="M"&&o.q===c))throw Error("Measure the condition wire first.");o.condition={q:c,value:1};}
 ops.push(o);step=ops.length;
 },clear:()=>{ops=[];step=0;},bell:(s)=>{if(Number(s.n)<2)throw Error("Bell preparation needs two qubits.");ops=[{name:"H",q:0},{name:"CX",control:0,q:1}];step=2;},ghz:s=>{ops=[{name:"H",q:0},...Array.from({length:Number(s.n)-1},(_,j)=>({name:"CX",control:0,q:j+1}))];step=ops.length;},prev:()=>step=Math.max(0,step-1),next:()=>step=Math.min(ops.length,step+1),end:()=>step=ops.length,resample:(s,b,r)=>val(r,"seed",s.seed+1),remove:(s,b)=>{const next=ops.filter((_,j)=>j!==Number(b.dataset.index));Q.circuit(Number(s.n),next,s.seed);ops=next;step=Math.min(step,ops.length);},up:(s,b)=>{const j=Number(b.dataset.index);if(j>0){const next=ops.slice();[next[j],next[j-1]]=[next[j-1],next[j]];Q.circuit(Number(s.n),next,s.seed);ops=next;}},export:s=>download("quantum-circuit.json",JSON.stringify({format:"qc2026-v1",qubits:Number(s.n),ordering:"q0-most-significant",operations:ops},null,2),"application/json")});
}
function download(name,data,type){const u=URL.createObjectURL(new Blob([data],{type})),a=document.createElement("a");a.href=u;a.download=name;a.click();setTimeout(()=>URL.revokeObjectURL(u),1000);}


export {shell,range,sel,fld,btn,table,matrix,stats,bars,amplitudes,complexPlot,sphere,plot,f,zstr,pct,esc,val,sharedShots,download};
export const baseLabs={
 bits:bitsLab,amplitudes:stateLab,basis:stateLab,practice,
 bloch:r=>stateLab(r,true),circuit:circuitLab,entanglement,
 noise:noiseLab,chsh:chshLab,teleport:teleportLab,dense:denseLab
};
