import * as T from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
import { GLTFExporter } from 'three/addons/exporters/GLTFExporter.js';
import { CONFIG, cylinderState, JOURNAL_PHASES, FIRING_ORDER, radians } from './mechanics.mjs';

const v = (x=0,y=0,z=0)=>new T.Vector3(x,y,z);
const Y=v(0,1,0), Z=v(0,0,1);
export function createEngineScene(host, onSelect, onManual) {
  const scene=new T.Scene();
  const renderer=new T.WebGLRenderer({antialias:true,alpha:true,preserveDrawingBuffer:true});
  renderer.setPixelRatio(Math.min(window.devicePixelRatio,2));
  renderer.setClearColor(0xf4f2ed,0);renderer.outputColorSpace=T.SRGBColorSpace;
  renderer.toneMapping=T.ACESFilmicToneMapping;renderer.toneMappingExposure=1.45;
  host.appendChild(renderer.domElement);renderer.domElement.setAttribute('aria-label','Interactive 3D V8 engine. Drag to orbit; pinch or scroll to zoom.');
  renderer.domElement.setAttribute('role','img');
  const pmrem=new T.PMREMGenerator(renderer);const env=pmrem.fromScene(new RoomEnvironment(),.04);scene.environment=env.texture;
  const camera=new T.PerspectiveCamera(34,1,.1,100);
  const controls=new OrbitControls(camera,renderer.domElement);controls.enableDamping=false;controls.minDistance=9;controls.maxDistance=28;controls.maxPolarAngle=Math.PI*.82;controls.target.set(0,1.5,0);controls.enablePan=false;
  controls.addEventListener('start',onManual);
  const reset=()=>{camera.position.set(10,8.3,12.5);controls.target.set(0,1.5,0);controls.update();};reset();
  scene.add(new T.HemisphereLight(0xffffff,0x8f8171,2.3));
  const key=new T.DirectionalLight(0xfff7ec,4.5);key.position.set(3,8,6);scene.add(key);
  const fill=new T.DirectionalLight(0xd7e1ea,3);fill.position.set(-7,4,-6);scene.add(fill);
  const root=new T.Group();root.name='Inside the Machine | procedural V8';scene.add(root);
  const mat=(color,metalness=.7,roughness=.28)=>new T.MeshStandardMaterial({color,metalness,roughness});
  const graphite=mat(0x30383b,.78,.3), dark=mat(0x1e2629,.65,.38), silver=mat(0xb6bdba,.86,.22), copper=mat(0xb77c51,.75,.26), steel=mat(0x606a6c,.86,.24), black=mat(0x202728,.25,.46), red=mat(0xba3d2b,.5,.25);
  const meshes=[];const housings=[];const covers=[];const pistonNodes=[];const rods=[];const valves=[];const labels=[];const camLobes=[];
  function mesh(geo,material,parent=root,name='') {const m=new T.Mesh(geo,material);m.name=name;parent.add(m);meshes.push(m);return m;}
  function cyl(radius,length,material,parent=root,name='',segments=40){return mesh(new T.CylinderGeometry(radius,radius,length,segments),material,parent,name);}
  function box(w,h,d,material,parent=root,name=''){return mesh(new T.BoxGeometry(w,h,d),material,parent,name);}
  function ring(radius,tube,material,parent=root,name=''){return mesh(new T.TorusGeometry(radius,tube,8,48),material,parent,name);}
  function lineBetween(m,a,b){m.position.copy(a).add(b).multiplyScalar(.5);m.quaternion.setFromUnitVectors(Y,b.clone().sub(a).normalize());m.scale.y=a.distanceTo(b);}
  function bolt(p,parent=root){const b=cyl(.067,.08,silver,parent,'Fastener',6);b.quaternion.setFromUnitVectors(Y,Z);b.position.copy(p);return b;}
  // Crankshaft: five main bearings, four common journals, substantial webs and counterweights.
  const crank=new T.Group();crank.name='Cross-plane crankshaft';root.add(crank);
  for(let j=0;j<5;j++){const m=cyl(.26,.32,silver,crank,'Main journal');m.rotation.x=Math.PI/2;m.position.z=(2-j)*CONFIG.journalPitch;
    const carrier=ring(.34,.10,graphite);carrier.position.set(0,0,m.position.z);}
  for(let j=0;j<4;j++){
    const a=radians(JOURNAL_PHASES[j]),px=CONFIG.radius*Math.sin(a),py=CONFIG.radius*Math.cos(a),z=(1.5-j)*CONFIG.journalPitch;
    const pin=cyl(.205,.91,silver,crank,`Common crankpin ${j+1}`);pin.rotation.x=Math.PI/2;pin.position.set(px,py,z);
    for(const dz of [-.54,.54]){
      const web=box(.57,1.34,.2,graphite,crank,'Crank web');web.position.set(px*.40,py*.40,z+dz);web.rotation.z=-a;
      const weight=cyl(.60,.23,steel,crank,'Counterweight',32);weight.rotation.x=Math.PI/2;weight.position.set(-px*.6,-py*.6,z+dz);
    }
  }
  const flywheel=cyl(1.20,.22,graphite,crank,'Flywheel');flywheel.rotation.x=Math.PI/2;flywheel.position.z=-3.62;
  for(let i=0;i<48;i++){const a=i/48*Math.PI*2;const tooth=box(.10,.12,.22,steel,crank);tooth.position.set(Math.sin(a)*1.19,Math.cos(a)*1.19,-3.62);tooth.rotation.z=-a;}
  const pulley=cyl(.74,.25,black,crank,'Crank pulley');pulley.rotation.x=Math.PI/2;pulley.position.z=3.62;
  const pulleyRing=ring(.63,.045,copper,crank);pulleyRing.position.z=3.76;
  for(let i=0;i<6;i++){const a=i/6*Math.PI*2;bolt(v(Math.sin(a)*.38,Math.cos(a)*.38,3.78),crank);}
  // Central OHV cam, mechanically clocked at half crank speed.
  const cam=new T.Group();cam.name='Camshaft | crank speed / 2';cam.position.y=1.02;root.add(cam);
  const camshaft=cyl(.14,6.8,copper,cam,'Camshaft');camshaft.rotation.x=Math.PI/2;
  const gear=cyl(.68,.15,copper,cam,'Cam sprocket');gear.rotation.x=Math.PI/2;gear.position.z=3.40;
  const gearSmall=cyl(.34,.16,steel,crank,'Crank sprocket');gearSmall.rotation.x=Math.PI/2;gearSmall.position.z=3.40;
  // Stylized chain, static envelope; exact tooth/roller meshing is intentionally omitted.
  const pts=[];for(let i=0;i<=100;i++){const a=i/100*Math.PI*2;pts.push(v(Math.sin(a)*.72,.49+Math.cos(a)*1.19,3.48));}
  const chain=mesh(new T.TubeGeometry(new T.CatmullRomCurve3(pts,true),100,.041,6,true),steel,root,'Timing chain envelope');
  // Cast housings move outward for inspection; the connected mechanism remains in place.
  const pan=box(2.40,.52,6.48,graphite,root,'Oil pan');pan.position.set(0,-1.1,0);housings.push({m:pan,base:pan.position.clone(),dir:v(0,-1.7,0)});
  for(const bank of [1,-1]){
    const beta=radians(bank*45),n=v(Math.sin(beta),Math.cos(beta),0),t=v(bank*Math.cos(beta),-bank*Math.sin(beta),0);
    const bgroup=new T.Group();bgroup.quaternion.setFromUnitVectors(Y,n);root.add(bgroup);
    // Sections retain the back half of each cylinder sleeve.
    for(let j=0;j<4;j++){
      const z=(1.5-j)*CONFIG.journalPitch+bank*.16;
      const sleeve=mesh(new T.CylinderGeometry(.745,.745,1.7,48,1,true,Math.PI*.55,Math.PI*1.25),steel,bgroup,'Sectioned cylinder liner');sleeve.position.set(0,2.66,z);sleeve.material=steel.clone();sleeve.material.side=T.DoubleSide;
      for(const y of [1.81,3.51]){const rim=mesh(new T.TorusGeometry(.747,.045,8,48,Math.PI*1.25),copper,bgroup,'Cut edge');rim.rotation.x=Math.PI/2;rim.rotation.z=-Math.PI*.55;rim.position.set(0,y,z);}
      const full=cyl(.81,1.80,graphite,bgroup,'Cylinder jacket');full.position.set(0,2.65,z);covers.push(full);
      housings.push({m:sleeve,base:sleeve.position.clone(),dir:v(bank*1.5,1.3,0)});
    }
    const rail=box(1.70,.18,6.8,graphite,bgroup,'Cylinder deck');rail.position.set(0,3.53,0);
    covers.push(rail);
    const head=box(1.57,.49,6.77,graphite,bgroup,'Cylinder head');head.position.set(0,3.82,0);covers.push(head);housings.push({m:head,base:head.position.clone(),dir:v(0,2.1,0)});
    const cover=box(1.37,.68,6.63,steel,bgroup,'Rocker cover');cover.position.set(0,4.40,0);covers.push(cover);housings.push({m:cover,base:cover.position.clone(),dir:v(0,2.9,0)});
    for(let k=0;k<5;k++){const rib=box(1.40,.025,.055,copper,bgroup,'Rocker-cover rib');rib.position.set(0,4.755,(k-2)*.98);covers.push(rib);}
    for(let j=0;j<4;j++){const cylNum=j*2+(bank===1?1:2);const st=cylinderState(0,cylNum),z=st.z;
      const piston=new T.Group();piston.name=`Piston ${cylNum}`;piston.quaternion.setFromUnitVectors(Y,n);root.add(piston);
      const body=cyl(.636,.48,silver,piston,'Piston skirt');body.userData.cylinder=cylNum;
      const crown=cyl(.643,.055,silver.clone(),piston,'Piston crown');crown.position.y=.263;crown.userData.cylinder=cylNum;
      for(const y of [.14,.205]){const r=ring(.64,.017,graphite,piston,'Piston ring');r.rotation.x=Math.PI/2;r.position.y=y;}
      const wrist=cyl(.12,1.18,copper,piston,'Wrist pin');wrist.rotation.x=Math.PI/2;
      pistonNodes.push({piston,crown,cylNum,n,z});
      const rod=box(.25,1,.18,copper,root,`Connecting rod ${cylNum}`);rod.userData.cylinder=cylNum;
      const big=ring(.26,.10,silver,root,'Big end');const small=ring(.135,.052,silver,root,'Small end');rods.push({rod,big,small,cylNum});
      const lab=document.createElement('button');lab.className='cylinder-tag';lab.textContent=String(cylNum);lab.setAttribute('aria-label',`Select cylinder ${cylNum}`);lab.onclick=()=>onSelect(cylNum);host.appendChild(lab);labels.push({el:lab,anchor:n.clone().multiplyScalar(3.67).add(v(0,0,z)),cylNum});
      // Two symbolic radial cam lobes per cylinder. Ideal 180° crank lift windows.
      for(const [type,zShift,start] of [['intake',-.23,360],['exhaust',.23,180]]){
        const fire=FIRING_ORDER.indexOf(cylNum)*90;
        const outline=new T.Shape();const samples=144;
        for(let i=0;i<=samples;i++){
          const polar=i/samples*Math.PI*2;
          const theta=2*(beta-polar)*180/Math.PI;
          const phase=((theta-fire)%720+720)%720;
          const lift=phase>=start&&phase<start+180?CONFIG.maxLift*Math.sin(Math.PI*(phase-start)/180)**2:0;
          const rad=.225+lift;const x=rad*Math.sin(polar),y=rad*Math.cos(polar);i?outline.lineTo(x,y):outline.moveTo(x,y);
        }
        const lobe=mesh(new T.ExtrudeGeometry(outline,{depth:.11,bevelEnabled:false}),steel,cam,`${type} timing lobe ${cylNum}`);lobe.position.z=z+zShift-.055;camLobes.push(lobe);
        const assembly=new T.Group();assembly.name=`${type} valve train ${cylNum}`;root.add(assembly);
        const pivot=v(0,1.02,z+zShift).addScaledVector(n,.225+3.504).addScaledVector(t,.34);
        const rocker=box(.68,.10,.13,steel,assembly,'1:1 ideal rocker');
        const push=cyl(.043,1,copper,assembly,'Pushrod');
        const valve=cyl(.047,.67,silver,assembly,'Valve stem');const face=cyl(.185,.07,type==='intake'?silver:copper,assembly,'Valve head');
        const pad=cyl(.13,.055,silver,assembly,'Sliding rocker contact pad');const springPoints=[];for(let q=0;q<=100;q++){const a=q/100*Math.PI*12;springPoints.push(v(.095*Math.sin(a),q/100*.42,.095*Math.cos(a)));}
        const spring=mesh(new T.TubeGeometry(new T.CatmullRomCurve3(springPoints),100,.019,5,false),copper,assembly,'Valve spring');spring.quaternion.setFromUnitVectors(Y,n);
        const pivotPin=cyl(.09,.22,copper,assembly,'Rocker pivot');pivotPin.quaternion.setFromUnitVectors(Y,Z);pivotPin.position.copy(pivot);
        valves.push({cylNum,type,n,t,pivot,rocker,push,valve,face,spring,pad});
      }
    }
  }
  // Front timing plate and block bridge make the assembled silhouette recognizable.
  const timing=box(1.9,2.5,.19,graphite);timing.position.set(0,.45,3.28);covers.push(timing);
  const valley=box(1.5,.30,5.8,steel);valley.position.set(0,2.20,0);covers.push(valley);
  const inlet=cyl(.43,.23,graphite);inlet.position.set(0,2.47,0);covers.push(inlet);
  for(const x of [-.55,.55])for(let j=0;j<4;j++){const pipe=cyl(.16,.7,copper);pipe.rotation.z=x>0?-.9:.9;pipe.position.set(x,2.55,(1.5-j)*1.45);covers.push(pipe);}
  const shadowCanvas=document.createElement('canvas');shadowCanvas.width=256;shadowCanvas.height=256;const ctx=shadowCanvas.getContext('2d');const g=ctx.createRadialGradient(128,128,8,128,128,124);g.addColorStop(0,'rgba(43,36,27,.28)');g.addColorStop(1,'rgba(43,36,27,0)');ctx.fillStyle=g;ctx.fillRect(0,0,256,256);
  const shadow=mesh(new T.PlaneGeometry(11,10),new T.MeshBasicMaterial({map:new T.CanvasTexture(shadowCanvas),transparent:true,depthWrite:false}),scene,'Studio contact shadow');shadow.rotation.x=-Math.PI/2;shadow.position.y=-1.72;
  const raycaster=new T.Raycaster();let down=[0,0];renderer.domElement.addEventListener('pointerdown',e=>down=[e.clientX,e.clientY]);renderer.domElement.addEventListener('pointerup',e=>{if(Math.hypot(e.clientX-down[0],e.clientY-down[1])>5)return;const r=host.getBoundingClientRect();raycaster.setFromCamera(new T.Vector2((e.clientX-r.left)/r.width*2-1,-(e.clientY-r.top)/r.height*2+1),camera);const hit=raycaster.intersectObjects(root.children,true).find(h=>h.object.userData.cylinder);if(hit)onSelect(hit.object.userData.cylinder);});
  let last={angle:30,cut:1,explode:0,selected:1};
  function update(state) {
    last={...last,...state};const {angle,cut,explode,selected}=last;
    crank.rotation.z=-radians(angle);cam.rotation.z=-radians(angle/2);
    covers.forEach(m=>{m.visible=cut<.985||explode>.01;const opacity=Math.max(1-cut,explode*.48);if(!m.userData.ownMat){m.material=m.material.clone();m.userData.ownMat=true;}m.material.transparent=opacity<1;m.material.opacity=opacity;m.material.depthWrite=opacity>.85;});
    housings.forEach(({m,base,dir})=>m.position.copy(base).addScaledVector(dir,explode));
    for(const p of pistonNodes){const s=cylinderState(angle,p.cylNum);p.piston.position.fromArray(s.wrist);p.crown.material.color.setHex(p.cylNum===selected?0xbd4630:0xb6bdba);p.crown.material.emissive.setHex(p.cylNum===selected&&s.stroke==='Power'?0x6b180c:0x000000);p.crown.material.emissiveIntensity=.18;}
    for(const r of rods){const s=cylinderState(angle,r.cylNum);const a=new T.Vector3().fromArray(s.pin),b=new T.Vector3().fromArray(s.wrist);lineBetween(r.rod,a,b);r.big.position.copy(a);r.small.position.copy(b);}
    for(const q of valves){const s=cylinderState(angle,q.cylNum),lift=q.type==='intake'?s.intakeLift:s.exhaustLift;const a=.34,L=3.504;let lo=0,hi=1.4;for(let k=0;k<28;k++){const mid=(lo+hi)/2;const implied=a*Math.sin(mid)+L-Math.sqrt(L*L-(a*(1-Math.cos(mid)))**2);if(implied<lift)lo=mid;else hi=mid;}const phi=(lo+hi)/2;const rockerLift=a*Math.sin(phi);const left=q.pivot.clone().addScaledVector(q.t,-a*Math.cos(phi)).addScaledVector(q.n,rockerLift);const right=q.pivot.clone().addScaledVector(q.t,a*Math.cos(phi)).addScaledVector(q.n,-rockerLift);
      q.rocker.scale.set(1,1,1);q.rocker.position.copy(q.pivot);q.rocker.quaternion.setFromUnitVectors(v(1,0,0),right.clone().sub(left).normalize());
      const bottom=v(0,1.02,s.z+(q.type==='intake'?-.23:.23)).addScaledVector(q.n,.225+lift);lineBetween(q.push,bottom,left);
      const valveTop=q.pivot.clone().addScaledVector(q.t,a).addScaledVector(q.n,-rockerLift);q.valve.quaternion.setFromUnitVectors(Y,q.n);q.valve.position.copy(valveTop).addScaledVector(q.n,-.335);
      q.pad.quaternion.copy(q.valve.quaternion);q.pad.position.copy(valveTop);q.face.quaternion.copy(q.valve.quaternion);q.face.position.copy(valveTop).addScaledVector(q.n,-.67);
      q.spring.position.copy(q.pivot).addScaledVector(q.t,a).addScaledVector(q.n,-.55);q.spring.scale.y=Math.max(.35,(.42-lift)/.42);
    }
    if(last.tour){const radius=17.5*(last.zoom||1);camera.position.set(Math.sin(last.azimuth)*radius,8.3,Math.cos(last.azimuth)*radius);controls.target.set(0,1.5,0);controls.update();}
    camera.fov=34+explode*7;camera.updateProjectionMatrix();scene.updateMatrixWorld(true);camera.updateMatrixWorld();
    const rect=host.getBoundingClientRect();for(const l of labels){const p=l.anchor.clone().project(camera);l.el.style.left=`${(p.x*.5+.5)*rect.width}px`;l.el.style.top=`${(-p.y*.5+.5)*rect.height}px`;l.el.classList.toggle('selected',l.cylNum===selected);l.el.style.opacity=cut>.45&&explode<.3?'1':'0';l.el.style.pointerEvents=cut>.45&&explode<.3?'auto':'none';}
    renderer.render(scene,camera);
  }
  const resize=()=>{const r=host.getBoundingClientRect();renderer.setSize(r.width,r.height);camera.aspect=r.width/r.height;camera.updateProjectionMatrix();update({});};const observer=new ResizeObserver(resize);observer.observe(host);resize();
  const zoom=amount=>{camera.position.sub(controls.target).multiplyScalar(amount).add(controls.target);controls.update();update({});};
  return {update,reset:()=>{reset();update({tour:false});},zoom,
    inspect:()=>({crankRotation:crank.rotation.z,camRotation:cam.rotation.z,camera:camera.position.toArray(),objects:meshes.length,triangles:renderer.info.render.triangles,journals:meshes.filter(m=>m.name.startsWith('Common crankpin')).map(m=>m.getWorldPosition(v()).toArray()),pistons:pistonNodes.map(p=>({cylinder:p.cylNum,wrist:p.piston.getWorldPosition(v()).toArray()})),valves:valves.map(q=>({cylinder:q.cylNum,type:q.type,valve:q.valve.position.toArray(),spring:q.spring.position.toArray(),pushLength:q.push.scale.y})),linked:rods.map(r=>({cylinder:r.cylNum,big:r.big.position.toArray(),small:r.small.position.toArray(),length:r.big.position.distanceTo(r.small.position)}))}),
    exportGLTF:async()=>{const result=await new GLTFExporter().parseAsync(root,{binary:true,onlyVisible:true});return Array.from(new Uint8Array(result));},
    dispose:()=>{observer.disconnect();controls.dispose();renderer.dispose();env.dispose();pmrem.dispose();meshes.forEach(m=>{m.geometry.dispose();m.material.dispose();});labels.forEach(l=>l.el.remove());renderer.domElement.remove();}
  };
}
