import { JoinWindow } from 'src/common/interfaces/join-window.interface';

/**
 * Computes an online session's join window: the link opens
 * `joinOpensMinutesBefore` before the session start (falling back to
 * `defaultOpensBeforeMinutes` when unset) and closes `closeBufferMinutes`
 * (default 0) after the session's scheduled end time (`endsAt`). A session
 * without a start time is not gated and is treated as always open with no
 * bounds. A session without a scheduled end has no upper bound either.
 *
 * Single source of truth shared by the attendance join guard and the
 * registration "join enabled" flag.
 */
export function computeJoinWindow(params: {
  startsAt: Date | string | null | undefined;
  joinOpensMinutesBefore?: number | null;
  endsAt?: Date | string | null;
  defaultOpensBeforeMinutes: number;
  closeBufferMinutes?: number;
  now?: Date;
}): JoinWindow {
  const toMs = (value: Date | string | null | undefined): number | null => {
    if (value == null) return null;
    const ms = new Date(value).getTime();
    return Number.isNaN(ms) ? null : ms;
  };

  // The window opens/closes on whole-minute boundaries: session times carry no
  // meaningful sub-minute precision, so any stray seconds/milliseconds are floored
  // off both the start and the end of the window.
  const floorToMinute = (ms: number): number => Math.floor(ms / 60_000) * 60_000;

  const nowMs = (params.now ?? new Date()).getTime();

  // Not gated — always open, no bounds.
  if (!params.startsAt) {
    return { opensAt: null, closesAt: null, isOpen: true };
  }

  const startMs = new Date(params.startsAt).getTime();
  const opensBefore = params.joinOpensMinutesBefore ?? params.defaultOpensBeforeMinutes;
  const opensAtMs = floorToMinute(startMs - opensBefore * 60_000);

  const scheduledEndMs = toMs(params.endsAt);
  const closeBufferMs = (params.closeBufferMinutes ?? 0) * 60_000;
  const closesAtMs = scheduledEndMs === null ? null : floorToMinute(scheduledEndMs) + closeBufferMs;

  const isOpen = nowMs >= opensAtMs && (closesAtMs === null || nowMs <= closesAtMs);
  return {
    opensAt: new Date(opensAtMs),
    closesAt: closesAtMs === null ? null : new Date(closesAtMs),
    isOpen,
  };
}
