/**
 * Session expiry warning dialog.
 *
 * Reads the idle timeout from the <html> element's data-session-idle-seconds
 * attribute and shows a modal dialog ~2 minutes before the session expires.
 * The user can extend the session (POST /admin/session-ping/data) or log out.
 *
 * Timer resets on user interaction (click, keypress, scroll) to stay in sync
 * with the server-side idle timer that is refreshed on every page request.
 */

const WARNING_LEAD_SECONDS = 120;
const TICK_INTERVAL_MS = 1000;
const MIN_IDLE_TIMEOUT = 180; // Don't show warning if timeout < 3 min

const INTERACTION_EVENTS = ['click', 'keydown', 'scroll', 'mousemove', 'touchstart'];
const INTERACTION_THROTTLE_MS = 30_000; // Only reset every 30s to limit overhead

/**
 * Read configuration from DOM.
 * @returns {{idleSeconds: number, pingUrl: string, logoutUrl: string, csrfKey: string, csrfToken: string}|null}
 */
const readConfig = () => {
  const root = document.documentElement;
  const idleSeconds = parseInt(root.dataset.sessionIdleSeconds || '', 10);
  if (!idleSeconds || idleSeconds < MIN_IDLE_TIMEOUT) {
    return null;
  }

  const pingUrl = root.dataset.sessionPingUrl || '';
  const logoutUrl = root.dataset.sessionLogoutUrl || '';
  const csrfKey = root.dataset.csrfKey || '';
  const csrfToken = root.dataset.csrfToken || '';

  if (!pingUrl || !logoutUrl || !csrfKey || !csrfToken) {
    return null;
  }

  return { idleSeconds, pingUrl, logoutUrl, csrfKey, csrfToken };
};

/**
 * Resolve the session warning dialog and its elements.
 * @returns {object|null}
 */
const resolveDialog = () => {
  const dialog = document.querySelector('[data-app-session-warning-dialog]');
  if (!(dialog instanceof HTMLDialogElement)) {
    return null;
  }

  const messageEl = dialog.querySelector('[data-session-warning-message]');
  const extendButton = dialog.querySelector('[data-session-warning-extend]');
  const logoutButton = dialog.querySelector('[data-session-warning-logout]');

  if (!(messageEl instanceof HTMLElement)
    || !(extendButton instanceof HTMLButtonElement)
    || !(logoutButton instanceof HTMLButtonElement)) {
    return null;
  }

  return { dialog, messageEl, extendButton, logoutButton };
};

/**
 * Format remaining seconds into a human-readable countdown string.
 * @param {number} seconds
 * @returns {string}
 */
const formatCountdown = (seconds) => {
  if (seconds <= 0) {
    return '0:00';
  }
  const mins = Math.floor(seconds / 60);
  const secs = seconds % 60;
  return `${mins}:${secs.toString().padStart(2, '0')}`;
};

/**
 * Initialize the session warning system.
 */
