// planet.js — orbital planet body: procedural surface shader + atmosphere rim shell.
import * as THREE from 'three';

const NOISE_GLSL = /* glsl */`
  vec3 hash3(vec3 p){
    p = vec3(dot(p,vec3(127.1,311.7,74.7)),
             dot(p,vec3(269.5,183.3,246.1)),
             dot(p,vec3(113.5,271.9,124.6)));
    return -1.0 + 2.0*fract(sin(p)*43758.5453123);
  }
  float snoise(vec3 p){
    vec3 i = floor(p); vec3 f = fract(p);
    vec3 u = f*f*(3.0-2.0*f);
    return mix(mix(mix(dot(hash3(i+vec3(0,0,0)),f-vec3(0,0,0)),
                       dot(hash3(i+vec3(1,0,0)),f-vec3(1,0,0)),u.x),
                   mix(dot(hash3(i+vec3(0,1,0)),f-vec3(0,1,0)),
                       dot(hash3(i+vec3(1,1,0)),f-vec3(1,1,0)),u.x),u.y),
               mix(mix(dot(hash3(i+vec3(0,0,1)),f-vec3(0,0,1)),
                       dot(hash3(i+vec3(1,0,1)),f-vec3(1,0,1)),u.x),
                   mix(dot(hash3(i+vec3(0,1,1)),f-vec3(0,1,1)),
                       dot(hash3(i+vec3(1,1,1)),f-vec3(1,1,1)),u.x),u.y),u.z);
  }
  float fbm(vec3 p){
    float a=0.5, s=0.0;
    for(int i=0;i<5;i++){ s+=a*snoise(p); p*=2.03; a*=0.5; }
    return s;
  }
`;

function surfaceMaterial(d) {
  const c = d.colors;
  return new THREE.ShaderMaterial({
    uniforms: {
      uBase: { value: c.spaceBase.clone() },
      uBand: { value: c.spaceBand.clone() },
      uCloud: { value: c.spaceCloud.clone() },
      uSunDir: { value: new THREE.Vector3(1, 0.3, 0.5).normalize() },
      uSunColor: { value: c.sun.clone() },
      uSeed: { value: (d.seed % 1000) * 0.013 },
      uTime: { value: 0 },
      uRidged: { value: d.terrain.ridged },
    },
    vertexShader: /* glsl */`
      varying vec3 vNormal; varying vec3 vPos; varying vec3 vWN; varying vec3 vView;
      void main(){
        vNormal = normalize(normalMatrix * normal);
        vWN = normalize(mat3(modelMatrix) * normal);
        vPos = position;
        vec4 mv = modelViewMatrix * vec4(position,1.0);
        vView = normalize(-mv.xyz);
        gl_Position = projectionMatrix * mv;
      }
    `,
    fragmentShader: /* glsl */`
      precision highp float;
      varying vec3 vNormal; varying vec3 vPos; varying vec3 vWN; varying vec3 vView;
      uniform vec3 uBase, uBand, uCloud, uSunDir, uSunColor;
      uniform float uSeed, uTime, uRidged;
      ${NOISE_GLSL}
      void main(){
        vec3 n = normalize(vPos);
        vec3 sp = n*2.2 + uSeed;
        float continents = fbm(sp*1.4);
        float detail = fbm(sp*5.5);
        float bands = fbm(vec3(n.y*5.0 + uSeed, n.x*1.5, n.z*1.5));
        // strong banding contrast between the two surface tones
        vec3 surf = mix(uBase, uBand, smoothstep(-0.28, 0.28, bands + detail*0.22));
        float landMask = smoothstep(0.06, 0.40, continents);
        surf = mix(surf, uBand*1.22, landMask*0.6);
        surf *= 0.85 + 0.30 * detail;   // fine mottling for surface texture
        // saturate toward vivid
        float lum = dot(surf, vec3(0.299,0.587,0.114));
        surf = clamp(mix(vec3(lum), surf, 1.6), 0.0, 1.0);
        // poles brighten
        surf = mix(surf, surf*1.3+0.05, smoothstep(0.66,0.97,abs(n.y)));
        // swirling cloud bands
        float cl = fbm(sp*2.3 + vec3(uTime*0.01,0.0,0.0));
        float clouds = smoothstep(0.16, 0.58, cl);
        surf = mix(surf, uCloud, clouds*0.42);
        // world-space lambert + soft terminator
        float ndl = dot(normalize(vWN), normalize(uSunDir));
        float lit = smoothstep(-0.08, 0.30, ndl);
        vec3 sunTint = mix(vec3(1.0), uSunColor, 0.55);
        vec3 col = surf * (0.035 + 0.95 * lit) * sunTint;
        // subtle limb sheen on the day side only
        float fres = pow(1.0 - abs(dot(normalize(vNormal), normalize(vView))), 3.0);
        col += uSunColor * fres * lit * 0.16;
        // faint night-side cloud glow
        col += uCloud * clouds * (1.0-lit) * 0.04;
        gl_FragColor = vec4(col, 1.0);
      }
    `,
  });
}

