// world-mountain.jsx — Cinematic Mont-Blanc: fbm terrain, snow on slopes, treeline, fog.

function initMountain(container) {
  const scene = new THREE.Scene();
  const fogCol = new THREE.Color(0xb7b1a4);
  scene.fog = new THREE.Fog(fogCol, 50, 260);

  const camera = new THREE.PerspectiveCamera(36, 1, 0.1, 800);
  camera.position.set(0, 28, 90);

  const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  renderer.setClearColor(0x000000, 0);
  container.appendChild(renderer.domElement);

  const sunDir = new THREE.Vector3(-0.55, 0.78, 0.30).normalize();

  // ══════════════════════════════════════════════════════════════════════════
  // SKY — cold dawn gradient with sun glow + atmospheric haze
  // ══════════════════════════════════════════════════════════════════════════
  const skyGeo = new THREE.SphereGeometry(500, 48, 32);
  const skyMat = new THREE.ShaderMaterial({
    side: THREE.BackSide, depthWrite: false,
    uniforms: { uTime: { value: 0 }, uSunDir: { value: sunDir.clone() } },
    vertexShader: `varying vec3 vP; void main(){ vP=position; gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0); }`,
    fragmentShader: `
      varying vec3 vP;
      uniform float uTime;
      uniform vec3 uSunDir;
      ${NOISE_GLSL}
      void main(){
        vec3 d = normalize(vP);
        float y = d.y * 0.5 + 0.5;
        // Cold alpine palette
        vec3 zenith    = vec3(0.18, 0.20, 0.26);
        vec3 midSky    = vec3(0.52, 0.55, 0.58);
        vec3 horizon   = vec3(0.88, 0.83, 0.74);
        vec3 col = mix(horizon, midSky, smoothstep(0.45, 0.65, y));
        col = mix(col, zenith, smoothstep(0.65, 0.98, y));
        // Subtle high cirrus
        float n = fbm(d*4.0 + vec3(uTime*0.01));
        float wisp = smoothstep(0.55, 0.78, n) * smoothstep(0.55, 0.85, y) * 0.4;
        col = mix(col, vec3(0.92,0.89,0.82), wisp);
        // Sun disc + halo behind peak
        float sd = max(dot(d, uSunDir), 0.0);
        float disc = smoothstep(0.997, 0.9994, sd);
        float halo = pow(sd, 80.0);
        col += vec3(1.0,0.95,0.86) * disc * 1.1;
        col += vec3(1.0,0.86,0.70) * halo * 0.45;
        // Soft glow over wider area
        col += vec3(1.0,0.86,0.72) * pow(sd, 6.0) * 0.10;
        gl_FragColor = vec4(col, 1.0);
      }
    `,
  });
  const sky = new THREE.Mesh(skyGeo, skyMat);
  scene.add(sky);

  // ══════════════════════════════════════════════════════════════════════════
  // TERRAIN — Real fbm heightmap displaced plane.
  //
  // The geometry is a long plane facing the camera. We bake the heightmap into
  // vertex Z (which becomes "up" after rotating -π/2 on X). Snow / rock / tree
  // colors are decided in the fragment shader from world position + slope.
  // ══════════════════════════════════════════════════════════════════════════
  function makeRange({ width, depth, segW, segD, x0, z0, seed, peakConfig, snowLine, treeLine, palette, layerDepth }) {
    const geo = new THREE.PlaneGeometry(width, depth, segW, segD);
    const pos = geo.attributes.position;
    // Envelope function: sum of Gaussians, one per peak in peakConfig.
    // peakConfig: [{x, z, w, dx, dz, h}] — w = decay along x, dz along z
    const envelope = (x, z) => {
      let h = 0;
      for (const peak of peakConfig) {
        const dx = (x - peak.x) * peak.wx;
        const dz = (z - peak.z) * peak.wz;
        h += peak.h * Math.exp(-(dx*dx + dz*dz));
      }
      return h;
    };
    for (let i = 0; i < pos.count; i++) {
      const x = pos.getX(i);
      const y = pos.getY(i); // becomes depth after rotation
      // Envelope from peak config — gives the "Mont-Blanc-looking" silhouette
      let h = envelope(x, y);
      // FBM detail layered on top — scaled by envelope so it doesn't blow up plains
      const sx = (x + 1000) * 0.04;
      const sy = (y + 1000) * 0.04;
      const detail = jFbm(sx, sy, seed, 6);            // 0..1
      const ridge  = jRidged(sx*2.2, sy*2.2, seed*1.7, 5); // 0..1
      // Combine: base elevation + ridges + detail jitter
      const rough = (detail - 0.45) * 6.0 + (ridge - 0.45) * 10.0;
      // Scale roughness by envelope (less rough on plains)
      h += rough * (0.25 + Math.min(1, h / 30) * 0.85);

      // Light shoreline near base — keeps the ridge from punching below ground
      h = Math.max(h, -0.5);
      pos.setZ(i, h);
    }
    geo.computeVertexNormals();

    const mat = new THREE.ShaderMaterial({
      uniforms: {
        uSunDir:    { value: sunDir.clone() },
        uFogColor:  { value: fogCol },
        uFogNear:   { value: 60 },
        uFogFar:    { value: 280 },
        uLayerDepth:{ value: layerDepth },
        uSnowLine:  { value: snowLine },
        uTreeLine:  { value: treeLine },
        uRockLo:    { value: new THREE.Color(palette.rockLo) },
        uRockHi:    { value: new THREE.Color(palette.rockHi) },
        uForest:    { value: new THREE.Color(palette.forest) },
        uGrass:     { value: new THREE.Color(palette.grass) },
        uSnow:      { value: new THREE.Color(palette.snow) },
        uIce:       { value: new THREE.Color(palette.ice) },
        uTime:      { value: 0 },
      },
      vertexShader: `
        varying vec3 vN; varying vec3 vW; varying float vH;
        void main(){
          vec4 wp = modelMatrix * vec4(position,1.0);
          vW = wp.xyz;
          vN = normalize(mat3(modelMatrix) * normal);
          // Y of the world position after rotation is the elevation
          vH = vW.y;
          gl_Position = projectionMatrix * viewMatrix * wp;
        }
      `,
      fragmentShader: `
        varying vec3 vN; varying vec3 vW; varying float vH;
        uniform vec3 uSunDir;
        uniform vec3 uFogColor;
        uniform float uFogNear; uniform float uFogFar; uniform float uLayerDepth;
        uniform float uSnowLine; uniform float uTreeLine;
        uniform vec3 uRockLo; uniform vec3 uRockHi;
        uniform vec3 uForest; uniform vec3 uGrass; uniform vec3 uSnow; uniform vec3 uIce;
        uniform float uTime;
        ${NOISE_GLSL}

        void main(){
          vec3 N = normalize(vN);
          float upness = max(N.y, 0.0);
          float h = vH;
          // Local noise to break up texture
          float nDetail = fbm(vec3(vW.xz*0.18, 0.0));
          float nFine   = fbm(vec3(vW.xz*0.7, 1.7));
          float nRock   = ridged(vec3(vW.xz*0.4, 3.2));

          // ── Rock base palette ──
          vec3 rock = mix(uRockLo, uRockHi, nDetail*0.5 + nFine*0.5);
          // Add darker striations to cliff faces
          rock = mix(rock, rock*0.55, smoothstep(0.65, 0.95, nRock) * (1.0 - upness));

          // ── Treeline / grassland (lower elevations, gentler slopes) ──
          float treeBand = smoothstep(uTreeLine-4.0, uTreeLine-1.0, h)
                        * (1.0 - smoothstep(uTreeLine+1.0, uTreeLine+5.0, h));
          // Trees only on slopes that aren't vertical
          treeBand *= smoothstep(0.35, 0.75, upness);
          // Add texture variation (patchy forest)
          treeBand *= smoothstep(0.30, 0.65, nFine);
          vec3 vegetated = mix(uGrass, uForest, smoothstep(0.40, 0.70, nDetail));
          // Below treeline get grass with some bare rock
          float lowland = (1.0 - smoothstep(uTreeLine, uTreeLine+2.0, h)) * smoothstep(0.45, 0.7, upness);

          // ── Snow accumulation: elevation gate × slope-friendly × patch ──
          float snowBase = smoothstep(uSnowLine - 4.0, uSnowLine + 2.0, h);
          // Snow holds on flatter slopes; cliffs stay bare
          float snowSlope = smoothstep(0.30, 0.62, upness);
          float snowPatch = smoothstep(0.30, 0.72, fbm(vec3(vW.xz*0.30, 5.0)));
          float snow = snowBase * snowSlope * (0.55 + 0.45*snowPatch);
          // Hard top-of-mountain snow regardless of slope
          float highSnow = smoothstep(uSnowLine + 8.0, uSnowLine + 18.0, h);
          snow = clamp(max(snow, highSnow*0.9), 0.0, 1.0);

          // Slight blue-tinted ice on the steep snow faces
          float ice = smoothstep(0.18, 0.40, upness) * smoothstep(uSnowLine+10.0, uSnowLine+20.0, h)
                    * smoothstep(0.40, 0.20, upness);
          vec3 snowMix = mix(uSnow, uIce, ice*0.6);

          // ── Combine layers ──
          vec3 base = rock;
          base = mix(base, vegetated, treeBand * 0.95);
          base = mix(base, vegetated * 0.85, lowland * 0.45);
          base = mix(base, snowMix, snow);

          // ── Lighting ──
          float ndl = max(dot(N, uSunDir), 0.0);
          float wrap = ndl*0.7 + 0.35;
          // Specular sheen on snow when sun hits
          vec3 V = normalize(cameraPosition - vW);
          vec3 H = normalize(V + uSunDir);
          float specSnow = pow(max(dot(N, H), 0.0), 50.0) * snow * 0.7;
          // Cool ambient on shadow side
          vec3 shadowFill = vec3(0.10, 0.14, 0.20) * (1.0 - wrap) * 0.7;

          vec3 col = base * wrap + shadowFill + vec3(1.0,0.96,0.88) * specSnow;

          // Subtle ambient occlusion in valleys (low-curvature dark)
          float ao = smoothstep(-2.0, 6.0, h) * 0.5 + 0.5;
          col *= ao;

          // ── Fog (layered: each band uses uLayerDepth bias) ──
          float d = length(vW - cameraPosition);
          float fogT = smoothstep(uFogNear + uLayerDepth*12.0, uFogFar + uLayerDepth*20.0, d);
          // Warm-tinted fog on lit side, cool on shadow side
          vec3 fogColAdj = mix(uFogColor*0.92, uFogColor*1.05, smoothstep(0.0, 0.5, ndl));
          col = mix(col, fogColAdj, fogT);

          gl_FragColor = vec4(col, 1.0);
        }
      `,
    });
    const mesh = new THREE.Mesh(geo, mat);
    mesh.rotation.x = -Math.PI / 2;
    mesh.position.set(x0, 0, z0);
    return { mesh, mat, geo };
  }

  // ── Loaded mountain from cloudy_mountain.glb ──────────────────────────────
  // Replaces the three procedural ranges. We normalize the GLB's bounding box
  // so peak height ≈ 60 and place the model so its base sits at y=0, centered
  // around z=-55, matching the original camera framing.
  const mountain = new THREE.Group();
  scene.add(mountain);
  const mountainSun = new THREE.DirectionalLight(0xfff1d8, 2.4);
  mountainSun.position.copy(sunDir).multiplyScalar(50);
  scene.add(mountainSun);
  const mountainFill = new THREE.HemisphereLight(0xc8cdd2, 0x2a2620, 0.55);
  scene.add(mountainFill);

  let mountainModel = null;
  const mountainDraco = new THREE.DRACOLoader();
  mountainDraco.setDecoderPath('https://unpkg.com/three@0.147.0/examples/js/libs/draco/');
  const mountainLoader = new THREE.GLTFLoader();
  mountainLoader.setDRACOLoader(mountainDraco);
  mountainLoader.load('snowy_mountain.glb', (gltf) => {
    mountainModel = gltf.scene;
    const box = new THREE.Box3().setFromObject(mountainModel);
    const size = new THREE.Vector3();
    box.getSize(size);
    const targetHeight = 60;
    const s = targetHeight / Math.max(size.y, 1e-3);
    mountainModel.scale.setScalar(s);
    const box2 = new THREE.Box3().setFromObject(mountainModel);
    const center = new THREE.Vector3();
    box2.getCenter(center);
    mountainModel.position.x -= center.x;
    mountainModel.position.y -= box2.min.y;
    mountainModel.position.z = -55 - center.z;
    // Augmenter la rugosité des matériaux PBR pour un aspect plus brut/rocheux
    mountainModel.traverse((obj) => {
      if (!obj.isMesh) return;
      const mat = obj.material;
      if (mat && mat.roughness !== undefined) {
        mat.roughness = Math.min(1.0, mat.roughness + 0.35);
        mat.metalness = Math.max(0.0, mat.metalness - 0.2);
      }
    });
    mountain.add(mountainModel);
  }, undefined, (err) => {
    console.error('Failed to load dev fiddle 211104 01 Scene Output 4096.glb', err);
  });

  // ══════════════════════════════════════════════════════════════════════════
  // Volumetric fog ribbons — drifting between mountain layers
  // ══════════════════════════════════════════════════════════════════════════
  function makeFogRibbon(yOffset, zPos, opacity, color, scrollSpeed) {
    const g = new THREE.PlaneGeometry(500, 90, 1, 1);
    const m = new THREE.ShaderMaterial({
      transparent: true, depthWrite: false,
      uniforms: {
        uTime: { value: 0 },
        uColor: { value: new THREE.Color(color) },
        uOpacity: { value: opacity },
        uSpeed: { value: scrollSpeed },
      },
      vertexShader: `varying vec2 vUv; void main(){ vUv=uv; gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0); }`,
      fragmentShader: `
        varying vec2 vUv;
        uniform float uTime; uniform float uSpeed;
        uniform vec3 uColor; uniform float uOpacity;
        ${NOISE_GLSL}
        void main(){
          vec2 uv = vUv;
          float n = fbm(vec3(uv*3.5 + vec2(uTime*uSpeed,0.0), uTime*0.04));
          float n2 = fbm(vec3(uv*8.0 + vec2(uTime*uSpeed*0.5,0.0), 5.7));
          float band = smoothstep(0.32, 0.62, n) * smoothstep(0.0, 0.18, uv.y) * smoothstep(1.0, 0.78, uv.y);
          band *= 0.85 + n2*0.4;
          gl_FragColor = vec4(uColor, band*uOpacity);
        }
      `,
    });
    const mesh = new THREE.Mesh(g, m);
    mesh.position.set(0, yOffset, zPos);
    return { mesh, mat: m, geo: g };
  }
  const fogs = [
    makeFogRibbon(8,   -85, 0.55, 0xc7c0b3, 0.025),
    makeFogRibbon(14,  -55, 0.62, 0xb5ad9e, 0.018),
    makeFogRibbon(4,   -30, 0.78, 0x9c9486, 0.030),
    makeFogRibbon(20,  -100, 0.40, 0xd2cbbd, 0.012),
  ];
  fogs.forEach((f) => scene.add(f.mesh));

  // ══════════════════════════════════════════════════════════════════════════
  // Subtle particles — high-altitude snow / dust drifting
  // ══════════════════════════════════════════════════════════════════════════
  const partGeo = new THREE.BufferGeometry();
  const PN = 350;
  const pp = new Float32Array(PN*3);
  const ps = new Float32Array(PN);
  for (let i=0;i<PN;i++){
    pp[i*3]   = (Math.random()-0.5)*200;
    pp[i*3+1] = 8 + Math.random()*30;
    pp[i*3+2] = -10 - Math.random()*120;
    ps[i] = 60 + Math.random()*120;
  }
  partGeo.setAttribute('position', new THREE.BufferAttribute(pp, 3));
  partGeo.setAttribute('size',     new THREE.BufferAttribute(ps, 1));
  const partMat = new THREE.ShaderMaterial({
    transparent: true, depthWrite: false,
    uniforms: { uTime: { value: 0 } },
    vertexShader: `
      attribute float size;
      varying float vA;
      uniform float uTime;
      void main(){
        vec3 p = position;
        p.x += sin(uTime*0.05 + position.z*0.04)*1.5;
        p.y += sin(uTime*0.07 + position.x*0.03)*0.3;
        vec4 mv = modelViewMatrix*vec4(p,1.0);
        gl_PointSize = size * (260.0 / -mv.z);
        gl_Position = projectionMatrix*mv;
        vA = 0.10 + 0.06*sin(uTime*0.4 + position.x);
      }
    `,
    fragmentShader: `
      varying float vA;
      void main(){
        vec2 c = gl_PointCoord - 0.5;
        float a = smoothstep(0.5,0.0,length(c));
        gl_FragColor = vec4(0.94,0.92,0.88, a*vA);
      }
    `,
  });
  const part = new THREE.Points(partGeo, partMat);
  scene.add(part);

  // ══════════════════════════════════════════════════════════════════════════
  // Camera path: high aerial → descend to the dome → push toward summit
  // ══════════════════════════════════════════════════════════════════════════
  // ── DEBUG MODE — append ?debug=mountain to the URL to disable the scripted
  // camera path and enable OrbitControls. Drag the mouse to orbit, scroll to
  // zoom, right-drag to pan. Press 'L' to dump the current camera position +
  // look-at target to the console — copy those numbers back here to bake them
  // into the path.
  const debugMode = new URLSearchParams(window.location.search).get('debug') === 'mountain';
  let controls = null;
  if (debugMode && THREE.OrbitControls) {
    // Force mountain stage to be the only visible, interactive scene.
    container.style.pointerEvents = 'auto';
    container.style.zIndex = '10';
    document.body.style.overflow = 'hidden';
    const otherStages = ['stage-cosmos', 'stage-atlas']
      .map((id) => document.getElementById(id)).filter(Boolean);
    // Initial pose roughly matches the end of the scripted path
    camera.position.set(0, 18, 28);
    controls = new THREE.OrbitControls(camera, renderer.domElement);
    controls.target.set(0, 25, -50);
    controls.enableDamping = true;
    controls.dampingFactor = 0.08;
    controls.update();

    const logCam = () => {
      const p = camera.position, tg = controls.target;
      const msg = `[mountain camera]
  position: (${p.x.toFixed(2)}, ${p.y.toFixed(2)}, ${p.z.toFixed(2)})
  target:   (${tg.x.toFixed(2)}, ${tg.y.toFixed(2)}, ${tg.z.toFixed(2)})
  fov:      ${camera.fov.toFixed(1)}°`;
      console.log(msg);
      // Also flash it on the panel so it's visible without DevTools
      const out = document.getElementById('dbg-out');
      if (out) out.textContent = msg;
    };

    window.addEventListener('keydown', (e) => {
      if (e.key.toLowerCase() === 'l') logCam();
    });

    // On-screen debug panel — works even when DevTools has focus
    const panel = document.createElement('div');
    panel.style.cssText = `
      position:fixed; top:12px; right:12px; z-index:9999;
      background:rgba(10,10,12,0.85); color:#e8e2d0; font-family:Geist Mono,monospace;
      font-size:11px; padding:10px 14px; border:1px solid #2a2622; border-radius:4px;
      letter-spacing:.04em; line-height:1.4; min-width:280px;
      pointer-events:auto;`;
    panel.innerHTML = `
      <div style="color:#c9b896; margin-bottom:6px; letter-spacing:.18em">DEBUG · MOUNTAIN</div>
      <div style="color:#8a8378; font-size:10px; margin-bottom:8px">
        drag = orbit · scroll = zoom · right-drag = pan
      </div>
      <button id="dbg-log" style="cursor:pointer; background:#c9b896; color:#0a0a0a;
        border:0; padding:6px 14px; font-family:inherit; font-size:11px;
        letter-spacing:.12em; text-transform:uppercase; margin-right:6px">Log camera</button>
      <pre id="dbg-out" style="margin:8px 0 0; white-space:pre-wrap; color:#c9c2b3;
        font-size:10.5px; max-width:300px"></pre>`;
    document.body.appendChild(panel);
    document.getElementById('dbg-log').addEventListener('click', logCam);

    // Independent render loop so we don't depend on the scroll-driven loop
    const t0 = performance.now();
    const debugTick = () => {
      const t = (performance.now() - t0) / 1000;
      controls.update();
      fogs.forEach((f) => { f.mat.uniforms.uTime.value = t; });
      skyMat.uniforms.uTime.value = t;
      partMat.uniforms.uTime.value = t;
      renderer.render(scene, camera);
      container.style.opacity = '1';
      otherStages.forEach((s) => { s.style.opacity = '0'; });
      requestAnimationFrame(debugTick);
    };
    debugTick();
    console.log('[mountain] DEBUG MODE active');
  }

  const update = (p, t) => {
    fogs.forEach((f) => { f.mat.uniforms.uTime.value = t; });
    skyMat.uniforms.uTime.value = t;
    partMat.uniforms.uTime.value = t;
    if (debugMode) { controls?.update(); return; }
    p = clamp01(p);
    const e = ease(p);
    // Approach from above-and-back
    // Constant-distance orbit around the massif (no dolly-in zoom).
    // The camera sweeps ~70° around the mountain center, keeping radius fixed.
    const orbitAngle  = lerp(Math.PI / 2 + 0.524, Math.PI / 2 + 1.724, p);
    const orbitRadius = 173;
    const cx = 0, cy = 30, cz = -30; // mountain center (+20 en avant)
    camera.position.x = cx + Math.sin(orbitAngle) * orbitRadius;
    camera.position.y = cy + 10 + Math.sin(t*0.04)*0.4; // ~40, slight bob
    camera.position.z = cz + Math.cos(orbitAngle) * orbitRadius;
    const lookY = lerp(25, 30, e);
    camera.lookAt(cx, lookY, cz);
    camera.fov = 36 + Math.sin(t*0.10)*0.3;
    camera.updateProjectionMatrix();
  };

  const setStyle = () => {};

  const resize = () => {
    const r = container.getBoundingClientRect();
    camera.aspect = r.width / r.height;
    camera.updateProjectionMatrix();
    renderer.setSize(r.width, r.height, false);
  };
  resize();

  const dispose = () => {
    renderer.dispose();
    if (renderer.domElement.parentNode) renderer.domElement.parentNode.removeChild(renderer.domElement);
    if (mountainModel) mountainModel.traverse((o) => {
      if (o.isMesh) {
        o.geometry?.dispose?.();
        if (Array.isArray(o.material)) o.material.forEach((m) => m.dispose?.());
        else o.material?.dispose?.();
      }
    });
    fogs.forEach(({geo, mat}) => { geo.dispose(); mat.dispose(); });
    skyGeo.dispose(); skyMat.dispose();
    partGeo.dispose(); partMat.dispose();
  };

  return { scene, camera, renderer, update, resize, dispose, setStyle };
}

window.initMountain = initMountain;
