(function (root, factory) {
  const createUnfiledDraftSync = factory();
  if (typeof module === 'object' && module.exports) module.exports = createUnfiledDraftSync;
  if (root) root.createUnfiledDraftSync = createUnfiledDraftSync;
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
  function createUnfiledDraftSync(options, mountedFetch) {
    if (mountedFetch) return createUnfiledDraftSync.mount(options, mountedFetch);
    const {
      captures, fetchJson, storage, getLogin = () => '', onState = () => {}, maxConflictRetries = 2,
      createConflictId = () => 'conflict-' + (globalThis.crypto?.randomUUID?.() || Date.now().toString(36)),
    } = options;
    const key = 'stackchain.unfiled-draft-sync.v1';
    let inFlight = null;
    let rerun = false;
    let queuedOwner = '';

    function read(login) {
      try {
        const all = JSON.parse(storage?.getItem(key) || '{}');
        const state = all?.[login];
        return {
          all:all && typeof all === 'object' ? all : {},
          known:new Set(Array.isArray(state?.known) ? state.known : []),
          deleted:new Set(Array.isArray(state?.deleted) ? state.deleted : []),
          base:state?.base && typeof state.base === 'object' ? state.base : {},
        };
      } catch (_error) {
        return {all:{}, known:new Set(), deleted:new Set(), base:{}};
      }
    }

    function write(login, state) {
      state.all[login] = {known:[...state.known], deleted:[...state.deleted], base:state.base};
      storage?.setItem(key, JSON.stringify(state.all));
    }

    function publish(status, message, details = {}) {
      onState({status, ...(message ? {message} : {}), ...details});
      const visibleStatus = globalThis.document?.querySelector?.('#my-work-action-status');
      if (message && visibleStatus) visibleStatus.textContent = message;
    }

    function sameDrafts(left, right) {
      return JSON.stringify(left) === JSON.stringify(right);
    }

    async function fingerprint(draft) {
      if (!draft) return null;
      const text = JSON.stringify(draft);
      if (globalThis.crypto?.subtle && globalThis.TextEncoder) {
        const digest = await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(text));
        return [...new Uint8Array(digest)].map(byte => byte.toString(16).padStart(2, '0')).join('');
      }
      let first = 2166136261, second = 3335557771;
      for (let index = 0; index < text.length; index += 1) {
        first = Math.imul(first ^ text.charCodeAt(index), 16777619);
        second = Math.imul(second ^ text.charCodeAt(index), 2246822519);
      }
      return text.length + ':' + (first >>> 0).toString(16) + (second >>> 0).toString(16);
    }

    async function reconcile(captures, drafts, owner) {
      if (typeof captures.reconcileRemote === 'function') {
        await captures.reconcileRemote(drafts, owner);
        return;
      }
      const finalIds = new Set(drafts.map(item => item.id));
      for (const item of await captures.exportOwned(owner)) {
        if (!finalIds.has(item.id)) await captures.discard(item.id);
      }
      await captures.mergeRemote(drafts, owner);
    }

    async function runSync(login) {
      const owner = String(login || '').trim();
      if (!owner) return false;
      publish('syncing', 'Syncing Drafts…');
      const state = read(owner);
      try {
        const remote = await fetchJson('api/v1/unfiled-drafts');
        const remoteDrafts = Array.isArray(remote?.drafts) ? remote.drafts : [];
        const local = await captures.exportOwned(owner);
        const merged = [];
        const localById = new Map(local.map(item => [item.id, item]));
        const remoteById = new Map(remoteDrafts.map(item => [item.id, item]));
        const ids = new Set([...state.known, ...localById.keys(), ...remoteById.keys()]);
        let preservedConflict = false;
        const usedIds = new Set(ids);
        for (const id of ids) {
          const localDraft = state.deleted.has(id) ? null : localById.get(id);
          const remoteDraft = remoteById.get(id);
          const localHash = await fingerprint(localDraft);
          const remoteHash = await fingerprint(remoteDraft);
          const baseHash = state.base[id] || null;
          const localChanged = localHash !== baseHash;
          const remoteChanged = remoteHash !== baseHash;
          if (localChanged && remoteChanged && localHash !== remoteHash) {
            preservedConflict = true;
            if (remoteDraft) merged.push(remoteDraft);
            if (localDraft) {
              let conflictId = String(createConflictId()).slice(0, 100);
              for (let suffix = 2; usedIds.has(conflictId); suffix += 1) {
                conflictId = (String(createConflictId()).slice(0, 95) + '-' + suffix).slice(0, 100);
              }
              usedIds.add(conflictId);
              const suffix = ' (conflict copy)';
              merged.push({...localDraft, id:conflictId,
                title:(String(localDraft.title).slice(0, 255 - suffix.length) + suffix)});
            }
            continue;
          }
          const selected = localChanged ? localDraft : (remoteChanged ? remoteDraft : (localDraft || remoteDraft));
          if (selected) merged.push(selected);
        }
        merged.sort((left, right) => Number(right.saved_at) - Number(left.saved_at));

        let finalSnapshot = remote;
        if (!sameDrafts(merged, remoteDrafts)) {
          finalSnapshot = await fetchJson('api/v1/unfiled-drafts', {
            method:'PUT', headers:{'Content-Type':'application/json'},
            body:JSON.stringify({revision:Number(remote.revision || 0), drafts:merged}),
          });
        }
        await reconcile(captures, finalSnapshot.drafts || [], owner);
        state.known = new Set((finalSnapshot.drafts || []).map(item => item.id));
        state.deleted.clear();
        state.base = {};
        for (const item of finalSnapshot.drafts || []) state.base[item.id] = await fingerprint(item);
        write(owner, state);
        publish(preservedConflict ? 'conflict-preserved' : 'ready', preservedConflict
          ? 'Draft changed on two devices · both versions were saved.'
          : 'Drafts synced across devices.');
        return true;
      } catch (error) {
        if (error?.status !== 409) {
          publish('pending', 'Saved on this device · sync pending');
        }
        throw error;
      }
    }

    function sync(login) {
      const owner = String(login || '').trim();
      if (!owner) return Promise.resolve(false);
      if (inFlight) {
        rerun = true;
        queuedOwner = owner;
        return inFlight;
      }
      inFlight = (async () => {
        let result = false;
        let conflictRetries = 0;
        let activeOwner = owner;
        do {
          rerun = false;
          try {
            result = await runSync(activeOwner);
          } catch (error) {
            if (error?.status !== 409) throw error;
            if (conflictRetries >= maxConflictRetries) {
              publish('conflict', 'Draft sync needs attention. Retry when your devices finish saving.', {retryable:true});
              throw error;
            }
            conflictRetries += 1;
            rerun = true;
          }
          if (queuedOwner) {
            if (queuedOwner !== activeOwner) conflictRetries = 0;
            activeOwner = queuedOwner;
            queuedOwner = '';
            rerun = true;
          }
        } while (rerun);
        return result;
      })().finally(() => { inFlight = null; });
      return inFlight;
    }

    async function remove(id, login) {
      const owner = String(login || '').trim();
      if (!owner) return false;
      const state = read(owner);
      state.deleted.add(String(id));
      write(owner, state);
      await captures.discard(String(id));
      return sync(owner);
    }

    function retry() {
      return sync(getLogin());
    }

    captures.subscribe?.((id, removed) => {
      const login = getLogin();
      if (login) void (removed ? remove(id, login) : sync(login)).catch(() => {});
    });

    return {sync, remove, retry};
  }
  createUnfiledDraftSync.mount = (captures, fetchJson) => {
    const getLogin = captures.currentLogin;
    const retryButton = globalThis.document?.querySelector?.('#retry-unfiled-draft-sync');
    const controller = createUnfiledDraftSync({
      captures, fetchJson, storage:globalThis.localStorage, getLogin,
      onState:state => { if (retryButton) retryButton.hidden = !state.retryable; },
    });
    retryButton?.addEventListener?.('click', async () => {
      retryButton.disabled = true;
      try { await controller.retry(); } catch (_error) { /* state remains actionable */ }
      finally { retryButton.disabled = false; }
    });
    const connect = () => {
      const login = getLogin();
      if (login) void controller.sync(login).catch(() => {});
      else globalThis.setTimeout(connect, 250);
    };
    globalThis.setTimeout(connect, 0);
    globalThis.addEventListener?.('online', connect);
    return controller;
  };
  return createUnfiledDraftSync;
});
