import * as qwikRouterConfig from '@qwik-router-config';
import { isBrowser, isDev } from '@qwik.dev/core';
import { _isSignalNotInvalid } from '@qwik.dev/core/internal';
// @ts-expect-error no types for preloader yet
import { p as preload } from '@qwik.dev/core/preloader';
import { ensureSlash } from '../../utils/pathname';
import { fetchRouteLoaderData, type RouteLoaderState } from './route-loaders';
import { loadRoute } from './routing';
import type { LoadedRoute } from './types';

/**
 * Prefetch a route's JS bundles and optionally its loader data.
 *
 * Resolves the route from the trie to get the routeName (for the bundle graph preloader) and
 * `$loaders$` (for data prefetching). The bundle graph is keyed by route name (e.g.
 * `products/[id]/`), not by actual pathname (e.g. `products/123/`).
 *
 * @param url - The URL pathname to prefetch
 * @param prefetchData - Whether to prefetch loader data
 * @param probability - Bundle preload probability (0-1, default 0.8)
 * @param manifestHash - Build manifest hash for loader URLs (from `useDocumentHead().manifestHash`)
 * @param prefetchBundle - Whether to prefetch route JS bundles
 * @param loaderState - In-memory loader signals; loaders with unexpired data are not fetched
 */
export async function prefetchRoute(
  url: URL,
  prefetchData?: boolean,
  probability = 0.8,
  manifestHash?: string,
  prefetchBundle = true,
  loaderState?: RouteLoaderState
) {
  if (!isBrowser || isDev) {
    return;
  }

  try {
    const loadedRoute = await loadRoute(
      (qwikRouterConfig as any).routes,
      (qwikRouterConfig as any).cacheModules,
      url.pathname
    );
    if (!loadedRoute) {
      return;
    }

    if (prefetchBundle) {
      // Preload JS bundles using the route NAME (not pathname) — the bundle graph
      // is keyed by route name (e.g. "products/[id]/") not actual path
      let routeName = loadedRoute.$routeName$;
      routeName = ensureSlash(routeName);
      if (routeName.length > 1 && routeName.startsWith('/')) {
        routeName = routeName.slice(1);
      }
      preload(routeName, probability);
    }

    if (!prefetchData || !manifestHash) {
      return;
    }

    prefetchLoaderData(loadedRoute, url, manifestHash, loaderState);
  } catch {
    // Silently ignore prefetch errors
  }
}

/**
 * Prefetch a route's loader data in parallel (fire-and-forget, consume body for caching). Loaders
 * whose in-memory signal still holds unexpired data are skipped.
 */
export const prefetchLoaderData = (
  loadedRoute: LoadedRoute,
  url: URL,
  manifestHash: string,
  loaderState?: RouteLoaderState
) => {
  if (!loadedRoute.$loaders$?.length || !loadedRoute.$loaderPaths$) {
    return;
  }
  const basePath = (qwikRouterConfig as any).basePathname ?? '/';
  for (const hash of loadedRoute.$loaders$) {
    if (_isSignalNotInvalid(loaderState?.[hash])) {
      continue;
    }
    let loaderPath = loadedRoute.$loaderPaths$[hash];
    if (!loaderPath) {
      continue;
    }
    if (basePath !== '/' && !loaderPath.startsWith(basePath)) {
      loaderPath = basePath + loaderPath.slice(1);
    }
    fetchRouteLoaderData(hash, loaderPath, manifestHash, {
      pageUrl: url,
      basePath,
    }).catch(() => {});
  }
};
