import { lazy, type ComponentType } from 'react';

export type RouteSection = 'monitor' | 'vehicles' | 'tracks' | 'history' | 'statistics' | 'access' | 'alerts' | 'operations' | 'users';
type RouteModule = { default: ComponentType };
type RoutePreloadConnection = { saveData?: boolean; effectiveType?: string };
type RoutePreloadRuntime = { visibilityState?: DocumentVisibilityState; deviceMemory?: number };
type RoutePreloadScheduler = (callback: () => void) => () => void;
type RouteLoadOptions = { timeoutMs?: number; retryDelayMs?: number; retries?: number };

const ROUTE_LOAD_TIMEOUT_MS = 10_000;
const ROUTE_RETRY_DELAY_MS = 180;

const importers: Record<RouteSection, () => Promise<RouteModule>> = {
  monitor: () => import('../pages/MonitorPage'),
  vehicles: () => import('../pages/VehiclePage'),
  tracks: () => import('../pages/TrackPage'),
  history: () => import('../pages/HistoryPage'),
  statistics: () => import('../pages/StatisticsPage'),
  access: () => import('../pages/AccessPage'),
  alerts: () => import('../pages/AlertsPage'),
  operations: () => import('../pages/OperationsPage'),
  users: () => import('../pages/UsersPage')
};

const pendingModules = new Map<RouteSection, Promise<RouteModule>>();
const likelyNextRoutes: Record<RouteSection, RouteSection[]> = {
  monitor: ['vehicles', 'tracks'],
  vehicles: ['monitor', 'tracks'],
  tracks: ['monitor', 'history'],
  history: ['statistics', 'tracks'],
  statistics: ['history', 'vehicles'],
  access: ['monitor', 'operations'],
  alerts: ['monitor', 'access'],
  operations: ['access', 'monitor'],
  users: ['monitor', 'vehicles']
};

function routeLoadTimeout<T>(promise: Promise<T>, section: string, timeoutMs: number) {
  return new Promise<T>((resolve, reject) => {
    const timer = window.setTimeout(() => {
      const error = new Error(`Route chunk load timed out after ${timeoutMs}ms: ${section}`);
      error.name = 'RouteChunkLoadTimeoutError';
      reject(error);
    }, timeoutMs);
    promise.then(
      (value) => { window.clearTimeout(timer); resolve(value); },
      (error) => { window.clearTimeout(timer); reject(error); }
    );
  });
}

function retryDelay(delayMs: number) {
  return new Promise<void>((resolve) => window.setTimeout(resolve, delayMs));
}

function isRouteLoadTimeout(error: unknown) {
  return error instanceof Error && error.name === 'RouteChunkLoadTimeoutError';
}

export async function loadRouteModule<T>(
  loader: () => Promise<T>,
  section: string,
  { timeoutMs = ROUTE_LOAD_TIMEOUT_MS, retryDelayMs = ROUTE_RETRY_DELAY_MS, retries = 1 }: RouteLoadOptions = {}
) {
  let lastError: unknown;
  for (let attempt = 0; attempt <= retries; attempt += 1) {
    try {
      return await routeLoadTimeout(Promise.resolve().then(loader), section, timeoutMs);
    } catch (error) {
      lastError = error;
      if (attempt >= retries || isRouteLoadTimeout(error)) break;
      await retryDelay(retryDelayMs);
    }
  }
  throw lastError;
}

function loadRoute(section: RouteSection) {
  const pending = pendingModules.get(section);
  if (pending) return pending;
  const next = loadRouteModule(importers[section], section);
  pendingModules.set(section, next);
  void next.catch(() => pendingModules.delete(section));
  return next;
}

export function createRouteComponent(section: RouteSection) {
  return lazy(() => loadRoute(section));
}

export function routeSectionForPath(pathname: string): RouteSection | undefined {
  const section = pathname.split('?')[0].split('#')[0].split('/').filter(Boolean)[0] as RouteSection | undefined;
  return section && section in importers ? section : undefined;
}

export async function preloadRoute(pathname: string) {
  const section = routeSectionForPath(pathname);
  if (!section) return undefined;
  try {
    return await loadRoute(section);
  } catch {
    // Intent/background preloads are speculative. loadRoute already evicts the
    // failed Promise so a later navigation can retry through the route boundary.
    return undefined;
  }
}

