// ============ SLEEP / "DRIFT" MODE ============
// Wordless and interaction-free. First choose how long the session lasts, then
// a realistic half-moon glides slowly left <-> right in a straight line across a
// dark, twinkling sky. The screen is kept awake for the whole session; only in
// the final wind-down does it gently ease to black, so it won't lock early.

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

// Session length options (minutes; null = endless)
const durations = [
  { label: '5', mins: 5 },
  { label: '10', mins: 10 },
  { label: '20', mins: 20 },
  { label: '30', mins: 30 },
  { label: '∞', mins: null },
];

const DIM_MAX = 0.985;

// a fresh random glide speed (px/s): from a slow crawl to a brisk slide
const randSpeed = () => 50 + Math.random() * 230;

// ---- Strong, self-contained keep-awake ----
// Layers several techniques so at least one holds on any given device:
//  1) Screen Wake Lock API (where supported / secure context)
//  2) a muted, playing <video> fed by a live canvas stream — no media file
//     needed, and a playing video keeps most screens awake (incl. http)
//  3) NoSleep.js as a bonus layer if it happened to load
//  4) a periodic re-arm, since the OS can quietly drop the wake lock
function createKeepAwake() {
  let wakeLock = null, video = null, drawTimer = null, rearmTimer = null, noSleep = null, audioCtx = null, on = false;

  async function requestWakeLock() {
    try {
      if ('wakeLock' in navigator && document.visibilityState === 'visible') {
        wakeLock = await navigator.wakeLock.request('screen');
      }
    } catch (e) { /* */ }
  }

  function startVideo() {
    try {
      if (video) { video.play().catch(() => {}); return; }
      const canvas = document.createElement('canvas');
      canvas.width = 2; canvas.height = 2;
      const ctx = canvas.getContext('2d');
      let t = 0;
      drawTimer = setInterval(() => { t ^= 1; ctx.fillStyle = t ? '#000000' : '#010101'; ctx.fillRect(0, 0, 2, 2); }, 800);
      video = document.createElement('video');
      video.setAttribute('playsinline', '');
      video.setAttribute('autoplay', ''); video.setAttribute('loop', '');
      Object.assign(video.style, { position: 'fixed', left: '0', top: '0', width: '1px', height: '1px', opacity: '0', pointerEvents: 'none', zIndex: '-1' });

      let stream = canvas.captureStream ? canvas.captureStream(4) : null;
      // iOS holds the screen better for a video with a (silent) AUDIO track,
      // so mix a 0-gain oscillator into the stream and play it unmuted.
      let hasAudio = false;
      try {
        const AC = window.AudioContext || window.webkitAudioContext;
        if (AC && stream) {
          audioCtx = new AC();
          if (audioCtx.state === 'suspended') audioCtx.resume(); // iOS starts suspended
          const dest = audioCtx.createMediaStreamDestination();
          const osc = audioCtx.createOscillator();
          const g = audioCtx.createGain();
          g.gain.value = 0; // truly silent
          osc.connect(g).connect(dest);
          osc.start();
          dest.stream.getAudioTracks().forEach(tr => stream.addTrack(tr));
          hasAudio = true;
        }
      } catch (e) { /* fall back to muted video */ }

      if (stream) video.srcObject = stream;
      if (!hasAudio) { video.muted = true; video.defaultMuted = true; video.setAttribute('muted', ''); }
      document.body.appendChild(video);
      video.play().catch(() => { video.muted = true; video.play().catch(() => {}); });
    } catch (e) { /* */ }
  }

  function enable() {
    on = true;
    requestWakeLock();
    startVideo();
    try { if (!noSleep && typeof NoSleep !== 'undefined') noSleep = new NoSleep(); if (noSleep) noSleep.enable(); } catch (e) { /* */ }
    if (!rearmTimer) {
      rearmTimer = setInterval(() => {
        if (on && document.visibilityState === 'visible') {
          requestWakeLock();
          if (audioCtx && audioCtx.state === 'suspended') audioCtx.resume();
          if (video) video.play().catch(() => {});
        }
      }, 12000);
    }
  }

  function disable() {
    on = false;
    try { if (wakeLock) { wakeLock.release(); wakeLock = null; } } catch (e) { /* */ }
    try { if (noSleep) noSleep.disable(); } catch (e) { /* */ }
    try { if (video) { video.pause(); video.srcObject = null; video.remove(); video = null; } } catch (e) { /* */ }
    try { if (audioCtx) { audioCtx.close(); audioCtx = null; } } catch (e) { /* */ }
    if (drawTimer) { clearInterval(drawTimer); drawTimer = null; }
    if (rearmTimer) { clearInterval(rearmTimer); rearmTimer = null; }
  }

  return { enable, disable };
}

