(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, 'Install'],
    ['offline', options.offlineButton, options.offlineStatus, options.enableOffline, 'Enable'],
    ['protection', options.protectionButton, options.protectionStatus, options.protectStorage, 'Protect'],
    ['push', options.pushButton, options.pushStatus, options.enablePush, 'Enable'],
    ['deadline', options.deadlineButton, options.deadlineStatus, options.enableDeadline, 'Enable'],
  ];
  let trigger = options.launcher;
  let ownsDetour = false;
  let backgroundInert = null;
  let pendingResume = true;

  function focusableControls() {
    return [...options.sheet.querySelectorAll(
      'button:not([disabled]), select:not([disabled]), input:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
    )].filter(control => !control.hidden);
  }

  function containBackground() {
    if (backgroundInert) return;
    backgroundInert = new Map((options.backgroundElements || []).map(element => [element, element.inert]));
    backgroundInert.forEach((_wasInert, element) => { element.inert = true; });
  }

  function releaseBackground() {
    if (!backgroundInert) return;
    backgroundInert.forEach((wasInert, element) => { element.inert = wasInert; });
    backgroundInert = null;
  }

  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, _action, defaultActionLabel]) => {
      const step = readiness[name];
      status.textContent = step.detail;
      button.textContent = step.actionLabel || defaultActionLabel;
      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();
    containBackground();
    options.sheet.hidden = false;
    if (options.history && !options.history.state?.deviceSetup) {
      options.history.pushState({...options.history.state, deviceSetup:true}, '');
    }
    options.closeButton.focus();
  }

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

  function close(resume = true) {
    if (options.history?.state?.deviceSetup) {
      pendingResume = resume;
      options.history.back();
      return;
    }
    finishClose(resume);
  }

  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) {
        event.preventDefault?.();
        close();
      }
      if (event.key !== 'Tab' || options.sheet.hidden) return;
      const controls = focusableControls();
      if (!controls.length) return;
      const first = controls[0];
      const last = controls[controls.length - 1];
      if (event.shiftKey && event.target === first) {
        event.preventDefault();
        last.focus();
      } else if (!event.shiftKey && event.target === last) {
        event.preventDefault();
        first.focus();
      }
    });
    options.historyTarget?.addEventListener('popstate', event => {
      if (!options.sheet.hidden && !event.state?.deviceSetup) {
        finishClose(pendingResume);
        pendingResume = true;
      }
    });
    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);
  const window = options.window || options.document.defaultView;
  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'),
    protectionButton:qs('#device-setup-protection'),
    deadlineButton:qs('#device-setup-deadline'),
    installStatus:qs('#device-setup-install-status'), offlineStatus:qs('#device-setup-offline-status'),
    protectionStatus:qs('#device-setup-protection-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,
    history:window?.history,
    historyTarget:window,
    backgroundElements:[
      qs('header'), qs('main'), qs('#mobile-task-dock'),
    ].filter(Boolean),
    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.'},
      protection:options.storageProtectionReadiness(),
      push:options.notificationReadiness(),
      deadline:options.deadlineReadiness(),
    }),
    install:() => options.installApp.install(),
    enableOffline:options.enableOffline,
    protectStorage:options.protectStorage,
    enablePush:options.enablePush,
    enableDeadline:options.enableDeadline,
  });
}
