import * as THREE from 'three';
import { Rng, fbm2, ridged2, valueNoise2 } from './noise.js';
import { planetName, biomeTag } from './names.js';

const hsl = (h, s, l) => new THREE.Color().setHSL(((h % 1) + 1) % 1, s, l);

// Curated world archetypes give guaranteed cross-planet contrast; seed jitters each one.
// Each entry anchors the hues; lightness/saturation ramps are derived.
const SCHEMES = [
  { id: 'coral',   gHue: 0.96, gSat: 0.66, gLight: 0.54, fHue: 0.74, fSat: 0.72, fLight: 0.58, sHue: 0.50, sSat: 0.58, cHue: 0.52, atm: 0.52, ridged: 0.2 },
  { id: 'ochre',   gHue: 0.09, gSat: 0.66, gLight: 0.46, fHue: 0.02, fSat: 0.70, fLight: 0.42, sHue: 0.60, sSat: 0.52, cHue: 0.11, atm: 0.60, ridged: 0.55 },
  { id: 'frost',   gHue: 0.56, gSat: 0.34, gLight: 0.70, fHue: 0.52, fSat: 0.58, fLight: 0.66, sHue: 0.66, sSat: 0.40, cHue: 0.55, atm: 0.62, ridged: 0.7 },
  { id: 'toxic',   gHue: 0.28, gSat: 0.66, gLight: 0.44, fHue: 0.16, fSat: 0.85, fLight: 0.54, sHue: 0.09, sSat: 0.55, cHue: 0.30, atm: 0.10, ridged: 0.3 },
  { id: 'ember',   gHue: 0.03, gSat: 0.68, gLight: 0.34, fHue: 0.09, fSat: 0.90, fLight: 0.56, sHue: 0.74, sSat: 0.55, cHue: 0.07, atm: 0.74, ridged: 0.75 },
  { id: 'violet',  gHue: 0.78, gSat: 0.58, gLight: 0.44, fHue: 0.88, fSat: 0.74, fLight: 0.62, sHue: 0.10, sSat: 0.55, cHue: 0.82, atm: 0.10, ridged: 0.35 },
  { id: 'verdant', gHue: 0.34, gSat: 0.62, gLight: 0.48, fHue: 0.42, fSat: 0.66, fLight: 0.55, sHue: 0.52, sSat: 0.52, cHue: 0.48, atm: 0.50, ridged: 0.25 },
];

const ARCHES = ['tubeTree', 'balloon', 'coralFan', 'crystalBush', 'spiralReed', 'umbrella'];

// Scheme indices ordered by ground hue; stride through them so neighbouring planets
// land far apart on the colour wheel (guaranteed cross-planet contrast).
const SCHEME_ORDER = SCHEMES.map((_, i) => i).sort((a, b) => SCHEMES[a].gHue - SCHEMES[b].gHue);
export function pickSchemes(rot, n) {
  const L = SCHEME_ORDER.length, out = [];
  for (let i = 0; i < n; i++) out.push(SCHEME_ORDER[(((rot | 0) + i * 3) % L + L) % L]);
  return out;
}

export function makePlanetSpec(seed, index, schemeIndex = 0) {
  const rng = new Rng(seed);
  const base = SCHEMES[((schemeIndex | 0) % SCHEMES.length + SCHEMES.length) % SCHEMES.length];
  const j = (v, amt) => v + rng.range(-amt, amt);

  const gHue = j(base.gHue, 0.03), fHue = j(base.fHue, 0.03), sHue = j(base.sHue, 0.025), cHue = j(base.cHue, 0.04);

  const palette = {
    groundLow: hsl(gHue, base.gSat, base.gLight * 0.7),
    groundMid: hsl(gHue, base.gSat, base.gLight),
    groundHigh: hsl(gHue + 0.02, base.gSat * 0.8, Math.min(0.85, base.gLight * 1.35)),
    rock: hsl(gHue - 0.02, base.gSat * 0.4, base.gLight * 0.5),
    flora: [
      hsl(fHue, base.fSat, base.fLight),
      hsl(fHue + 0.05, base.fSat * 0.9, base.fLight * 0.85),
      hsl(fHue - 0.06, base.fSat, Math.min(0.7, base.fLight * 1.15)),
    ],
    crystal: hsl(cHue, 0.85, 0.6),
    crystalEmissive: hsl(cHue, 0.95, 0.55),
    skyTop: hsl(sHue + 0.02, base.sSat * 0.95, 0.24),
    skyHorizon: hsl(sHue, base.sSat, 0.56),
    fog: hsl(sHue, base.sSat * 0.85, 0.5),
    sun: hsl(j(0.09, 0.04), 0.35, 0.62),
    ambient: hsl(sHue, base.sSat * 0.6, 0.5),
    atmosphere: hsl(base.atm, 0.75, 0.6),
    space: hsl(gHue, base.gSat * 0.85, base.gLight * 0.85),
    spaceB: hsl(gHue + 0.04, base.gSat * 0.6, base.gLight * 1.1),
    cloud: hsl(sHue + 0.04, 0.25, 0.92),
  };

  // Flora archetype subset (3-4 of them) chosen per planet.
  const shuffled = [...ARCHES].sort(() => rng.next() - 0.5);
  const archetypes = shuffled.slice(0, 3 + (rng.chance(0.5) ? 1 : 0));

  return {
    seed, index,
    name: planetName(seed),
    biome: biomeTag(seed),
    schemeId: base.id,
    palette,
    terrain: {
      amp: rng.range(12, 22),
      freq: rng.range(0.010, 0.018),
      octaves: 5,
      gain: rng.range(0.45, 0.55),
      warp: rng.range(0.8, 1.8),
      ridged: base.ridged + rng.range(-0.1, 0.1),
    },
    flora: {
      archetypes,
      density: rng.range(1.0, 1.5),
      scaleRange: [rng.range(0.9, 1.2), rng.range(2.0, 3.0)],
      speciesCount: 6,
    },
    hasRing: index === 1 || rng.chance(0.25),
    gravity: rng.range(7, 11),
    fogDensity: rng.range(0.0045, 0.0065),
    rotSpeed: rng.range(0.01, 0.03) * rng.sign(),
  };
}

