// Planisphere.jsx — circular star wheel.
// Projection: stereographic-ish polar, N pole at centre.
// r = (90° - dec) / 130° * R   (so dec +90 = 0, dec -40 = R)
// theta derived from RA (hours → radians), 0h at top, spinning clockwise (looking up).

function parseRA(ra) {
  if (!ra || ra === '—' || ra === 'moves') return null;
  const m = ra.match(/(\d+)h\s*(\d+)m/);
  if (!m) return null;
  return parseInt(m[1]) + parseInt(m[2]) / 60; // hours
}
function parseDec(dec) {
  if (!dec || dec === '—' || dec === 'moves') return null;
  const m = dec.match(/([+−-])?(\d+)°\s*(\d+)/);
  if (!m) return null;
  const sign = m[1] === '−' || m[1] === '-' ? -1 : 1;
  return sign * (parseInt(m[2]) + parseInt(m[3]) / 60);
}

// For planets / Moon / constellations without precise coords, hand-place
// with (raHours, dec) approximations so they sit in a plausible spot.
const PLANI_OVERRIDES = {
  'moon':       { ra: 5,    dec: 15 },
  'tycho':      { ra: 5.1,  dec: 14 },
  'mare-tranq': { ra: 5.2,  dec: 17 },
  'venus':      { ra: 3,    dec: 10 },
  'mars':       { ra: 14,   dec: -12 },
  'jupiter':    { ra: 17,   dec: -20 },
  'saturn':     { ra: 22,   dec: -10 },
  'uranus':     { ra: 2.5,  dec: 14 },
  'orion':      { ra: 5.5,  dec: 0 },
  'ursa-major': { ra: 11,   dec: 55 },
  'cassiopeia': { ra: 1,    dec: 60 },
};

function polar(raH, dec, R) {
  // RA 0h at top (theta = -90°), increasing eastward (counter-clockwise as seen on map)
  const angle = (raH / 24) * 2 * Math.PI - Math.PI / 2;
  // dec +90 at center, dec -40 at edge
  const radius = ((90 - dec) / 130) * R;
  return {
    x: 50 + (Math.cos(angle) * radius) / R * 50,
    y: 50 + (Math.sin(angle) * radius) / R * 50,
    r: radius,
    angle,
  };
}

