(function (root, factory) {
  if (typeof module === 'object' && module.exports) module.exports = factory;
  else root.createMobileDeviceSetup = factory;
})(typeof self !== 'undefined' ? self : this, function createMobileDeviceSetup(options) {
  const promptDismissKey = 'stackchain.device-setup-prompt-dismissed-until';
  const promptDismissMs = 7 * 24 * 60 * 60 * 1000;
  const steps = [
    ['install', options.installButton, options.installStatus, options.install],
    ['offline', options.offlineButton, options.offlineStatus, options.enableOffline],
    ['push', options.pushButton, options.pushStatus, options.enablePush],
    ['deadline', options.deadlineButton, options.deadlineStatus, options.enableDeadline],
  ];
  let trigger = options.launcher;
  let ownsDetour = false;

  function readinessCounts(readiness) {
    const available = steps.filter(([name]) => readiness[name].state !== 'unavailable').length;
    const complete = steps.filter(([name]) => readiness[name].state === 'complete').length;
    return {available, complete};
  }

  function renderPrompt(readiness) {
    if (!options.promptCard) return;
    const {available, complete} = readinessCounts(readiness);
    let dismissed = false;
    try {
      dismissed = Number(options.promptStorage?.getItem(promptDismissKey) || 0) > (options.now?.() ?? Date.now());
    } catch (_error) {}
    options.promptSummary.textContent = `${complete} of ${available} steps complete`;
    options.promptCard.hidden = dismissed || available === 0 || complete === available;
  }

  async function render() {
    const readiness = await options.getReadiness();
    let available = 0;
    let complete = 0;
    steps.forEach(([name, button, status]) => {
      const step = readiness[name];
      status.textContent = step.detail;
      button.hidden = step.state === 'complete' || step.state === 'unavailable';
      button.disabled = step.state === 'pending';
      if (step.state !== 'unavailable') available += 1;
      if (step.state === 'complete') complete += 1;
    });
    options.readyStatus.textContent = complete === available
      ? 'This device is ready.'
      : `${complete} of ${available} available steps ready.`;
    renderPrompt(readiness);
    return readiness;
  }

  async function open(event) {
    trigger = event?.currentTarget || options.launcher;
    if (options.isMobile?.()) {
      ownsDetour = options.timerView?.beginDetour?.('device-setup')?.reason === 'device-setup';
    }
    await render();
    options.sheet.hidden = false;
    options.closeButton.focus();
  }

  function close(resume = true) {
    options.sheet.hidden = true;
    if (resume && ownsDetour) options.timerView?.finishDetour?.();
    ownsDetour = false;
    trigger?.focus?.();
  }

  async function start() {
    options.launcher.addEventListener('click', open);
    options.promptLauncher?.addEventListener('click', open);
    options.promptDismiss?.addEventListener('click', () => {
      try {
        options.promptStorage?.setItem(promptDismissKey, String((options.now?.() ?? Date.now()) + promptDismissMs));
      } catch (_error) {}
      options.promptCard.hidden = true;
    });
    options.closeButton.addEventListener('click', close);
    options.returnButton?.addEventListener('click', () => close(false));
    options.sheet.addEventListener('click', event => {
      if (event.target === options.sheet) close();
    });
    options.escapeTarget.addEventListener('keydown', event => {
      if (event.key === 'Escape' && !options.sheet.hidden) close();
    });
    steps.forEach(([_name, button, _status, action]) => {
      button.addEventListener('click', async () => {
        button.disabled = true;
        try { await action(); }
        finally { await render(); }
      });
    });
    renderPrompt(await options.getReadiness());
  }

  return {start, open, close, render};
});

if (typeof module === 'object' && module.exports) {
  module.exports.mount = mountMobileDeviceSetup;
} else {
  self.createMobileDeviceSetup.mount = mountMobileDeviceSetup;
}

function mountMobileDeviceSetup(options) {
  const qs = selector => options.document.querySelector(selector);
  return createMobileDeviceSetup({
    launcher:qs('#open-device-setup'), closeButton:qs('#close-device-setup'),
    sheet:qs('#device-setup-sheet'), installButton:qs('#device-setup-install'),
    offlineButton:qs('#device-setup-offline'), pushButton:qs('#device-setup-push'),
    deadlineButton:qs('#device-setup-deadline'),
    installStatus:qs('#device-setup-install-status'), offlineStatus:qs('#device-setup-offline-status'),
    pushStatus:qs('#device-setup-push-status'), deadlineStatus:qs('#device-setup-deadline-status'),
    readyStatus:qs('#device-setup-ready-status'),
    promptCard:qs('#device-readiness-card'), promptSummary:qs('#device-readiness-summary'),
    promptLauncher:qs('#finish-device-setup'), promptDismiss:qs('#dismiss-device-readiness'),
    promptStorage:options.promptStorage,
    returnButton:qs('#return-from-device-setup'),
    timerView:options.timerView,
    isMobile:options.isMobile || (() => innerWidth <= 600),
    escapeTarget:options.document,
    getReadiness:() => ({
      install:options.installApp.state(),
      offline:!options.offlineAvailable()
        ? {state:'unavailable', detail:'Offline saving is unavailable in this browser.'}
        : options.offlineEnabled()
          ? {state:'complete', detail:qs('#offline-work-status').textContent || 'Offline work is saved.'}
          : {state:'incomplete', detail:'Private My Work and Today data are not saved offline.'},
      push:qs('#push-updates').disabled
        ? {state:'unavailable', detail:qs('#push-update-status').textContent || 'Update notifications are unavailable.'}
        : qs('#push-updates').checked
          ? {state:'complete', detail:'New update notifications are enabled.'}
          : {state:'incomplete', detail:qs('#push-update-status').textContent || 'New update notifications are off.'},
      deadline:options.deadlineReadiness(),
    }),
    install:() => options.installApp.install(),
    enableOffline:options.enableOffline,
    enablePush:options.enablePush,
    enableDeadline:options.enableDeadline,
  });
}
