(function (root, factory) {
  const api = factory();
  if (typeof module !== 'undefined' && module.exports) module.exports = api;
  else root.createOfflineWorkStore = api;
})(typeof self !== 'undefined' ? self : this, function () {
  'use strict';

  const ENABLED_KEY = 'stackchain.offline-work.enabled.v1';
  const SNAPSHOT_KEY = 'stackchain.offline-work.snapshot.v1';
  const DETAILS_KEY = 'stackchain.offline-work.details.v1';
  const VERSION = 1;
  const DEFAULT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
  const ITEM_FIELDS = [
    'id', 'number', 'title', 'state', 'repository', 'labels', 'assignees',
    'updated_at', 'due_date', 'milestone', 'url', 'work_reasons',
  ];
  const NOTIFICATION_FIELDS = [
    'id', 'number', 'title', 'unread', 'repository', 'subject_type', 'updated_at', 'url',
  ];
  const DETAIL_FIELDS = [
    'title', 'body', 'state', 'labels', 'assignees', 'author', 'url', 'due_date', 'milestone',
    'id', 'repository', 'subject_type', 'subject_body', 'source_updated_at', 'head_sha', 'ci_state',
  ];
  const COMMENT_FIELDS = ['id', 'author', 'body', 'created_at', 'updated_at', 'url'];
  const REVIEW_FILE_FIELDS = [
    'filename', 'status', 'additions', 'deletions', 'diff_available', 'diff_binary', 'diff_truncated',
  ];
  const REVIEW_FIELDS = ['id', 'state', 'body', 'submitted_at'];

  function createIndexedDbTransaction(indexedDB, dbName = 'stackchain-offline-work-v2') {
    if (!indexedDB) return null;
    let databasePromise;
    function database() {
      if (!databasePromise) databasePromise = new Promise((resolve, reject) => {
        const request = indexedDB.open(dbName, 1);
        request.onupgradeneeded = () => {
          if (!request.result.objectStoreNames.contains('work')) {
            request.result.createObjectStore('work', { keyPath: 'id' });
          }
        };
        request.onsuccess = () => {
          const db = request.result;
          db.onversionchange = () => { db.close(); databasePromise = undefined; };
          resolve(db);
        };
        request.onerror = () => reject(request.error || new Error('Offline work database failed to open.'));
      });
      return databasePromise;
    }
    const requested = request => new Promise((resolve, reject) => {
      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
    return async work => {
      const db = await database();
      return new Promise((resolve, reject) => {
        const transaction = db.transaction('work', 'readwrite');
        const store = transaction.objectStore('work');
        let result;
        let workError;
        transaction.oncomplete = () => workError ? undefined : resolve(result);
        transaction.onerror = () => reject(transaction.error);
        transaction.onabort = () => reject(workError || transaction.error || new Error('Offline work transaction aborted.'));
        Promise.resolve(work({
          get: key => requested(store.get(key)),
          getAll: () => requested(store.getAll()),
          put: value => requested(store.put(value)),
          delete: key => requested(store.delete(key)),
          clear: () => requested(store.clear()),
        })).then(value => { result = value; }).catch(error => {
          workError = error;
          try { transaction.abort(); } catch (_) { reject(error); }
        });
      });
    };
  }

  function pick(source, fields) {
    const output = {};
    fields.forEach(field => {
      if (source && source[field] !== undefined) output[field] = source[field];
    });
    return output;
  }

  function createOfflineWorkStore({
    storage, indexedDB, transaction, now = () => new Date(), maxAgeMs = DEFAULT_MAX_AGE_MS,
    maxDetails = 10, initializationTimeoutMs = 1500,
  }) {
    const transact = transaction || createIndexedDbTransaction(indexedDB);
    let migrationPromise;
    let readinessPromise;
    let readinessGeneration = 0;
    let readinessState = transact ? { state:'initializing', reason:'' } : { state:'ready', reason:'' };
    let recordCache = null;
    let pendingDurableClear = false;

    function hydrateLegacyCache() {
      const cache = new Map();
      try {
        const snapshot = JSON.parse(storage.getItem(SNAPSHOT_KEY) || 'null');
        if (snapshot) cache.set('snapshot', { ...snapshot, id:'snapshot' });
        const details = JSON.parse(storage.getItem(DETAILS_KEY) || '[]');
        if (Array.isArray(details)) details.forEach(record => {
          if (!record?.key || !record?.user_login || !record?.data) return;
          const id = 'detail:' + record.user_login + ':' + record.key;
          cache.set(id, { ...record, id });
        });
      } catch (_) { cache.clear(); }
      recordCache = cache;
    }

    function migrateLegacy(generation) {
      if (!transact) return Promise.resolve(true);
      if (!migrationPromise) migrationPromise = (async () => {
        let snapshot = null;
        let details = [];
        try {
          snapshot = JSON.parse(storage.getItem(SNAPSHOT_KEY) || 'null');
          const parsedDetails = JSON.parse(storage.getItem(DETAILS_KEY) || '[]');
          details = Array.isArray(parsedDetails) ? parsedDetails : [];
        } catch (_) {
          return false;
        }
        if (!snapshot && !details.length) return true;
        await transact(async records => {
          if (snapshot && !(await records.get('snapshot'))) {
            await records.put({ ...snapshot, id:'snapshot' });
          }
          for (const record of details.slice(-Math.max(1, maxDetails))) {
            if (!record?.key || !record?.user_login || !record?.data) continue;
            const id = 'detail:' + record.user_login + ':' + record.key;
            if (!(await records.get(id))) await records.put({ ...record, id });
          }
        });
        if (generation !== readinessGeneration || readinessState.state === 'degraded') return false;
        storage.removeItem(SNAPSHOT_KEY);
        storage.removeItem(DETAILS_KEY);
        return true;
      })().catch(() => false);
      return migrationPromise;
    }

    async function initialize(generation) {
      const migrated = await migrateLegacy(generation);
      if (!migrated || generation !== readinessGeneration || readinessState.state === 'degraded') return false;
      if (pendingDurableClear) {
        await transact(async records => { await records.clear(); });
        if (generation !== readinessGeneration || readinessState.state === 'degraded') return false;
        pendingDurableClear = false;
        recordCache = new Map();
        return true;
      }
      return transact(async records => {
        const currentTime = now().getTime();
        const valid = [];
        for (const record of await records.getAll()) {
          const savedAt = Date.parse(record?.saved_at || '');
          const validShape = record?.id === 'snapshot' ?
            record.version === VERSION && record.user_login && record.data :
            record?.id?.startsWith('detail:') && record.user_login && record.key && record.data;
          if (!validShape || !Number.isFinite(savedAt) || currentTime - savedAt > maxAgeMs) {
            if (record?.id) await records.delete(record.id);
          } else valid.push(record);
        }
        if (generation !== readinessGeneration || readinessState.state === 'degraded') return false;
        recordCache = new Map(valid.map(record => [record.id, record]));
        return true;
      });
    }

    function startReady() {
      const generation = ++readinessGeneration;
      readinessState = { state:'initializing', reason:'' };
      const operation = initialize(generation).catch(() => false);
      readinessPromise = new Promise(resolve => {
        const timer = setTimeout(() => {
          if (generation !== readinessGeneration || readinessState.state !== 'initializing') return;
          hydrateLegacyCache();
          readinessState = { state:'degraded', reason:'deadline' };
          resolve(false);
        }, Math.max(1, Number(initializationTimeoutMs) || 1500));
        operation.then(available => {
          if (generation !== readinessGeneration || readinessState.state === 'degraded') return;
          clearTimeout(timer);
          if (available) readinessState = { state:'ready', reason:'' };
          else {
            hydrateLegacyCache();
            readinessState = { state:'degraded', reason:'unavailable' };
          }
          resolve(Boolean(available));
        });
      });
      return readinessPromise;
    }

    function ready() {
      if (!transact || readinessState.state === 'ready') return Promise.resolve(true);
      if (readinessState.state === 'degraded') return Promise.resolve(false);
      return readinessPromise || startReady();
    }

    function status() {
      return { ...readinessState };
    }

    function retry() {
      if (!transact) return Promise.resolve(true);
      migrationPromise = undefined;
      readinessPromise = undefined;
      return startReady();
    }
    function enabled() {
      try { return storage.getItem(ENABLED_KEY) === 'true'; }
      catch (_) { return false; }
    }

    function clear() {
      if (transact) {
        try {
          storage.removeItem(SNAPSHOT_KEY);
          storage.removeItem(DETAILS_KEY);
        } catch (_) { /* IndexedDB remains the source of truth. */ }
        if (readinessState.state === 'degraded') {
          pendingDurableClear = true;
          recordCache = new Map();
          return Promise.resolve(true);
        }
        migrationPromise = Promise.resolve(true);
        return transact(async records => {
          await records.clear();
          recordCache = new Map();
          return true;
        }).catch(() => false);
      }
      try {
        storage.removeItem(SNAPSHOT_KEY);
        storage.removeItem(DETAILS_KEY);
      }
      catch (_) { return false; }
      return true;
    }

    function detailKey(item) {
      if (item?.kind === 'update') {
        const notificationId = Number(item?.notification_id || 0);
        return Number.isInteger(notificationId) && notificationId > 0 ? 'update:' + notificationId : '';
      }
      const kind = item?.kind === 'review' ? 'review' :
        item?.kind === 'pull' ? 'pull' : item?.kind === 'issue' ? 'issue' : '';
      const repository = String(item?.repository || '');
      const number = Number(item?.number || 0);
      return kind && repository && number > 0 ? [kind, repository, number].join(':') : '';
    }

    function readDetails() {
      try {
        const value = JSON.parse(storage.getItem(DETAILS_KEY) || '[]');
        return Array.isArray(value) ? value : [];
      } catch (_) {
        try { storage.removeItem(DETAILS_KEY); } catch (_error) { /* Best effort purge. */ }
        return [];
      }
    }

    function saveDetail(login, item, detail) {
      const key = detailKey(item);
      login = String(login || '').trim();
      if (!enabled() || !login || !key || !detail) return false;
      const conversation = detail.conversation || {};
      const data = {
        ...pick(detail, DETAIL_FIELDS),
        conversation: {
          comments: (conversation.comments || []).slice(-20).map(comment => pick(comment, COMMENT_FIELDS)),
          page: Number(conversation.page || 1),
          older_page: conversation.older_page ?? null,
          total: Number(conversation.total || 0),
        },
      };
      if (item?.is_review || item?.kind === 'review') {
        data.files = (detail.files || []).slice(0, 50).map(file => ({
          ...pick(file, REVIEW_FILE_FIELDS),
          diff_lines: (file?.diff_lines || []).slice(0, 400).map(line => String(line)),
        }));
        data.reviews = (detail.reviews || []).slice(-20).map(review => ({
          ...pick(review, REVIEW_FIELDS),
          user: pick(review?.user, ['login']),
        }));
      }
      const record = {
        key,
        user_login: login,
        saved_at: now().toISOString(),
        data,
      };
      if (transact) {
        record.id = 'detail:' + login + ':' + key;
        return ready().then(available => available ? transact(async records => {
          const existing = (await records.getAll()).filter(candidate =>
            candidate?.id?.startsWith('detail:') && candidate.id !== record.id
          );
          await records.put(record);
          const overflow = existing.concat(record).sort((a, b) =>
            String(a.saved_at).localeCompare(String(b.saved_at))
          ).slice(0, -Math.max(1, maxDetails));
          for (const candidate of overflow) await records.delete(candidate.id);
          overflow.forEach(candidate => recordCache.delete(candidate.id));
          recordCache.set(record.id, record);
          return true;
        }) : false).catch(() => false);
      }
      const records = readDetails().filter(candidate =>
        !(candidate?.key === key && candidate?.user_login === login)
      );
      records.push(record);
      try { storage.setItem(DETAILS_KEY, JSON.stringify(records.slice(-Math.max(1, maxDetails)))); }
      catch (_) { return false; }
      return true;
    }

    function loadDetail(login, item) {
      const key = detailKey(item);
      login = String(login || '').trim();
      if (!login || !key) return null;
      if (transact) {
        const id = 'detail:' + login + ':' + key;
        if (!recordCache) return ready().then(() => loadDetail(login, item));
        const record = recordCache.get(id);
        if (!record) return null;
        const savedAt = Date.parse(record.saved_at || '');
        if (!record.data || !Number.isFinite(savedAt) || now().getTime() - savedAt > maxAgeMs) {
          recordCache.delete(id);
          transact(async records => { await records.delete(id); }).catch(() => {});
          return null;
        }
        return { ...record.data, saved_at: record.saved_at };
      }
      const records = readDetails();
      const currentTime = now().getTime();
      const valid = records.filter(record => {
        const savedAt = Date.parse(record?.saved_at || '');
        return record?.key && record?.user_login && record?.data && Number.isFinite(savedAt) &&
          currentTime - savedAt <= maxAgeMs;
      });
      if (valid.length !== records.length) {
        try { storage.setItem(DETAILS_KEY, JSON.stringify(valid)); } catch (_) { /* Best effort purge. */ }
      }
      const record = valid.find(candidate => candidate.key === key && candidate.user_login === login);
      return record ? { ...record.data, saved_at: record.saved_at } : null;
    }

    function setEnabled(value) {
      try {
        storage.setItem(ENABLED_KEY, value ? 'true' : 'false');
        if (!value) clear();
        return true;
      } catch (_) { return false; }
    }

    function save(snapshot) {
      if (!enabled() || !snapshot?.user?.login) return false;
      const record = {
        id: 'snapshot',
        version: VERSION,
        user_login: String(snapshot.user.login),
        saved_at: now().toISOString(),
        data: {
          user: pick(snapshot.user, ['login', 'full_name']),
          issues: (snapshot.issues || []).map(item => pick(item, ITEM_FIELDS)),
          pull_requests: (snapshot.pull_requests || []).map(item => pick(item, ITEM_FIELDS)),
          notifications: (snapshot.notifications || []).map(item => pick(item, NOTIFICATION_FIELDS)),
          work_pagination: snapshot.work_pagination || {},
          notification_pagination: snapshot.notification_pagination || {},
        },
      };
      if (transact) {
        return ready().then(available => available ? transact(async records => {
          await records.put(record);
          recordCache.set(record.id, record);
          return true;
        }) : false).catch(() => false);
      }
      try { storage.setItem(SNAPSHOT_KEY, JSON.stringify(record)); }
      catch (_) { return false; }
      return true;
    }

    function load(expectedLogin) {
      if (transact) {
        if (!recordCache) return ready().then(() => load(expectedLogin));
        const record = recordCache.get('snapshot');
        const savedAt = Date.parse(record?.saved_at || '');
        const invalid = record?.version !== VERSION || !record?.user_login || !record?.data ||
          !Number.isFinite(savedAt);
        const expired = Number.isFinite(savedAt) && now().getTime() - savedAt > maxAgeMs;
        if (invalid || expired) {
          if (record) {
            recordCache.delete('snapshot');
            transact(async records => { await records.delete('snapshot'); }).catch(() => {});
          }
          return null;
        }
        if (expectedLogin && record.user_login !== expectedLogin) return null;
        return { ...record.data, saved_at: record.saved_at };
      }
      let record;
      try { record = JSON.parse(storage.getItem(SNAPSHOT_KEY) || 'null'); }
      catch (_) { clear(); return null; }
      const savedAt = Date.parse(record?.saved_at || '');
      const invalid = record?.version !== VERSION || !record?.user_login || !record?.data ||
        !Number.isFinite(savedAt);
      const expired = Number.isFinite(savedAt) && now().getTime() - savedAt > maxAgeMs;
      if (invalid || expired) {
        clear();
        return null;
      }
      if (expectedLogin && record.user_login !== expectedLogin) return null;
      return { ...record.data, saved_at: record.saved_at };
    }

    return { enabled, setEnabled, ready, retry, status, save, load, saveDetail, loadDetail, clear };
  }

  return createOfflineWorkStore;
});
