(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, deadlineControl, deadlineStatus, deadlineHour, deadlineDays, notification, serviceWorker, fetchJson,
}) {
  let configuration = null;

  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 deadlineReadiness() {
    if (!configuration?.available || deadlineControl?.disabled) {
      return {state:'unavailable', detail:deadlineStatus?.textContent || 'Deadline reminders are unavailable.'};
    }
    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 (deadlineControl) deadlineControl.checked = false;
    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 = await notification.requestPermission();
    if (permission !== 'granted') {
      control.checked = false;
      status.textContent = 'Notifications are blocked. Allow them in your browser settings to enable updates.';
      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;
    return subscription;
  }

  async function enable() {
    await ensureSubscription();
  }

  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 && !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;
      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 enableDeadline() {
    deadlineControl.checked = true;
    return changeDeadline();
  }

  async function init() {
    if (!control || !notification || !serviceWorker) return;
    control.addEventListener('change', change);
    deadlineControl?.addEventListener('change', changeDeadline);
    configuration = await fetchJson('api/v1/push-subscription');
    if (!configuration.available) {
      control.disabled = true;
      if (deadlineControl) deadlineControl.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 (deadlineHour) deadlineHour.value = String(configuration.reminder_hour ?? 9);
    if (deadlineDays) deadlineDays.value = String(configuration.reminder_days ?? 2);
    status.textContent = configuration.subscribed
      ? 'New update notifications enabled for this device.'
      : 'New update notifications are off for this device.';
    if (deadlineStatus) deadlineStatus.textContent = configuration.deadline_enabled
      ? enabledDeadlineText(configuration.reminder_hour, configuration.reminder_days)
      : 'Deadline reminders are off for this device.';
  }

  return {init, change, changeDeadline, enableDeadline, deadlineReadiness};
});