// Sample terrain height at world x,z (shared by surface mesh, placement, collision).
export function terrainHeight(spec, x, z) {
  const t = spec.terrain;
  const wx = x + fbm2(x * t.freq * 0.5, z * t.freq * 0.5, spec.seed + 99, 2) * 40 * t.warp;
  const wz = z + fbm2(x * t.freq * 0.5 + 5, z * t.freq * 0.5 + 5, spec.seed + 17, 2) * 40 * t.warp;
  let h = fbm2(wx * t.freq, wz * t.freq, spec.seed, t.octaves, 2.0, t.gain);
  const r = ridged2(wx * t.freq * 1.3, wz * t.freq * 1.3, spec.seed + 7, 4);
  h = h * (1 - t.ridged) + (r * 2 - 1) * t.ridged;
  return h * t.amp;
}

// ---- equirectangular canvas texture for the orbit-view sphere ----
function planetTexture(spec, size = 512) {
  const c = document.createElement('canvas');
  c.width = size; c.height = size / 2;
  const ctx = c.getContext('2d');
  const img = ctx.createImageData(c.width, c.height);
  const a = spec.palette.space, b = spec.palette.spaceB, low = spec.palette.groundLow;
  const tmp = new THREE.Color();
  for (let y = 0; y < c.height; y++) {
    const lat = y / c.height; // 0..1
    const polar = Math.pow(Math.abs(lat - 0.5) * 2, 2.2); // caps
    for (let x = 0; x < c.width; x++) {
      const u = x / c.width;
      // banding + blotches
      const band = 0.5 + 0.5 * Math.sin((lat * 8 + fbm2(u * 6, lat * 6, spec.seed, 3)) * Math.PI * 2);
      let n = fbm2(u * 7, lat * 14, spec.seed + 3, 5) * 0.5 + 0.5;
      n = n * 0.6 + band * 0.4;
      tmp.copy(low).lerp(a, n).lerp(b, Math.max(0, n - 0.6) * 1.4);
      tmp.lerp(spec.palette.groundHigh, polar * 0.8); // icy/bright caps
      const i = (y * c.width + x) * 4;
      img.data[i] = tmp.r * 255; img.data[i + 1] = tmp.g * 255; img.data[i + 2] = tmp.b * 255; img.data[i + 3] = 255;
    }
  }
  ctx.putImageData(img, 0, 0);
  const tex = new THREE.CanvasTexture(c);
  tex.colorSpace = THREE.SRGBColorSpace;
  tex.anisotropy = 4;
  return tex;
}

function cloudTexture(spec, size = 512) {
  const c = document.createElement('canvas');
  c.width = size; c.height = size / 2;
  const ctx = c.getContext('2d');
  const img = ctx.createImageData(c.width, c.height);
  for (let y = 0; y < c.height; y++) {
    const lat = y / c.height;
    for (let x = 0; x < c.width; x++) {
      const u = x / c.width;
      let n = fbm2(u * 9, lat * 18, spec.seed + 200, 5) * 0.5 + 0.5;
      n = Math.pow(Math.max(0, n - 0.45) * 1.8, 1.4);
      const i = (y * c.width + x) * 4;
      img.data[i] = 255; img.data[i + 1] = 255; img.data[i + 2] = 255;
      img.data[i + 3] = Math.min(255, n * 255);
    }
  }
  ctx.putImageData(img, 0, 0);
  const tex = new THREE.CanvasTexture(c);
  tex.colorSpace = THREE.SRGBColorSpace;
  return tex;
}