const init = () => {
  const config = readConfig();
  if (!config) {
    return;
  }

  const elements = resolveDialog();
  if (!elements) {
    return;
  }

  const { dialog, messageEl, extendButton, logoutButton } = elements;
  const messageTemplate = messageEl.dataset.template || '';

  let lastActivity = Date.now();
  let warningTimerId = 0;
  let countdownTimerId = 0;
  let remainingSeconds = 0;
  let isDialogOpen = false;

  // ── Countdown ──────────────────────────────────────────────

  const updateCountdownDisplay = () => {
    const text = messageTemplate
      ? messageTemplate.replace('{countdown}', formatCountdown(remainingSeconds))
      : formatCountdown(remainingSeconds);
    messageEl.textContent = text;
  };

  const stopCountdown = () => {
    if (countdownTimerId) {
      clearInterval(countdownTimerId);
      countdownTimerId = 0;
    }
  };

  const closeDialog = () => {
    stopCountdown();
    isDialogOpen = false;
    try {
      if (dialog.open) {
        dialog.close();
      }
    } catch {
      // no-op
    }
    document.body.classList.remove('modal-is-open');
  };

  const startCountdown = () => {
    stopCountdown();
    remainingSeconds = WARNING_LEAD_SECONDS;
    updateCountdownDisplay();

    countdownTimerId = setInterval(() => {
      remainingSeconds -= 1;
      updateCountdownDisplay();

      if (remainingSeconds <= 0) {
        stopCountdown();
        closeDialog();
        // Session expired — reload triggers the server-side timeout flow.
        window.location.reload();
      }
    }, TICK_INTERVAL_MS);
  };

  // ── Warning timer ──────────────────────────────────────────

  const scheduleWarning = () => {
    if (warningTimerId) {
      clearTimeout(warningTimerId);
    }

    const delayMs = (config.idleSeconds - WARNING_LEAD_SECONDS) * 1000;
    if (delayMs <= 0) {
      return;
    }

    warningTimerId = setTimeout(() => {
      showWarningDialog();
    }, delayMs);
  };

  const showWarningDialog = () => {
    if (isDialogOpen) {
      return;
    }
    isDialogOpen = true;

    startCountdown();
    document.body.classList.add('modal-is-open');

    try {
      dialog.showModal();
    } catch {
      isDialogOpen = false;
      document.body.classList.remove('modal-is-open');
      return;
    }

    extendButton.focus();
  };

  // ── Extend session (AJAX ping) ─────────────────────────────

  const extendSession = async () => {
    extendButton.disabled = true;

    try {
      const body = new URLSearchParams({
        [config.csrfKey]: config.csrfToken,
      });

      const response = await fetch(config.pingUrl, {
        method: 'POST',
        headers: {
          'X-Requested-With': 'XMLHttpRequest',
        },
        body,
      });

      if (!response.ok) {
        // Session already expired — reload.
        window.location.reload();
        return;
      }

      const data = await response.json();

      // Server may return an updated idle timeout.
      if (typeof data.idle_timeout_seconds === 'number' && data.idle_timeout_seconds > 0) {
        config.idleSeconds = data.idle_timeout_seconds;
      }

      lastActivity = Date.now();
      closeDialog();
      scheduleWarning();
    } catch {
      // Network error — reload to let server handle it.
      window.location.reload();
    } finally {
      extendButton.disabled = false;
    }
  };

  // ── Logout ─────────────────────────────────────────────────

  const doLogout = () => {
    closeDialog();
    window.location.href = config.logoutUrl;
  };

  // ── User interaction tracking ──────────────────────────────

  let lastInteractionReset = Date.now();

  const onUserInteraction = () => {
    const now = Date.now();
    if ((now - lastInteractionReset) < INTERACTION_THROTTLE_MS) {
      return;
    }
    lastInteractionReset = now;

    // Only reset timer when dialog is NOT showing.
    if (!isDialogOpen) {
      lastActivity = now;
      scheduleWarning();
    }
  };

  // ── Focus trap (keyboard) ──────────────────────────────────

  const onKeyDown = (event) => {
    if (event.key === 'Escape') {
      // Prevent default ESC close — user must choose extend or logout.
      event.preventDefault();
      return;
    }

    if (event.key !== 'Tab') {
      return;
    }

    const focusable = [extendButton, logoutButton].filter(
      (el) => !el.disabled && !el.hidden
    );
    if (!focusable.length) {
      event.preventDefault();
      return;
    }

    const first = focusable[0];
    const last = focusable[focusable.length - 1];
    const active = document.activeElement;

    if (event.shiftKey) {
      if (active === first || !dialog.contains(active)) {
        event.preventDefault();
        last.focus();
      }
    } else if (active === last) {
      event.preventDefault();
      first.focus();
    }
  };

  // Prevent closing via backdrop click
  const onBackdropClick = (event) => {
    if (event.target === dialog) {
      event.preventDefault();
    }
  };

  // Prevent native cancel (ESC)
  const onCancel = (event) => {
    event.preventDefault();
  };

  // ── Wire up events ─────────────────────────────────────────

  extendButton.addEventListener('click', (e) => {
    e.preventDefault();
    extendSession();
  });

  logoutButton.addEventListener('click', (e) => {
    e.preventDefault();
    doLogout();
  });

  dialog.addEventListener('keydown', onKeyDown);
  dialog.addEventListener('click', onBackdropClick);
  dialog.addEventListener('cancel', onCancel);

  for (const eventName of INTERACTION_EVENTS) {
    document.addEventListener(eventName, onUserInteraction, { passive: true });
  }

  // ── Start ──────────────────────────────────────────────────

  scheduleWarning();
};

// Auto-init when module loads.
if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', init);
} else {
  init();
}
