import test from "node:test";
import assert from "node:assert/strict";
import * as Q from "../dist/quantum.js";
import {decoder,pauliProduct} from "../dist/labs-advanced.js";
const close=(a,b,t=1e-10)=>assert.ok(Math.abs(a-b)<t, a+" ≠ "+b);
const vec=(a,b)=>a.forEach((z,j)=>z.forEach((x,k)=>close(x,b[j][k])));
test("full requested Z X Y probabilities and Y sign",()=>{
 const v=[Q.C(2),Q.C(0,3)];
 vec(Q.projection(v,"Y").amplitudes,[[5/Math.sqrt(26),0],[-1/Math.sqrt(26),0]]);
 for(const [basis,ps]of [["Z",[4/13,9/13]],["X",[.5,.5]],["Y",[25/26,1/26]]])Q.projection(v,basis).probabilities.forEach((p,j)=>close(p,ps[j]));
 assert.throws(()=>Q.normalize([Q.C(),Q.C()]));
});
test("unitary norm, inverse, Bloch radius over random states",()=>{
 const r=Q.rng(17);
 for(let k=0;k<100;k++){const v=Q.normalize(Array.from({length:2},()=>Q.C(r()-.5,r()-.5)));
 for(const g of ["H","X","Y","Z","S","Sdg","T","Rx","Ry","Rz","P"]){const G=Q.gate(g,2*r()),out=Q.apply1(v,1,0,G);close(Q.norm(out),1);vec(Q.apply1(out,1,0,Q.dagger(G)),v);}
 close(Math.hypot(...Q.bloch(v)),1);
 }
});
test("MSB convention and controlled truth tables",()=>{
 vec(Q.apply1(Q.ket(3),3,0,Q.X),Q.ket(3,4));
 for(let x=0;x<8;x++){const out=Q.apply1(Q.ket(3,x),3,2,Q.X,[0,1]);vec(out,Q.ket(3,(x&6)===6?x^1:x));}
 for(let x=0;x<4;x++)vec(Q.apply1(Q.ket(2,x),2,1,Q.X,[0]),Q.ket(2,(x&2)?x^1:x));
});
test("Bell preparation, inverse, and reduced states",()=>{
 const v=Q.circuit(2,[{name:"H",q:0},{name:"CX",q:1,control:0}]).state;
 vec(v,[[Math.SQRT1_2,0],[0,0],[0,0],[Math.SQRT1_2,0]]);
 const rho=Q.reduced(v,2,[0]);vec(rho[0],[[.5,0],[0,0]]);close(Q.purity(rho),.5);close(Q.entropyQubit(rho),1);
});
test("worked partial trace has the correct complex sign",()=>{
 const v=Q.normalize([Q.C(1),Q.C(0,2),Q.C(2),Q.C()]);
 const a=Q.reduced(v,2,[0]),b=Q.reduced(v,2,[1]);
 vec(a[0],[[5/9,0],[2/9,0]]);vec(b[0],[[5/9,0],[0,-2/9]]);close(Q.purity(a),49/81);close(Q.purity(a),Q.purity(b));
});
test("measurement collapse and repeated trajectory sampling",()=>{
 let v=Q.blochState(1.1,.6),m=Q.measure(v,1,0,()=>0);assert.equal(m.value,1);vec(m.state,[Q.C(),Q.normalize([v[1]])[0]]);
 const ops=[{name:"H",q:0},{name:"M",q:0},{name:"H",q:0}],c=Q.circuitShots(1,ops,6000,15);assert.ok(c[0]>2700&&c[0]<3300);
});
test("teleportation correct for all branches of 100 arbitrary states",()=>{
 const r=Q.rng(29);for(let k=0;k<100;k++){const v=Q.normalize([Q.C(r()-.5,r()-.5),Q.C(r()-.5,r()-.5)]);for(const b of Q.teleport(v).branches){close(b.p,.25);close(b.fidelity,1);}}
});
test("dense coding decodes each two-bit message",()=>{
 for(let a=0;a<2;a++)for(let b=0;b<2;b++)Q.dense(a,b).probabilities.forEach((p,j)=>close(p,j===2*a+b?1:0));
});
test("CHSH quantum value and no-signalling marginals",()=>{
 const r=Q.chsh([0,Math.PI/2,Math.PI/4,-Math.PI/4]);close(r.S,2*Math.sqrt(2));
 for(const e of r.correlations){const p=[(1+e)/4,(1-e)/4,(1-e)/4,(1+e)/4];close(p[0]+p[1],.5);close(p[0]+p[2],.5);}
});
test("channels preserve trace, Hermiticity, and qubit positivity",()=>{
 const r=Q.rng(91);for(let k=0;k<100;k++){let rho=Q.outer(Q.blochState(r()*Math.PI,r()*7));for(const ch of ["amplitude","phase","bit","depolarizing"]){const out=Q.channel(rho,ch,r());close(Q.trace(out)[0],1);out.forEach((row,j)=>vec(row,Q.dagger(out)[j]));assert.ok(Math.hypot(...Q.rhoBloch(out))<=1+1e-10);}}
});
test("channel endpoint conventions and exact damping example",()=>{
 const plus=Q.outer([Q.C(Math.SQRT1_2),Q.C(Math.SQRT1_2)]);
 close(Q.purity(Q.channel(plus,"phase",.5)),.5);
 close(Q.rhoBloch(Q.channel(plus,"phase",1))[0],-1);
 const d=Q.channel(plus,"amplitude",.25);vec(d[0],[[5/8,0],[Math.sqrt(3)/4,0]]);close(Q.purity(d),29/32);
 close(Q.purity(Q.channel(plus,"depolarizing",.2)),.82);
});
test("Deutsch truth table and BV all secrets through five bits",()=>{
 for(let t=0;t<4;t++){const r=Q.oracleAlgorithm(1,"Deutsch",t),expected=(t&1)^((t>>1)&1);close(r.probabilities[expected],1);}
 for(let n=1;n<=5;n++)for(let s=0;s<2**n;s++)close(Q.oracleAlgorithm(n,"BV",s).probabilities[s],1);
});
test("DJ promise outcomes",()=>{
 for(let n=1;n<=5;n++){close(Q.oracleAlgorithm(n,"DJ-constant",1).probabilities[0],1);for(let s=1;s<2**n;s++)close(Q.oracleAlgorithm(n,"DJ-balanced",s).probabilities[0],0);}
});
test("Simon exact support and sample-only reconstruction",()=>{
 for(let n=2;n<=5;n++)for(let s=1;s<2**n;s++){const r=Q.simon(n,s,40,s);r.probabilities.forEach((p,y)=>close(p,(Q.popcount(y&s)&1)?0:1/2**(n-1)));assert.ok(r.candidates.includes(s));assert.equal(r.rank,n-1);}
 assert.throws(()=>Q.simon(3,0));
});
test("Grover exact rotation and overshoot",()=>{
 for(let n=2;n<=6;n++)for(let k=0;k<=10;k++){const r=Q.grover(n,[1,2],k);close(r.success,r.theory);close(Q.norm(r.state),1);}
 close(Q.grover(2,[2],1).success,1);close(Q.grover(2,[2],2).success,.25);
 close(Q.grover(3,[5],2).success,121/128);
});
test("QFT definition, inverse, and two-qubit decomposition",()=>{
 for(let n=1;n<=5;n++){const v=Q.normalize(Array.from({length:2**n},(_,j)=>Q.C(Math.cos(j),Math.sin(j*2))));vec(Q.qft(Q.qft(v),true),v);}
 vec(Q.qft(Q.ket(2,1)),[[.5,0],[0,.5],[-.5,0],[0,-.5]]);
 for(let x=0;x<4;x++){let v=Q.apply1(Q.ket(2,x),2,0,Q.H);v=Q.apply1(v,2,0,Q.gate("P",Math.PI/2),[1]);v=Q.apply1(v,2,1,Q.H);v=Q.swap(v,2,0,1);vec(v,Q.qft(Q.ket(2,x)));}
});
test("QPE exact grid and off-grid normalization",()=>{
 for(let m=2;m<=6;m++)for(let x=0;x<2**m;x++)close(Q.qpe(m,x/2**m).probabilities[x],1);
 for(const p of [.013,.123,.51,.999])close(Q.qpe(6,p).probabilities.reduce((a,b)=>a+b,0),1);
});
test("Shor Fourier marginal, unsuccessful samples, and verified factors",()=>{
 const r=Q.shor(15,2,8);r.probabilities.forEach((p,y)=>close(p,y%64===0?.25:0));
 assert.deepEqual(Q.factorFromOutcome(15,2,8,0).factors,[]);
 assert.deepEqual(Q.factorFromOutcome(15,2,8,64).factors.sort((a,b)=>a-b),[3,5]);
 assert.deepEqual(Q.factorFromOutcome(21,2,9,85).factors.sort((a,b)=>a-b),[3,7]);
 assert.equal(Q.shor(15,3,8).gcd,3);
 const s=Q.shor(21,2,9);close(s.probabilities.reduce((a,b)=>a+b,0),1);
});
test("continued-fraction worked example",()=>assert.deepEqual(Q.continuedFractions(171,512).map(c=>[c.p,c.q]),[[0,1],[1,2],[1,3],[171,512]]));
test("repetition exact formula and all single-error recoveries",()=>{
 for(const p of [0,.01,.1,.3,.5,1])close(Q.repetition(p,3),3*p*p-2*p*p*p);
 for(const d of [3,5,7,9,11])for(let q=0;q<d;q++)assert.equal(Q.repetitionSyndrome(1<<q,d).logicalFailure,false);
 assert.equal(Q.repetitionSyndrome(5,3).logicalFailure,true);
});
test("surface-code checks, all single-Pauli correction, and logical strings",()=>{
 const checks=Q.rotatedSurface3();assert.ok(checks.every(a=>checks.every(b=>Q.pauliCommutes(a.p,b.p))));
 const dec=decoder();assert.equal(dec.size,256);
 for(let q=0;q<9;q++)for(const p of ["X","Y","Z"]){const e=Array(9).fill("I");e[q]=p;const s=e.join(""),r=pauliProduct(s,dec.get(Q.syndromeFor(s).join("")));assert.ok(Q.pauliCommutes(r,"ZZZIIIIII")&&Q.pauliCommutes(r,"XIIXIIXII"));}
 assert.ok(Q.syndromeFor("XIIXIIXII").every(x=>x===0));assert.ok(!Q.pauliCommutes("XIIXIIXII","ZZZIIIIII"));
});
test("surface-code distance: no weight-one/two logical Pauli",()=>{
 for(let a=0;a<9;a++)for(let b=a;b<9;b++)for(const p of ["X","Y","Z"])for(const q of ["X","Y","Z"]){const e=Array(9).fill("I");e[a]=p;e[b]=q;const s=e.join("");if(Q.syndromeFor(s).every(x=>!x))assert.ok(Q.pauliCommutes(s,"ZZZIIIIII")&&Q.pauliCommutes(s,"XIIXIIXII"));}
});
test("resource worked example and threshold flag",()=>{
 const r=Q.resource({p:.001,threshold:.01,A:.1,d:17,locations:1e8,logical:100,cycle:1e-6,depth:1e6});close(r.logicalError,1e-10,1e-20);close(r.unionBound,.01);assert.equal(r.physical,57700);close(r.time,17);assert.equal(Q.resource({p:.02}).below,false);
});
test("Hamiltonian exact dynamics and convergent product formula",()=>{
 for(const t of [.1,1,2]){const a=Q.hamiltonian(t,10),b=Q.hamiltonian(t,100);close(Q.norm(a.exact),1);assert.ok(b.error<a.error);}
});
test("VQE analytic energy, ground state, and shift gradient",()=>{
 const theta=Math.PI-Math.atan(.5),phi=Math.PI;close(Q.vqe(theta,phi).energy,-Math.sqrt(1.25));
 for(const t of [.1,1,3,5]){const d=(Q.vqe(t+Math.PI/2,.8).energy-Q.vqe(t-Math.PI/2,.8).energy)/2;close(Q.vqe(t,.8).gradient,d);}
});
test("QAOA objective bounds and diagonal phase-only case",()=>{
 for(const g of [0,.2,1,2]){const r=Q.qaoa(g,0);r.probabilities.forEach(p=>close(p,1/8));close(r.expectation,1.5);}
 for(const g of [.2,1,2])for(const b of [-.3,.3,1]){const r=Q.qaoa(g,b);close(Q.norm(r.state),1);assert.ok(r.expectation>=0&&r.expectation<=2);}
});
test("BB84 ideal correctness and intercept-resend disturbance",()=>{
 const ideal=Q.bb84(10000,0,0,3);assert.equal(ideal.errors,0);assert.ok(ideal.sifted>4500&&ideal.sifted<5500);
 const attacked=Q.bb84(50000,1,0,3);assert.ok(Math.abs(attacked.qber-.25)<.015);close(attacked.expectedQber,.25);
 close(Q.bb84(20,.4,.02,1).expectedQber,.116);
});
test("mitigation coefficients, noiseless endpoint, and reproducible sampling",()=>{
 const z=Q.mitigation(0,100,2);close(z.linear,1);close(z.quadratic,1);
 const m=Q.mitigation(.2,1000,8);close(m.linearExact,2*Math.exp(-.2)-Math.exp(-.4));assert.deepEqual(Q.sample([.2,.8],100,5),Q.sample([.2,.8],100,5));
});

test("classical control requires an earlier measurement record",()=>{
 assert.throws(()=>Q.circuit(2,[{name:'X',q:1,condition:{q:0,value:1}}]),/Measure q0/);
 const v=Q.circuit(2,[{name:'X',q:0},{name:'M',q:0},{name:'X',q:1,condition:{q:0,value:1}}]).state;
 vec(v,Q.ket(2,3));
});

test("controlled phase supports the exact two-qubit QFT circuit",()=>{
 for(let x=0;x<4;x++){
  const v=Q.circuit(2,[{name:'H',q:0},{name:'CP',q:0,control:1,angle:Math.PI/2},{name:'H',q:1},{name:'SWAP',q:0,control:1}],1,Q.ket(2,x)).state;
  vec(v,Q.qft(Q.ket(2,x)));
 }
});