function Planisphere({ logs, onPick }) {
  const [hover, setHover] = React.useState(null);
  const [rotation, setRotation] = React.useState(0);
  const [autoRotate, setAutoRotate] = React.useState(true);
  const [location, setLocation] = React.useState(null); // {lat, lon, source}
  const [now, setNow] = React.useState(() => new Date());

  // Request geolocation once.
  React.useEffect(() => {
    if (!navigator.geolocation) {
      setLocation({ lat: 40, lon: -74, source: 'default (NYC)' });
      return;
    }
    navigator.geolocation.getCurrentPosition(
      (pos) => setLocation({ lat: pos.coords.latitude, lon: pos.coords.longitude, source: 'your location' }),
      () => setLocation({ lat: 40, lon: -74, source: 'default (NYC)' }),
      { timeout: 8000 }
    );
  }, []);

  // Tick time every minute so the sky creeps.
  React.useEffect(() => {
    const t = setInterval(() => setNow(new Date()), 60000);
    return () => clearInterval(t);
  }, []);

  // Compute auto rotation from LST. The planisphere is drawn with RA 0h at top
  // when rotation = 0. To put the current meridian (LST hours) at top, we need
  // to rotate the wheel by -(LST / 24) * 360, because RA increases clockwise
  // in our polar() projection (theta = ra/24 * 2PI - PI/2).
  const autoRotationDeg = React.useMemo(() => {
    if (!location) return 0;
    const lst = window.localSiderealTime(now, location.lon);
    return -(lst / 24) * 360;
  }, [location, now]);

  const appliedRotation = autoRotate ? autoRotationDeg + rotation : rotation;
  const seen = Object.keys(logs).length;
  const total = window.CATALOG.length;

  const placed = window.CATALOG.map(o => {
    const ov = PLANI_OVERRIDES[o.id];
    let raH, dec;
    if (ov) { raH = ov.ra; dec = ov.dec; }
    else { raH = parseRA(o.ra); dec = parseDec(o.dec); }
    if (raH == null || dec == null) return null;
    const pos = polar(raH, dec, 1);
    return { ...o, raH, dec, pos };
  }).filter(Boolean);

  // RA hour labels 0..23
  const hours = Array.from({ length: 24 }, (_, i) => i);
  // Declination rings: +60, +30, 0 (equator), -30
  const decRings = [60, 30, 0, -30];
  // Month labels around outer rim (opposite RA — traditional planisphere)
  const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

  return (
    <div className="plani">
      <div className="plani__legend">
        <div className="plani__legend-item">
          <span className="dot dot--seen" /> <span>Seen</span>
        </div>
        <div className="plani__legend-item">
          <span className="dot dot--unseen" /> <span>Still to find</span>
        </div>
        <div className="plani__legend-item mono">
          {total - seen} TARGETS REMAINING
        </div>
      </div>

      <div className="plani__stage">
        {/* Outer fixed month ring (doesn't rotate) */}
        <div className="plani__months">
          {months.map((m, i) => {
            const a = (i / 12) * 2 * Math.PI - Math.PI / 2; // Jan at top
            const R = 49; // % from center
            const x = 50 + Math.cos(a) * R;
            const y = 50 + Math.sin(a) * R;
            const deg = (i / 12) * 360 - 90; // tangent orientation (text perpendicular to radius — actually radial, pointing outward)
            return (
              <div
                key={m}
                className="plani__month mono"
                style={{
                  left: `${x}%`,
                  top: `${y}%`,
                  transform: `translate(-50%, -50%) rotate(${deg + 90}deg)`,
                }}
              >{m.toUpperCase()}</div>
            );
          })}
        </div>

        {/* Rotating wheel */}
        <div className="plani__wheel" style={{ transform: `rotate(${appliedRotation}deg)` }}>
          <svg viewBox="0 0 100 100" className="plani__svg">
            <defs>
              <radialGradient id="diskBg" cx="50%" cy="50%" r="50%">
                <stop offset="0%" stopColor="var(--bg-3)" />
                <stop offset="75%" stopColor="var(--bg-2)" />
                <stop offset="100%" stopColor="var(--bg)" />
              </radialGradient>
              <radialGradient id="mw" cx="50%" cy="50%" r="50%">
                <stop offset="0%" stopColor="transparent" />
                <stop offset="100%" stopColor="transparent" />
              </radialGradient>
              <clipPath id="disk">
                <circle cx="50" cy="50" r="48" />
              </clipPath>
            </defs>

            {/* Disk background */}
            <circle cx="50" cy="50" r="48" fill="url(#diskBg)" stroke="var(--line-strong)" strokeWidth="0.25" />

            {/* Milky Way band — a faint diagonal swath */}
            <g clipPath="url(#disk)" opacity="0.5">
              <ellipse cx="50" cy="50" rx="44" ry="9" fill="var(--ink-100)" opacity="0.05" transform="rotate(28 50 50)" />
              <ellipse cx="50" cy="50" rx="40" ry="5" fill="var(--ink-100)" opacity="0.06" transform="rotate(28 50 50)" />
            </g>

            {/* Declination rings */}
            {decRings.map(dec => {
              const r = ((90 - dec) / 130) * 48;
              return (
                <circle key={dec} cx="50" cy="50" r={r}
                  fill="none" stroke="var(--ink-100)" strokeWidth="0.15"
                  strokeDasharray={dec === 0 ? "0" : "0.6 0.8"}
                  opacity={dec === 0 ? 0.35 : 0.18} />
              );
            })}

            {/* Celestial equator label */}
            <text x="50" y={50 - ((90 - 0) / 130) * 48 - 1} textAnchor="middle"
              fontSize="1.6" fill="var(--ink-400)" fontFamily="var(--font-mono)"
              letterSpacing="0.2">CELESTIAL EQUATOR</text>

            {/* RA hour ticks */}
            {hours.map(h => {
              const angle = (h / 24) * 2 * Math.PI - Math.PI / 2;
              const x1 = 50 + Math.cos(angle) * 47;
              const y1 = 50 + Math.sin(angle) * 47;
              const x2 = 50 + Math.cos(angle) * (h % 6 === 0 ? 44 : 45.5);
              const y2 = 50 + Math.sin(angle) * (h % 6 === 0 ? 44 : 45.5);
              return (
                <line key={h} x1={x1} y1={y1} x2={x2} y2={y2}
                  stroke="var(--ink-200)" strokeWidth={h % 6 === 0 ? 0.3 : 0.15}
                  opacity={h % 6 === 0 ? 0.7 : 0.4} />
              );
            })}

            {/* RA hour numbers (every 2h) */}
            {hours.filter(h => h % 2 === 0).map(h => {
              const angle = (h / 24) * 2 * Math.PI - Math.PI / 2;
              const tx = 50 + Math.cos(angle) * 42;
              const ty = 50 + Math.sin(angle) * 42;
              return (
                <text key={h} x={tx} y={ty} textAnchor="middle" dominantBaseline="middle"
                  fontSize="1.8" fill="var(--ink-300)" fontFamily="var(--font-mono)"
                  transform={`rotate(${-appliedRotation} ${tx} ${ty})`}>
                  {h}ʰ
                </text>
              );
            })}

            {/* North celestial pole / Polaris marker */}
            <g>
              <circle cx="50" cy="50" r="0.7" fill="var(--ink-100)" />
              <circle cx="50" cy="50" r="1.8" fill="none" stroke="var(--ink-400)" strokeWidth="0.12" strokeDasharray="0.5 0.5" />
              <text x="50" y="46.8" textAnchor="middle" fontSize="1.4" fill="var(--ink-400)" fontFamily="var(--font-mono)"
                transform={`rotate(${-appliedRotation} 50 46.8)`}>NCP</text>
            </g>

            {/* Declination labels */}
            {[60, 30, -30].map(dec => {
              const r = ((90 - dec) / 130) * 48;
              return (
                <text key={dec} x={50 + 0.4} y={50 - r + 1.1} fontSize="1.3" fill="var(--ink-400)"
                  fontFamily="var(--font-mono)" opacity="0.7"
                  transform={`rotate(${-appliedRotation} ${50 + 0.4} ${50 - r + 1.1})`}>
                  {dec > 0 ? `+${dec}°` : `${dec}°`}
                </text>
              );
            })}
          </svg>

          {/* Object markers (rotate with wheel) */}
          {placed.map(o => {
            const isSeen = !!logs[o.id];
            return (
              <button
                key={o.id}
                className="plani__marker"
                data-seen={isSeen}
                data-cat={o.cat}
                style={{
                  left: `${o.pos.x}%`,
                  top: `${o.pos.y}%`,
                }}
                onMouseEnter={() => setHover(o.id)}
                onMouseLeave={() => setHover(null)}
                onClick={() => onPick(o)}
                aria-label={o.name}
              >
                <span className="plani__marker-halo" />
                <span className="plani__marker-dot" />
                <span
                  className="plani__marker-label mono"
                  data-hover={hover === o.id}
                  style={{ transform: `translate(-50%, 6px) rotate(${-appliedRotation}deg)` }}
                >{o.name}</span>
              </button>
            );
          })}
        </div>

        {/* Fixed overlay: horizon oval hint, center pivot */}
        <div className="plani__pivot" />
      </div>

      <div className="plani__statusbar mono">
        <div className="plani__status">
          <span className="plani__status-dot" data-live={autoRotate && !!location} />
          {location ? (
            <>
              <span>{location.source.toUpperCase()}</span>
              <span className="plani__status-sep">·</span>
              <span>{now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</span>
              <span className="plani__status-sep">·</span>
              <span>LST {(() => { const l = window.localSiderealTime(now, location.lon); const h = Math.floor(l); const m = Math.floor((l - h) * 60); return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`; })()}</span>
            </>
          ) : (
            <span>LOCATING…</span>
          )}
        </div>
        <label className="plani__auto">
          <input type="checkbox" checked={autoRotate} onChange={e => { setAutoRotate(e.target.checked); if (e.target.checked) setRotation(0); }} />
          <span>Align to tonight's sky</span>
        </label>
      </div>

      <div className="plani__controls">
        <button className="plani__btn" onClick={() => setRotation(r => r - 15)} title="Rotate back">
          <svg width="14" height="14" viewBox="0 0 14 14"><path d="M9 3 L4 7 L9 11" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/></svg>
        </button>
        <input
          type="range"
          min="-180" max="180" step="1"
          value={rotation}
          onChange={e => setRotation(parseInt(e.target.value))}
          className="plani__slider"
          aria-label="Offset rotation"
        />
        <button className="plani__btn" onClick={() => setRotation(r => r + 15)} title="Rotate forward">
          <svg width="14" height="14" viewBox="0 0 14 14"><path d="M5 3 L10 7 L5 11" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/></svg>
        </button>
        <button className="plani__reset mono" onClick={() => setRotation(0)}>CENTER</button>
      </div>

      <div className="plani__note mono">
        {autoRotate && location
          ? `THE WHEEL IS ALIGNED TO YOUR SKY RIGHT NOW · RA ${(() => { const l = window.localSiderealTime(now, location.lon); return `${Math.floor(l)}ʰ`; })()} IS OVERHEAD`
          : 'SPIN THE WHEEL TO MATCH WHAT YOU SEE · MONTHS LABEL THE OUTER RIM · HOURS MARK RIGHT ASCENSION'}
      </div>
    </div>
  );
}

Object.assign(window, { Planisphere });
