/* global React, ReactDOM */
const { useState, useEffect, useRef, useCallback, useMemo } = React;

// ============================================================
// TWEAK DEFAULTS
// ============================================================
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "lanes": 3,
  "fallSpeed": 2,
  "noteDensity": 1.0,
  "theme": "neon-pink"
}/*EDITMODE-END*/;

const THEMES = {
  "neon-pink":   ["#ff2d6f", "#36e25a", "#ffb629", "#29b6ff", "#c44dff"],
  "synthwave":   ["#ff006e", "#ffbe0b", "#fb5607", "#3a86ff", "#8338ec"],
  "vapor":       ["#ff71ce", "#01cdfe", "#05ffa1", "#b967ff", "#fffb96"],
  "fire-ice":    ["#ff3030", "#ff8c00", "#00d9ff", "#0066ff", "#a020f0"],
  "monochrome":  ["#ffffff", "#bbbbbb", "#7c7c7c", "#dddddd", "#ffffff"],
};

const KEY_MAPS = {
  3: ["a", "s", "d"],
  4: ["d", "f", "j", "k"],
  5: ["s", "d", "f", "j", "k"],
};

let UID = 0;
const uid = () => ++UID;

// ============================================================
// GAME PLAY (the rhythm screen)
// ============================================================
function GameScreen({ song, onFinish, onExit }) {
  const [tweaks, setTweaks] = useTweaks(TWEAK_DEFAULTS);
  const lanes = tweaks.lanes;

  const colors = useMemo(() => {
    const base = THEMES[tweaks.theme] || THEMES["neon-pink"];
    if (!song) return base;
    const norm = (c) => String(c).toLowerCase();
    const used = new Set([norm(song.color)]);
    const result = [song.color];
    for (const c of base) {
      if (result.length >= 5) break;
      if (used.has(norm(c))) continue;
      result.push(c);
      used.add(norm(c));
    }
    while (result.length < 5) result.push(base[result.length] || "#888");
    return result;
  }, [tweaks.theme, song]);

  const [started, setStarted] = useState(false);
  const [paused, setPaused] = useState(false);
  const [score, setScore] = useState(0);
  const [combo, setCombo] = useState(0);
  const [maxCombo, setMaxCombo] = useState(0);
  const [accuracy, setAccuracy] = useState(100);
  const [health, setHealth] = useState(100);
  const [timeLeft, setTimeLeft] = useState(song?.duration || 60);
  const [songDuration, setSongDuration] = useState(song?.duration || 60);

  const [notes, setNotes] = useState([]);
  const [bursts, setBursts] = useState([]);
  const [feedbacks, setFeedbacks] = useState([]);
  const [pressedLanes, setPressedLanes] = useState(new Set());
  const [comboPulse, setComboPulse] = useState(0);

  const lastSpawnRef = useRef(0);
  const stats = useRef({ hits: 0, misses: 0, totalNotes: 0 });
  const audioCtxRef = useRef(null);
  const songAudioRef = useRef(null);
  const finishedRef = useRef(false);
  const scoreRef = useRef(0);
  const maxComboRef = useRef(0);
  useEffect(() => { scoreRef.current = score; }, [score]);
  useEffect(() => { maxComboRef.current = maxCombo; }, [maxCombo]);

  const keyMap = useMemo(() => KEY_MAPS[lanes] || KEY_MAPS[3], [lanes]);

  // Hit confirmation blip (synth, separate from the song audio)
  const blip = useCallback((kind, laneIdx) => {
    try {
      if (!audioCtxRef.current) {
        audioCtxRef.current = new (window.AudioContext || window.webkitAudioContext)();
      }
      const ctx = audioCtxRef.current;
      const o = ctx.createOscillator();
      const g = ctx.createGain();
      const baseFreq = [220, 277, 330, 392, 494][laneIdx % 5];
      const mult = kind === "perfect" ? 2 : kind === "great" ? 1.5 : 1;
      o.frequency.value = baseFreq * mult;
      o.type = "triangle";
      g.gain.setValueAtTime(0.08, ctx.currentTime);
      g.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.18);
      o.connect(g).connect(ctx.destination);
      o.start();
      o.stop(ctx.currentTime + 0.2);
    } catch (e) {}
  }, []);

  const finishSong = useCallback(() => {
    if (finishedRef.current) return;
    finishedRef.current = true;
    if (songAudioRef.current) {
      try { songAudioRef.current.pause(); } catch (e) {}
    }
    const total = stats.current.hits + stats.current.misses;
    const acc = total ? Math.round((stats.current.hits / total) * 100) : 0;
    let stars = 0;
    if (acc >= 95) stars = 5;
    else if (acc >= 85) stars = 4;
    else if (acc >= 70) stars = 3;
    else if (acc >= 50) stars = 2;
    else if (acc >= 25) stars = 1;
    onFinish?.({
      score: Math.floor(scoreRef.current),
      maxCombo: maxComboRef.current,
      accuracy: acc,
      hits: stats.current.hits,
      totalNotes: stats.current.totalNotes,
      stars,
    });
  }, [onFinish]);

  // Set up song audio element when started
  useEffect(() => {
    if (!started || !song?.src) return;
    const a = new Audio(song.src);
    a.preload = "auto";
    a.volume = 0.85;
    songAudioRef.current = a;

    const onMeta = () => {
      if (Number.isFinite(a.duration)) {
        setSongDuration(a.duration);
        setTimeLeft(a.duration);
      }
    };
    const onEnded = () => finishSong();
    a.addEventListener("loadedmetadata", onMeta);
    a.addEventListener("ended", onEnded);

    a.play().catch(() => { /* autoplay block — user already tapped to start */ });

    return () => {
      a.removeEventListener("loadedmetadata", onMeta);
      a.removeEventListener("ended", onEnded);
      try { a.pause(); } catch (e) {}
      songAudioRef.current = null;
    };
  }, [started, song, finishSong]);

  // Pause/resume the song audio in lockstep with paused state
  useEffect(() => {
    const a = songAudioRef.current;
    if (!a) return;
    if (paused) { try { a.pause(); } catch (e) {} }
    else { a.play().catch(() => {}); }
  }, [paused]);

  const registerMiss = useCallback(() => {
    setCombo(0);
    setHealth(h => Math.max(0, h - 4));
    setAccuracy(() => {
      const total = stats.current.hits + stats.current.misses;
      return total ? Math.round((stats.current.hits / total) * 100) : 100;
    });
    spawnFeedback("miss");
  }, []);

  const spawnFeedback = useCallback((kind, laneIdx) => {
    const id = uid();
    setFeedbacks(prev => [...prev.slice(-3), { id, kind, lane: laneIdx }]);
    setTimeout(() => setFeedbacks(prev => prev.filter(f => f.id !== id)), 850);
  }, []);

  const spawnBurst = useCallback((laneIdx, color) => {
    const id = uid();
    setBursts(prev => [...prev, { id, lane: laneIdx, color }]);
    setTimeout(() => setBursts(prev => prev.filter(b => b.id !== id)), 650);
  }, []);

  // Main loop: advance notes, spawn (BPM-driven), drive timer from audio.currentTime
  useEffect(() => {
    if (!started || paused) return;
    let raf;
    let last = performance.now();

    // Spawn interval: one note per beat, modulated by noteDensity (1 = on the beat)
    const beatMs = song?.bpm ? (60000 / song.bpm) : 500;
    const spawnInterval = beatMs / Math.max(0.5, tweaks.noteDensity);

    const loop = (now) => {
      const dt = Math.min(50, now - last);
      last = now;

      // Timer — prefer audio.currentTime when available, fallback to elapsed
      const a = songAudioRef.current;
      const remaining = a && Number.isFinite(a.duration)
        ? Math.max(0, a.duration - a.currentTime)
        : Math.max(0, songDuration - dt / 1000);
      setTimeLeft(remaining);
      if (remaining <= 0.05) {
        finishSong();
        return;
      }

      const travelMs = 4200 / tweaks.fallSpeed;
      setNotes(prev => {
        const next = [];
        for (const n of prev) {
          const ny = n.y + dt / travelMs;
          if (ny > 1.18) {
            stats.current.misses++;
            registerMiss();
          } else {
            next.push({ ...n, y: ny });
          }
        }
        return next;
      });

      // Stop spawning ~travelMs before song end so falling notes can complete
      const stopSpawnBefore = (travelMs / 1000) + 0.3;
      if (remaining > stopSpawnBefore && now - lastSpawnRef.current > spawnInterval) {
        lastSpawnRef.current = now;
        setNotes(prev => {
          const lane = Math.floor(Math.random() * lanes);
          const newNotes = [{ id: uid(), lane, y: 0 }];
          stats.current.totalNotes++;
          if (Math.random() < 0.18 && lanes >= 3) {
            const lane2 = (lane + 1 + Math.floor(Math.random() * (lanes - 1))) % lanes;
            newNotes.push({ id: uid(), lane: lane2, y: 0 });
            stats.current.totalNotes++;
          }
          return [...prev, ...newNotes];
        });
      }

      raf = requestAnimationFrame(loop);
    };
    raf = requestAnimationFrame(loop);
    return () => cancelAnimationFrame(raf);
  }, [started, paused, lanes, tweaks.fallSpeed, tweaks.noteDensity, song, songDuration, finishSong, registerMiss]);

  const tapLane = useCallback((laneIdx) => {
    if (!started || paused) {
      if (!started) setStarted(true);
      return;
    }
    setPressedLanes(prev => { const next = new Set(prev); next.add(laneIdx); return next; });
    setTimeout(() => setPressedLanes(prev => { const next = new Set(prev); next.delete(laneIdx); return next; }), 130);

    setNotes(prev => {
      let best = -1;
      let bestDist = Infinity;
      for (let i = 0; i < prev.length; i++) {
        const n = prev[i];
        if (n.lane !== laneIdx) continue;
        const dist = Math.abs(n.y - 1.0);
        if (dist < bestDist) { bestDist = dist; best = i; }
      }
      if (best === -1 || bestDist > 0.32) return prev;

      let kind = "good", pts = 50;
      if (bestDist < 0.10) { kind = "perfect"; pts = 300; }
      else if (bestDist < 0.18) { kind = "great"; pts = 150; }
      else { kind = "good"; pts = 75; }

      stats.current.hits++;
      const next = [...prev];
      next.splice(best, 1);

      setCombo(c => { const nc = c + 1; setMaxCombo(m => Math.max(m, nc)); return nc; });
      setComboPulse(p => p + 1);
      setScore(s => s + pts * (1 + Math.floor((combo + 1) / 10) * 0.1));
      setHealth(h => Math.min(100, h + (kind === "perfect" ? 2 : 1)));
      setAccuracy(() => {
        const total = stats.current.hits + stats.current.misses;
        return total ? Math.round((stats.current.hits / total) * 100) : 100;
      });
      spawnBurst(laneIdx, colors[laneIdx]);
      spawnFeedback(kind, laneIdx);
      blip(kind, laneIdx);
      return next;
    });
  }, [started, paused, combo, colors, spawnBurst, spawnFeedback, blip]);

  useEffect(() => {
    const onDown = (e) => {
      if (e.repeat) return;
      const idx = keyMap.indexOf(e.key.toLowerCase());
      if (idx >= 0) {
        e.preventDefault();
        tapLane(idx);
      }
      if (e.key === "Escape") setPaused(p => !p);
    };
    window.addEventListener("keydown", onDown);
    return () => window.removeEventListener("keydown", onDown);
  }, [keyMap, tapLane]);

  const comboScale = Math.min(combo, 60);
  const timePct = (timeLeft / songDuration) * 100;

  return (
    <div className="stage" style={{
      "--lane-1": colors[0], "--lane-2": colors[1], "--lane-3": colors[2],
      "--lane-4": colors[3], "--lane-5": colors[4],
    }}>
      <div className="hud">
        <div className="hud-top">
          <div className="hud-stat">
            <div className="hud-label">דיוק</div>
            <div className="hud-pct">{accuracy}%</div>
          </div>
          <div className="hud-stat" style={{ alignItems: "center", flex: 1 }}>
            <div className="hud-label">{song ? song.title : "ניקוד"}</div>
            <div className="hud-score">{Math.floor(score).toLocaleString().padStart(7, "0")}</div>
          </div>
          <button className="hud-pause" onClick={() => setPaused(p => !p)} aria-label="Pause">
            {paused ? (
              <svg viewBox="0 0 16 16" fill="currentColor"><path d="M3 2 L13 8 L3 14 Z"/></svg>
            ) : (
              <svg viewBox="0 0 16 16" fill="currentColor"><rect x="3" y="2" width="3" height="12"/><rect x="10" y="2" width="3" height="12"/></svg>
            )}
          </button>
        </div>
        <div className="health-wrap">
          <div className="health-bar"><div className="health-fill" style={{ width: `${health}%` }} /></div>
        </div>
        {song && (
          <div className="time-bar-wrap">
            <span className="time-num">{Math.ceil(timeLeft)}s</span>
            <div className="time-bar"><div className="time-fill" style={{ width: `${timePct}%`, background: song.color, boxShadow: `0 0 12px ${song.color}` }} /></div>
          </div>
        )}
      </div>

      <div className={`combo ${combo >= 2 ? "" : "hidden"} ${comboPulse % 2 ? "pulse" : "pulse-alt"}`}
           key={`combo-${comboPulse}`} style={{ "--combo-scale": comboScale }}>
        <div className="combo-num">{combo}</div>
        <div className="combo-label">קומבו</div>
      </div>

      <div className="track">
        {Array.from({ length: lanes }).map((_, i) => (
          <div key={i} className={`lane ${pressedLanes.has(i) ? "active" : ""}`} style={{ "--lane-color": colors[i] }}>
            <div className="lane-streak" style={{ "--lane-color": colors[i] }} />
          </div>
        ))}

        {notes.map(n => {
          const laneWidthPct = 100 / lanes;
          const left = laneWidthPct * (n.lane + 0.5);
          const yPct = (n.y * 100).toFixed(2);
          const offsetMul = n.y.toFixed(3);
          return (
            <div key={n.id} className="note"
                 style={{
                   left: `${left}%`,
                   top: `calc(${yPct}% - (var(--hit-zone-h) - 50px) * ${offsetMul})`,
                   "--lane-color": colors[n.lane],
                 }}/>
          );
        })}

        <div className="hit-zone">
          <div className="hit-zone-line" />
          <div className="pads">
            {Array.from({ length: lanes }).map((_, i) => (
              <div key={i} className={`pad ${pressedLanes.has(i) ? "pressed" : ""}`} style={{ "--lane-color": colors[i] }}
                   onPointerDown={(e) => { e.preventDefault(); tapLane(i); }}>
                <div className="pad-ring" />
              </div>
            ))}
          </div>
        </div>

        {bursts.map(b => {
          const laneWidthPct = 100 / lanes;
          const left = laneWidthPct * (b.lane + 0.5);
          return (
            <div key={b.id} className="burst" style={{ left: `${left}%`, "--lane-color": b.color }}>
              <div className="ring" /><div className="flash" />
              {Array.from({ length: 8 }).map((_, i) => {
                const angle = (i / 8) * Math.PI * 2;
                return <div key={i} className="spark" style={{ "--dx": `${Math.cos(angle)*60}px`, "--dy": `${Math.sin(angle)*60}px` }} />;
              })}
            </div>
          );
        })}

        {feedbacks.map(f => {
          const colorMap = { perfect: "var(--fb-perfect)", great: "var(--fb-great)", good: "var(--fb-good)", miss: "var(--fb-miss)" };
          const labelMap = { perfect: "מושלם!", great: "מצוין!", good: "טוב", miss: "החטאה" };
          return (
            <div key={f.id} className="feedback" style={{ "--fb-color": colorMap[f.kind], color: colorMap[f.kind] }}>
              {labelMap[f.kind]}
            </div>
          );
        })}
      </div>

      {!started && (
        <div className="tap-hint" onPointerDown={() => setStarted(true)}>
          <h1>{song ? song.title : "NEON BEAT"}</h1>
          <p>הקש כדי להתחיל</p>
          <div className="keys">
            {keyMap.map(k => <kbd key={k}>{k === " " ? "SPC" : k.toUpperCase()}</kbd>)}
          </div>
        </div>
      )}

      {started && paused && (
        <div className="paused-overlay">
          <h2>הושהה</h2>
          <button className="btn-resume" onClick={() => setPaused(false)}>המשך</button>
          <button className="btn-resume" style={{borderColor:'#ff2d6f', boxShadow:'0 0 16px rgba(255,45,110,0.5)', background:'rgba(255,45,110,0.1)'}}
                  onClick={() => onExit?.()}>צא לתפריט</button>
        </div>
      )}

      <TweaksPanel title="Tweaks">
        <TweakSection label="משחק">
          <TweakRadio label="מסלולים"
                      options={[{ value: 3, label: "3" }, { value: 4, label: "4" }, { value: 5, label: "5" }]}
                      value={tweaks.lanes} onChange={(v) => setTweaks({ lanes: v })} />
          <TweakSlider label="מהירות" min={1} max={10} step={0.5} value={tweaks.fallSpeed} onChange={(v) => setTweaks({ fallSpeed: v })} />
          <TweakSlider label="צפיפות" min={0.5} max={2.5} step={0.1} value={tweaks.noteDensity} onChange={(v) => setTweaks({ noteDensity: v })} />
        </TweakSection>
        <TweakSection label="עיצוב">
          <TweakSelect label="פלטה"
                       options={[
                         { value: "neon-pink", label: "נאון ורוד" },
                         { value: "synthwave", label: "סינת'-ווייב" },
                         { value: "vapor", label: "ויפר-ווייב" },
                         { value: "fire-ice", label: "אש וקרח" },
                         { value: "monochrome", label: "מונוכרום" },
                       ]}
                       value={tweaks.theme} onChange={(v) => setTweaks({ theme: v })} />
        </TweakSection>
      </TweaksPanel>
    </div>
  );
}

