/* eslint-disable */
/* global React, ReactDOM, THREE */

const { useState, useEffect, useRef, useMemo, useCallback } = React;

// ========================================================
// FRAME DATA
// ========================================================
const SUN_TEXTURE =
  "https://cdn.jsdelivr.net/gh/jeromeetienne/threex.planets@master/images/sunmap.jpg";

const LIFECYCLE_FRAMES = [
  {
    id: "F-01",
    when: "4.6 billion years ago",
    text: "Our Sun was born inside a giant cloud of dust and gas.",
    audio: "lifecycle1.mp3",
    instrument: "JWST · NIRCam",
    filter: "F200W · F335M · F444W",
    target: "Stellar Nursery · NGC 1333 analog",
    coords: "RA 03ʰ 29ᵐ 02ˢ · DEC +31° 16′ 00″",
    exposure: "EXP 8400s",
    scaleLabel: "0.4 ly",
    pipColor: "#ffb98a",
    showOrbits: false,
    sceneKind: "nebula-birth",
  },
  {
    id: "F-02",
    when: "4 billion years ago",
    text: "It started shining! Earth wasn't here yet.",
    audio: "lifecycle2.mp3",
    instrument: "SOHO · MDI",
    filter: "Continuum · Fe I 6173 Å",
    target: "Sol · Zero-Age Main Sequence",
    coords: "T₀ + 0.6 Gyr · L = 0.7 L⊙",
    exposure: "EXP 8.4s",
    scaleLabel: "1 R⊙",
    pipColor: "#ffe6a8",
    showOrbits: false,
    sceneKind: "sun-young",
  },
  {
    id: "F-03",
    when: "Today",
    text: "This is the Sun you see in the sky every morning.",
    audio: "lifecycle3.mp3",
    instrument: "SDO · AIA",
    filter: "171 Å · Fe IX/X",
    target: "Sol · 4.6 Gyr · Main Sequence",
    coords: "RA 02ʰ 31ᵐ 49ˢ · DEC +14° 11′ 33″",
    exposure: "EXP 2.0s",
    scaleLabel: "1 R⊙",
    pipColor: "#ffb347",
    showOrbits: true,
    sceneKind: "sun-today",
  },
  {
    id: "F-04",
    when: "In about 5 billion years",
    text: "When it grows old, the Sun will start to puff up…",
    audio: "lifecycle4.mp3",
    instrument: "Predicted · MESA v23",
    filter: "Bolometric · Synthetic",
    target: "Sol · Subgiant Branch",
    coords: "T₊ 5.0 Gyr · L = 2.2 L⊙",
    exposure: "MODEL",
    scaleLabel: "≈ 2 R⊙",
    pipColor: "#ff8c3a",
    showOrbits: "fading",
    sceneKind: "sun-subgiant",
  },
  {
    id: "F-05",
    when: "In about 6 billion years",
    text: "…big enough to swallow Mercury and Venus!",
    audio: "lifecycle5.mp3",
    instrument: "Predicted · MESA v23",
    filter: "Bolometric · Synthetic",
    target: "Sol · Red Giant Branch Tip",
    coords: "T₊ 6.5 Gyr · L = 2700 L⊙",
    exposure: "MODEL",
    scaleLabel: "≈ 200 R⊙",
    pipColor: "#d44218",
    showOrbits: false,
    sceneKind: "sun-redgiant",
  },
  {
    id: "F-06",
    when: "Far in the future",
    text: "Then it shrinks into a tiny diamond-bright star and rests.",
    audio: "lifecycle6.mp3",
    instrument: "HST · WFC3/UVIS",
    filter: "[O III] 502nm · Hα 656nm · [N II] 658nm",
    target: "Planetary Nebula · Sol Remnant",
    coords: "T₊ 8.0 Gyr · M ≈ 0.54 M⊙",
    exposure: "EXP 2400s",
    scaleLabel: "0.5 ly",
    pipColor: "#9adcff",
    showOrbits: false,
    sceneKind: "white-dwarf",
  },
];

function playLifecycleClip(frameIndex) {
  const frame = LIFECYCLE_FRAMES[frameIndex];
  if (!frame || !frame.audio) return;
  if (window.__narration) window.__narration.play(frame.audio);
}

// ========================================================
// CORONA TEXTURE — same recipe as main page
// ========================================================
function makeCoronaTexture(stops) {
  const canvas = document.createElement("canvas");
  canvas.width = 512;
  canvas.height = 512;
  const ctx = canvas.getContext("2d");
  const grad = ctx.createRadialGradient(256, 256, 0, 256, 256, 256);
  for (const [pos, col] of stops) grad.addColorStop(pos, col);
  ctx.fillStyle = grad;
  ctx.fillRect(0, 0, 512, 512);
  return new THREE.CanvasTexture(canvas);
}

