function createReleaseReceipt({ storage, getLogin, fetchJson, launcher = null, dialog = null, statusNode = null, checksNode = null, releaseNode = null, listNode = null, documentRef = null, windowRef = null, setTimer = setTimeout, clearTimer = clearTimeout, pollMs = 30000 }) {
  const prefix = 'stackchain.release-receipt.v1:';
  const limit = 12;
  let entries = [];
  let refreshing = null;
  let timer = null;
  let bound = false;
  documentRef ||= typeof document !== 'undefined' ? document : null;
  windowRef ||= typeof window !== 'undefined' ? window : null;

  const login = () => String(getLogin?.() || '').trim().toLowerCase();
  const key = () => prefix + login();
  const identity = value => String(value.repository || '') + '@' + String(value.commit_sha || '');
  const path = value => 'api/v1/repos/' + String(value.repository || '').split('/')
    .map(encodeURIComponent).join('/') + '/release-receipt/' + encodeURIComponent(value.commit_sha);

  function valid(value, account) {
    return value && value.account === account && value.repository && value.commit_sha;
  }

  function persist() {
    if (!entries.length) storage?.removeItem(key());
    else storage?.setItem(key(), JSON.stringify({ version: 2, account: login(), entries }));
  }

  const hasPending = () => entries.some(entry => !entry.status?.release && entry.status?.label !== 'Checks failed');

  function schedule(delay = pollMs) {
    if (!bound || documentRef?.hidden || !hasPending()) return;
    if (timer !== null) clearTimer(timer);
    timer = setTimer(async () => {
      timer = null;
      try { await refresh(); }
      catch (error) { if (statusNode) statusNode.textContent = error.message + ' Retry when connected.'; }
      schedule();
    }, delay);
  }

  function restore() {
    const account = login();
    if (!account) return [];
    try {
      const parsed = JSON.parse(storage?.getItem(prefix + account) || 'null');
      const stored = parsed?.version === 2 && parsed.account === account
        ? parsed.entries : (valid(parsed, account) ? [parsed] : []);
      entries = Array.isArray(stored) ? stored.filter(value => valid(value, account)).slice(-limit) : [];
      if (entries.length && parsed?.version !== 2) persist();
    } catch (_error) { entries = []; }
    render();
    schedule();
    return entries.map(value => ({ ...value }));
  }

  function capture(item, mergeResult) {
    const account = login();
    const commitSha = String(mergeResult?.merge_commit_sha || '').trim();
    if (!account || !item?.repository || !commitSha) throw new Error('The exact merge commit is unavailable.');
    if (!entries.length) restore();
    const entry = {
      account,
      repository: item.repository,
      number: Number(item.number),
      key: item.key || item.repository + '#' + item.number,
      commit_sha: commitSha,
      status: null,
      captured_at: new Date().toISOString(),
    };
    const existing = entries.findIndex(value => identity(value) === identity(entry));
    if (existing >= 0) {
      entry.status = entries[existing].status || null;
      entries.splice(existing, 1, entry);
    } else {
      entries.push(entry);
      entries = entries.slice(-limit);
    }
    persist();
    render();
    schedule();
    return { ...entry };
  }

  function summarize(payload) {
    const checks = Array.isArray(payload?.checks) ? payload.checks : [];
    const failing = checks.filter(check => ['failure', 'error'].includes(check.state)).map(check => check.name).filter(Boolean);
    const pending = checks.filter(check => check.state === 'pending').map(check => check.name).filter(Boolean);
    if (payload?.release) return { ...payload, label: 'Released · ' + payload.release.tag, checks: [] };
    if (failing.length) return { ...payload, label: 'Checks failed', checks: failing };
    if (payload?.ci_state === 'success') return { ...payload, label: 'Checks passed · waiting for release', checks: [] };
    return { ...payload, label: 'Checks running', checks: pending };
  }

  function render() {
    const visible = entries.map((entry, index) => ({ entry, index }))
      .sort((a, b) => Number(b.entry.status?.label === 'Checks failed') - Number(a.entry.status?.label === 'Checks failed') || a.index - b.index)
      .map(value => value.entry);
    const first = visible[0] || null;
    const failed = visible.filter(entry => entry.status?.label === 'Checks failed').length;
    if (launcher) {
      launcher.hidden = !entries.length;
      launcher.textContent = failed
        ? failed + ' release ' + (failed === 1 ? 'failure' : 'failures') + ' · ' + visible.length + ' tracked'
        : visible.length + ' ' + (visible.length === 1 ? 'merge' : 'merges') + ' · tracking release';
    }
    if (statusNode) statusNode.textContent = first?.status?.label || 'Checking the exact merge commit…';
    if (checksNode) checksNode.textContent = (first?.status?.checks || []).join(', ');
    if (releaseNode) {
      releaseNode.hidden = !first?.status?.release?.url;
      if (first?.status?.release?.url) {
        releaseNode.href = first.status.release.url;
        releaseNode.textContent = 'Open release ' + first.status.release.tag;
      }
    }
    if (listNode) {
      const rows = visible.map(entry => {
        const row = document.createElement('article');
        row.className = 'release-watchlist-item';
        const copy = document.createElement('div');
        const title = document.createElement('strong');
        title.textContent = entry.key;
        const state = document.createElement('span');
        state.className = 'small';
        state.textContent = entry.status?.label || 'Checking the exact merge commit…';
        copy.append(title, state);
        if (entry.status?.release?.url) {
          const link = document.createElement('a');
          link.href = entry.status.release.url;
          link.textContent = 'Open release ' + entry.status.release.tag;
          copy.append(link);
        }
        const button = document.createElement('button');
        button.type = 'button';
        button.textContent = 'Dismiss';
        button.setAttribute('aria-label', 'Dismiss ' + entry.key + ' from release tracking');
        button.addEventListener('click', () => dismiss(entry.repository, entry.commit_sha));
        row.append(copy, button);
        return row;
      });
      listNode.replaceChildren(...rows);
    }

  }

  async function refreshEntry(entry) {
    const payload = await fetchJson(path(entry), { headers: { Accept: 'application/json' } });
    if (payload?.commit_sha !== entry.commit_sha) throw new Error('Release evidence did not match the merged commit.');
    entry.status = summarize(payload);
    return entry.status;
  }

  async function refresh() {
    if (refreshing) return refreshing;
    if (!entries.length) restore();
    if (!entries.length) return [];
    refreshing = (async () => {
      const statuses = [];
      for (const entry of entries) {
        try { statuses.push(await refreshEntry(entry)); }
        catch (error) {
          entry.status = { label: 'Status unavailable', checks: [], error: String(error?.message || error) };
          statuses.push(entry.status);
        }
      }
      persist();
      render();
      return statuses;
    })();
    try { return await refreshing; }
    finally { refreshing = null; }
  }

  function dismiss(repository, commitSha) {
    if (repository && commitSha) entries = entries.filter(entry => identity(entry) !== repository + '@' + commitSha);
    else entries = [];
    persist();
    render();
    schedule();
    if (!entries.length && dialog?.open) dialog.close();
    return entries.map(value => ({ ...value }));
  }

  function bind() {
    bound = true;
    launcher?.addEventListener('click', async () => {
      dialog?.showModal?.();
      try { await refresh(); }
      catch (error) { if (statusNode) statusNode.textContent = error.message + ' Retry when connected.'; }
    });
    documentRef?.addEventListener('visibilitychange', () => {
      if (documentRef.hidden) {
        if (timer !== null) clearTimer(timer);
        timer = null;
      } else schedule(0);
    });
    windowRef?.addEventListener('online', () => schedule(0));
    schedule();
  }

  return { capture, restore, refresh, dismiss, bind, fetchJson };
}

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