// ============================================================
// LOADING SCREEN — while songs/manifest.json is being fetched
// ============================================================
function LoadingScreen() {
  return (
    <div className="screen login-screen">
      <div className="bg-grid" />
      <div className="brand-mark">
        <h1 className="brand-title">NEON BEAT</h1>
        <div className="brand-sub">טוען...</div>
      </div>
    </div>
  );
}

// ============================================================
// APP — top-level router
// ============================================================
function App() {
  const [store, updateStore] = useStore();
  const { songs, loaded } = useSongs();
  const [route, setRoute] = useState(() => store.name ? "hub" : "login");
  const [currentSong, setCurrentSong] = useState(null);
  const [lastResult, setLastResult] = useState(null);

  // Default-select the first song once it's loaded
  useEffect(() => {
    if (!currentSong && songs.length > 0) setCurrentSong(songs[0]);
  }, [songs, currentSong]);

  const handleLogin = (name) => {
    updateStore({ name });
    setRoute("hub");
  };
  const handleLogout = () => {
    updateStore({ name: "" });
    setRoute("login");
  };

  const handleFinishSong = (result) => {
    const entry = {
      id: Date.now() + "-" + Math.random().toString(36).slice(2, 8),
      name: store.name,
      song: currentSong.id,
      score: result.score,
      accuracy: result.accuracy,
      maxCombo: result.maxCombo,
      stars: result.stars,
      date: Date.now(),
    };
    const sorted = [...(store.leaderboard || []), entry].sort((a, b) => b.score - a.score);
    const isNewBest = sorted[0]?.id === entry.id;
    updateStore(s => ({ ...s, leaderboard: sorted.slice(0, 20) }));
    setLastResult({ ...result, isNewBest });
    setRoute("results");
  };

  if (!loaded) return <LoadingScreen />;

  return (
    <>
      {route === "login" && <LoginScreen onLogin={handleLogin} savedName={store.name} />}
      {route === "hub" && (
        <HubScreen name={store.name}
                   songCount={songs.length}
                   onPlay={() => currentSong ? setRoute("game") : setRoute("songs")}
                   onSongs={() => setRoute("songs")}
                   onLeaderboard={() => setRoute("leaderboard")}
                   onLogout={handleLogout} />
      )}
      {route === "songs" && (
        <SongSelectScreen songs={songs} selected={currentSong}
                          onSelect={setCurrentSong}
                          onPlay={(s) => { setCurrentSong(s); setRoute("game"); }}
                          onBack={() => setRoute("hub")} />
      )}
      {route === "leaderboard" && (
        <LeaderboardScreen entries={store.leaderboard || []}
                           currentName={store.name}
                           onBack={() => setRoute("hub")} />
      )}
      {route === "game" && currentSong && (
        <GameScreen key={currentSong.id + "-" + Date.now()}
                    song={currentSong}
                    onFinish={handleFinishSong}
                    onExit={() => setRoute("hub")} />
      )}
      {route === "results" && lastResult && currentSong && (
        <ResultsScreen result={lastResult} song={currentSong} name={store.name}
                       onAgain={() => setRoute("game")}
                       onSongs={() => setRoute("songs")}
                       onHub={() => setRoute("hub")} />
      )}
    </>
  );
}

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