function createTodaySessionSync({
  fetchJson, getDeviceId, timer, onRemote = () => {}, onTransferred = () => {}, onStatus = () => {},
  setInterval = globalThis.setInterval, clearInterval = globalThis.clearInterval,
}) {
  let current = null;
  let ownedRevision = 0;
  let pollTimer = null;
  let requestTail = Promise.resolve();
  let latestSnapshot = null;
  let publishRequested = 0;
  let publishSent = 0;
  const endpoint = 'api/v1/today/session';
  const deviceId = () => String(getDeviceId?.() || '').trim();

  function serialize(operation) {
    const result = requestTail.then(operation, operation);
    requestTail = result.catch(() => null);
    return result;
  }

  function adopt(session) {
    if (!session || !Number.isInteger(session.revision)) return null;
    const previousOwned = current?.device_id === deviceId() &&
      (current?.running || Number.isFinite(current?.break_deadline_at));
    current = session;
    if (session.device_id === deviceId()) {
      ownedRevision = session.revision;
      onRemote(null);
    } else if ((session.running || Number.isFinite(session.break_deadline_at)) && session.identity) {
      if (previousOwned) {
        timer?.pause?.();
        onTransferred(session);
      }
      onRemote(session);
    } else {
      onRemote(null);
    }
    return session;
  }

  async function refreshNow() {
    try {
      onStatus('syncing');
      const session = await fetchJson(endpoint);
      onStatus('online');
      return adopt(session);
    } catch (error) {
      onStatus('offline', error);
      return null;
    }
  }

  const refresh = () => serialize(refreshNow);

  function claim() {
    return serialize(async () => {
      if ((!current?.running && !Number.isFinite(current?.break_deadline_at)) ||
          !current.identity || current.device_id === deviceId() || !deviceId()) return null;
      try {
        onStatus('syncing');
        const body = {
          base_revision:current.revision, device_id:deviceId(), identity:current.identity,
          elapsed_ms:current.elapsed_ms, running:true,
        };
        if (Object.hasOwn(current, 'break_deadline_at')) body.break_deadline_at = null;
        const session = await fetchJson(endpoint, {
          method:'PATCH', headers:{'Content-Type':'application/json'},
          body:JSON.stringify(body),
        });
        adopt(session);
        timer?.adopt?.(session.identity, session.elapsed_ms, session.running);
        return session;
      } catch (error) {
        if (error?.status === 409 || error?.code === 'session_changed') {
          onStatus('conflict', error);
          await refreshNow();
        } else {
          onStatus('offline', error);
        }
        return null;
      }
    });
  }

  async function publishNow(snapshot) {
    if (!snapshot?.identity || !deviceId()) return null;
    try {
      onStatus('syncing');
      const body = {
        base_revision:ownedRevision, device_id:deviceId(), identity:snapshot.identity,
        elapsed_ms:Math.max(0, Math.floor(Number(snapshot.elapsed_ms) || 0)),
        running:Boolean(snapshot.running),
      };
      if (Object.hasOwn(snapshot, 'break_deadline_at') || Object.hasOwn(current || {}, 'break_deadline_at')) {
        body.break_deadline_at = Number.isFinite(snapshot.break_deadline_at) ?
          Math.floor(snapshot.break_deadline_at) : null;
      }
      const session = await fetchJson(endpoint, {
        method:'PATCH', headers:{'Content-Type':'application/json'},
        body:JSON.stringify(body),
      });
      adopt(session);
      return session;
    } catch (error) {
      if (error?.status === 409 || error?.code === 'session_changed') {
        onStatus('conflict', error);
        await refreshNow();
      } else {
        onStatus('offline', error);
      }
      return null;
    }
  }

  function publish(snapshot = timer?.snapshot?.()) {
    snapshot = snapshot?.identity ? snapshot : latestSnapshot;
    if (!snapshot?.identity || !deviceId()) return Promise.resolve(null);
    latestSnapshot = snapshot;
    publishRequested += 1;
    return serialize(async () => {
      if (publishSent === publishRequested) return current;
      if (current?.device_id && current.device_id !== deviceId()) {
        publishSent = publishRequested;
        return current;
      }
      const version = publishRequested;
      const result = await publishNow(latestSnapshot);
      publishSent = current?.device_id && current.device_id !== deviceId()
        ? publishRequested
        : version;
      return result;
    });
  }

  const pulse = () => current?.device_id === deviceId() && current?.running
    ? publish()
    : refresh();

  return {
    refresh, claim, publish,
    session:() => current,
    start(intervalMs = 15000) {
      if (pollTimer !== null) return false;
      pulse();
      pollTimer = setInterval?.(pulse, intervalMs);
      pollTimer?.unref?.();
      return true;
    },
    stop() {
      if (pollTimer === null) return false;
      clearInterval?.(pollTimer);
      pollTimer = null;
      return true;
    },
  };
}

function todaySessionHandoffSummary(session, item) {
  if (Number.isFinite(session?.break_deadline_at)) {
    const end = new Date(session.break_deadline_at).toLocaleTimeString([], {
      hour:'numeric', minute:'2-digit',
    });
    return `On break until ${end} · ready to resume here`;
  }
  const minutes = Math.max(0, Math.floor(Number(session?.elapsed_ms || 0) / 60000));
  return `${item?.title || 'Current Today item'} · ${minutes} min elapsed`;
}

function attachTodaySessionHandoff({
  fetchJson, storage, timer, qs, items, identity, selectToday, startItem, announce, renderTimer,
}) {
  const deviceKey = 'stackchain.today-session-device.v1';
  const getDeviceId = () => {
    let value = storage.getItem(deviceKey);
    if (!value) {
      value = globalThis.crypto?.randomUUID?.() || Math.random().toString(36).slice(2);
      storage.setItem(deviceKey, value);
    }
    return value;
  };
  let offered = null;
  const sessionStatus = qs('#today-session-sync-status');
  const showSessionStatus = message => {
    sessionStatus.textContent = message;
    sessionStatus.hidden = !message;
  };
  const sync = createTodaySessionSync({
    fetchJson, getDeviceId, timer,
    onStatus:state => showSessionStatus(
      state === 'syncing' ? 'Session syncing…' :
        (state === 'offline' ? 'Session offline · will retry.' : '')
    ),
    onRemote:session => {
      offered = session;
      const handoff = qs('#today-session-handoff');
      handoff.hidden = !session;
      if (!session) return;
      const item = items().find(entry => identity(entry) === session.identity);
      qs('#today-session-handoff-summary').textContent = todaySessionHandoffSummary(session, item);
      qs('#continue-today-session').textContent = Number.isFinite(session.break_deadline_at) ?
        'Resume Today here' : 'Continue here';
    },
    onTransferred:() => {
      showSessionStatus('Session continued on another device.');
      announce('Today continued on another device. Timer paused here.');
      renderTimer();
    },
  });
  qs('#continue-today-session').addEventListener('click', async () => {
    const target = offered;
    const claimed = await sync.claim();
    if (!claimed || !target) return;
    selectToday();
    const item = items().find(entry => identity(entry) === claimed.identity);
    if (item) startItem(item);
    else announce('Today session moved here; refresh work to open its item.');
    renderTimer();
  });
  sync.start(5000);
  return sync;
}

if (typeof module !== 'undefined' && module.exports) module.exports = {
  createTodaySessionSync, attachTodaySessionHandoff, todaySessionHandoffSummary,
};