// Tinted variant of the Sun texture (red giant has cooler/redder map)
function makeTintedSunTexture(srcTex, tint) {
  const img = srcTex.image;
  if (!img || !img.width) return srcTex;
  const c = document.createElement("canvas");
  c.width = img.width;
  c.height = img.height;
  const ctx = c.getContext("2d");
  ctx.drawImage(img, 0, 0);
  ctx.globalCompositeOperation = "multiply";
  ctx.fillStyle = tint;
  ctx.fillRect(0, 0, c.width, c.height);
  const t = new THREE.CanvasTexture(c);
  t.colorSpace = THREE.SRGBColorSpace;
  return t;
}

// Procedural cool-star (red giant) noise texture for huge convection cells
function makeRedGiantTexture() {
  const W = 1024,
    H = 512;
  const c = document.createElement("canvas");
  c.width = W;
  c.height = H;
  const ctx = c.getContext("2d");
  const img = ctx.createImageData(W, H);
  const d = img.data;

  // Simple value noise
  const seed = 89;
  const hash = (x, y) => {
    let h = (x * 374761393 + y * 668265263 + seed * 1274126177) | 0;
    h = (h ^ (h >>> 13)) * 1274126177;
    h = h ^ (h >>> 16);
    return (h >>> 0) / 4294967296;
  };
  const lerp = (a, b, t) => a + (b - a) * t;
  const fade = (t) => t * t * (3 - 2 * t);
  const n2 = (x, y) => {
    const xi = Math.floor(x),
      yi = Math.floor(y);
    const xf = x - xi,
      yf = y - yi;
    const u = fade(xf),
      v = fade(yf);
    return lerp(
      lerp(hash(xi, yi), hash(xi + 1, yi), u),
      lerp(hash(xi, yi + 1), hash(xi + 1, yi + 1), u),
      v,
    );
  };
  const fbm = (x, y) => {
    let s = 0,
      a = 1,
      f = 1,
      n = 0;
    for (let i = 0; i < 5; i++) {
      s += a * n2(x * f, y * f);
      n += a;
      a *= 0.55;
      f *= 2.05;
    }
    return s / n;
  };

  for (let y = 0; y < H; y++) {
    for (let x = 0; x < W; x++) {
      const u = x / W,
        v = y / H;
      // Big cells
      const cells = fbm(u * 6, v * 3 + 0.3);
      // Fine granulation
      const fine = fbm(u * 30, v * 18);
      const m = cells * 0.85 + fine * 0.25;
      // Map to red-giant palette
      const t = Math.max(0, Math.min(1, m));
      // Hot bright cell centers -> dim cool intercell lanes
      let r, g, b;
      if (t > 0.55) {
        const k = (t - 0.55) / 0.45;
        r = 200 + 55 * k;
        g = 90 + 100 * k;
        b = 40 + 50 * k;
      } else {
        const k = t / 0.55;
        r = 60 + 140 * k;
        g = 10 + 80 * k;
        b = 5 + 35 * k;
      }
      const idx = (y * W + x) * 4;
      d[idx] = r;
      d[idx + 1] = g;
      d[idx + 2] = b;
      d[idx + 3] = 255;
    }
  }
  ctx.putImageData(img, 0, 0);
  const t = new THREE.CanvasTexture(c);
  t.colorSpace = THREE.SRGBColorSpace;
  return t;
}

