// state.jsx — shared room state via WebSocket to the backend.
// The backend (with SQLite persistence) is authoritative; we send commands and
// receive `state` snapshots. NO room state is cached in localStorage.
// localStorage is used ONLY for the user's own identity (a stable userId so
// reloads keep the same handle in the room). Everything else comes from the
// server.

(function () {
  // Identity-only. NOT used for room state.
  const ME_KEY = "ktv:me";

  function getSlug() {
    const url = new URL(window.location.href);
    let slug = url.searchParams.get("room") || (url.hash.match(/room=([^&]+)/) || [])[1];
    if (!slug) {
      const adj = ["neon", "midnight", "velvet", "echo", "moon", "tiger", "comet", "lotus", "ghost", "fox"];
      const noun = ["lounge", "alley", "wave", "drive", "static", "river", "tape", "den", "loop", "bay"];
      slug = adj[Math.floor(Math.random()*adj.length)] + "-" + noun[Math.floor(Math.random()*noun.length)] + "-" + Math.floor(Math.random()*900+100);
      url.searchParams.set("room", slug);
      window.history.replaceState({}, "", url.toString());
    }
    return slug;
  }

  const SLUG = getSlug();

  // One-time cleanup: earlier builds cached room state under "ktv:room:<slug>"
  // and the slug under "ktv:slug". The backend is now the only source of truth,
  // so wipe any leftovers to avoid confusion when debugging.
  try {
    for (let i = localStorage.length - 1; i >= 0; i--) {
      const k = localStorage.key(i);
      if (k && (k === "ktv:slug" || k.startsWith("ktv:room:") || k === "ktv:layout")) {
        localStorage.removeItem(k);
      }
    }
  } catch (e) {}

  // ── Initial blank room (only used until the first server snapshot arrives) ──
  const blankRoom = () => ({
    slug: SLUG,
    queue: [], history: [], current: null,
    users: {}, danmaku: [],
    rev: 0,
    // legacy fields some UI bits still read; harmless to keep
    playback: { seq: 0 },
  });

  // ── Me ──────────────────────────────────────────────────────
  function loadMe() {
    try {
      const raw = localStorage.getItem(ME_KEY);
      if (raw) return JSON.parse(raw);
    } catch (e) {}
    const id = "u_" + Math.random().toString(36).slice(2, 9);
    const me = { id, name: "", emoji: "🎤", anonymous: false, configured: false };
    localStorage.setItem(ME_KEY, JSON.stringify(me));
    return me;
  }

  // ── Connection singleton ────────────────────────────────────
  // One WebSocket per page, multiplexed across hooks via subscribers.
  function makeConnection(slug) {
    let ws = null;
    let state = blankRoom();
    let me = null;
    let helloed = false;
    let pending = [];
    let reconnectTimer = null;
    const subs = new Set();

    function notify() { for (const fn of subs) { try { fn(state); } catch {} } }

    function buildHello() {
      if (!me) return null;
      const display = me.anonymous
        ? { name: "匿名", emoji: "👤", anonymous: true }
        : { name: (me.name || "未命名").slice(0, 16), emoji: me.emoji || "🎤", anonymous: false };
      return { type: "hello", userId: me.id, ...display };
    }

    function connect() {
      if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) return;
      const proto = location.protocol === "https:" ? "wss" : "ws";
      const url = `${proto}://${location.host}/ws?room=${encodeURIComponent(slug)}`;
      try { ws = new WebSocket(url); } catch (e) { scheduleReconnect(); return; }

      ws.addEventListener("open", () => {
        helloed = false;
        const hello = buildHello();
        if (hello) {
          ws.send(JSON.stringify(hello));
          helloed = true;
          for (const m of pending) ws.send(JSON.stringify(m));
          pending = [];
        }
      });
      ws.addEventListener("message", (ev) => {
        let msg;
        try { msg = JSON.parse(ev.data); } catch { return; }
        if (msg.type === "state" && msg.state) {
          state = { ...blankRoom(), ...msg.state };
          notify();
        }
      });
      ws.addEventListener("close", () => { ws = null; helloed = false; scheduleReconnect(); });
      ws.addEventListener("error", () => { try { ws && ws.close(); } catch {} });
    }
    function scheduleReconnect() {
      if (reconnectTimer) return;
      reconnectTimer = setTimeout(() => { reconnectTimer = null; connect(); }, 1500);
    }

    function send(msg) {
      if (ws && ws.readyState === WebSocket.OPEN && helloed) {
        ws.send(JSON.stringify(msg));
      } else {
        pending.push(msg);
        connect();
      }
    }

    function setMe(next) {
      const prev = me;
      me = next;
      if (!prev || !ws || ws.readyState !== WebSocket.OPEN) {
        // first time or not connected yet — let connect()/open send hello with the current me
        if (!ws) connect();
        return;
      }
      if (prev.id !== next.id) {
        // userId changed — reconnect so the server gets a fresh hello
        try { ws.close(); } catch {}
        return;
      }
      // same user, profile patch
      const display = next.anonymous
        ? { name: "匿名", emoji: "👤", anonymous: true }
        : { name: (next.name || "未命名").slice(0, 16), emoji: next.emoji || "🎤", anonymous: false };
      try {
        ws.send(JSON.stringify({ type: "profile.update", ...display }));
      } catch {}
    }

    function subscribe(fn) {
      subs.add(fn);
      // push the current snapshot synchronously so React state lines up
      try { fn(state); } catch {}
      return () => subs.delete(fn);
    }

    // Heartbeat (keeps presence fresh; spec calls for 20–30s).
    setInterval(() => {
      if (ws && ws.readyState === WebSocket.OPEN && helloed) {
        try { ws.send(JSON.stringify({ type: "heartbeat" })); } catch {}
      }
    }, 25000);

    return { send, setMe, subscribe, getState: () => state };
  }

  const conn = makeConnection(SLUG);

  // ── Hook: shared room ───────────────────────────────────────
  function useRoom() {
    const [room, setRoom] = React.useState(() => conn.getState());
    React.useEffect(() => conn.subscribe(setRoom), []);
    return [room, conn.send];
  }

  // ── Hook: me ────────────────────────────────────────────────
  function useMe() {
    const [me, setMe] = React.useState(loadMe);
    // push the latest me into the connection so hello/profile.update use it
    React.useEffect(() => { conn.setMe(me); }, [me.id, me.name, me.emoji, me.anonymous]);
    const update = React.useCallback((patch) => {
      setMe((prev) => {
        const next = typeof patch === "function" ? patch(prev) : { ...prev, ...patch };
        try { localStorage.setItem(ME_KEY, JSON.stringify(next)); } catch (e) {}
        return next;
      });
    }, []);
    return [me, update];
  }

  // Heartbeat is now run inside the connection — keep this no-op so existing
  // call sites (App.jsx) don't break.
  function useHeartbeat() { /* no-op: handled by connection */ }

  window.KTV = window.KTV || {};
  Object.assign(window.KTV, {
    SLUG, useRoom, useMe, useHeartbeat, blankRoom,
    _conn: conn,
  });
})();
