/* Main app with localStorage persistence, custom name/confirm modal,
   audio-unlock affordance, and theme cycle. */

const { useState: uS, useEffect: uE, useRef: uR, useMemo: uM } = React;

const SceneStarIcon = () => (
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" aria-hidden="true">
    <path d="M12 2l3 7h7l-5.5 4 2 7L12 16l-6.5 4 2-7L2 9h7z" />
  </svg>
);

const DEFAULT_SCENES = [
  { id: 'night', name: 'Night Scene',
    icon: (<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M20 13A8 8 0 1111 4a6 6 0 009 9z"/></svg>),
    mix: { brown: 0.55, rain: 0.35 } },
  { id: 'morning', name: 'Morning Scene',
    icon: (<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 3v2M12 19v2M3 12h2M19 12h2M5.6 5.6l1.4 1.4M17 17l1.4 1.4M5.6 18.4L7 17M17 7l1.4-1.4"/></svg>),
    mix: { forest: 0.6, stream: 0.3 } },
  { id: 'ocean', name: 'Beach Nap',
    icon: (<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" aria-hidden="true"><path d="M3 13c2-2 4 0 6-1s4 2 6 0 3 1 4 0"/><path d="M3 18c2-2 4 0 6-1s4 2 6 0 3 1 4 0"/></svg>),
    mix: { ocean: 0.7, wind: 0.25 } },
  { id: 'focus', name: 'Deep Work',
    icon: (<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" aria-hidden="true"><circle cx="12" cy="12" r="8"/><circle cx="12" cy="12" r="3"/></svg>),
    mix: { brown: 0.5, fan: 0.3 } },
];

const THEME_CYCLE = ['light', 'dark', 'dark-deep'];
const THEME_LABELS = { 'light': 'Light theme', 'dark': 'Dark theme', 'dark-deep': 'Deep dark theme' };

const STORAGE_KEY = 'hush.v1';

const SCENE_ICONS = { star: <SceneStarIcon /> };

function loadPersisted() {
  try {
    const raw = localStorage.getItem(STORAGE_KEY);
    if (!raw) return null;
    const data = JSON.parse(raw);
    if (Array.isArray(data.scenes)) {
      data.scenes = data.scenes.map(sc => {
        if (sc.iconKey === 'star') return { ...sc, icon: SCENE_ICONS.star };
        const builtin = DEFAULT_SCENES.find(d => d.id === sc.id);
        return { ...sc, icon: builtin ? builtin.icon : SCENE_ICONS.star };
      });
    }
    return data;
  } catch {
    return null;
  }
}

function persist(data) {
  try {
    const safeScenes = (data.scenes || []).map(sc => ({
      id: sc.id, name: sc.name, mix: sc.mix,
      iconKey: DEFAULT_SCENES.find(d => d.id === sc.id) ? sc.id : 'star',
    }));
    localStorage.setItem(STORAGE_KEY, JSON.stringify({
      mix: data.mix,
      scenes: safeScenes,
      timerMinutes: data.timerMinutes,
      fadeIn: data.fadeIn,
      themeIdx: data.themeIdx,
    }));
  } catch {}
}

/* ───────── Modal: name / confirm ───────── */
function Modal({ kind, title, message, defaultValue, onSubmit, onCancel }) {
  const [value, setValue] = uS(defaultValue || '');
  const inputRef = uR(null);
  uE(() => {
    if (kind === 'prompt' && inputRef.current) {
      inputRef.current.focus();
      inputRef.current.select();
    }
    const onKey = (e) => {
      if (e.key === 'Escape') onCancel();
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, []);

  const submit = (e) => {
    e.preventDefault();
    if (kind === 'prompt') {
      const v = value.trim();
      if (!v) return;
      onSubmit(v);
    } else {
      onSubmit(true);
    }
  };

  return (
    <div className="modal-scrim" onMouseDown={(e) => { if (e.target === e.currentTarget) onCancel(); }}>
      <form className="neu modal-card" onSubmit={submit}>
        <div>
          <div className="t-label">{kind === 'prompt' ? 'Save mix' : 'Confirm'}</div>
          <div className="t-title" style={{ marginTop: 4 }}>{title}</div>
          {message && <div className="t-body" style={{ marginTop: 6, fontSize: 12.5 }}>{message}</div>}
        </div>
        {kind === 'prompt' && (
          <input
            ref={inputRef}
            className="modal-input"
            value={value}
            onChange={(e) => setValue(e.target.value)}
            placeholder="Mix name"
            maxLength={40}
            autoComplete="off"
            spellCheck="false"
          />
        )}
        <div className="modal-row">
          <button type="button" className="btn" onClick={onCancel}>Cancel</button>
          <button type="submit" className="btn primary">
            {kind === 'prompt' ? 'Save' : 'Delete'}
          </button>
        </div>
      </form>
    </div>
  );
}

function App() {
  const persisted = uM(loadPersisted, []);

  const [themeIdx, setThemeIdx] = uS(persisted?.themeIdx ?? 0);
  const [mix, setMix] = uS(persisted?.mix ?? {});
  const [scenes, setScenes] = uS(() => {
    if (persisted?.scenes) return persisted.scenes;
    return DEFAULT_SCENES;
  });
  const [timerMinutes, setTimerMinutes] = uS(persisted?.timerMinutes ?? 30);
  const [timerRunning, setTimerRunning] = uS(false);
  const [timerRemaining, setTimerRemaining] = uS(0);
  const [fadeIn, setFadeIn] = uS(persisted?.fadeIn ?? 2);
  const [announce, setAnnounce] = uS('');
  const [modal, setModal] = uS(null);
  const [soundOn, setSoundOn] = uS(true);
  const sleepWipeTimerRef = uR(null);

  const theme = THEME_CYCLE[themeIdx];

  uE(() => {
    const root = document.documentElement;
    if (theme === 'light') {
      root.setAttribute('data-theme', 'light');
      root.removeAttribute('data-contrast');
    } else if (theme === 'dark') {
      root.setAttribute('data-theme', 'dark');
      root.setAttribute('data-contrast', 'normal');
    } else {
      root.setAttribute('data-theme', 'dark');
      root.setAttribute('data-contrast', 'deep');
    }
    const meta = document.querySelector('meta[name="theme-color"]:not([media])') || document.querySelector('meta[name="theme-color"]');
    if (meta) {
      meta.setAttribute('content', theme === 'light' ? '#e6e7ee' : theme === 'dark' ? '#34373d' : '#1a1c22');
    }
  }, [theme]);

  uE(() => {
    /* Power-off fade is always fast (300ms) so the button feels responsive,
       even when the user has set a long Fade-on-start. Power-on uses fadeIn. */
    const fadeMs = soundOn ? (fadeIn * 1000 || 300) : 300;
    SOUNDS.forEach(s => {
      const v = (mix[s.id] || 0) * (soundOn ? 1 : 0);
      window.SleepAudio.set(s.id, v, fadeMs);
    });
  }, [mix, soundOn]);

  /* Persistence — debounced so drag-driven mix updates don't thrash localStorage */
  uE(() => {
    const id = setTimeout(() => {
      persist({ mix, scenes, timerMinutes, fadeIn, themeIdx });
    }, 250);
    return () => clearTimeout(id);
  }, [mix, scenes, timerMinutes, fadeIn, themeIdx]);

  uE(() => {
    if (!timerRunning) return;
    const t = setInterval(() => {
      setTimerRemaining(r => {
        if (r <= 1) {
          clearInterval(t);
          window.SleepAudio.masterFadeOut(8);
          setTimerRunning(false);
          if (sleepWipeTimerRef.current) clearTimeout(sleepWipeTimerRef.current);
          sleepWipeTimerRef.current = setTimeout(() => {
            setMix({});
            sleepWipeTimerRef.current = null;
          }, 8100);
          setAnnounce('Sleep timer finished');
          return 0;
        }
        return r - 1;
      });
    }, 1000);
    return () => clearInterval(t);
  }, [timerRunning]);

  /* Cancel a pending post-sleep wipe if the user interacts before it fires */
  const cancelSleepWipe = () => {
    if (sleepWipeTimerRef.current) {
      clearTimeout(sleepWipeTimerRef.current);
      sleepWipeTimerRef.current = null;
    }
  };
  uE(() => () => cancelSleepWipe(), []);

  const cycleTheme = () => {
    const next = (themeIdx + 1) % THEME_CYCLE.length;
    setThemeIdx(next);
    setAnnounce(THEME_LABELS[THEME_CYCLE[next]] + ' applied');
  };

  const api = {
    setVolume: (id, v) => { cancelSleepWipe(); setMix(m => ({ ...m, [id]: v })); },
    stopAll: () => { cancelSleepWipe(); setMix({}); setAnnounce('All sounds stopped'); },
    setTimerMinutes: (m) => {
      setTimerMinutes(m);
      if (m === 0) { setTimerRunning(false); setTimerRemaining(0); }
    },
    toggleTimer: (on) => {
      if (on && timerMinutes > 0) {
        setTimerRemaining(timerMinutes * 60);
        setTimerRunning(true);
        setSoundOn(true);
        window.SleepAudio.init?.();
        setAnnounce('Sleep timer started');
      } else {
        setTimerRunning(false);
        setTimerRemaining(0);
      }
    },
    saveScene: () => {
      const snapshot = { ...mix };
      const active = Object.entries(snapshot).filter(([, v]) => v > 0.01);
      if (active.length === 0) {
        setAnnounce('Nothing to save — bring at least one pebble inward first.');
        return;
      }
      setModal({
        kind: 'prompt',
        title: 'Name this mix',
        defaultValue: `Mix ${scenes.length + 1}`,
        onSubmit: (name) => {
          setScenes(s => [...s, {
            id: 'scene-' + Date.now(),
            name,
            icon: SCENE_ICONS.star,
            mix: snapshot,
          }]);
          setAnnounce(`Scene ${name} saved`);
          setModal(null);
        },
        onCancel: () => setModal(null),
      });
    },
    loadScene: (id) => {
      const sc = scenes.find(s => s.id === id);
      if (sc) {
        cancelSleepWipe();
        setMix({ ...sc.mix });
        setSoundOn(true);
        window.SleepAudio.init?.();
        setAnnounce(`Loaded ${sc.name}`);
      }
    },
    requestDeleteScene: (id) => {
      const sc = scenes.find(s => s.id === id);
      if (!sc) return;
      setModal({
        kind: 'confirm',
        title: `Delete "${sc.name}"?`,
        message: 'This scene will be removed from your saved mixes.',
        onSubmit: () => {
          setScenes(s => s.filter(x => x.id !== id));
          setAnnounce(`Deleted ${sc.name}`);
          setModal(null);
        },
        onCancel: () => setModal(null),
      });
    },
    setFadeIn,
    toggleSound: (on) => {
      setSoundOn(on);
      setAnnounce(on ? 'Sound on' : 'Sound off');
    },
  };

  const state = {
    mix, scenes,
    timer: { minutes: timerMinutes, running: timerRunning, remaining: timerRemaining },
    fadeIn,
    soundOn,
  };

  const activeCount = Object.values(mix).filter(v => v > 0.01).length;

  const themeIcon = theme === 'light' ? (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M20 13A8 8 0 1111 4a6 6 0 009 9z"/></svg>
  ) : theme === 'dark' ? (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M2 12h2M20 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4"/></svg>
  ) : (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M19 14a7 7 0 11-9-9 5 5 0 009 9z"/><circle cx="17" cy="6" r="0.9" fill="currentColor"/></svg>
  );

  return (
    <main className="app" role="main" aria-label="Hush sleep noise machine">
      <header className="app-head">
        <div>
          <h1>Hush</h1>
          <div className="sub" aria-live="polite">
            {activeCount === 0
              ? 'Good evening'
              : `${activeCount} sound${activeCount !== 1 ? 's' : ''} playing`}
          </div>
        </div>
        <div style={{ display: 'flex', gap: 10 }}>
          <IconBtn size={42}
            onClick={cycleTheme}
            ariaLabel={`Theme: ${THEME_LABELS[theme]}. Click to change.`}
            title="Cycle theme">
            {themeIcon}
          </IconBtn>
        </div>
      </header>

      <NovelLayout state={state} api={api} />

      <div aria-live="polite" aria-atomic="true" style={{
        position: 'absolute', width: 1, height: 1, padding: 0, margin: -1,
        overflow: 'hidden', clip: 'rect(0,0,0,0)', whiteSpace: 'nowrap', border: 0,
      }}>{announce}</div>

      {modal && (
        <Modal
          kind={modal.kind}
          title={modal.title}
          message={modal.message}
          defaultValue={modal.defaultValue}
          onSubmit={modal.onSubmit}
          onCancel={modal.onCancel}
        />
      )}

      <style>{`
        @keyframes wv {
          0% { transform: scaleY(0.25); opacity: 0.5; }
          100% { transform: scaleY(1); opacity: 1; }
        }
        @media (prefers-reduced-motion: reduce) {
          *, *::before, *::after {
            animation-duration: 0.01ms !important;
            transition-duration: 0.01ms !important;
          }
        }
      `}</style>
    </main>
  );
}

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