// ========================================================
// SUN STAGE — Three.js renderer for the disk frames (2-5)
// Base recipe matches the main page; corona/color/scale vary per frame
// ========================================================
function buildSunStage(container, kind) {
  const w = container.clientWidth,
    h = container.clientHeight;
  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(40, w / h, 0.1, 1000);
  camera.position.set(0, 0, 3.2);
  const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  renderer.setSize(w, h);
  renderer.setClearColor(0x000000, 0);
  container.appendChild(renderer.domElement);

  scene.add(new THREE.AmbientLight(0xffffff, 1.2));

  // Disk size per stage (relative to camera at z=3.2; sphere radius 1 fills modestly)
  const sizeForStage = {
    "sun-young": 0.55,
    "sun-today": 0.75,
    "sun-subgiant": 0.95,
    "sun-redgiant": 1.25,
  };
  const baseColor = {
    "sun-young": 0xffe6a0,
    "sun-today": 0xffd66b,
    "sun-subgiant": 0xff9a44,
    "sun-redgiant": 0xff5a20,
  };
  const coronaStops = {
    "sun-young": [
      [0, "rgba(255,238,180,0.95)"],
      [0.18, "rgba(255,200,100,0.55)"],
      [0.35, "rgba(255,140,60,0.18)"],
      [0.5, "rgba(255,90,30,0)"],
      [1, "rgba(255,90,30,0)"],
    ],
    "sun-today": [
      [0, "rgba(255,224,138,1)"],
      [0.15, "rgba(255,170,60,0.7)"],
      [0.32, "rgba(255,110,40,0.25)"],
      [0.5, "rgba(255,91,26,0)"],
      [1, "rgba(255,91,26,0)"],
    ],
    "sun-subgiant": [
      [0, "rgba(255,180,90,0.95)"],
      [0.18, "rgba(255,110,30,0.5)"],
      [0.35, "rgba(180,40,10,0.18)"],
      [0.5, "rgba(120,20,5,0)"],
      [1, "rgba(120,20,5,0)"],
    ],
    "sun-redgiant": [
      [0, "rgba(255,140,70,0.85)"],
      [0.2, "rgba(220,60,20,0.4)"],
      [0.38, "rgba(120,20,5,0.12)"],
      [0.5, "rgba(60,5,2,0)"],
      [1, "rgba(60,5,2,0)"],
    ],
  };
  const coronaScale = {
    "sun-young": 2.0,
    "sun-today": 2.4,
    "sun-subgiant": 2.6,
    "sun-redgiant": 2.7,
  };

  const scale = sizeForStage[kind] || 0.75;

  const geo = new THREE.SphereGeometry(1, 64, 64);
  const mat = new THREE.MeshBasicMaterial({
    color: baseColor[kind] || 0xffffff,
  });
  const sphere = new THREE.Mesh(geo, mat);
  sphere.scale.set(scale, scale, scale);
  sphere.rotation.z = (23.4 * Math.PI) / 180;
  scene.add(sphere);

  // Texture: real NASA sun map for everything; red giant gets procedural cool-star map
  let disposed = false;
  if (kind === "sun-redgiant") {
    const tex = makeRedGiantTexture();
    mat.map = tex;
    mat.color.set(0xffffff);
    mat.needsUpdate = true;
  } else {
    const loader = new THREE.TextureLoader();
    loader.crossOrigin = "anonymous";
    loader.load(SUN_TEXTURE, (tex) => {
      if (disposed) {
        tex.dispose();
        return;
      }
      tex.colorSpace = THREE.SRGBColorSpace;
      if (kind === "sun-young") {
        // brighter, more yellow-white
        mat.map = tex;
        mat.color.set(0xfff3d0);
      } else if (kind === "sun-subgiant") {
        // tint warmer/orange
        mat.map = makeTintedSunTexture(tex, "#ff8a3a");
        mat.color.set(0xffffff);
      } else {
        mat.map = tex;
        mat.color.set(0xffffff);
      }
      mat.needsUpdate = true;
    });
  }

  // Corona sprite
  const coronaTex = makeCoronaTexture(
    coronaStops[kind] || coronaStops["sun-today"],
  );
  const spriteMat = new THREE.SpriteMaterial({
    map: coronaTex,
    transparent: true,
    blending: THREE.AdditiveBlending,
    depthWrite: false,
  });
  const sprite = new THREE.Sprite(spriteMat);
  const cs = scale * (coronaScale[kind] || 2.6);
  sprite.scale.set(cs, cs, 1);
  scene.add(sprite);

  // Slow rotation, slower for bigger stars
  const speed =
    kind === "sun-redgiant" ? 0.0006 : kind === "sun-subgiant" ? 0.001 : 0.0015;
  let raf;
  function tick() {
    sphere.rotation.y += speed;
    renderer.render(scene, camera);
    raf = requestAnimationFrame(tick);
  }
  tick();

  const ro = new ResizeObserver(() => {
    const nw = container.clientWidth,
      nh = container.clientHeight;
    if (nw && nh) {
      renderer.setSize(nw, nh);
      camera.aspect = nw / nh;
      camera.updateProjectionMatrix();
    }
  });
  ro.observe(container);

  return () => {
    disposed = true;
    cancelAnimationFrame(raf);
    ro.disconnect();
    renderer.dispose();
    geo.dispose();
    mat.dispose();
    if (mat.map) mat.map.dispose();
    coronaTex.dispose();
    spriteMat.dispose();
    if (renderer.domElement.parentNode)
      renderer.domElement.parentNode.removeChild(renderer.domElement);
  };
}

