/* global React */

// ============================================================
// PERSISTENT STORE — player name + leaderboard in localStorage
// ============================================================

const STORAGE_KEY = "neonbeat:v1";

function loadStore() {
  try {
    const raw = localStorage.getItem(STORAGE_KEY);
    if (raw) return JSON.parse(raw);
  } catch (e) {}
  return { name: "", leaderboard: [] };
}

function saveStore(store) {
  try { localStorage.setItem(STORAGE_KEY, JSON.stringify(store)); } catch (e) {}
}

function useStore() {
  const [store, setStore] = React.useState(loadStore);
  const update = React.useCallback((patch) => {
    setStore(prev => {
      const next = typeof patch === "function" ? patch(prev) : { ...prev, ...patch };
      saveStore(next);
      return next;
    });
  }, []);
  return [store, update];
}

// ============================================================
// DYNAMIC SONG LOADER
//
// songs/manifest.json is an array of objects:
//   [
//     { "file": "01.mp3", "title": "שמעון א", "bpm": 90,  "difficulty": 1 },
//     { "file": "02.mp3", "title": "שמעון ב", "bpm": 100, "difficulty": 2 }
//   ]
//
// Filenames must be ASCII (Cloudflare CDN doesn't serve Hebrew filenames).
// Hebrew titles live inside the JSON, which works fine over CDN.
//
// Field rules:
//   file         relative path under /songs/ (typically NN.mp3)
//   title        display title (Hebrew or English, spaces allowed)
//   bpm          integer 60..200, drives note spawn timing
//   difficulty   integer 1..5, shown as star pips
//
// Auto-derived (NOT in JSON):
//   id / order    array position (1-indexed)
//   duration      read from <audio>.duration on metadata-loaded
//   color/accent  assigned from PALETTE by order
//   subtitle      "Chapter N"
//
// Backward-compat: the loader also accepts the old string format
//   "01__title__90__1.mp3" — but only ASCII titles will play in production.
//
// To add songs:
//   1. drop *.mp3 files into /songs/ (ASCII filenames, e.g. 04.mp3)
//   2. add an entry to songs/manifest.json with title/bpm/difficulty
//   3. reload the page
// ============================================================

const PALETTE = [
  { color: "#ff2d6f", accent: "#ff6aa8" },
  { color: "#36e25a", accent: "#7afca0" },
  { color: "#ffb629", accent: "#ffd06a" },
  { color: "#29b6ff", accent: "#7ad2ff" },
  { color: "#c44dff", accent: "#dd8aff" },
  { color: "#ff006e", accent: "#ff5ca0" },
  { color: "#05ffa1", accent: "#6dffc4" },
  { color: "#ff71ce", accent: "#ffadde" },
  { color: "#fffb96", accent: "#ffffd6" },
];

// Parses one entry into a song object. Accepts an object (preferred) or the
// legacy string-filename format. Returns null if essential fields are missing.
function parseSongEntry(entry, indexZeroBased) {
  const order = indexZeroBased + 1;

  // Legacy: bare filename string with embedded metadata "01__title__90__1.mp3"
  if (typeof entry === "string") {
    const m = entry.match(/^(.+)\.(mp3|m4a|ogg|wav)$/i);
    if (!m) return null;
    const parts = m[1].split("__");
    if (parts.length < 4) return null;
    const bpm = parseInt(parts[2], 10);
    const difficulty = parseInt(parts[3], 10);
    if (!parts[1].trim() || !Number.isFinite(bpm) || !Number.isFinite(difficulty)) return null;
    return buildSong({ file: entry, title: parts[1].trim(), bpm, difficulty }, order);
  }

  // Preferred: object form
  if (entry && typeof entry === "object" && entry.file && entry.title) {
    const bpm = parseInt(entry.bpm, 10);
    const difficulty = parseInt(entry.difficulty, 10);
    if (!Number.isFinite(bpm) || !Number.isFinite(difficulty)) return null;
    return buildSong(entry, order);
  }
  return null;
}

function buildSong({ file, title, bpm, difficulty }, order) {
  const palette = PALETTE[(order - 1) % PALETTE.length] || PALETTE[0];
  return {
    id: order,
    file,
    src: `songs/${file}`,
    title: String(title).trim(),
    subtitle: `Chapter ${order}`,
    bpm: Math.max(60, Math.min(220, bpm)),
    difficulty: Math.max(1, Math.min(5, difficulty)),
    duration: 60,
    color: palette.color,
    accent: palette.accent,
  };
}

// Loads songs/manifest.json and turns each entry into a song.
async function loadSongs() {
  try {
    const res = await fetch("songs/manifest.json", { cache: "no-store" });
    if (!res.ok) return [];
    const data = await res.json();
    const entries = Array.isArray(data) ? data : (data.songs || []);
    return entries.map((e, i) => parseSongEntry(e, i)).filter(Boolean);
  } catch (e) {
    return [];
  }
}

// React hook: loads songs once, then enriches each with real duration when its
// audio metadata becomes available.
function useSongs() {
  const [songs, setSongs] = React.useState([]);
  const [loaded, setLoaded] = React.useState(false);

  React.useEffect(() => {
    let cancelled = false;
    loadSongs().then((list) => {
      if (cancelled) return;
      setSongs(list);
      setLoaded(true);
      // Enrich with real duration from metadata
      list.forEach((song) => {
        const a = new Audio();
        a.preload = "metadata";
        a.src = song.src;
        a.addEventListener("loadedmetadata", () => {
          if (cancelled || !Number.isFinite(a.duration)) return;
          setSongs((prev) =>
            prev.map((s) => (s.id === song.id ? { ...s, duration: Math.round(a.duration) } : s))
          );
        });
      });
    });
    return () => { cancelled = true; };
  }, []);

  return { songs, loaded };
}

window.useStore = useStore;
window.useSongs = useSongs;
window.parseSongEntry = parseSongEntry;
