// Constellation orbital map: every created planet orbits a shared star.
// Click a planet to open its viewer.

const { useState: useStateC, useEffect: useEffectC, useRef: useRefC } = React;

function Constellation({ starfieldDensity, animIntensity }) {
  const canvasRef = useRefC(null);
  const [hovered, setHovered] = useStateC(null);
  const [planets, setPlanets] = useStateC([]);
  const stateRef = useRefC({ planets: [], hovered: null, selected: null });

  useEffectC(() => {
    const all = window.PLANETALIN.allPlanets();
    setPlanets(all);
    stateRef.current.planets = all;
  }, []);

  useEffectC(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    let raf;
    let stars = [];
    let farStars = [];
    let shootingStars = [];
    let alive = true;

    // MK-class color distribution — same curve as the 3D renderer.
    function pickStarColor(roll) {
      if (roll < 0.42) return `rgb(255, ${Math.floor(155 + Math.random()*30)}, ${Math.floor(115 + Math.random()*30)})`;
      if (roll < 0.68) return `rgb(255, ${Math.floor(200 + Math.random()*20)}, ${Math.floor(155 + Math.random()*25)})`;
      if (roll < 0.83) return `rgb(255, ${Math.floor(237 + Math.random()*12)}, ${Math.floor(204 + Math.random()*25)})`;
      if (roll < 0.93) return `rgb(250, 250, ${Math.floor(235 + Math.random()*15)})`;
      if (roll < 0.985) return `rgb(${Math.floor(204 + Math.random()*20)}, ${Math.floor(225 + Math.random()*15)}, 255)`;
      return 'rgb(153, 191, 255)'; // rare O-type blue
    }

    function resize() {
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      const w = canvas.clientWidth || 800, h = canvas.clientHeight || 500;
      canvas.width = w*dpr; canvas.height = h*dpr;
      ctx.setTransform(dpr,0,0,dpr,0,0);
      stars = [];
      farStars = [];
      // Denser field + an additional far layer. Density scales with canvas area.
      const n = Math.floor(w*h/1500 * starfieldDensity);
      const bandY = h * 0.5; // Milky-Way equator runs through the center
      const bandH = h * 0.35;
      for (let i=0;i<n;i++){
        // 55% of stars cluster near the galactic plane, the rest scatter
        const nearBand = Math.random() < 0.55;
        const y = nearBand
          ? bandY + (Math.random()-0.5) * bandH
          : Math.random()*h;
        // Power-law size: most tiny, rare bright stars
        const sizeBias = Math.pow(Math.random(), 4);
        stars.push({
          x: Math.random()*w,
          y,
          r: 0.2 + sizeBias*1.8,
          a: 0.35 + Math.random()*0.55,
          p: Math.random()*Math.PI*2,
          c: pickStarColor(Math.random()),
          flare: sizeBias > 0.85,
        });
      }
      const fn = Math.floor(w*h/900 * starfieldDensity);
      for (let i=0;i<fn;i++){
        farStars.push({
          x: Math.random()*w,
          y: Math.random()*h,
          r: 0.15 + Math.random()*0.35,
          a: 0.15 + Math.random()*0.3,
          p: Math.random()*Math.PI*2,
        });
      }
    }
    resize();
    const ro = new ResizeObserver(resize);
    ro.observe(canvas);

    // Classify each planet into an atmospheric family.
    // The orbital band + hue anchor is determined by the family, so related
    // worlds cluster together around the star.
    function classify(p) {
      const pct = {};
      (p.atmosphere || []).forEach(a => { pct[a.sym] = (pct[a.sym]||0) + a.pct; });
      const sum = (keys) => keys.reduce((s,k) => s + (pct[k]||0), 0);
      const gas = sum(['H','He']);
      const air = sum(['N','O','H₂O','Ar','O₃']);
      const cold = sum(['CH4','Ne','Kr','Xe']);
      const hot = sum(['SO₂','CO₂','NH₃','PH₃','C']);
      const max = Math.max(gas, air, cold, hot);
      if (max < 20) return 4;
      if (max === gas) return 0;   // inner — hydrogen/helium (close, hot)
      if (max === hot) return 1;   // next — sulfurous/carbonaceous
      if (max === air) return 2;   // middle — temperate breathable
      if (max === cold) return 3;  // outer — icy/methane
      return 4;                    // exotic/other — outermost
    }
    const FAMILY_COUNT = 5;
    function orbitRadius(fam, indexInFam, famCount, w, h) {
      const maxR = Math.max(130, Math.min(w,h)/2 - 40);
      const minR = 65;
      // Each family gets a ring band; worlds within a family space out by a
      // small radial wobble so they don't overlap on the same arc.
      const bandStep = (maxR - minR) / FAMILY_COUNT;
      const band = minR + fam * bandStep;
      const wobble = famCount > 1 ? ((indexInFam % 3) - 1) * (bandStep * 0.18) : 0;
      return band + bandStep*0.45 + wobble;
    }

    function draw(t) {
      if (!alive) return;
      raf = requestAnimationFrame(draw);
      const w = canvas.clientWidth, h = canvas.clientHeight;
      const cx = w/2, cy = h/2;
      ctx.clearRect(0,0,w,h);

      const bgGrad = ctx.createRadialGradient(cx, cy, 20, cx, cy, Math.max(w,h));
      bgGrad.addColorStop(0, 'rgba(40, 25, 70, 0.4)');
      bgGrad.addColorStop(1, 'rgba(8, 6, 20, 0)');
      ctx.fillStyle = bgGrad;
      ctx.fillRect(0,0,w,h);

      // Milky-Way band — horizontal gradient adds a diffuse glow across the
      // center that sells the galactic-plane bias in the star distribution.
      const mwGrad = ctx.createLinearGradient(0, h*0.25, 0, h*0.75);
      mwGrad.addColorStop(0.0,  'rgba(120, 110, 180, 0)');
      mwGrad.addColorStop(0.5,  'rgba(150, 130, 210, 0.06)');
      mwGrad.addColorStop(1.0,  'rgba(120, 110, 180, 0)');
      ctx.fillStyle = mwGrad;
      ctx.fillRect(0, 0, w, h);

      // Far layer — tiny, slow-twinkling, pushed back with low alpha
      farStars.forEach(s => {
        const tw = 0.55 + 0.45*Math.sin(t*0.0012 + s.p);
        ctx.globalAlpha = s.a * tw;
        ctx.fillStyle = '#dcd6ff';
        ctx.beginPath();
        ctx.arc(s.x, s.y, s.r, 0, Math.PI*2);
        ctx.fill();
      });

      // Near layer — MK-class colored, with per-star twinkle rate + scintillation.
      stars.forEach(s => {
        const rate = 1 + (s.p % 1.3);
        const tw = 0.55 + 0.45 * Math.sin(t*0.002*rate + s.p)
                        * (0.85 + 0.15 * Math.sin(t*0.008 + s.p*13.7));
        ctx.globalAlpha = Math.max(0, s.a * tw);
        ctx.fillStyle = s.c;
        ctx.beginPath();
        ctx.arc(s.x, s.y, s.r, 0, Math.PI*2);
        ctx.fill();
        // Diffraction cross on the brightest stars only
        if (s.flare) {
          const sp = s.r * 4 * tw;
          ctx.globalAlpha = s.a * tw * 0.55;
          ctx.strokeStyle = s.c;
          ctx.lineWidth = 0.5;
          ctx.beginPath();
          ctx.moveTo(s.x - sp, s.y); ctx.lineTo(s.x + sp, s.y);
          ctx.moveTo(s.x, s.y - sp); ctx.lineTo(s.x, s.y + sp);
          ctx.stroke();
        }
      });
      ctx.globalAlpha = 1;

      // Shooting stars — spawn occasionally, fade along their path.
      if (Math.random() < 0.004 * animIntensity && shootingStars.length < 2) {
        shootingStars.push({
          x: Math.random()*w,
          y: Math.random()*h*0.6,
          vx: (4 + Math.random()*3),
          vy: (1 + Math.random()*1.5),
          life: 1.0,
        });
      }
      shootingStars = shootingStars.filter(m => m.life > 0);
      shootingStars.forEach(m => {
        const tailLen = 60;
        const grad = ctx.createLinearGradient(m.x, m.y, m.x - m.vx*tailLen/5, m.y - m.vy*tailLen/5);
        grad.addColorStop(0, `rgba(255,240,220,${m.life})`);
        grad.addColorStop(1, 'rgba(255,240,220,0)');
        ctx.strokeStyle = grad;
        ctx.lineWidth = 1.2;
        ctx.beginPath();
        ctx.moveTo(m.x, m.y);
        ctx.lineTo(m.x - m.vx*tailLen/5, m.y - m.vy*tailLen/5);
        ctx.stroke();
        m.x += m.vx * animIntensity;
        m.y += m.vy * animIntensity;
        m.life -= 0.015;
      });

      const pl = stateRef.current.planets;

      // Compute family assignments once per frame — cheap.
      const famOf = pl.map(classify);
      const famIdx = pl.map(() => 0);
      const famCount = Array(FAMILY_COUNT).fill(0);
      pl.forEach((_, i) => { famIdx[i] = famCount[famOf[i]]++; });

      // Draw the five family orbital bands — dotted rings.
      for (let f = 0; f < FAMILY_COUNT; f++) {
        if (famCount[f] === 0) continue;
        const r = orbitRadius(f, 0, famCount[f], w, h);
        ctx.strokeStyle = 'rgba(180,160,220,0.15)';
        ctx.setLineDash([2, 6]);
        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.ellipse(cx, cy, r, r*0.55, 0, 0, Math.PI*2);
        ctx.stroke();
      }
      ctx.setLineDash([]);

      const starR = 20 + 1.5*Math.sin(t*0.002);
      const starGrad = ctx.createRadialGradient(cx, cy, 4, cx, cy, starR*4);
      starGrad.addColorStop(0, 'rgba(255,240,180,1)');
      starGrad.addColorStop(0.3, 'rgba(255,200,120,0.6)');
      starGrad.addColorStop(1, 'rgba(255,200,120,0)');
      ctx.fillStyle = starGrad;
      ctx.beginPath(); ctx.arc(cx, cy, starR*4, 0, Math.PI*2); ctx.fill();
      ctx.fillStyle = '#fff7d6';
      ctx.beginPath(); ctx.arc(cx, cy, 12, 0, Math.PI*2); ctx.fill();

      pl.forEach((p, i) => {
        const fam = famOf[i];
        const fi = famIdx[i];
        const fc = famCount[fam];
        const r = orbitRadius(fam, fi, fc, w, h);
        // Phase distributes worlds of the same family evenly around the ring,
        // with a small per-family orientation offset so families don't align.
        const famOffset = fam * 0.6;
        const period = 40 + fam*14 + (p.orbitAU||1)*4;
        const phase = famOffset + (fc > 1 ? (fi / fc) * Math.PI*2 : i * 1.37);
        const ang = phase + (t/1000) * (Math.PI*2/period) * animIntensity;
        const x = cx + Math.cos(ang)*r;
        const y = cy + Math.sin(ang)*r * 0.55;

        const pal = window.PLANETALIN.derivePalette(p.atmosphere || []);
        const pr = 9 + Math.min(14, (p.rings?.enabled?3:0) + (p.moons?.length||0)*0.9 + Math.log2(2+(p.orbitAU||1))*2);

        // Outer glow — uses derived glow color
        const g = ctx.createRadialGradient(x, y, 1, x, y, pr*3);
        g.addColorStop(0, pal.glow + 'cc');
        g.addColorStop(1, pal.glow + '00');
        ctx.fillStyle = g;
        ctx.beginPath(); ctx.arc(x, y, pr*3, 0, Math.PI*2); ctx.fill();

        // Rings (behind the orb, using cloud color)
        if (p.rings?.enabled) {
          ctx.strokeStyle = pal.cloud + 'aa';
          ctx.lineWidth = 1.5;
          ctx.beginPath();
          ctx.ellipse(x, y, pr*1.8, pr*0.5, 0, 0, Math.PI*2);
          ctx.stroke();
        }

        // Mini-planet body: radial gradient from cloud (lit side) to deep (shadow side)
        const bodyGrad = ctx.createRadialGradient(
          x - pr*0.35, y - pr*0.35, pr*0.1,
          x, y, pr*1.05
        );
        bodyGrad.addColorStop(0, pal.cloud);
        bodyGrad.addColorStop(0.55, pal.atmos);
        bodyGrad.addColorStop(1, pal.deep);
        ctx.fillStyle = bodyGrad;
        ctx.beginPath(); ctx.arc(x, y, pr, 0, Math.PI*2); ctx.fill();

        // Highlight speck
        ctx.fillStyle = pal.cloud + 'cc';
        ctx.beginPath(); ctx.arc(x-pr*0.4, y-pr*0.4, pr*0.18, 0, Math.PI*2); ctx.fill();

        // Tiny creator seal tick (only if planet has a seal)
        if (p.seal) {
          ctx.save();
          ctx.fillStyle = `oklch(65% 0.20 ${p.seal.hue})`;
          ctx.beginPath();
          ctx.arc(x + pr*0.75, y - pr*0.75, 2.5, 0, Math.PI*2);
          ctx.fill();
          ctx.restore();
        }

        const isH = stateRef.current.hovered === i;
        if (isH) {
          ctx.strokeStyle = '#fff';
          ctx.lineWidth = 1.5;
          ctx.beginPath(); ctx.arc(x, y, pr+6, 0, Math.PI*2); ctx.stroke();
          ctx.fillStyle = '#fff';
          ctx.font = '13px "Space Grotesk", sans-serif';
          ctx.textAlign = 'center';
          ctx.fillText(p.name, x, y - pr - 14);
          ctx.fillStyle = 'rgba(255,255,255,0.5)';
          ctx.font = '10px "JetBrains Mono", monospace';
          ctx.fillText(p.id, x, y - pr - 28);
        }

        p.__x = x; p.__y = y; p.__r = pr;
      });
    }
    raf = requestAnimationFrame(draw);

    function onMove(ev) {
      const rect = canvas.getBoundingClientRect();
      const mx = ev.clientX - rect.left, my = ev.clientY - rect.top;
      let hit = null;
      stateRef.current.planets.forEach((p, i) => {
        const dx = mx-p.__x, dy = my-p.__y;
        if (dx*dx+dy*dy < (p.__r+8)*(p.__r+8)) hit = i;
      });
      if (hit !== stateRef.current.hovered) {
        stateRef.current.hovered = hit;
        setHovered(hit);
        canvas.style.cursor = hit !== null ? 'pointer' : 'default';
      }
    }
    function onClick() {
      const i = stateRef.current.hovered;
      if (i == null) return;
      const p = stateRef.current.planets[i];
      window.PLANETALIN.go(`planet/${p.id}`);
    }
    canvas.addEventListener('mousemove', onMove);
    canvas.addEventListener('click', onClick);
    canvas.addEventListener('mouseleave', () => { stateRef.current.hovered = null; setHovered(null); });

    return () => {
      alive = false;
      cancelAnimationFrame(raf);
      ro.disconnect();
      canvas.removeEventListener('mousemove', onMove);
      canvas.removeEventListener('click', onClick);
    };
  }, [starfieldDensity, animIntensity]);

  const count = planets.length;
  const hoveredPlanet = hovered != null ? planets[hovered] : null;

  return (
    <div className="constellation">
      <div className="cons-header">
        <div>
          <div className="cons-eyebrow">CATALOGUE · {count} {count===1?'world':'worlds'}</div>
          <h1 className="cons-title" style={{fontFamily:'"Cormorant Garamond", serif'}}>Your Constellation</h1>
          <p className="cons-sub">
            {count === 0
              ? 'An empty sky is also a sky. Shape your first world.'
              : 'Every world here bears your seal. Time passes; they drift on.'}
          </p>
        </div>
        <div className="cons-actions">
          <button className="ghost-btn" onClick={() => window.PLANETALIN.go('')}>← Home</button>
          <button className="solid-btn" onClick={() => window.PLANETALIN.go('create')}>+ New world</button>
        </div>
      </div>

      <div className="cons-stage">
        <canvas ref={canvasRef} className="cons-canvas" />
        {count === 0 && (
          <div className="cons-empty">
            <div className="cons-empty-star">
              <svg viewBox="0 0 60 60"><circle cx="30" cy="30" r="8" fill="#fff7d6"/><circle cx="30" cy="30" r="22" fill="none" stroke="rgba(255,220,140,0.35)" strokeWidth="1" strokeDasharray="2 4"/></svg>
            </div>
            <div className="cons-empty-copy" style={{fontFamily:'"Cormorant Garamond", serif'}}>
              No worlds yet.<br/>
              <span className="cons-empty-sub">The star is lit and waiting.</span>
            </div>
          </div>
        )}
        {hoveredPlanet && (
          <div className="cons-hint">click to open · {hoveredPlanet.id}</div>
        )}
      </div>

      {count > 0 && (
      <div className="cons-list">
        {planets.map((p,i) => {
          const pal = window.PLANETALIN.derivePalette(p.atmosphere || []);
          return (
            <button key={p.id} className="cons-card"
              onMouseEnter={() => { stateRef.current.hovered = i; setHovered(i); }}
              onMouseLeave={() => { stateRef.current.hovered = null; setHovered(null); }}
              onClick={() => window.PLANETALIN.go(`planet/${p.id}`)}
            >
              <div className="cons-orb" style={{
                background: `radial-gradient(circle at 30% 30%, ${pal.cloud}, ${pal.atmos} 55%, ${pal.deep})`,
                boxShadow: `0 0 24px ${pal.glow}88`
              }}>
                {p.rings?.enabled && <div className="cons-orb-ring" style={{borderColor: pal.cloud}} />}
              </div>
              <div className="cons-card-meta">
                <div className="cons-card-nm" style={{fontFamily:'"Cormorant Garamond", serif'}}>{p.name}</div>
                <div className="cons-card-id">{p.id}</div>
                <div className="cons-card-sub">
                  {p.surface} · {p.moons.length} moon{p.moons.length===1?'':'s'}
                  {p.seal && <span className="cons-card-seal" style={{color:`oklch(70% 0.18 ${p.seal.hue})`}} title={p.seal.name}> · {p.seal.glyph}</span>}
                </div>
              </div>
              <span
                className="cons-card-fork"
                role="button"
                tabIndex={0}
                onClick={(e) => { e.stopPropagation(); window.PLANETALIN.go(`create/${p.id}`); }}
                onKeyDown={(e) => { if (e.key === 'Enter') { e.stopPropagation(); window.PLANETALIN.go(`create/${p.id}`); } }}
                title="Fork this world — start a new one from this spec (costs your daily window)"
              >
                ⤳ fork
              </span>
            </button>
          );
        })}
      </div>
      )}
    </div>
  );
}

window.PLANETALINConstellation = { Constellation };
