// ExitOS · cloud sync client.
// Talks to the Worker at window.EXOS_SYNC_ENDPOINT.
// Three responsibilities:
//   1. Server-side license validation (replaces / supplements local Set)
//   2. Auto-sync portfolio state up to KV (debounced)
//   3. Pair-code dance for moving a license to a second device
//
// Designed to fail gracefully · if the Worker is unreachable, the app keeps
// working locally. Nothing here should block UI.

const SYNC_ENDPOINT = () => (typeof window !== "undefined" && window.EXOS_SYNC_ENDPOINT) || "";
const SYNC_TIMEOUT_MS = 8000;

async function syncFetch(path, init = {}) {
  const base = SYNC_ENDPOINT();
  if (!base) throw new Error("sync_disabled");
  const ctrl = new AbortController();
  const timer = setTimeout(() => ctrl.abort(), SYNC_TIMEOUT_MS);
  try {
    const res = await fetch(`${base}${path}`, { ...init, signal: ctrl.signal });
    const data = await res.json().catch(() => ({}));
    return { ok: res.ok, status: res.status, data };
  } finally {
    clearTimeout(timer);
  }
}

// ----- License -----
async function serverCheckLicense(code) {
  try {
    const { data } = await syncFetch("/v1/license/check", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ code }),
    });
    return { ok: true, valid: !!data.valid, activated: !!data.activated, reason: data.reason };
  } catch (err) {
    return { ok: false, reason: String(err?.message || err) };
  }
}

async function serverActivateLicense(code) {
  try {
    const { ok, data } = await syncFetch("/v1/license/activate", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ code }),
    });
    return { ok, first: !!data.first_activation, reason: data.reason };
  } catch (err) {
    return { ok: false, reason: String(err?.message || err) };
  }
}

// ----- State -----
async function pullState(code) {
  try {
    const { ok, data } = await syncFetch(`/v1/state/${encodeURIComponent(code)}`);
    return { ok, state: data.state || null, updated_at: data.updated_at || null };
  } catch (err) {
    return { ok: false, reason: String(err?.message || err) };
  }
}

async function pushState(code, state) {
  try {
    const { ok, data } = await syncFetch(`/v1/state/${encodeURIComponent(code)}`, {
      method: "PUT",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ state }),
    });
    return { ok, updated_at: data.updated_at };
  } catch (err) {
    return { ok: false, reason: String(err?.message || err) };
  }
}

// ----- Pairing -----
async function createPairCode(licenseCode) {
  try {
    const { ok, data } = await syncFetch("/v1/pair", {
      method: "POST",
      headers: { "x-license-code": licenseCode },
    });
    return { ok, pair: data.pair_code, expires_at: data.expires_at, reason: data.reason };
  } catch (err) {
    return { ok: false, reason: String(err?.message || err) };
  }
}

async function consumePairCode(pair) {
  try {
    const { ok, data } = await syncFetch(`/v1/pair/${encodeURIComponent(pair)}`);
    return {
      ok,
      license_code: data.license_code,
      state: data.state || null,
      updated_at: data.updated_at || null,
      reason: data.reason,
    };
  } catch (err) {
    return { ok: false, reason: String(err?.message || err) };
  }
}

// ----- Debounce helper for store -----
function debounce(fn, ms) {
  let t;
  return (...args) => {
    clearTimeout(t);
    t = setTimeout(() => fn(...args), ms);
  };
}

function syncEnabled() {
  return !!SYNC_ENDPOINT();
}

Object.assign(window, {
  serverCheckLicense, serverActivateLicense,
  pullState, pushState,
  createPairCode, consumePairCode,
  syncEnabled, syncDebounce: debounce,
});