function atmosphereMaterial(d, side = THREE.BackSide) {
  return new THREE.ShaderMaterial({
    transparent: true,
    blending: THREE.AdditiveBlending,
    depthWrite: false,
    side,
    uniforms: {
      uColor: { value: d.colors.atmosphere.clone() },
      uSunDir: { value: new THREE.Vector3(1, 0.3, 0.5).normalize() },
      uPower: { value: 3.2 },
      uIntensity: { value: 1.15 },
    },
    vertexShader: /* glsl */`
      varying vec3 vNormal; varying vec3 vView; varying vec3 vWN;
      void main(){
        vNormal = normalize(normalMatrix * normal);
        vWN = normalize(mat3(modelMatrix) * normal);
        vec4 mv = modelViewMatrix * vec4(position,1.0);
        vView = normalize(-mv.xyz);
        gl_Position = projectionMatrix * mv;
      }
    `,
    fragmentShader: /* glsl */`
      varying vec3 vNormal; varying vec3 vView; varying vec3 vWN;
      uniform vec3 uColor, uSunDir; uniform float uPower, uIntensity;
      void main(){
        float fres = pow(1.0 - abs(dot(normalize(vNormal), normalize(vView))), uPower);
        float sun = clamp(dot(normalize(vWN), normalize(uSunDir))*0.6+0.55, 0.0, 1.0);
        gl_FragColor = vec4(uColor * fres * uIntensity * sun, fres * sun);
      }
    `,
  });
}

// Build an orbital planet. Returns { group, setSun(dir), update(dt), surfaceMat }.
export function makeSpacePlanet(d, radius) {
  const group = new THREE.Group();
  const surfaceMat = surfaceMaterial(d);
  const body = new THREE.Mesh(new THREE.SphereGeometry(radius, 96, 64), surfaceMat);
  group.add(body);

  const atmoMat = atmosphereMaterial(d);
  const atmo = new THREE.Mesh(new THREE.SphereGeometry(radius * 1.16, 64, 48), atmoMat);
  group.add(atmo);

  // thin inner haze (front side) for a soft edge into the disk
  const innerMat = atmosphereMaterial(d, THREE.FrontSide);
  innerMat.uniforms.uPower.value = 5.0;
  innerMat.uniforms.uIntensity.value = 0.5;
  const inner = new THREE.Mesh(new THREE.SphereGeometry(radius * 1.02, 64, 48), innerMat);
  group.add(inner);

  if (d.hasRing) {
    const ringGeo = new THREE.RingGeometry(radius * 1.5, radius * 2.4, 128);
    // tilt UVs so the ring shades radially
    const ringMat = new THREE.MeshBasicMaterial({
      color: d.colors.spaceBand.clone().multiplyScalar(1.2),
      transparent: true, opacity: 0.5, side: THREE.DoubleSide, blending: THREE.AdditiveBlending, depthWrite: false,
    });
    const ring = new THREE.Mesh(ringGeo, ringMat);
    ring.rotation.x = Math.PI * 0.42;
    ring.rotation.y = 0.3;
    group.add(ring);
  }

  const mats = [surfaceMat, atmoMat, innerMat];
  return {
    group, body, surfaceMat,
    setSun(dir) { mats.forEach((m) => m.uniforms.uSunDir && (m.uniforms.uSunDir.value.copy(dir))); },
    update(dt) { surfaceMat.uniforms.uTime.value += dt; body.rotation.y += dt * 0.015; },
  };
}
