// public/assets/sse.js
/* English comments: shared SSE client with lease + cache */

(function () {
  const SSE_LEASE_KEY = 'scmedia_sse_lease';
  const SSE_LAST = 'scmedia_sse_last';
  const SSE_LAST_TYPE = 'scmedia_sse_last_type';
  const SSE_CONNECTED = 'scmedia_sse_connected';
  const SSE_RECONNECTS = 'scmedia_sse_reconnects';
  const SSE_KEY_EXP = 'scmedia_sse_key_exp';

  let client = null;
  let retryMs = 5000;
  let tabId = '';
  let leaseTimer = null;
  let opening = false;
  const listeners = {};

  function emit(type, payload) {
    const list = listeners[type] || [];
    list.forEach((cb) => {
      try {
        cb(payload);
      } catch (e) {
        // ignore
      }
    });
  }

  function on(type, cb) {
    if (!type || typeof cb !== 'function') return;
    listeners[type] = listeners[type] || [];
    listeners[type].push(cb);
  }

  function loadKeyExp() {
    return Number(sessionStorage.getItem(SSE_KEY_EXP) || 0);
  }

  function cacheKeyExp(ttlSeconds) {
    const exp = Date.now() + Math.max(10, ttlSeconds) * 1000;
    sessionStorage.setItem(SSE_KEY_EXP, String(exp));
    return exp;
  }

  function isKeyFresh() {
    const exp = loadKeyExp();
    return exp > Date.now() + 1000;
  }

  function setConnected(ok) {
    localStorage.setItem(SSE_CONNECTED, ok ? '1' : '0');
    if (ok) {
      localStorage.setItem(SSE_LAST, String(Date.now()));
    }
  }

  function noteEvent(type) {
    setConnected(true);
    localStorage.setItem(SSE_LAST, String(Date.now()));
    if (type) {
      localStorage.setItem(SSE_LAST_TYPE, String(type));
    }
    refreshLease();
  }

  function claimLease() {
    if (!tabId) {
      const existing = sessionStorage.getItem('scmedia_sse_tab_id');
      tabId = existing || Math.random().toString(36).slice(2);
      sessionStorage.setItem('scmedia_sse_tab_id', tabId);
    }
    const now = Date.now();
    const ttlMs = 15000;
    const raw = localStorage.getItem(SSE_LEASE_KEY) || '';
    let lease = null;
    try {
      lease = raw ? JSON.parse(raw) : null;
    } catch (e) {
      lease = null;
    }
    if (lease && lease.ts && (now - lease.ts) < ttlMs && lease.id !== tabId) {
      return false;
    }
    localStorage.setItem(SSE_LEASE_KEY, JSON.stringify({ ts: now, id: tabId }));
    return true;
  }

  function refreshLease() {
    localStorage.setItem(SSE_LEASE_KEY, JSON.stringify({ ts: Date.now(), id: tabId }));
  }

  function releaseLease() {
    const raw = localStorage.getItem(SSE_LEASE_KEY) || '';
    try {
      const lease = raw ? JSON.parse(raw) : null;
      if (lease && lease.id && lease.id !== tabId) return;
    } catch (e) {
      // ignore
    }
    localStorage.removeItem(SSE_LEASE_KEY);
  }

  async function open() {
    if (opening) return;
    opening = true;
    if (!claimLease()) {
      opening = false;
      return;
    }
    if (window.Auth?.isAccessExpired?.() && window.Auth?.refreshTokens) {
      await window.Auth.refreshTokens();
    }
    const token = window.Auth?.getAccessToken?.();
    if (!token) {
      opening = false;
      return;
    }

    if (!isKeyFresh()) {
      const res = await fetch('/api/auth/sse-key', {
        method: 'POST',
        headers: { Authorization: `Bearer ${token}` },
      });
      const data = await res.json().catch(() => null);
      const key = data?.data?.key || '';
      const ttl = Number(data?.data?.expires_in || 60);
      if (!data?.ok || !key) {
        releaseLease();
        opening = false;
        return;
      }
      cacheKeyExp(ttl);
      await new Promise((r) => setTimeout(r, 50));
    }

    const es = new EventSource('/api/events', { withCredentials: true });
    client = es;
    es.addEventListener('open', () => {
      retryMs = 5000;
      setConnected(true);
      refreshLease();
      if (leaseTimer) clearInterval(leaseTimer);
      leaseTimer = setInterval(refreshLease, 5000);
      emit('open');
    });
    const handle = (type) => {
      noteEvent(type);
      emit(type);
    };
    es.addEventListener('message', () => handle('message'));
    es.addEventListener('tick', () => handle('tick'));
    es.addEventListener('sources', (e) => {
      noteEvent('sources');
      let payload = null;
      try { payload = JSON.parse(e.data); } catch (e2) { payload = null; }
      emit('sources', payload);
    });
    es.addEventListener('jobs', (e) => {
      noteEvent('jobs');
      let payload = null;
      try { payload = JSON.parse(e.data); } catch (e2) { payload = null; }
      emit('jobs', payload);
    });
    es.addEventListener('error', () => {
      if (client) {
        client.close();
        client = null;
      }
      if (leaseTimer) {
        clearInterval(leaseTimer);
        leaseTimer = null;
      }
      const prev = Number(localStorage.getItem(SSE_RECONNECTS) || 0);
      localStorage.setItem(SSE_RECONNECTS, String(prev + 1));
      releaseLease();
      opening = false;
      const delay = retryMs;
      retryMs = Math.min(retryMs * 2, 60000);
      setTimeout(open, delay);
    });
    opening = false;
  }

  function start() {
    if (!window.EventSource) return;
    if (client) return;
    open().catch(() => {});
  }

  function stop() {
    if (client) {
      client.close();
      client = null;
    }
    if (leaseTimer) {
      clearInterval(leaseTimer);
      leaseTimer = null;
    }
    localStorage.removeItem(SSE_CONNECTED);
    localStorage.removeItem(SSE_LAST);
    localStorage.removeItem(SSE_LAST_TYPE);
    localStorage.removeItem(SSE_LEASE_KEY);
    sessionStorage.removeItem(SSE_KEY_EXP);
    document.cookie = 'scmedia_sse_key=; path=/; max-age=0';
  }

  window.Sse = { start, stop, on };
})();
