(function(root, factory) {
  if (typeof module === 'object' && module.exports) module.exports = factory;
  else root.createPushNotifications = factory;
})(typeof self !== 'undefined' ? self : this, function createPushNotifications({
  control, status, testControl, deadlineControl, deadlineStatus, deadlineHour, deadlineDays,
  startDayControl, startDayStatus, startDayHour,
  deadlineSnooze, deadlineSnoozeStatus, deadlineSnoozeReview, onReviewDeadlines,
  notification, serviceWorker, fetchJson,
}) {
  let configuration = null;
  let pendingIntent = null;
  let recoveryPromise = null;

  function blockedReadiness() {
    return {
      state:'blocked',
      detail:'Notifications are blocked. Allow them in browser settings, then check again.',
      actionLabel:'Check again',
    };
  }

  function pendingRecoveryReadiness() {
    return notification.permission === 'granted'
      ? {state:'blocked', detail:'Notification permission changed. Check again to finish setup.', actionLabel:'Check again'}
      : blockedReadiness();
  }

  function notificationReadiness() {
    if (!configuration?.available || control?.disabled) {
      return {state:'unavailable', detail:status?.textContent || 'Update notifications are unavailable.'};
    }
    if (configuration.subscribed) {
      return {state:'complete', detail:'New update notifications are enabled.'};
    }
    if (pendingIntent === 'updates') return pendingRecoveryReadiness();
    if (notification.permission === 'denied') return blockedReadiness();
    return {state:'incomplete', detail:status?.textContent || 'New update notifications are off.'};
  }

  function renderDeliveryHealth() {
    const health = Object.values(configuration?.delivery_health || {});
    const degraded = health.find(item => item?.state === 'degraded');
    if (testControl) testControl.hidden = !configuration?.subscribed;
    if (configuration?.subscribed && degraded) {
      const count = Number(degraded.consecutive_failures || 1);
      status.textContent = `Update notifications need attention after ${count} failed ${count === 1 ? 'delivery' : 'deliveries'}. Send a test notification.`;
      return;
    }
    status.textContent = configuration?.subscribed
      ? 'New update notifications enabled for this device.'
      : 'New update notifications are off for this device.';
  }

  async function testDelivery() {
    if (!testControl) return;
    testControl.disabled = true;
    status.textContent = 'Sending a test notification…';
    try {
      await fetchJson('api/v1/push-subscription/test', {method:'POST'});
      configuration.delivery_health = {unread:{state:'healthy',consecutive_failures:0}};
      status.textContent = 'Test delivered. Update notifications are working on this device.';
    } catch (error) {
      status.textContent = 'Test delivery failed. Check your connection, then try again.';
    } finally {
      testControl.disabled = false;
    }
  }

  function formattedHour(value) {
    return `${String(Number(value)).padStart(2, '0')}:00`;
  }

  function enabledDeadlineText(hour, days) {
    const value = Number(days ?? 2);
    const horizon = value === 0 ? 'due today' : `next ${value} days`;
    return `Deadline reminders enabled for ${formattedHour(hour)} local time, ${horizon}.`;
  }

  function renderDeadlineSnooze() {
    if (!deadlineSnooze) return;
    const wakeAt = Number(configuration?.snoozed_until || 0);
    deadlineSnooze.hidden = !wakeAt;
    if (wakeAt && deadlineSnoozeStatus) {
      const localTime = new Intl.DateTimeFormat(undefined, {
        hour:'numeric', minute:'2-digit',
      }).format(new Date(wakeAt * 1000));
      deadlineSnoozeStatus.textContent = `Deadline reminders snoozed until ${localTime}.`;
    }
  }

  async function reviewSnoozedDeadlines() {
    deadlineSnoozeReview.disabled = true;
    try {
      await fetchJson('api/v1/push-subscription/deadlines/snooze', {method:'DELETE'});
      configuration.snoozed_until = null;
      renderDeadlineSnooze();
      onReviewDeadlines?.();
    } catch (error) {
      deadlineSnoozeStatus.textContent = 'Could not resume deadline reminders. Check your connection and try again.';
    } finally {
      deadlineSnoozeReview.disabled = false;
    }
  }

  function deadlineReadiness() {
    if (!configuration?.available || deadlineControl?.disabled) {
      return {state:'unavailable', detail:deadlineStatus?.textContent || 'Deadline reminders are unavailable.'};
    }
    if (pendingIntent === 'deadline') return pendingRecoveryReadiness();
    return configuration.deadline_enabled
      ? {state:'complete', detail:enabledDeadlineText(configuration.reminder_hour, configuration.reminder_days)}
      : {state:'incomplete', detail:deadlineStatus?.textContent || 'Choose when to receive deadline reminders.'};
  }

  function applicationServerKey(value) {
    const padded = value.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - value.length % 4) % 4);
    if (typeof atob === 'function') {
      return Uint8Array.from(atob(padded), character => character.charCodeAt(0));
    }
    return Uint8Array.from(Buffer.from(padded, 'base64'));
  }

  async function disable() {
    const registration = await serviceWorker.ready;
    const subscription = await registration.pushManager.getSubscription();
    await fetchJson('api/v1/push-subscription', {method:'DELETE'});
    await subscription?.unsubscribe?.();
    control.checked = false;
    if (testControl) testControl.hidden = true;
    if (deadlineControl) deadlineControl.checked = false;
    if (startDayControl) startDayControl.checked = false;
    configuration.subscribed = false;
    configuration.deadline_enabled = false;
    configuration.start_day_enabled = false;
    pendingIntent = null;
    status.textContent = 'New update notifications are off for this device.';
    if (deadlineStatus) deadlineStatus.textContent = 'Deadline reminders are off for this device.';
  }

  async function ensureSubscription() {
    const permission = notification.permission === 'granted'
      ? 'granted'
      : await notification.requestPermission();
    if (permission !== 'granted') {
      control.checked = false;
      status.textContent = blockedReadiness().detail;
      return null;
    }
    const registration = await serviceWorker.ready;
    let subscription = await registration.pushManager.getSubscription();
    if (!subscription) {
      subscription = await registration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: applicationServerKey(configuration.public_key),
      });
    }
    await fetchJson('api/v1/push-subscription', {
      method:'PUT',
      headers:{'Content-Type':'application/json'},
      body:JSON.stringify(subscription.toJSON()),
    });
    control.checked = true;
    status.textContent = 'New update notifications enabled for this device.';
    configuration.subscribed = true;
    if (testControl) testControl.hidden = false;
    return subscription;
  }

  async function enable() {
    pendingIntent = 'updates';
    const subscription = await ensureSubscription();
    if (subscription) pendingIntent = null;
    return subscription;
  }

  async function change() {
    control.disabled = true;
    try {
      if (control.checked) await enable();
      else await disable();
    } catch (error) {
      control.checked = !control.checked;
      status.textContent = 'Could not change update notifications. Check your connection and try again.';
    } finally {
      control.disabled = false;
    }
  }

  async function changeDeadline() {
    deadlineControl.disabled = true;
    if (deadlineHour) deadlineHour.disabled = true;
    if (deadlineDays) deadlineDays.disabled = true;
    try {
      const registration = await serviceWorker.ready;
      let subscription = await registration.pushManager.getSubscription();
      if (deadlineControl.checked) pendingIntent = 'deadline';
      if (deadlineControl.checked && !subscription) subscription = await ensureSubscription();
      if (deadlineControl.checked && !subscription) {
        deadlineControl.checked = false;
        deadlineStatus.textContent = status.textContent;
        return false;
      }
      const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
      const reminderHour = Number(deadlineHour?.value ?? configuration?.reminder_hour ?? 9);
      const reminderDays = Number(deadlineDays?.value ?? configuration?.reminder_days ?? 2);
      await fetchJson('api/v1/push-subscription/deadlines', {
        method:'PUT',
        headers:{'Content-Type':'application/json'},
        body:JSON.stringify({enabled:deadlineControl.checked, timezone, reminder_hour:reminderHour, reminder_days:reminderDays}),
      });
      configuration.deadline_enabled = deadlineControl.checked;
      configuration.reminder_hour = reminderHour;
      configuration.reminder_days = reminderDays;
      configuration.timezone = timezone;
      pendingIntent = null;
      deadlineStatus.textContent = deadlineControl.checked
        ? enabledDeadlineText(reminderHour, reminderDays)
        : 'Deadline reminders are off for this device.';
      return true;
    } catch (error) {
      deadlineControl.checked = !deadlineControl.checked;
      deadlineStatus.textContent = 'Could not change deadline reminders. Check your connection and try again.';
      return false;
    } finally {
      deadlineControl.disabled = false;
      if (deadlineHour) deadlineHour.disabled = false;
      if (deadlineDays) deadlineDays.disabled = false;
    }
  }

  async function changeStartDay() {
    startDayControl.disabled = true;
    if (startDayHour) startDayHour.disabled = true;
    try {
      const registration = await serviceWorker.ready;
      let subscription = await registration.pushManager.getSubscription();
      if (startDayControl.checked) pendingIntent = 'start-day';
      if (startDayControl.checked && !subscription) subscription = await ensureSubscription();
      if (startDayControl.checked && !subscription) {
        startDayControl.checked = false;
        startDayStatus.textContent = status.textContent;
        return false;
      }
      const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
      const reminderHour = Number(startDayHour?.value ?? configuration?.start_day_reminder_hour ?? 9);
      await fetchJson('api/v1/push-subscription/start-day', {
        method:'PUT',
        headers:{'Content-Type':'application/json'},
        body:JSON.stringify({enabled:startDayControl.checked, timezone, reminder_hour:reminderHour}),
      });
      configuration.start_day_enabled = startDayControl.checked;
      configuration.start_day_timezone = timezone;
      configuration.start_day_reminder_hour = reminderHour;
      pendingIntent = null;
      startDayStatus.textContent = startDayControl.checked
        ? `Start-day reminder enabled for ${formattedHour(reminderHour)} local time.`
        : 'Start-day reminders are off for this device.';
      return true;
    } catch (_error) {
      startDayControl.checked = !startDayControl.checked;
      startDayStatus.textContent = 'Could not change start-day reminders. Check your connection and try again.';
      return false;
    } finally {
      startDayControl.disabled = false;
      if (startDayHour) startDayHour.disabled = false;
    }
  }

  async function enableDeadline() {
    deadlineControl.checked = true;
    return changeDeadline();
  }

  async function recoverPermission(intent = null) {
    if (!pendingIntent && ['updates', 'deadline', 'start-day'].includes(intent)) pendingIntent = intent;
    if (!pendingIntent || notification.permission !== 'granted') return false;
    if (recoveryPromise) return recoveryPromise;
    recoveryPromise = (async () => {
      if (pendingIntent === 'deadline') {
        deadlineControl.checked = true;
        return changeDeadline();
      }
      if (pendingIntent === 'start-day') {
        startDayControl.checked = true;
        return changeStartDay();
      }
      return Boolean(await enable());
    })();
    try {
      return await recoveryPromise;
    } finally {
      recoveryPromise = null;
    }
  }

  async function init() {
    if (!control || !notification || !serviceWorker) return;
    control.addEventListener('change', change);
    testControl?.addEventListener('click', testDelivery);
    deadlineControl?.addEventListener('change', changeDeadline);
    startDayControl?.addEventListener('change', changeStartDay);
    deadlineSnoozeReview?.addEventListener('click', reviewSnoozedDeadlines);
    configuration = await fetchJson('api/v1/push-subscription');
    if (!configuration.available) {
      control.disabled = true;
      if (deadlineControl) deadlineControl.disabled = true;
      if (startDayControl) startDayControl.disabled = true;
      status.textContent = 'New update notifications are not available on this server.';
      return;
    }
    control.checked = Boolean(configuration.subscribed);
    if (deadlineControl) deadlineControl.checked = Boolean(configuration.deadline_enabled);
    if (startDayControl) startDayControl.checked = Boolean(configuration.start_day_enabled);
    if (deadlineHour) deadlineHour.value = String(configuration.reminder_hour ?? 9);
    if (deadlineDays) deadlineDays.value = String(configuration.reminder_days ?? 2);
    if (startDayHour) startDayHour.value = String(configuration.start_day_reminder_hour ?? 9);
    renderDeliveryHealth();
    if (deadlineStatus) deadlineStatus.textContent = configuration.deadline_enabled
      ? enabledDeadlineText(configuration.reminder_hour, configuration.reminder_days)
      : 'Deadline reminders are off for this device.';
    if (startDayStatus) startDayStatus.textContent = configuration.start_day_enabled
      ? `Start-day reminder enabled for ${formattedHour(configuration.start_day_reminder_hour)} local time.`
      : 'Start-day reminders are off for this device.';
    renderDeadlineSnooze();
  }

  return {init, change, changeDeadline, changeStartDay, enableDeadline, deadlineReadiness, notificationReadiness, recoverPermission};
});