// Atmosphere rim shader (fresnel backside glow).
function atmosphereMaterial(color) {
  return new THREE.ShaderMaterial({
    uniforms: { uColor: { value: color }, uPower: { value: 3.2 }, uIntensity: { value: 1.35 } },
    vertexShader: `
      varying vec3 vN; varying vec3 vView;
      void main(){
        vec4 wp = modelMatrix * vec4(position,1.0);
        vN = normalize(mat3(modelMatrix) * normal);
        vView = normalize(cameraPosition - wp.xyz);
        gl_Position = projectionMatrix * viewMatrix * wp;
      }`,
    fragmentShader: `
      uniform vec3 uColor; uniform float uPower; uniform float uIntensity;
      varying vec3 vN; varying vec3 vView;
      void main(){
        float f = pow(1.0 - abs(dot(vN, vView)), uPower);
        gl_FragColor = vec4(uColor * uIntensity, f);
      }`,
    blending: THREE.AdditiveBlending,
    transparent: true,
    side: THREE.BackSide,
    depthWrite: false,
  });
}

// Build the orbit-view planet (surface sphere + clouds + atmosphere + optional ring).
export function createPlanetMesh(spec, radius) {
  const group = new THREE.Group();
  group.name = 'planet_' + spec.name;

  const surfMat = new THREE.MeshStandardMaterial({
    map: planetTexture(spec),
    roughness: 0.92, metalness: 0.0,
    emissive: spec.palette.space, emissiveIntensity: 0.04,
  });
  const surf = new THREE.Mesh(new THREE.SphereGeometry(radius, 64, 48), surfMat);
  surf.name = 'surface';
  group.add(surf);

  const cloudMat = new THREE.MeshStandardMaterial({
    map: cloudTexture(spec), transparent: true, opacity: 0.55,
    roughness: 1, metalness: 0, depthWrite: false, color: spec.palette.cloud,
  });
  const clouds = new THREE.Mesh(new THREE.SphereGeometry(radius * 1.012, 48, 32), cloudMat);
  clouds.name = 'clouds';
  group.add(clouds);

  const atmo = new THREE.Mesh(
    new THREE.SphereGeometry(radius * 1.14, 48, 32),
    atmosphereMaterial(spec.palette.atmosphere)
  );
  atmo.name = 'atmosphere';
  group.add(atmo);

  if (spec.hasRing) {
    const ringTex = ringTexture(spec);
    const ringGeo = new THREE.RingGeometry(radius * 1.5, radius * 2.4, 96, 1);
    // fix UVs so the texture maps radially
    const pos = ringGeo.attributes.position, uv = ringGeo.attributes.uv;
    const v = new THREE.Vector3();
    for (let i = 0; i < pos.count; i++) {
      v.fromBufferAttribute(pos, i);
      const d = (v.length() - radius * 1.5) / (radius * 0.9);
      uv.setXY(i, d, 0.5);
    }
    const ring = new THREE.Mesh(ringGeo, new THREE.MeshBasicMaterial({
      map: ringTex, transparent: true, side: THREE.DoubleSide, depthWrite: false,
      opacity: 0.9, blending: THREE.NormalBlending,
    }));
    ring.rotation.x = Math.PI / 2 - 0.32;
    ring.name = 'ring';
    group.add(ring);
  }

  group.userData.surf = surf;
  group.userData.clouds = clouds;
  group.userData.spec = spec;
  group.userData.radius = radius;
  return group;
}

function ringTexture(spec) {
  const c = document.createElement('canvas');
  c.width = 256; c.height = 4;
  const ctx = c.getContext('2d');
  for (let x = 0; x < 256; x++) {
    const t = x / 256;
    const n = valueNoise2(t * 40, 0, spec.seed + 88);
    const gap = (Math.sin(t * 26) * 0.5 + 0.5) * n;
    const alpha = Math.pow(gap, 1.4) * (1 - Math.abs(t - 0.5));
    const col = spec.palette.spaceB.clone().lerp(spec.palette.groundHigh, n);
    ctx.fillStyle = `rgba(${col.r * 255 | 0},${col.g * 255 | 0},${col.b * 255 | 0},${alpha * 1.4})`;
    ctx.fillRect(x, 0, 1, 4);
  }
  const tex = new THREE.CanvasTexture(c);
  tex.colorSpace = THREE.SRGBColorSpace;
  return tex;
}
