(function (root, factory) {
  if (typeof module === 'object' && module.exports) module.exports = factory;
  else root.createUpdateTriageSession = factory;
})(typeof self !== 'undefined' ? self : this, function createUpdateTriageSession(options) {
  const key = options.key || 'stackchain.update-triage.v1';
  const identity = item => Number.isInteger(item?.notification_id) ? String(item.notification_id) : '';
  const login = () => String(options.getLogin() || '').trim();
  let state = null;
  let running = false;
  let lastOutcome = null;

  function read() {
    const owner = login();
    if (!owner) return null;
    try {
      const value = JSON.parse(options.storage.getItem(key) || 'null');
      if (![1, 2].includes(value?.version) || value.login !== owner || !Array.isArray(value.identities) ||
          !value.identities.length || typeof value.current !== 'string' || !Array.isArray(value.completed)) return null;
      const identities = value.identities.filter(value => typeof value === 'string' && value);
      const completed = value.completed.filter(value => identities.includes(value));
      const kept = Array.isArray(value.kept) ? value.kept.filter(value => identities.includes(value)) : [];
      if (!identities.length || !identities.includes(value.current)) return null;
      return { version:2, login:owner, identities, current:value.current, completed, kept };
    } catch (_) {
      return null;
    }
  }

  function persist() {
    if (!state) return;
    options.storage.setItem(key, JSON.stringify(state));
  }

  function available() {
    const byIdentity = new Map((options.getItems() || []).map(item => [identity(item), item]));
    return state.identities.filter(id => !state.completed.includes(id) && byIdentity.has(id))
      .map(id => byIdentity.get(id));
  }

  function nextAvailable() {
    if (!running || !state) return null;
    const candidates = available().filter(item => identity(item) !== state.current);
    return candidates.find(item => state.identities.indexOf(identity(item)) > state.identities.indexOf(state.current)) || candidates[0] || null;
  }

  function finish() {
    const unread = new Set((options.getItems() || []).map(identity).filter(Boolean));
    const keptIdentities = state ? state.kept.filter(id => unread.has(id)) : [];
    const reviewed = state ? state.completed.length : 0;
    const outcome = { reviewed, resolved:reviewed - keptIdentities.length, kept:keptIdentities.length, keptIdentities };
    lastOutcome = outcome;
    running = false;
    state = null;
    options.storage.removeItem(key);
    options.onFinish(outcome);
    return false;
  }

  function openCurrent(preferredIndex = null) {
    const items = available();
    if (!items.length) return finish();
    let index = items.findIndex(item => identity(item) === state.current);
    if (index < 0) index = Math.min(preferredIndex ?? 0, items.length - 1);
    state.current = identity(items[index]);
    persist();
    const originalIndex = state.identities.indexOf(state.current);
    const progress = {
      index:originalIndex + 1,
      total:state.identities.length,
    };
    if (items[index]?.update_reason) progress.reason = String(items[index].update_reason);
    options.onProgress(progress);
    options.onOpen(items[index]);
    return true;
  }

  function advance(openNext = true) {
    if (!running || !state) return false;
    const originalIndex = state.identities.indexOf(state.current);
    if (!state.completed.includes(state.current)) state.completed.push(state.current);
    const candidates = available();
    const next = candidates.find(item => state.identities.indexOf(identity(item)) > originalIndex) || candidates[0];
    if (!next) return finish();
    state.current = identity(next);
    if (openNext) return openCurrent();
    persist();
    const progress = {
      index:state.identities.indexOf(state.current) + 1,
      total:state.identities.length,
    };
    if (next?.update_reason) progress.reason = String(next.update_reason);
    options.onProgress(progress);
    return true;
  }

  return {
    active: () => running,
    resumable: () => Boolean(read()),
    start() {
      const identities = (options.getItems() || []).map(identity).filter(Boolean);
      if (!identities.length) return finish();
      state = { version:2, login:login(), identities, current:identities[0], completed:[], kept:[] };
      if (!state.login) return false;
      running = true;
      return openCurrent();
    },
    resume() {
      state = read();
      if (!state) return false;
      running = true;
      return openCurrent(state.identities.indexOf(state.current));
    },
    reconcile() {
      if (!running || !state) return false;
      return openCurrent(state.identities.indexOf(state.current));
    },
    completeAndNext: advance,
    acceptCompleted: () => advance(false),
    keepUnreadAndNext() {
      if (!running || !state) return false;
      if (!state.kept.includes(state.current)) state.kept.push(state.current);
      return advance();
    },
    reviewKept(identities = lastOutcome?.keptIdentities || []) {
      const unread = new Set((options.getItems() || []).map(identity).filter(Boolean));
      const kept = (identities || []).filter(id => unread.has(id));
      if (!kept.length) return false;
      state = { version:2, login:login(), identities:kept, current:kept[0], completed:[], kept:[] };
      if (!state.login) return false;
      running = true;
      return openCurrent();
    },
    next: nextAvailable,
    items: () => state ? available().slice() : [],
    end: finish,
  };
});
