"use client";

import { useEffect } from "react";

type SessionIdleGuardProps = {
  /** Seconds without user activity before ending the browser session. */
  idleSeconds: number;
  /** Clear-cookie bounce path (absolute path). */
  expiredPath: string;
  /**
   * Optional authenticated BFF that touches Yii last_seen_at.
   * Called at most once per minute while the user is active.
   */
  heartbeatPath?: string;
  /** sessionStorage key so remounts/HMR do not reset the idle clock. */
  storageKey?: string;
};

/** Intentional interaction only — mousemove/scroll were resetting idle while the tab sat open. */
const ACTIVITY_EVENTS = ["pointerdown", "keydown", "touchstart", "click"] as const;

const HEARTBEAT_MIN_MS = 60_000;
const CHECK_EVERY_MS = 5_000;

/**
 * Ends the browser session after idleSeconds with no input.
 *
 * Needed because the edge proxy only validates JWT signature/exp — Yii idle
 * revoke runs only on API hits, so a tab left open would otherwise stay "logged in".
 */
export function SessionIdleGuard({
  idleSeconds,
  expiredPath,
  heartbeatPath,
  storageKey = "auth:idle:lastActivityAt",
}: SessionIdleGuardProps) {
  useEffect(() => {
    if (!Number.isFinite(idleSeconds) || idleSeconds < 60) {
      return;
    }

    const idleMs = idleSeconds * 1000;
    let ended = false;
    let lastHeartbeatAt = 0;

    const readLastActivity = (): number => {
      try {
        const raw = sessionStorage.getItem(storageKey);
        const parsed = raw ? Number(raw) : NaN;
        if (Number.isFinite(parsed) && parsed > 0) {
          return parsed;
        }
      } catch {
        /* private mode / blocked storage */
      }
      return Date.now();
    };

    const writeLastActivity = (ts: number) => {
      try {
        sessionStorage.setItem(storageKey, String(ts));
      } catch {
        /* ignore */
      }
    };

    let lastActivityAt = readLastActivity();

    const clearStoredActivity = () => {
      try {
        sessionStorage.removeItem(storageKey);
      } catch {
        /* ignore */
      }
    };

    const expire = () => {
      if (ended) return;
      ended = true;
      clearStoredActivity();
      const redirect = `${window.location.pathname}${window.location.search}`;
      const url = new URL(expiredPath, window.location.origin);
      if (redirect && !redirect.startsWith("/login") && !redirect.includes("session-expired")) {
        url.searchParams.set("redirect", redirect);
      }
      window.location.assign(url.toString());
    };

    const pingHeartbeat = () => {
      if (!heartbeatPath || ended) return;
      const now = Date.now();
      if (now - lastHeartbeatAt < HEARTBEAT_MIN_MS) return;
      lastHeartbeatAt = now;
      void fetch(heartbeatPath, {
        method: "GET",
        credentials: "same-origin",
        cache: "no-store",
        headers: { Accept: "application/json" },
      })
        .then((response) => {
          if (response.status === 401) {
            expire();
          }
        })
        .catch(() => {
          /* network blip — idle timer still applies */
        });
    };

    const noteActivity = () => {
      if (ended) return;
      lastActivityAt = Date.now();
      writeLastActivity(lastActivityAt);
      pingHeartbeat();
    };

    const checkIdle = () => {
      if (ended) return;
      // Re-read in case another tab updated storage.
      lastActivityAt = readLastActivity();
      if (Date.now() - lastActivityAt >= idleMs) {
        expire();
      }
    };

    for (const name of ACTIVITY_EVENTS) {
      window.addEventListener(name, noteActivity, true);
    }

    const onVisibility = () => {
      if (document.visibilityState === "visible") {
        checkIdle();
      }
    };
    document.addEventListener("visibilitychange", onVisibility);

    const timer = window.setInterval(checkIdle, CHECK_EVERY_MS);
    // Immediate check (covers remount after already-idle).
    checkIdle();

    return () => {
      ended = true;
      window.clearInterval(timer);
      document.removeEventListener("visibilitychange", onVisibility);
      for (const name of ACTIVITY_EVENTS) {
        window.removeEventListener(name, noteActivity, true);
      }
    };
  }, [idleSeconds, expiredPath, heartbeatPath, storageKey]);

  return null;
}
