/**
 * Session expiry warning dialog.
 */
import { warnOnce, resolveHost } from '../core/app-dom.js';
import { postForm } from '../core/app-http.js';

const WARNING_LEAD_SECONDS = 120;
const TICK_INTERVAL_MS = 1000;
const MIN_IDLE_TIMEOUT = 180; // Don't show warning if timeout < 3 min
const KEEPALIVE_INTERVAL_MS = 300_000; // Sync with server every 5 min (if active)
const INTERACTION_EVENTS = ['click', 'keydown', 'scroll', 'mousemove', 'touchstart'];
const INTERACTION_THROTTLE_MS = 30_000; // Only reset every 30s to limit overhead

const toPositiveInt = (value, fallback = 0) => {
  const parsed = Number.parseInt(String(value ?? ''), 10);
  if (!Number.isFinite(parsed) || parsed <= 0) {
    return fallback;
  }
  return parsed;
};

const toTrimmed = (value) => String(value ?? '').trim();

const requestSubmitWithFallback = (form) => {
  if (!(form instanceof HTMLFormElement)) {
    return false;
  }

  if (typeof form.requestSubmit === 'function') {
    form.requestSubmit();
    return true;
  }

  form.submit();
  return true;
};

const resolveConfig = (runtimeConfig = {}) => {
  const root = document.documentElement;
  const config = runtimeConfig && typeof runtimeConfig === 'object' && !Array.isArray(runtimeConfig)
    ? runtimeConfig
    : {};

  const enabled = config.enabled !== false;
  if (!enabled) {
    return null;
  }

  const idleSeconds = toPositiveInt(config.idleSeconds ?? root.dataset.sessionIdleSeconds, 0);
  const pingUrl = toTrimmed(config.pingUrl ?? root.dataset.sessionPingUrl);
  const logoutFormId = toTrimmed(config.logoutFormId ?? root.dataset.sessionLogoutFormId);
  const csrfKey = toTrimmed(config.csrfKey ?? root.dataset.csrfKey);
  const csrfToken = toTrimmed(config.csrfToken ?? root.dataset.csrfToken);
  const selector = toTrimmed(config.selector || '[data-app-session-warning-dialog]') || '[data-app-session-warning-dialog]';

  if (idleSeconds < MIN_IDLE_TIMEOUT) {
    return null;
  }
  if (!pingUrl || !logoutFormId || !csrfKey || !csrfToken) {
    warnOnce('UI_CONFIG_INVALID', 'Session warning config is incomplete', {
      module: 'session-warning',
    });
    return null;
  }

  return {
    idleSeconds,
    pingUrl,
    logoutFormId,
    csrfKey,
    csrfToken,
    selector,
  };
};

const resolveDialog = (host, selector) => {
  const dialog = host instanceof HTMLDialogElement && host.matches(selector)
    ? host
    : host.querySelector(selector);
  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 };
};

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')}`;
};

export function initSessionWarning(root = document, runtimeConfig = {}) {
  const host = resolveHost(root);
  const config = resolveConfig(runtimeConfig);
  if (!config) {
    return { destroy: () => {} };
  }

  const elements = resolveDialog(host, config.selector);
  if (!elements) {
    warnOnce('UI_EL_MISSING', `Missing session warning dialog: ${config.selector}`, {
      module: 'session-warning',
    });
    return { destroy: () => {} };
  }

  const { dialog, messageEl, extendButton, logoutButton } = elements;
  if (dialog.dataset.sessionWarningBound === '1' && dialog._sessionWarningApi) {
    return dialog._sessionWarningApi;
  }

  const messageTemplate = messageEl.dataset.template || '';
  const cleanupFns = [];
  const bind = (target, eventName, handler, options = undefined) => {
    target.addEventListener(eventName, handler, options);
    cleanupFns.push(() => target.removeEventListener(eventName, handler, options));
  };

  let warningTimerId = 0;
  let countdownTimerId = 0;
  let keepaliveTimerId = 0;
  let remainingSeconds = 0;
  let isDialogOpen = false;
  let lastInteractionReset = Date.now();
  let hasInteractionSinceLastSync = false;

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

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

  const clearWarningTimer = () => {
    if (!warningTimerId) {
      return;
    }
    clearTimeout(warningTimerId);
    warningTimerId = 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();
        window.location.reload();
      }
    }, TICK_INTERVAL_MS);
  };

  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();
  };

  const scheduleWarning = () => {
    clearWarningTimer();
    const delayMs = (config.idleSeconds - WARNING_LEAD_SECONDS) * 1000;
    if (delayMs <= 0) {
      return;
    }
    warningTimerId = setTimeout(() => {
      showWarningDialog();
    }, delayMs);
  };

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

    try {
      const data = await postForm(config.pingUrl, {
        [config.csrfKey]: config.csrfToken,
      });
      if (typeof data.idle_timeout_seconds === 'number' && data.idle_timeout_seconds > 0) {
        config.idleSeconds = data.idle_timeout_seconds;
      }

      closeDialog();
      scheduleWarning();
    } catch {
      window.location.reload();
    } finally {
      extendButton.disabled = false;
    }
  };

  const doLogout = () => {
    closeDialog();
    const form = document.getElementById(config.logoutFormId);
    if (!requestSubmitWithFallback(form)) {
      warnOnce('UI_EL_MISSING', `Missing logout form: #${config.logoutFormId}`, {
        module: 'session-warning',
      });
    }
  };

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

  const onKeepalive = async () => {
    if (!hasInteractionSinceLastSync || isDialogOpen) {
      return;
    }
    hasInteractionSinceLastSync = false;

    try {
      const data = await postForm(config.pingUrl, {
        [config.csrfKey]: config.csrfToken,
      });
      if (typeof data.idle_timeout_seconds === 'number' && data.idle_timeout_seconds > 0) {
        config.idleSeconds = data.idle_timeout_seconds;
      }
    } catch {
      // Network error — warning dialog still handles timeout/expiry.
    }
  };

  const onKeyDown = (event) => {
    if (event.key === 'Escape') {
      event.preventDefault();
      return;
    }

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

    const focusable = [extendButton, logoutButton].filter((element) => !element.disabled && !element.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();
      }
      return;
    }

    if (active === last) {
      event.preventDefault();
      first.focus();
    }
  };

  const onBackdropClick = (event) => {
    if (event.target === dialog) {
      event.preventDefault();
    }
  };

  const onCancel = (event) => {
    event.preventDefault();
  };

  bind(extendButton, 'click', (event) => {
    event.preventDefault();
    extendSession();
  });
  bind(logoutButton, 'click', (event) => {
    event.preventDefault();
    doLogout();
  });
  bind(dialog, 'keydown', onKeyDown);
  bind(dialog, 'click', onBackdropClick);
  bind(dialog, 'cancel', onCancel);
  for (const eventName of INTERACTION_EVENTS) {
    bind(document, eventName, onUserInteraction, { passive: true });
  }

  keepaliveTimerId = setInterval(onKeepalive, KEEPALIVE_INTERVAL_MS);
  scheduleWarning();

  const destroy = () => {
    cleanupFns.forEach((cleanup) => cleanup());
    cleanupFns.length = 0;

    clearWarningTimer();
    stopCountdown();
    if (keepaliveTimerId) {
      clearInterval(keepaliveTimerId);
      keepaliveTimerId = 0;
    }
    closeDialog();
    delete dialog.dataset.sessionWarningBound;
    delete dialog._sessionWarningApi;
  };

  const api = { destroy };
  dialog.dataset.sessionWarningBound = '1';
  dialog._sessionWarningApi = api;
  return api;
}
