import { createHrefFromUrl } from "./create-href-from-url";
import { fetchServerResponse } from "./fetch-server-response";
import { PrefetchCacheEntryStatus, PrefetchKind } from "./router-reducer-types";
import { prefetchQueue } from "./reducers/prefetch-reducer";
/**
 * Creates a cache key for the router prefetch cache
 *
 * @param url - The URL being navigated to
 * @param nextUrl - an internal URL, primarily used for handling rewrites. Defaults to '/'.
 * @return The generated prefetch cache key.
 */ function createPrefetchCacheKey(url, nextUrl) {
    const pathnameFromUrl = createHrefFromUrl(url, // Ensures the hash is not part of the cache key as it does not impact the server fetch
    false);
    // nextUrl is used as a cache key delimiter since entries can vary based on the Next-URL header
    if (nextUrl) {
        return nextUrl + "%" + pathnameFromUrl;
    }
    return pathnameFromUrl;
}
/**
 * Returns a prefetch cache entry if one exists. Otherwise creates a new one and enqueues a fetch request
 * to retrieve the prefetch data from the server.
 */ export function getOrCreatePrefetchCacheEntry(param) {
    let { url, nextUrl, tree, buildId, prefetchCache, kind } = param;
    let existingCacheEntry = undefined;
    // We first check if there's a more specific interception route prefetch entry
    // This is because when we detect a prefetch that corresponds with an interception route, we prefix it with nextUrl (see `createPrefetchCacheKey`)
    // to avoid conflicts with other pages that may have the same URL but render different things depending on the `Next-URL` header.
    const interceptionCacheKey = createPrefetchCacheKey(url, nextUrl);
    const interceptionData = prefetchCache.get(interceptionCacheKey);
    if (interceptionData) {
        existingCacheEntry = interceptionData;
    } else {
        // If we dont find a more specific interception route prefetch entry, we check for a regular prefetch entry
        const prefetchCacheKey = createPrefetchCacheKey(url);
        const prefetchData = prefetchCache.get(prefetchCacheKey);
        if (prefetchData) {
            existingCacheEntry = prefetchData;
        }
    }
    if (existingCacheEntry) {
        // Grab the latest status of the cache entry and update it
        existingCacheEntry.status = getPrefetchEntryCacheStatus(existingCacheEntry);
        // when `kind` is provided, an explicit prefetch was requested.
        // if the requested prefetch is "full" and the current cache entry wasn't, we want to re-prefetch with the new intent
        const switchedToFullPrefetch = existingCacheEntry.kind !== PrefetchKind.FULL && kind === PrefetchKind.FULL;
        if (switchedToFullPrefetch) {
            return createLazyPrefetchEntry({
                tree,
                url,
                buildId,
                nextUrl,
                prefetchCache,
                // If we didn't get an explicit prefetch kind, we want to set a temporary kind
                // rather than assuming the same intent as the previous entry, to be consistent with how we
                // lazily create prefetch entries when intent is left unspecified.
                kind: kind != null ? kind : PrefetchKind.TEMPORARY
            });
        }
        // If the existing cache entry was marked as temporary, it means it was lazily created when attempting to get an entry,
        // where we didn't have the prefetch intent. Now that we have the intent (in `kind`), we want to update the entry to the more accurate kind.
        if (kind && existingCacheEntry.kind === PrefetchKind.TEMPORARY) {
            existingCacheEntry.kind = kind;
        }
        // We've determined that the existing entry we found is still valid, so we return it.
        return existingCacheEntry;
    }
    // If we didn't return an entry, create a new one.
    return createLazyPrefetchEntry({
        tree,
        url,
        buildId,
        nextUrl,
        prefetchCache,
        kind: kind || // in dev, there's never gonna be a prefetch entry so we want to prefetch here
        (process.env.NODE_ENV === "development" ? PrefetchKind.AUTO : PrefetchKind.TEMPORARY)
    });
}
/*
 * Used to take an existing cache entry and prefix it with the nextUrl, if it exists.
 * This ensures that we don't have conflicting cache entries for the same URL (as is the case with route interception).
 */ function prefixExistingPrefetchCacheEntry(param) {
    let { url, nextUrl, prefetchCache } = param;
    const existingCacheKey = createPrefetchCacheKey(url);
    const existingCacheEntry = prefetchCache.get(existingCacheKey);
    if (!existingCacheEntry) {
        // no-op -- there wasn't an entry to move
        return;
    }
    const newCacheKey = createPrefetchCacheKey(url, nextUrl);
    prefetchCache.set(newCacheKey, existingCacheEntry);
    prefetchCache.delete(existingCacheKey);
}
/**
 * Use to seed the prefetch cache with data that has already been fetched.
 */ export function createPrefetchCacheEntryForInitialLoad(param) {
    let { nextUrl, tree, prefetchCache, url, kind, data } = param;
    const [, , , intercept] = data;
    // if the prefetch corresponds with an interception route, we use the nextUrl to prefix the cache key
    const prefetchCacheKey = intercept ? createPrefetchCacheKey(url, nextUrl) : createPrefetchCacheKey(url);
    const prefetchEntry = {
        treeAtTimeOfPrefetch: tree,
        data: Promise.resolve(data),
        kind,
        prefetchTime: Date.now(),
        lastUsedTime: Date.now(),
        key: prefetchCacheKey,
        status: PrefetchCacheEntryStatus.fresh
    };
    prefetchCache.set(prefetchCacheKey, prefetchEntry);
    return prefetchEntry;
}
/**
 * Creates a prefetch entry entry and enqueues a fetch request to retrieve the data.
 */ function createLazyPrefetchEntry(param) {
    let { url, kind, tree, nextUrl, buildId, prefetchCache } = param;
    const prefetchCacheKey = createPrefetchCacheKey(url);
    // initiates the fetch request for the prefetch and attaches a listener
    // to the promise to update the prefetch cache entry when the promise resolves (if necessary)
    const data = prefetchQueue.enqueue(()=>fetchServerResponse(url, tree, nextUrl, buildId, kind).then((prefetchResponse)=>{
            // TODO: `fetchServerResponse` should be more tighly coupled to these prefetch cache operations
            // to avoid drift between this cache key prefixing logic
            // (which is currently directly influenced by the server response)
            const [, , , intercepted] = prefetchResponse;
            if (intercepted) {
                prefixExistingPrefetchCacheEntry({
                    url,
                    nextUrl,
                    prefetchCache
                });
            }
            return prefetchResponse;
        }));
    const prefetchEntry = {
        treeAtTimeOfPrefetch: tree,
        data,
        kind,
        prefetchTime: Date.now(),
        lastUsedTime: null,
        key: prefetchCacheKey,
        status: PrefetchCacheEntryStatus.fresh
    };
    prefetchCache.set(prefetchCacheKey, prefetchEntry);
    return prefetchEntry;
}
export function prunePrefetchCache(prefetchCache) {
    for (const [href, prefetchCacheEntry] of prefetchCache){
        if (getPrefetchEntryCacheStatus(prefetchCacheEntry) === PrefetchCacheEntryStatus.expired) {
            prefetchCache.delete(href);
        }
    }
}
// These values are set by `define-env-plugin` (based on `nextConfig.experimental.staleTimes`)
// and default to 5 minutes (static) / 30 seconds (dynamic)
const DYNAMIC_STALETIME_MS = Number(process.env.__NEXT_CLIENT_ROUTER_DYNAMIC_STALETIME) * 1000;
const STATIC_STALETIME_MS = Number(process.env.__NEXT_CLIENT_ROUTER_STATIC_STALETIME) * 1000;
function getPrefetchEntryCacheStatus(param) {
    let { kind, prefetchTime, lastUsedTime } = param;
    // We will re-use the cache entry data for up to the `dynamic` staletime window.
    if (Date.now() < (lastUsedTime != null ? lastUsedTime : prefetchTime) + DYNAMIC_STALETIME_MS) {
        return lastUsedTime ? PrefetchCacheEntryStatus.reusable : PrefetchCacheEntryStatus.fresh;
    }
    // For "auto" prefetching, we'll re-use only the loading boundary for up to `static` staletime window.
    // A stale entry will only re-use the `loading` boundary, not the full data.
    // This will trigger a "lazy fetch" for the full data.
    if (kind === "auto") {
        if (Date.now() < prefetchTime + STATIC_STALETIME_MS) {
            return PrefetchCacheEntryStatus.stale;
        }
    }
    // for "full" prefetching, we'll re-use the cache entry data for up to `static` staletime window.
    if (kind === "full") {
        if (Date.now() < prefetchTime + STATIC_STALETIME_MS) {
            return PrefetchCacheEntryStatus.reusable;
        }
    }
    return PrefetchCacheEntryStatus.expired;
}

//# sourceMappingURL=prefetch-cache-utils.js.map