function lucideSvg(name, size, color) {
  const pascal = name.split('-').map(p => p.charAt(0).toUpperCase() + p.slice(1)).join('');
  if (typeof lucide !== 'undefined' && lucide.icons && lucide.icons[pascal]) {
    const svg = lucide.createElement(lucide.icons[pascal]);
    if (svg) {
      svg.setAttribute('width', size);
      svg.setAttribute('height', size);
      svg.setAttribute('stroke', color);
      svg.setAttribute('stroke-width', '2');
      return svg;
    }
  }
  return null;
}

function SleepDrift() {
  const [phase, setPhase] = useState('choose'); // 'choose' | 'drift' | 'done'
  const phaseRef = useRef('choose');

  const stageRef = useRef(null);
  const drifterRef = useRef(null);
  const dimRef = useRef(null);

  const motion = useRef({
    x: 0, dir: 1,
    w: 0, h: 0, size: 120,
    xMin: 0, xMax: 0, baseY: 0,
    t: 0, speed: 90, target: 90, nextSpeedT: 0,
  });
  const rafRef = useRef(null);
  const lastTsRef = useRef(0);
  const startRef = useRef(0);       // session start timestamp (ms)
  const durMsRef = useRef(null);    // session length in ms, or null for endless
  const noSleepRef = useRef(null); // holds the layered keep-awake controller
  const visCleanupRef = useRef(null);

  // twinkling starfield, fixed across renders
  const stars = React.useMemo(() => (
    [...Array(70)].map((_, i) => ({
      id: i,
      left: Math.random() * 100,
      top: Math.random() * 100,
      size: 0.8 + Math.random() * 2.2,
      delay: Math.random() * 6,
      dur: 2.2 + Math.random() * 4.5,
      tw: 0.5 + Math.random() * 0.45,
    }))
  ), []);

  const measure = useCallback(() => {
    const el = stageRef.current;
    if (!el) return;
    const m = motion.current;
    m.w = el.clientWidth;
    m.h = el.clientHeight;
    m.size = Math.max(80, Math.min(150, Math.min(m.w, m.h) * 0.20));
    m.xMin = m.size / 2 + 14;
    m.xMax = m.w - m.size / 2 - 14;
    m.baseY = m.h * 0.5;
    if (m.x === 0) m.x = m.xMin;
    if (drifterRef.current) {
      drifterRef.current.style.width = m.size + 'px';
      drifterRef.current.style.height = m.size + 'px';
    }
  }, []);

  // Keep the screen awake with the layered controller above.
  const enableKeepAwake = useCallback(() => {
    if (!noSleepRef.current) noSleepRef.current = createKeepAwake();
    noSleepRef.current.enable();
  }, []);
  const disableKeepAwake = useCallback(() => {
    if (noSleepRef.current) noSleepRef.current.disable();
  }, []);

  // start drifting for the chosen length
  const begin = useCallback((mins) => {
    durMsRef.current = mins == null ? null : mins * 60 * 1000;
    startRef.current = performance.now();
    phaseRef.current = 'drift';
    setPhase('drift');
    enableKeepAwake(); // must run inside this tap gesture

    // re-enable keep-awake if the tab is hidden then shown again
    const onVis = () => {
      if (document.visibilityState === 'visible' && phaseRef.current === 'drift') enableKeepAwake();
    };
    document.addEventListener('visibilitychange', onVis);
    visCleanupRef.current = () => document.removeEventListener('visibilitychange', onVis);
  }, [enableKeepAwake]);

  // motion + session timing loop (runs only while drifting)
  useEffect(() => {
    if (phase !== 'drift') return;

    measure();
    window.addEventListener('resize', measure);
    window.addEventListener('orientationchange', measure);
    lastTsRef.current = 0;

    const tick = (ts) => {
      const m = motion.current;
      if (!lastTsRef.current) lastTsRef.current = ts;
      let dt = (ts - lastTsRef.current) / 1000;
      lastTsRef.current = ts;
      if (dt > 0.05) dt = 0.05;
      m.t += dt;

      // pick a new random target speed at intervals -> very obvious changes
      if (m.t >= m.nextSpeedT) { m.target = randSpeed(); m.nextSpeedT = m.t + 1.2 + Math.random() * 2.3; }
      // ease toward it fast enough that the speed-up / slow-down is clearly seen
      m.speed += (m.target - m.speed) * Math.min(1, dt * 5);

      // straight, level glide left <-> right; new speed at each turn too
      m.x += m.dir * m.speed * dt;
      if (m.x >= m.xMax) { m.x = m.xMax; m.dir = -1; m.target = randSpeed(); m.nextSpeedT = m.t + 1.2 + Math.random() * 2.3; }
      else if (m.x <= m.xMin) { m.x = m.xMin; m.dir = 1; m.target = randSpeed(); m.nextSpeedT = m.t + 1.2 + Math.random() * 2.3; }
      if (drifterRef.current) {
        drifterRef.current.style.transform = `translate(${m.x - m.size / 2}px, ${m.baseY - m.size / 2}px)`;
      }

      // session timing -> gentle wind-down dim near the end
      const dur = durMsRef.current;
      if (dur != null) {
        const elapsed = performance.now() - startRef.current;
        // stay fully bright for the whole session; only a short fade right at the end
        const windDown = Math.min(12000, dur * 0.1);
        let o = 0;
        if (elapsed > dur - windDown) {
          o = Math.min(DIM_MAX, ((elapsed - (dur - windDown)) / windDown) * DIM_MAX);
        }
        if (dimRef.current) dimRef.current.style.opacity = o.toFixed(3);
        if (elapsed >= dur) { endSession(); return; }
      }

      rafRef.current = requestAnimationFrame(tick);
    };
    rafRef.current = requestAnimationFrame(tick);

    return () => {
      cancelAnimationFrame(rafRef.current);
      window.removeEventListener('resize', measure);
      window.removeEventListener('orientationchange', measure);
    };
  }, [phase, measure]);

  const endSession = useCallback(() => {
    cancelAnimationFrame(rafRef.current);
    if (dimRef.current) dimRef.current.style.opacity = DIM_MAX.toFixed(3);
    phaseRef.current = 'done';
    setPhase('done');
    disableKeepAwake();
    if (visCleanupRef.current) visCleanupRef.current();
  }, [disableKeepAwake]);

  // tidy up on unmount
  useEffect(() => () => { disableKeepAwake(); if (visCleanupRef.current) visCleanupRef.current(); }, [disableKeepAwake]);

  const again = useCallback((e) => {
    e.stopPropagation();
    if (dimRef.current) dimRef.current.style.opacity = '0';
    phaseRef.current = 'choose';
    setPhase('choose');
  }, []);

  const goHome = useCallback((e) => {
    e.stopPropagation();
    disableKeepAwake();
    window.location.href = 'index.html';
  }, [disableKeepAwake]);

  return (
    <div className="sleep-stage" ref={stageRef}>
      {stars.map(s => (
        <div key={s.id} className="star" style={{
          left: s.left + '%', top: s.top + '%',
          width: s.size + 'px', height: s.size + 'px',
          animationDelay: s.delay + 's',
          '--dur': s.dur + 's',
          '--tw': s.tw,
        }} />
      ))}

      {/* the half-moon (present during drift + done) */}
      {phase !== 'choose' && (
        <div className="drifter" ref={drifterRef}>
          <div className="moon" />
        </div>
      )}

      {/* exit (hidden once fully dimmed/done) */}
      {phase === 'drift' && (
        <button className="sleep-home" onPointerDown={goHome} aria-label="home"><HomeIcon /></button>
      )}

      {/* duration chooser */}
      {phase === 'choose' && (
        <div className="sleep-choose">
          <div className="choose-moon"><div className="moon" style={{ width: '100%', height: '100%' }} /></div>
          <div className="dur-row">
            {durations.map(d => (
              <button key={d.label} className="dur-btn" onPointerDown={(e) => { e.stopPropagation(); begin(d.mins); }}>
                {d.mins == null
                  ? <span className="inf">∞</span>
                  : <><span className="n">{d.label}</span><span className="u">MIN</span></>}
              </button>
            ))}
          </div>
        </div>
      )}

      {/* end-of-session: faint restart / home over the dark */}
      {phase === 'done' && (
        <div className="sleep-done">
          <button className="done-btn" onPointerDown={again} aria-label="again"><MoonIcon /></button>
          <button className="done-btn" onPointerDown={goHome} aria-label="home"><HomeIcon /></button>
        </div>
      )}

      <div className="sleep-dim" ref={dimRef} />
    </div>
  );
}

function makeStaticIcon(name, size, color) {
  return function StaticIcon() {
    const ref = useRef(null);
    useEffect(() => {
      if (!ref.current) return;
      ref.current.innerHTML = '';
      const svg = lucideSvg(name, size, color);
      if (svg) ref.current.appendChild(svg);
    }, []);
    return <span ref={ref} style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }} />;
  };
}
const HomeIcon = makeStaticIcon('home', 24, 'rgba(232,235,245,0.6)');
const MoonIcon = makeStaticIcon('moon', 24, 'rgba(232,235,245,0.6)');

ReactDOM.createRoot(document.getElementById('root')).render(<SleepDrift />);
