function createTodayWork({ storage, getLogin, limit = 5 }) {
  const prefix = 'stackchain.today-work.v1.';
  const planningPrefix = 'stackchain.today-planning.v1.';

  function identity(item) {
    if (!item) return '';
    const kind = item.is_review ? 'review' : (item.kind || 'work');
    const number = Number.isInteger(item.number) ? item.number : '';
    const notification = Number.isInteger(item.notification_id) ? item.notification_id : '';
    return [kind, item.repository || '', number, notification].join(':');
  }

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

  function storageKey() {
    return ownerKey(prefix);
  }

  function planningKey() {
    return ownerKey(planningPrefix);
  }

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

  function write(ids) {
    const key = storageKey();
    if (!key || !storage) return false;
    try {
      if (ids.length) storage.setItem(key, JSON.stringify(ids));
      else storage.removeItem(key);
      return true;
    } catch (_error) {
      return false;
    }
  }

  function planning() {
    const key = planningKey();
    if (!key || !storage) return { capacity_minutes: null, estimates: {} };
    try {
      const value = JSON.parse(storage.getItem(key) || 'null');
      const capacity = Number.isInteger(value?.capacity_minutes) && value.capacity_minutes > 0
        ? value.capacity_minutes : null;
      const ids = new Set(read());
      const estimates = Object.fromEntries(Object.entries(value?.estimates || {}).filter(([id, minutes]) =>
        ids.has(id) && Number.isInteger(minutes) && minutes > 0
      ));
      return { capacity_minutes: capacity, estimates };
    } catch (_error) {
      return { capacity_minutes: null, estimates: {} };
    }
  }

  function replacePlanning(value) {
    const key = planningKey();
    if (!key || !storage) return false;
    const ids = new Set(read());
    const capacity = Number.isInteger(value?.capacity_minutes) && value.capacity_minutes > 0
      ? value.capacity_minutes : null;
    const estimates = Object.fromEntries(Object.entries(value?.estimates || {}).filter(([id, minutes]) =>
      ids.has(id) && Number.isInteger(minutes) && minutes > 0
    ));
    try {
      storage.setItem(key, JSON.stringify({ capacity_minutes: capacity, estimates }));
      return true;
    } catch (_error) {
      return false;
    }
  }

  function add(item) {
    if (!storageKey()) return 'unavailable';
    const id = identity(item);
    if (!id) return 'unavailable';
    const ids = read();
    if (ids.includes(id)) return 'exists';
    if (ids.length >= limit) return 'full';
    ids.push(id);
    return write(ids) ? 'added' : 'unavailable';
  }

  function addMany(items) {
    if (!storageKey()) return { status: 'unavailable', ids: [] };
    const current = read();
    const additions = [];
    for (const item of items || []) {
      const id = identity(item);
      if (!id) return { status: 'unavailable', ids: [] };
      if (!current.includes(id) && !additions.includes(id)) additions.push(id);
    }
    if (!additions.length) return { status: 'exists', ids: [] };
    if (current.length + additions.length > limit) return { status: 'full', ids: [] };
    return write(current.concat(additions)) ?
      { status: 'added', ids: additions } : { status: 'unavailable', ids: [] };
  }

  function replace(ids) {
    const unique = [];
    for (const id of ids || []) {
      if (typeof id === 'string' && id && !unique.includes(id) && unique.length < limit) {
        unique.push(id);
      }
    }
    const saved = write(unique);
    if (saved && planningKey()) replacePlanning(planning());
    return saved;
  }

  function remove(item) {
    const id = identity(item);
    const ids = read();
    const next = ids.filter(candidate => candidate !== id);
    const saved = next.length !== ids.length && write(next);
    if (saved) replacePlanning(planning());
    return saved;
  }

  function capture(item) {
    const id = identity(item);
    const ids = read();
    const index = ids.indexOf(id);
    if (index < 0) return null;
    return { id, index, ids: ids.slice(), plan: planning() };
  }

  function restore(snapshot) {
    if (!snapshot || typeof snapshot.id !== 'string' || !Array.isArray(snapshot.ids)) return 'unavailable';
    const current = read();
    const expected = snapshot.ids.filter(id => id !== snapshot.id);
    if (current.includes(snapshot.id)) return 'changed';
    if (current.length >= limit) return 'full';
    if (current.length !== expected.length || current.some((id, index) => id !== expected[index])) return 'changed';
    if (!write(snapshot.ids)) return 'unavailable';
    if (!replacePlanning(snapshot.plan)) {
      write(current);
      return 'unavailable';
    }
    return 'restored';
  }

  function move(item, direction) {
    const ids = read();
    const index = ids.indexOf(identity(item));
    const target = direction === 'up' ? index - 1 : direction === 'down' ? index + 1 : -1;
    if (index < 0 || target < 0 || target >= ids.length) return false;
    [ids[index], ids[target]] = [ids[target], ids[index]];
    return write(ids);
  }

  function reconcile(items, { pruneMissing = false, onPrune } = {}) {
    const available = new Map((items || []).map(item => [identity(item), item]));
    const ids = read();
    const retained = pruneMissing ? ids.filter(id => available.has(id)) : ids;
    const retired = ids.filter(id => !retained.includes(id));
    if (retired.length) {
      const reported = onPrune?.(retired);
      if (reported !== false) {
        write(retained);
        replacePlanning(planning());
      }
    }
    return retained.flatMap(id => available.has(id) ? [available.get(id)] : []);
  }

  function contains(item) {
    return read().includes(identity(item));
  }

  function position(item) {
    const ids = read();
    const index = ids.indexOf(identity(item));
    return {
      can_up: index > 0,
      can_down: index >= 0 && index < ids.length - 1,
    };
  }

  function runway(items, currentIndex = 0) {
    const plan = planning();
    const minutes = (items || []).slice(Math.max(0, currentIndex)).map(item => plan.estimates[identity(item)] || null);
    const total = values => values.some(value => value === null) ? null :
      values.reduce((sum, value) => sum + value, 0);
    return {
      current_minutes: minutes[0] || null,
      remaining_minutes: total(minutes),
      future_minutes: total(minutes.slice(1)),
      capacity_minutes: plan.capacity_minutes,
    };
  }

  return { identity, read, replace, planning, replacePlanning, runway, add, addMany, remove, capture, restore, move, reconcile, contains, position, limit };
}

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