function createNotificationReadOutbox({
  storage, fetchJson, backgroundSync, coordinator, getOwnerLogin = () => '', now = () => Date.now(), maxItems = 100,
}) {
  const storageKey = 'stackchain.notification-read-outbox.v1';

  function read() {
    try {
      const record = JSON.parse(storage?.getItem(storageKey) || 'null');
      if (record?.version !== 1 || !Array.isArray(record.items)) return [];
      return record.items.filter(item => item?.kind === 'notification-read' &&
        Number.isInteger(item.notificationId) && item.notificationId > 0 && item.ownerLogin);
    } catch (_error) { return []; }
  }

  function write(items, mirror = true) {
    storage?.setItem(storageKey, JSON.stringify({ version: 1, items }));
    coordinator?.notify('notification-read');
    if (mirror && backgroundSync?.reconcile) {
      Promise.resolve(backgroundSync.reconcile(items, 'notification-read'))
        .then(() => items.length ? backgroundSync.requestSync?.() : undefined)
        .catch(() => { /* Foreground reconnect remains available. */ });
    }
  }

  function itemId(ownerLogin, notificationId) {
    return 'notification-read:' + ownerLogin + ':' + notificationId;
  }

  async function enqueueDurably(notificationId) {
    notificationId = Number(notificationId);
    const ownerLogin = String(getOwnerLogin() || '').trim();
    if (!ownerLogin) throw new Error('Confirm your Gitea account before queueing this update.');
    if (!Number.isInteger(notificationId) || notificationId <= 0) throw new Error('Choose a valid update.');
    const items = read();
    const id = itemId(ownerLogin, notificationId);
    let item = items.find(candidate => candidate.id === id);
    if (!item) {
      if (items.length >= maxItems) throw new Error('Update acknowledgement queue is full. Reconnect before clearing more updates.');
      item = {
        id, kind: 'notification-read', notificationId, ownerLogin,
        status: 'queued', queuedAt: Number(now()),
      };
      items.push(item);
      write(items, false);
    }
    if (!backgroundSync?.reconcile || !backgroundSync?.requestSync) {
      return { item: { ...item }, background: false, durability: 'foreground-only' };
    }
    try {
      await backgroundSync.reconcile(read(), 'notification-read');
      await backgroundSync.requestSync();
      return { item: { ...item }, background: true, durability: 'background' };
    } catch (error) {
      return { item: { ...item }, background: false, durability: 'foreground-only', error };
    }
  }

  function suppress(items, login = getOwnerLogin()) {
    const pending = new Set(read().filter(item => item.ownerLogin === String(login || '').trim())
      .map(item => item.notificationId));
    return (items || []).filter(item => !pending.has(Number(item?.notification_id ?? item?.id)));
  }

  async function send(item, currentLogin) {
    if (!currentLogin || item.ownerLogin !== currentLogin) return { blocked: true };
    if (backgroundSync?.send) return backgroundSync.send(item, currentLogin);
    await fetchJson('api/v1/notifications/' + encodeURIComponent(item.notificationId) + '/read', {
      method: 'PATCH', headers: { Accept: 'application/json' },
    });
    return { confirmed: true };
  }

  async function flush(currentLogin) {
    currentLogin = String(currentLogin || '').trim();
    const confirmed = [];
    let items = read();
    for (const item of items) {
      if (item.ownerLogin !== currentLogin || item.status === 'attention') continue;
      try {
        const result = await send(item, currentLogin);
        if (result?.blocked || result?.busy) continue;
        if (result?.attention) {
          items = items.map(candidate => candidate.id === item.id ? {
            ...candidate, status: 'attention', error: String(result.error?.message || 'Update needs attention').slice(0, 240),
          } : candidate);
          write(items);
          continue;
        }
        confirmed.push(item.notificationId);
        items = items.filter(candidate => candidate.id !== item.id);
        write(items);
      } catch (error) {
        const status = Number(error?.status || 0);
        if (status >= 400 && status < 500) {
          items = items.map(candidate => candidate.id === item.id ? {
            ...candidate, status: 'attention', error: String(error?.message || 'Update needs attention').slice(0, 240),
          } : candidate);
          write(items);
          continue;
        }
        break;
      }
    }
    return { confirmed, remaining: read() };
  }

  function reconcileBackground(records) {
    const states = new Map((records || []).filter(item => item?.kind === 'notification-read')
      .map(item => [item.id, item]));
    const items = read().flatMap(item => {
      const state = states.get(item.id);
      if (state?.status === 'sent') return [];
      if (state?.status === 'attention') return [{
        ...item, status: 'attention', error: String(state.error || 'Update needs attention').slice(0, 240),
      }];
      return [item];
    });
    write(items);
    return items;
  }

  return { enqueueDurably, flush, suppress, reconcileBackground, list: () => read().map(item => ({ ...item })) };
}

if (typeof module !== 'undefined' && module.exports) module.exports = createNotificationReadOutbox;