function browserConnection(): RoutePreloadConnection | undefined {
  return (navigator as Navigator & { connection?: RoutePreloadConnection }).connection;
}

function browserRuntime(): RoutePreloadRuntime {
  return {
    visibilityState: document.visibilityState,
    deviceMemory: (navigator as Navigator & { deviceMemory?: number }).deviceMemory
  };
}

export function routePreloadBudget(connection = browserConnection(), runtime = browserRuntime()) {
  if (runtime.visibilityState === 'hidden' || connection?.saveData) return 0;
  const effectiveType = String(connection?.effectiveType ?? '').toLowerCase();
  if (effectiveType.includes('2g') || (runtime.deviceMemory != null && runtime.deviceMemory <= 2)) return 0;
  if (effectiveType.includes('3g') || (runtime.deviceMemory != null && runtime.deviceMemory <= 4)) return 1;
  return 2;
}

export function shouldPreloadRoutes(connection = browserConnection(), runtime = browserRuntime()) {
  return routePreloadBudget(connection, runtime) > 0;
}

export function routePreloadCandidates(pathname: string) {
  const activeSection = routeSectionForPath(pathname);
  return activeSection ? likelyNextRoutes[activeSection] : [];
}

function shouldContinuePreloading() {
  return document.visibilityState !== 'hidden';
}

export function shouldPreloadRouteOnIntent(connection = browserConnection()) {
  if (connection?.saveData) return false;
  return !String(connection?.effectiveType ?? '').toLowerCase().includes('2g');
}

function scheduleWhenIdle(callback: () => void) {
  if (typeof window.requestIdleCallback === 'function') {
    const id = window.requestIdleCallback(callback, { timeout: 2_000 });
    return () => window.cancelIdleCallback(id);
  }
  const id = window.setTimeout(callback, 900);
  return () => window.clearTimeout(id);
}

export function scheduleIdleRoutePreloads({
  activePathname,
  connection = browserConnection(),
  runtime = browserRuntime(),
  preload = preloadRoute,
  schedule = scheduleWhenIdle,
  isVisible = shouldContinuePreloading
}: {
  activePathname: string;
  connection?: RoutePreloadConnection;
  runtime?: RoutePreloadRuntime;
  preload?: (pathname: string) => Promise<unknown> | undefined;
  schedule?: RoutePreloadScheduler;
  isVisible?: () => boolean;
}) {
  const budget = routePreloadBudget(connection, runtime);
  if (!budget) return () => undefined;
  const queue = routePreloadCandidates(activePathname).slice(0, budget);
  let cancelled = false;
  let cancelScheduled: () => void = () => undefined;

  const warmNext = () => {
    if (cancelled || !isVisible()) return;
    const section = queue.shift();
    if (!section) return;
    void Promise.resolve().then(() => preload(`/${section}`))
      .finally(() => {
        if (!cancelled && queue.length && isVisible()) cancelScheduled = schedule(warmNext);
      });
  };

  cancelScheduled = schedule(warmNext);
  return () => {
    cancelled = true;
    cancelScheduled();
  };
}

export const RoutePages = {
  Monitor: createRouteComponent('monitor'),
  Vehicles: createRouteComponent('vehicles'),
  Tracks: createRouteComponent('tracks'),
  History: createRouteComponent('history'),
  Statistics: createRouteComponent('statistics'),
  Access: createRouteComponent('access'),
  Alerts: createRouteComponent('alerts'),
  Operations: createRouteComponent('operations'),
  Users: createRouteComponent('users')
} as const;

export const RoutePageFactories = {
  Monitor: () => createRouteComponent('monitor'),
  Vehicles: () => createRouteComponent('vehicles'),
  Tracks: () => createRouteComponent('tracks'),
  History: () => createRouteComponent('history'),
  Statistics: () => createRouteComponent('statistics'),
  Access: () => createRouteComponent('access'),
  Alerts: () => createRouteComponent('alerts'),
  Operations: () => createRouteComponent('operations'),
  Users: () => createRouteComponent('users')
} as const;
