

const { useState: useStateL, useEffect: useEffectL, useRef: useRefL, useCallback: useCallbackL } = React;

const Live = (() => {
  const listeners = new Set();         
  const instancesByPad = new Map();    
  let socket = null;
  let status = "connecting";           
  let reconnectAttempt = 0;
  let reconnectTimer = null;

  function emit(msg) {
    listeners.forEach((fn) => { try { fn(msg); } catch (e) { console.error(e); } });
  }

  function notifyStatus(next) {
    status = next;
    emit({ type: "__status", status: next });
  }

  function connect() {
    const proto = location.protocol === "https:" ? "wss:" : "ws:";
    const url = `${proto}//${location.host}/api/live`;
    notifyStatus("connecting");
    try { socket = new WebSocket(url); }
    catch (e) { scheduleReconnect(); return; }

    socket.onopen = () => {
      reconnectAttempt = 0;
      notifyStatus("open");
    };
    socket.onmessage = (ev) => {
      let msg;
      try { msg = JSON.parse(ev.data); } catch { return; }
      if (msg.type === "snapshot") {
        instancesByPad.clear();
        for (const inst of (msg.instances || [])) instancesByPad.set(inst.id, inst);
      } else if (msg.type === "instance_update") {
        instancesByPad.set(msg.instance.id, msg.instance);
      }
      emit(msg);
    };
    socket.onclose = () => { notifyStatus("closed"); scheduleReconnect(); };
    socket.onerror = () => { try { socket.close(); } catch {} };
  }

  function scheduleReconnect() {
    if (reconnectTimer) return;
    const delay = Math.min(15000, 500 * Math.pow(1.6, reconnectAttempt++));
    reconnectTimer = setTimeout(() => { reconnectTimer = null; connect(); }, delay);
  }

  
  async function bootstrap() {
    try {
      const r = await fetch("/api/instances");
      if (!r.ok) return;
      const list = await r.json();
      instancesByPad.clear();
      for (const inst of list) instancesByPad.set(inst.id, inst);
      emit({ type: "snapshot", instances: list });
    } catch {}
  }

  function subscribe(fn) { listeners.add(fn); return () => listeners.delete(fn); }
  function getInstances() { return [...instancesByPad.values()]; }
  function getStatus() { return status; }

  
  
  
  async function control(padCodes, action, args) {
    try {
      const r = await fetch("/api/control", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ pad_codes: padCodes, action, args: args || {} }),
      });
      if (!r.ok) {
        emit({ type: "notice", level: "bad", message: `control ${action}: HTTP ${r.status}` });
        return null;
      }
      return await r.json();
    } catch (e) {
      emit({ type: "notice", level: "bad", message: `control ${action}: сеть недоступна` });
      return null;
    }
  }

  

  return { connect, bootstrap, subscribe, getInstances, getStatus, control, emit };
})();

function useLiveInstances() {
  const [tick, setTick] = useStateL(0);
  useEffectL(() => Live.subscribe((m) => {
    if (m.type === "snapshot" || m.type === "instance_update") setTick((t) => t + 1);
  }), []);
  
  return React.useMemo(() => Live.getInstances(), [tick]);
}

function useLiveStatus() {
  const [s, setS] = useStateL(Live.getStatus());
  useEffectL(() => Live.subscribe((m) => { if (m.type === "__status") setS(m.status); }), []);
  return s;
}

function useLiveLogs(padCode, { limit = 500 } = {}) {
  const [logs, setLogs] = useStateL([]);
  const padRef = useRefL(padCode);
  padRef.current = padCode;

  useEffectL(() => {
    if (!padCode) { setLogs([]); return; }
    let cancelled = false;
    fetch(`/api/instances/${encodeURIComponent(padCode)}/logs?limit=${limit}`)
      .then((r) => r.ok ? r.json() : [])
      .then((rows) => { if (!cancelled) setLogs(rows); })
      .catch(() => {});

    const off = Live.subscribe((m) => {
      if (m.type === "log" && m.pad_code === padRef.current) {
        setLogs((prev) => {
          const next = prev.length >= 1000 ? prev.slice(-900) : prev.slice();
          next.push(m.line);
          return next;
        });
      }
    });
    return () => { cancelled = true; off(); };
  }, [padCode, limit]);

  return logs;
}

function useToasts() {
  const [toasts, setToasts] = useStateL([]);
  useEffectL(() => Live.subscribe((m) => {
    if (m.type !== "notice") return;
    const id = Math.random().toString(36).slice(2);
    const toast = {
      id,
      level: m.level || "warn",
      message: m.message || "",
      pads: m.pads || null,
    };
    setToasts((prev) => [...prev.slice(-4), toast]);
    setTimeout(() => setToasts((prev) => prev.filter((t) => t.id !== id)), 6000);
  }), []);
  const dismiss = (id) => setToasts((prev) => prev.filter((t) => t.id !== id));
  return [toasts, dismiss];
}

Object.assign(window, { Live, useLiveInstances, useLiveStatus, useLiveLogs, useToasts });