// ========================================================
// NEBULA + WHITE DWARF — full-frame 2D canvas paintings
// (these aren't a textured sphere, they're scenes — multi-octave fbm)
// ========================================================
function paintNebulaBirth(ctx, W, H) {
  const seed = 101;
  const hash = (x, y, s = seed) => {
    let h = (x * 374761393 + y * 668265263 + s * 1274126177) | 0;
    h = (h ^ (h >>> 13)) * 1274126177;
    h = h ^ (h >>> 16);
    return (h >>> 0) / 4294967296;
  };
  const lerp = (a, b, t) => a + (b - a) * t;
  const fade = (t) => t * t * (3 - 2 * t);
  const noise2 = (x, y, s) => {
    const xi = Math.floor(x),
      yi = Math.floor(y);
    const xf = x - xi,
      yf = y - yi;
    const u = fade(xf),
      v = fade(yf);
    return lerp(
      lerp(hash(xi, yi, s), hash(xi + 1, yi, s), u),
      lerp(hash(xi, yi + 1, s), hash(xi + 1, yi + 1, s), u),
      v,
    );
  };
  const fbm = (x, y, s, oct = 6) => {
    let sum = 0,
      a = 1,
      f = 1,
      n = 0;
    for (let i = 0; i < oct; i++) {
      sum += a * noise2(x * f, y * f, s);
      n += a;
      a *= 0.55;
      f *= 2.05;
    }
    return sum / n;
  };

  const img = ctx.createImageData(W, H);
  const d = img.data;
  for (let y = 0; y < H; y++) {
    for (let x = 0; x < W; x++) {
      const u = x / W,
        v = y / H;
      const cloud = fbm(u * 2.5, v * 2.5, 101);
      const warm = fbm(u * 3.0 + 0.7, v * 3.0 + 0.3, 102);
      const cyan = fbm(u * 4.0 - 0.4, v * 4.0 + 1.2, 103);
      const dust = fbm(u * 5.0, v * 5.0, 202);
      const fil = fbm(u * 14, v * 14, 303, 3);
      const cx = 0.5,
        cy = 0.5;
      const dx = u - cx,
        dy = v - cy;
      const dist2 = dx * dx + dy * dy;
      const centralGlow = Math.exp(-dist2 / 0.15);

      let r = 6,
        g = 5,
        b = 14;
      const warmField =
        Math.max(0, warm - 0.45) * 2.2 * (0.4 + centralGlow * 1.4);
      r += warmField * 200;
      g += warmField * 95;
      b += warmField * 80;
      const pinkField =
        Math.max(0, cloud - 0.55) * 1.8 * (0.3 + centralGlow * 1.0);
      r += pinkField * 180;
      g += pinkField * 60;
      b += pinkField * 110;
      const cyanGlow = Math.exp(-((u - 0.7) ** 2 + (v - 0.35) ** 2) / 0.12);
      const cyanField = Math.max(0, cyan - 0.5) * 1.6 * cyanGlow;
      r += cyanField * 60;
      g += cyanField * 160;
      b += cyanField * 200;
      const filField = Math.max(0, fil - 0.6) * 0.6;
      r += filField * 220;
      g += filField * 180;
      b += filField * 140;
      const dustMask = Math.pow(Math.max(0, 1 - dust * 1.5), 1.5);
      r *= dustMask;
      g *= dustMask;
      b *= dustMask;
      const vign =
        1 - Math.pow(Math.max(Math.abs(u - 0.5), Math.abs(v - 0.5)) * 1.4, 3);
      const vk = Math.max(0.3, vign);
      r *= vk;
      g *= vk;
      b *= vk;

      const i = (y * W + x) * 4;
      d[i] = Math.min(255, r);
      d[i + 1] = Math.min(255, g);
      d[i + 2] = Math.min(255, b);
      d[i + 3] = 255;
    }
  }
  ctx.putImageData(img, 0, 0);

  // Embedded protostar — centered to line up with frames 2-5's Sun position
  const sx = W / 2,
    sy = H / 2;
  ctx.save();
  ctx.globalCompositeOperation = "screen";
  ctx.translate(sx, sy);
  ctx.rotate(-0.2);
  for (const dir of [-1, 1]) {
    const grad = ctx.createLinearGradient(0, 0, 0, 380 * dir);
    grad.addColorStop(0, "rgba(255,200,140,0.45)");
    grad.addColorStop(0.5, "rgba(255,140,80,0.18)");
    grad.addColorStop(1, "rgba(255,140,80,0)");
    ctx.fillStyle = grad;
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.lineTo(-140, 380 * dir);
    ctx.lineTo(140, 380 * dir);
    ctx.closePath();
    ctx.fill();
  }
  ctx.rotate(0.2);
  ctx.translate(-sx, -sy);

  const protoGrad = ctx.createRadialGradient(sx, sy, 0, sx, sy, 220);
  protoGrad.addColorStop(0, "rgba(255,250,220,0.95)");
  protoGrad.addColorStop(0.08, "rgba(255,230,170,0.85)");
  protoGrad.addColorStop(0.3, "rgba(255,160,80,0.45)");
  protoGrad.addColorStop(1, "rgba(255,80,30,0)");
  ctx.fillStyle = protoGrad;
  ctx.beginPath();
  ctx.arc(sx, sy, 220, 0, Math.PI * 2);
  ctx.fill();
  ctx.fillStyle = "#ffffff";
  ctx.beginPath();
  ctx.arc(sx, sy, 4, 0, Math.PI * 2);
  ctx.fill();
  ctx.strokeStyle = "rgba(255,235,200,0.65)";
  ctx.lineWidth = 0.9;
  for (let k = 0; k < 6; k++) {
    const a = (k / 6) * Math.PI * 2 + Math.PI / 12;
    ctx.beginPath();
    ctx.moveTo(sx - Math.cos(a) * 200, sy - Math.sin(a) * 200);
    ctx.lineTo(sx + Math.cos(a) * 200, sy + Math.sin(a) * 200);
    ctx.stroke();
  }
  ctx.restore();

  // Embedded background stars
  const stars = [
    { x: 0.23, y: 0.42, s: 2.2 },
    { x: 0.78, y: 0.62, s: 1.8 },
    { x: 0.38, y: 0.22, s: 1.5 },
    { x: 0.66, y: 0.78, s: 1.3 },
    { x: 0.85, y: 0.3, s: 1.0 },
  ];
  ctx.save();
  ctx.globalCompositeOperation = "screen";
  for (const st of stars) {
    const x = st.x * W,
      y = st.y * H;
    const halo = ctx.createRadialGradient(x, y, 0, x, y, st.s * 12);
    halo.addColorStop(0, "rgba(255,235,200,0.6)");
    halo.addColorStop(1, "rgba(255,235,200,0)");
    ctx.fillStyle = halo;
    ctx.beginPath();
    ctx.arc(x, y, st.s * 12, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = "#ffffff";
    ctx.beginPath();
    ctx.arc(x, y, st.s, 0, Math.PI * 2);
    ctx.fill();
    if (st.s >= 1.5) {
      ctx.strokeStyle = `rgba(255,235,200,0.5)`;
      ctx.lineWidth = 0.5;
      for (let k = 0; k < 6; k++) {
        const a = (k / 6) * Math.PI * 2 + Math.PI / 12;
        ctx.beginPath();
        ctx.moveTo(x - Math.cos(a) * st.s * 10, y - Math.sin(a) * st.s * 10);
        ctx.lineTo(x + Math.cos(a) * st.s * 10, y + Math.sin(a) * st.s * 10);
        ctx.stroke();
      }
    }
  }
  ctx.restore();
}

function paintWhiteDwarf(ctx, W, H) {
  const seedA = 311,
    seedB = 412;
  const hash = (x, y, s) => {
    let h = (x * 374761393 + y * 668265263 + s * 1274126177) | 0;
    h = (h ^ (h >>> 13)) * 1274126177;
    h = h ^ (h >>> 16);
    return (h >>> 0) / 4294967296;
  };
  const lerp = (a, b, t) => a + (b - a) * t;
  const fade = (t) => t * t * (3 - 2 * t);
  const noise2 = (x, y, s) => {
    const xi = Math.floor(x),
      yi = Math.floor(y);
    const xf = x - xi,
      yf = y - yi;
    const u = fade(xf),
      v = fade(yf);
    return lerp(
      lerp(hash(xi, yi, s), hash(xi + 1, yi, s), u),
      lerp(hash(xi, yi + 1, s), hash(xi + 1, yi + 1, s), u),
      v,
    );
  };
  const fbm = (x, y, s, oct = 5) => {
    let sum = 0,
      a = 1,
      f = 1,
      n = 0;
    for (let i = 0; i < oct; i++) {
      sum += a * noise2(x * f, y * f, s);
      n += a;
      a *= 0.55;
      f *= 2.05;
    }
    return sum / n;
  };

  const img = ctx.createImageData(W, H);
  const d = img.data;
  for (let y = 0; y < H; y++) {
    for (let x = 0; x < W; x++) {
      const u = x / W,
        v = y / H;
      const dx = u - 0.5,
        dy = v - 0.5;
      const dist = Math.sqrt(dx * dx + dy * dy);
      const angle = Math.atan2(dy, dx);

      const oiiiBand = Math.exp(-Math.pow((dist - 0.34) / 0.07, 2));
      const haBand = Math.exp(-Math.pow((dist - 0.22) / 0.05, 2));
      const cavity = Math.exp(-Math.pow((dist - 0.1) / 0.06, 2));
      const filAng = fbm(
        Math.cos(angle * 6) * 2,
        Math.sin(angle * 6) * 2 + dist * 8,
        seedA,
        4,
      );
      const filFine = fbm(u * 10, v * 10, seedB, 4);
      const filMod = 0.5 + filAng * 0.7 + (filFine - 0.5) * 0.5;
      const turb = fbm(u * 6, v * 6, seedA, 5);
      const turbMod = 0.5 + turb * 1.0;

      let r = 4,
        g = 4,
        b = 12;
      const oiii = oiiiBand * filMod * turbMod;
      r += oiii * 60;
      g += oiii * 200;
      b += oiii * 195;
      const ha = haBand * filMod * turbMod * 1.1;
      r += ha * 230;
      g += ha * 80;
      b += ha * 130;
      const nii = haBand * 0.6 * filMod * turbMod;
      r += nii * 80;
      g += nii * 50;
      b += nii * 10;
      r += cavity * 60;
      g += cavity * 100;
      b += cavity * 180;
      const vign = Math.max(0.4, 1 - Math.pow(dist / 0.6, 4));
      r *= vign;
      g *= vign;
      b *= vign;

      const i = (y * W + x) * 4;
      d[i] = Math.min(255, r);
      d[i + 1] = Math.min(255, g);
      d[i + 2] = Math.min(255, b);
      d[i + 3] = 255;
    }
  }
  ctx.putImageData(img, 0, 0);

  // Bipolar lobes
  ctx.save();
  ctx.globalCompositeOperation = "screen";
  for (const sgn of [-1, 1]) {
    const lobe = ctx.createRadialGradient(
      W / 2,
      H / 2 + sgn * 200,
      0,
      W / 2,
      H / 2 + sgn * 200,
      180,
    );
    lobe.addColorStop(
      0,
      sgn < 0 ? "rgba(120,220,210,0.18)" : "rgba(255,140,180,0.18)",
    );
    lobe.addColorStop(1, "rgba(120,220,210,0)");
    ctx.fillStyle = lobe;
    ctx.beginPath();
    ctx.ellipse(W / 2, H / 2 + sgn * 200, 90, 200, 0, 0, Math.PI * 2);
    ctx.fill();
  }
  ctx.restore();

  // Central white dwarf
  const sx = W / 2,
    sy = H / 2;
  ctx.save();
  ctx.globalCompositeOperation = "screen";
  for (const [rad, op] of [
    [160, 0.35],
    [70, 0.6],
    [25, 0.95],
  ]) {
    const g1 = ctx.createRadialGradient(sx, sy, 0, sx, sy, rad);
    g1.addColorStop(0, `rgba(255,255,255,${op})`);
    g1.addColorStop(0.25, `rgba(220,235,255,${op * 0.55})`);
    g1.addColorStop(1, "rgba(150,180,240,0)");
    ctx.fillStyle = g1;
    ctx.beginPath();
    ctx.arc(sx, sy, rad, 0, Math.PI * 2);
    ctx.fill();
  }
  ctx.lineCap = "round";
  for (let k = 0; k < 6; k++) {
    const a = (k / 6) * Math.PI * 2 + Math.PI / 12;
    const len = 240;
    const grad = ctx.createLinearGradient(
      sx - Math.cos(a) * len,
      sy - Math.sin(a) * len,
      sx + Math.cos(a) * len,
      sy + Math.sin(a) * len,
    );
    grad.addColorStop(0, "rgba(220,235,255,0)");
    grad.addColorStop(0.45, "rgba(220,235,255,0.5)");
    grad.addColorStop(0.5, "rgba(255,255,255,1)");
    grad.addColorStop(0.55, "rgba(220,235,255,0.5)");
    grad.addColorStop(1, "rgba(220,235,255,0)");
    ctx.strokeStyle = grad;
    ctx.lineWidth = k === 0 ? 1.2 : 0.9;
    ctx.beginPath();
    ctx.moveTo(sx - Math.cos(a) * len, sy - Math.sin(a) * len);
    ctx.lineTo(sx + Math.cos(a) * len, sy + Math.sin(a) * len);
    ctx.stroke();
  }
  ctx.fillStyle = "#ffffff";
  ctx.beginPath();
  ctx.arc(sx, sy, 5, 0, Math.PI * 2);
  ctx.fill();
  ctx.restore();
}

// ========================================================
// REACT WRAPPERS
// ========================================================
function SunCanvas({ kind }) {
  const ref = useRef(null);
  useEffect(() => {
    const dispose = buildSunStage(ref.current, kind);
    return dispose;
  }, [kind]);
  return <div ref={ref} className="three-canvas" />;
}

function NebulaCanvas({ kind }) {
  const ref = useRef(null);
  useEffect(() => {
    const canvas = ref.current;
    if (!canvas) return;
    const parent = canvas.parentElement;
    const draw = () => {
      const rect = parent.getBoundingClientRect();
      const W = Math.max(800, Math.round(rect.width));
      const H = Math.max(800, Math.round(rect.height));
      canvas.width = W;
      canvas.height = H;
      canvas.style.width = "100%";
      canvas.style.height = "100%";
      const ctx = canvas.getContext("2d");
      ctx.clearRect(0, 0, W, H);
      if (kind === "nebula-birth") paintNebulaBirth(ctx, W, H);
      else if (kind === "white-dwarf") paintWhiteDwarf(ctx, W, H);
    };
    draw();
    const ro = new ResizeObserver(draw);
    ro.observe(parent);
    return () => ro.disconnect();
  }, [kind]);
  return <canvas ref={ref} className="scene-canvas" />;
}

// ========================================================
// STARFIELD backdrop (only for the disk frames)
// ========================================================
function StarField({ frameIndex }) {
  const ref = useRef(null);
  useEffect(() => {
    const canvas = ref.current;
    if (!canvas) return;
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    const seed = 2030 + frameIndex * 17;
    const hash = (x, y) => {
      let h = (x * 374761393 + y * 668265263 + seed * 1274126177) | 0;
      h = (h ^ (h >>> 13)) * 1274126177;
      h = h ^ (h >>> 16);
      return (h >>> 0) / 4294967296;
    };
    let counter = 0;
    const r = () => {
      counter++;
      return hash(counter, frameIndex * 7);
    };

    const resize = () => {
      const w = canvas.clientWidth,
        h = canvas.clientHeight;
      canvas.width = w * dpr;
      canvas.height = h * dpr;
      const ctx = canvas.getContext("2d");
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      ctx.clearRect(0, 0, w, h);
      counter = 0;

      for (let i = 0; i < 5; i++) {
        const x = r() * w,
          y = r() * h;
        const rad = 120 + r() * 280;
        const grad = ctx.createRadialGradient(x, y, 0, x, y, rad);
        const baseHue = 220 + (r() - 0.5) * 30;
        grad.addColorStop(0, `hsla(${baseHue}, 30%, 25%, 0.06)`);
        grad.addColorStop(1, `hsla(${baseHue}, 30%, 25%, 0)`);
        ctx.fillStyle = grad;
        ctx.fillRect(x - rad, y - rad, rad * 2, rad * 2);
      }

      const count = Math.floor((w * h) / 1800);
      for (let i = 0; i < count; i++) {
        const x = r() * w,
          y = r() * h;
        const m = r();
        const isBright = m > 0.992;
        const isMid = !isBright && m > 0.94;
        const size = isBright
          ? 1.5 + r() * 1.2
          : isMid
            ? 0.9 + r() * 0.5
            : 0.4 + r() * 0.5;
        const alpha = isBright
          ? 0.85 + r() * 0.15
          : isMid
            ? 0.4 + r() * 0.4
            : 0.2 + r() * 0.4;
        const cr = r();
        let col;
        if (cr > 0.94) col = `rgba(255, 200, 160, ${alpha})`;
        else if (cr > 0.85) col = `rgba(255, 235, 200, ${alpha})`;
        else if (cr > 0.55) col = `rgba(245, 248, 255, ${alpha})`;
        else col = `rgba(200, 220, 255, ${alpha})`;
        ctx.fillStyle = col;
        ctx.beginPath();
        ctx.arc(x, y, size, 0, Math.PI * 2);
        ctx.fill();

        if (isBright && size > 2) {
          ctx.strokeStyle = `rgba(220, 235, 255, ${alpha * 0.5})`;
          ctx.lineWidth = 0.5;
          const len = size * 7;
          for (let k = 0; k < 6; k++) {
            const a = (k / 6) * Math.PI * 2 + Math.PI / 12;
            ctx.beginPath();
            ctx.moveTo(x, y);
            ctx.lineTo(x + Math.cos(a) * len, y + Math.sin(a) * len);
            ctx.stroke();
          }
        }
      }
    };
    resize();
    window.addEventListener("resize", resize);
    return () => window.removeEventListener("resize", resize);
  }, [frameIndex]);
  return <canvas className="starfield-canvas" ref={ref} />;
}

// ========================================================
// ORBITS
// ========================================================
function Orbits({ frame, opacity }) {
  if (opacity <= 0) return null;
  const isSubgiant = frame === 3;
  const merc = isSubgiant ? 0.05 : 1;
  return (
    <svg
      className="orbits-svg"
      viewBox="0 0 1000 1000"
      preserveAspectRatio="xMidYMid meet"
    >
      <g opacity={opacity}>
        <ellipse
          cx="500"
          cy="500"
          rx="320"
          ry="80"
          className="orbit-ring"
          opacity={merc}
        />
        <ellipse cx="500" cy="500" rx="395" ry="105" className="orbit-ring" />
        <ellipse cx="500" cy="500" rx="475" ry="135" className="orbit-ring" />
        <g>
          <circle cx="700" cy="528" r="4" fill="#a89078" opacity={merc} />
          <text x="710" y="520" className="orbit-label" opacity={merc}>
            MERCURY
          </text>
        </g>
        <g>
          <circle cx="290" cy="488" r="6" fill="#d8a45a" />
          <text x="200" y="478" className="orbit-label">
            VENUS
          </text>
        </g>
        <g>
          <circle cx="500" cy="370" r="6.5" fill="#5b8fd3" />
          <text x="510" y="362" className="orbit-label">
            EARTH
          </text>
        </g>
      </g>
    </svg>
  );
}

// ========================================================
// MAIN COMPONENT
// ========================================================
function SunLifecycleFlipbook({ onClose }) {
  const [frame, setFrame] = useState(0);
  const data = LIFECYCLE_FRAMES[frame];

  useEffect(() => {
    playLifecycleClip(0);
    return () => {
      if (window.__narration) window.__narration.stop();
    };
  }, []);

  const advance = useCallback(() => {
    setFrame((f) => {
      const next = (f + 1) % LIFECYCLE_FRAMES.length;
      playLifecycleClip(next);
      return next;
    });
  }, []);
  const goBack = useCallback(() => {
    setFrame((f) => {
      const prev = (f - 1 + LIFECYCLE_FRAMES.length) % LIFECYCLE_FRAMES.length;
      playLifecycleClip(prev);
      return prev;
    });
  }, []);

  useEffect(() => {
    const onKey = (e) => {
      if (e.key === "Escape") onClose();
      else if (e.key === "ArrowRight" || e.key === " " || e.key === "Enter") {
        e.preventDefault();
        advance();
      } else if (e.key === "ArrowLeft") {
        e.preventDefault();
        goBack();
      }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [advance, goBack, onClose]);

  const orbitOpacity =
    data.showOrbits === true ? 1 : data.showOrbits === "fading" ? 0.35 : 0;

  const isFullFrame = data.sceneKind === "nebula-birth";
  const isNebula =
    data.sceneKind === "nebula-birth" || data.sceneKind === "white-dwarf";
  const isSun = !isNebula;

  const timestamp = useMemo(() => {
    const d = new Date();
    return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, "0")}-${String(d.getUTCDate()).padStart(2, "0")}T${String(d.getUTCHours()).padStart(2, "0")}:${String(d.getUTCMinutes()).padStart(2, "0")}Z`;
  }, [frame]);

  return (
    <div
      className="lifecycle-overlay"
      onClick={advance}
      aria-label="Tap to advance"
    >
      <StarField frameIndex={frame} />
      <div
        className="hud-top"
        onClick={(e) => e.stopPropagation()}
        style={{ cursor: "default" }}
      >
        <div className="hud-brand">
          <span className="brand-mark" aria-hidden="true" />
          <span className="brand-text">
            <span className="lg">SOL · LIFE-CYCLE ARCHIVE</span>
            <span className="sm">SPACE EXPLORER · OBS NETWORK</span>
          </span>
        </div>
        <div className="hud-mid">
          <span className="kv">
            <span className="k">INSTR</span>
            <span className="v">{data.instrument}</span>
          </span>
          <span className="kv">
            <span className="k">FILT</span>
            <span className="v">{data.filter}</span>
          </span>
          <span className="kv">
            <span className="k">TARGET</span>
            <span className="v">{data.target}</span>
          </span>
          <span className="kv">
            <span className="k">
              {data.coords.includes("RA") ? "POS" : "EPOCH"}
            </span>
            <span className="v">{data.coords}</span>
          </span>
        </div>
        <div className="hud-right">
          <span className="rec">
            <span className="dot" />
            <span>REC</span>
          </span>
          <span>{timestamp}</span>
          <span>
            FRAME <span className="frame-num">{data.id}</span> / 06
          </span>
        </div>
      </div>

      <div className="lifecycle-stage-wrap">
        <div className="progress-row" onClick={(e) => e.stopPropagation()}>
          {LIFECYCLE_FRAMES.map((f, i) => (
            <div
              key={f.id}
              className={
                "step " +
                (i === frame ? "is-active" : i < frame ? "is-past" : "")
              }
            >
              <span className="num">{f.id}</span>
              <span className="pip" />
              <span className="label">
                {
                  [
                    "Birth",
                    "Young",
                    "Today",
                    "Aging",
                    "Red Giant",
                    "White Dwarf",
                  ][i]
                }
              </span>
            </div>
          ))}
        </div>

        <div
          className={
            "lifecycle-plate " + (isFullFrame ? "is-fullframe" : "is-disk")
          }
        >
          <div className="scene-stack">
            {isSun ? (
              <SunCanvas kind={data.sceneKind} />
            ) : (
              <NebulaCanvas kind={data.sceneKind} />
            )}
            <Orbits frame={frame} opacity={orbitOpacity} />
          </div>

          <div className="plate-scale">
            <div className="bar">
              <span className="line" />
              <span>{data.scaleLabel}</span>
            </div>
            <div>NORTH ↑ · EAST ←</div>
          </div>
          <div className="plate-instrument">
            <span>
              <span
                className="pip"
                style={{
                  background: data.pipColor,
                  boxShadow: `0 0 8px ${data.pipColor}`,
                }}
              />
              {data.instrument}
            </span>
            <span>{data.exposure}</span>
          </div>
        </div>
      </div>

      <div className="hud-bottom">
        <button
          className="lifecycle-back"
          onClick={(e) => {
            e.stopPropagation();
            onClose();
          }}
        >
          ← BACK TO SUN
        </button>
        <div className="caption-card">
          <div className="caption-when">{data.when}</div>
          <div className="caption-text">{data.text}</div>
        </div>
        <div className="tap-hint">
          TAP TO ADVANCE
          <span className="arrow" />
        </div>
      </div>
    </div>
  );
}

window.SunLifecycleFlipbook = SunLifecycleFlipbook;
