(function (root, factory) {
  if (typeof module === 'object' && module.exports) module.exports = factory;
  else root.createTodayHandoff = factory;
})(typeof self !== 'undefined' ? self : this, function createTodayHandoff(options) {
  const prefix = 'stackchain.today-handoff.v1.';
  const storage = options.storage || null;
  const selected = new Set();
  const excluded = new Set();

  function key() {
    const login = String(options.getLogin?.() || '').trim().toLowerCase();
    return login ? prefix + encodeURIComponent(login) : '';
  }

  function pending() {
    const storageKey = key();
    if (!storageKey || !storage) return [];
    try {
      const value = JSON.parse(storage.getItem(storageKey) || '[]');
      return Array.isArray(value) ? value.filter(id => typeof id === 'string' && id) : [];
    } catch (_) {
      return [];
    }
  }

  function write(ids) {
    const storageKey = key();
    if (!storageKey || !storage) return false;
    const unique = [...new Set(ids.filter(id => typeof id === 'string' && id))];
    try {
      if (unique.length) storage.setItem(storageKey, JSON.stringify(unique));
      else storage.removeItem(storageKey);
      return true;
    } catch (_) {
      return false;
    }
  }

  function capture(ids) {
    const merged = [...pending()];
    (ids || []).forEach(id => {
      if (typeof id === 'string' && id && !merged.includes(id)) merged.push(id);
      if (typeof id === 'string' && id) {
        selected.add(id);
        excluded.delete(id);
      }
    });
    return write(merged);
  }

  function review(items) {
    const byIdentity = new Map((items || options.items?.() || []).map(item => [options.identity(item), item]));
    return pending().flatMap(identity => {
      const item = byIdentity.get(identity);
      if (!excluded.has(identity)) selected.add(identity);
      return item ? [{ identity, item, selected:selected.has(identity) }] : [];
    });
  }

  function choose(identity, include) {
    if (!pending().includes(identity)) return false;
    if (include) {
      selected.add(identity);
      excluded.delete(identity);
    } else {
      selected.delete(identity);
      excluded.add(identity);
    }
    return true;
  }

  function render(items, list, escapeHtml) {
    const rows = review(items);
    list.innerHTML = rows.map(row => {
      const title = String(row.item.title || row.identity).slice(0, 180);
      const context = String(row.item.key || row.item.repository || 'Work item').slice(0, 180);
      return '<div class="today-handoff-item"><span><strong>' + escapeHtml(title) + '</strong>' +
        '<span class="small muted">' + escapeHtml(context) + '</span></span><label><input type="checkbox" ' +
        'data-today-handoff="' + escapeHtml(row.identity) + '"' + (row.selected ? ' checked' : '') +
        '> Today</label></div>';
    }).join('');
    list.querySelectorAll('[data-today-handoff]').forEach(input => input.addEventListener('change', () =>
      choose(input.dataset.todayHandoff, input.checked)));
    return rows;
  }

  async function commit(items) {
    const rows = review(items);
    const remaining = pending();
    let scheduled = 0;
    let blocked = 0;
    for (const row of rows) {
      if (!selected.has(row.identity)) continue;
      if (options.todayWork.contains(row.item)) {
        const index = remaining.indexOf(row.identity);
        if (index >= 0) remaining.splice(index, 1);
        selected.delete(row.identity);
        continue;
      }
      const added = options.todayWork.add(row.item);
      if (added !== 'added') {
        blocked += 1;
        continue;
      }
      if (!options.todaySync.enqueue('add', row.identity)) {
        options.todayWork.remove?.(row.item);
        blocked += 1;
        continue;
      }
      const index = remaining.indexOf(row.identity);
      if (index >= 0) remaining.splice(index, 1);
      selected.delete(row.identity);
      scheduled += 1;
    }
    write(remaining);
    if (scheduled) await options.todaySync.flush();
    return { scheduled, blocked, remaining:remaining.length };
  }

  function mount({ qs, escapeHtml, onComplete = () => {} }) {
    const list = qs('#today-handoff-items');
    let prompted = false;
    function open() {
      if (prompted) return false;
      const rows = render(null, list, escapeHtml);
      if (!rows.length) return false;
      qs('#today-handoff-status').textContent = rows.length +
        (rows.length === 1 ? ' commitment is ready to review.' : ' commitments are ready to review.');
      const dialog = qs('#today-handoff-dialog');
      if (!dialog.open) dialog.showModal();
      prompted = true;
      requestAnimationFrame(() => (list.querySelector('input') || qs('#confirm-today-handoff')).focus());
      return true;
    }
    qs('#close-today-handoff').addEventListener('click', () => qs('#today-handoff-dialog').close());
    qs('#confirm-today-handoff').addEventListener('click', async event => {
      const button = event.currentTarget;
      button.disabled = true;
      qs('#today-handoff-status').textContent = 'Saving Today’s plan…';
      try {
        const result = await commit();
        if (result.blocked) qs('#today-handoff-status').textContent =
          'Today is full. ' + result.remaining + ' commitment remains safe in My Work.';
        else qs('#today-handoff-dialog').close();
        onComplete(result);
      } finally {
        button.disabled = false;
      }
    });
    return { open, reset:() => { prompted = false; } };
  }

  return { capture, choose, commit, mount, pending, render, review };
});
