function createUpdateFollowUp({ storage = null, getLogin = () => '' } = {}) {
  const sourceKey = 'stackchain.update-follow-up-source.v1';
  const clean = (value, limit) => String(value || '').replace(/\s+/g, ' ').trim().slice(0, limit);
  const safeRepository = value => {
    const candidate = String(value || '');
    return /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(candidate) &&
      candidate.split('/').every(part => part !== '.' && part !== '..') ? candidate : '';
  };
  const safeUrl = value => {
    try {
      const parsed = new URL(String(value || ''));
      return parsed.protocol === 'https:' || parsed.protocol === 'http:' ? parsed.href : '';
    } catch (_error) { return ''; }
  };

  function draft(detail = {}) {
    const source = safeUrl(detail.url || detail.latest_comment?.url);
    const context = clean(detail.latest_comment?.body || detail.subject_body, 9000);
    const title = clean(detail.title, 228);
    const sections = [];
    if (source) sections.push('Source: ' + source);
    if (context) sections.push('Latest context:\n> ' + context.replace(/\n/g, '\n> '));
    return {
      repository: safeRepository(detail.repository),
      title: ('Follow up: ' + (title || 'Unread update')).slice(0, 240),
      body: sections.join('\n\n').slice(0, 9500),
    };
  }

  function readSource() {
    const login = String(getLogin() || '').trim();
    if (!login) return null;
    try {
      const record = JSON.parse(storage?.getItem(sourceKey) || 'null');
      return record?.version === 1 && record.ownerLogin === login &&
        Number.isInteger(record.source?.notification_id) && record.source.notification_id > 0 ? record : null;
    } catch (_error) { return null; }
  }

  function writeSource(record) {
    storage?.setItem(sourceKey, JSON.stringify(record));
  }

  function stageSource(item = {}) {
    const ownerLogin = String(getLogin() || '').trim();
    const notificationId = Number(item.notification_id);
    if (!ownerLogin) throw new Error('Confirm your Gitea account before continuing this follow-up.');
    if (!Number.isInteger(notificationId) || notificationId <= 0) throw new Error('Choose a valid source update.');
    const source = {
      notification_id: notificationId,
      repository: safeRepository(item.repository),
      number: Number.isInteger(item.number) && item.number > 0 ? item.number : null,
      title: clean(item.title, 240), kind: 'update', has_update: true,
    };
    writeSource({ version:1, ownerLogin, source, admission:null });
    return { ...source };
  }

  function source() {
    const record = readSource();
    return record ? { ...record.source } : null;
  }

  function discardSource() {
    if (!readSource()) return false;
    storage?.removeItem(sourceKey);
    return true;
  }

  async function complete({ admit, queueRead, advance }) {
    const record = readSource();
    if (!record) throw new Error('Reopen the source update before continuing.');
    let admission = record.admission;
    if (!admission) {
      admission = await admit();
      writeSource({ ...record, admission });
    }
    await queueRead(record.source.notification_id);
    const advanced = await advance({ ...record.source });
    storage?.removeItem(sourceKey);
    return { admission, advanced:Boolean(advanced) };
  }

  return { draft, stageSource, source, discardSource, complete };
}

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