/**
 * Segment Resolution
 *
 * Extracted from createRouter closure. Contains all segment resolution functions
 * for both fresh (full match) and revalidation (partial match) paths.
 *
 * Functions receive a `deps` parameter for closure-bound helpers from createRouter.
 */
import { DataNotFoundError, invariant, } from "../errors";
import { createErrorInfo, createErrorSegment, createNotFoundInfo, createNotFoundSegment, } from "./error-handling.js";
import { revalidate, } from "./loader-resolution.js";
import { evaluateRevalidation } from "./revalidation.js";
import { getRequestContext } from "../server/request-context.js";
import { DefaultErrorFallback } from "../default-error-boundary.js";
import { debugLog } from "./logging.js";
/**
 * Handle Response returns from handlers.
 * When a handler returns a Response (e.g., redirect), throw it to trigger
 * the short-circuit mechanism. Otherwise return the ReactNode.
 */
export function handleHandlerResult(result) {
    if (result instanceof Response) {
        throw result;
    }
    if (result instanceof Promise) {
        return result.then((resolved) => {
            if (resolved instanceof Response) {
                throw resolved;
            }
            return resolved;
        });
    }
    return result;
}
// ---------------------------------------------------------------------------
// Fresh path (full match, no revalidation)
// ---------------------------------------------------------------------------
/**
 * Resolve loaders for an entry and emit segments.
 * Loaders are run lazily via ctx.use() and memoized for parallel execution.
 */
export async function resolveLoaders(entry, ctx, belongsToRoute, deps, shortCodeOverride) {
    const loaderEntries = entry.loader ?? [];
    if (loaderEntries.length === 0)
        return [];
    const shortCode = shortCodeOverride ?? entry.shortCode;
    const hasLoading = "loading" in entry && entry.loading !== undefined;
    const loadingDisabled = hasLoading && entry.loading === false;
    return Promise.all(loaderEntries.map(async ({ loader }, i) => {
        const segmentId = `${shortCode}D${i}.${loader.$$id}`;
        return {
            id: segmentId,
            namespace: entry.id,
            type: "loader",
            index: i,
            component: null,
            params: ctx.params,
            loaderId: loader.$$id,
            loaderData: deps.wrapLoaderPromise(loadingDisabled ? await ctx.use(loader) : ctx.use(loader), entry, segmentId, ctx.pathname),
            belongsToRoute,
        };
    }));
}
/**
 * Resolve segments from EntryData.
 * Executes middlewares, loaders, parallels, and handlers in correct order.
 * Returns array: [main segment, ...orphan layout segments]
 */
export async function resolveSegment(entry, routeKey, params, context, loaderPromises, deps, isRouteEntry = false, options) {
    const segments = [];
    if (entry.type === "layout" || entry.type === "cache") {
        if (!options?.skipLoaders) {
            const loaderSegments = await resolveLoaders(entry, context, false, deps);
            segments.push(...loaderSegments);
        }
        for (const parallelEntry of entry.parallel) {
            const parallelSegments = await resolveParallelEntry(parallelEntry, params, context, false, entry.shortCode, deps);
            segments.push(...parallelSegments);
        }
        context._currentSegmentId = entry.shortCode;
        const component = typeof entry.handler === "function"
            ? handleHandlerResult(await entry.handler(context))
            : entry.handler;
        segments.push({
            id: entry.shortCode,
            namespace: entry.id,
            type: "layout",
            index: 0,
            component,
            loading: entry.loading === false ? null : entry.loading,
            params,
            belongsToRoute: false,
            layoutName: entry.id,
            ...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
        });
        for (const orphan of entry.layout) {
            const orphanSegments = await resolveOrphanLayout(orphan, params, context, loaderPromises, false, deps);
            segments.push(...orphanSegments);
        }
    }
    else if (entry.type === "route") {
        if (!options?.skipLoaders) {
            const loaderSegments = await resolveLoaders(entry, context, true, deps);
            segments.push(...loaderSegments);
        }
        for (const orphan of entry.layout) {
            const orphanSegments = await resolveOrphanLayout(orphan, params, context, loaderPromises, true, deps);
            segments.push(...orphanSegments);
        }
        for (const parallelEntry of entry.parallel) {
            const parallelSegments = await resolveParallelEntry(parallelEntry, params, context, true, entry.shortCode, deps);
            segments.push(...parallelSegments);
        }
        context._currentSegmentId = entry.shortCode;
        let component;
        if (entry.loading) {
            const result = handleHandlerResult(entry.handler(context));
            component = result instanceof Promise ? deps.trackHandler(result) : result;
        }
        else {
            component = handleHandlerResult(await entry.handler(context));
        }
        segments.push({
            id: entry.shortCode,
            namespace: entry.id,
            type: "route",
            index: 0,
            component,
            loading: entry.loading === false ? null : entry.loading,
            params,
            belongsToRoute: true,
            ...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
        });
    }
    else {
        throw new Error(`Unknown entry type: ${entry.type}`);
    }
    return segments;
}
/**
 * Resolve orphan layout with its middlewares, loaders, and parallels.
 */
export async function resolveOrphanLayout(orphan, params, context, loaderPromises, belongsToRoute, deps) {
    invariant(orphan.type === "layout" || orphan.type === "cache", `Expected orphan to be a layout or cache, got: ${orphan.type}`);
    const loaderSegments = await resolveLoaders(orphan, context, belongsToRoute, deps);
    const segments = [...loaderSegments];
    for (const parallelEntry of orphan.parallel) {
        const parallelSegments = await resolveParallelEntry(parallelEntry, params, context, belongsToRoute, orphan.shortCode, deps);
        segments.push(...parallelSegments);
    }
    const component = typeof orphan.handler === "function"
        ? handleHandlerResult(await orphan.handler(context))
        : orphan.handler;
    segments.push({
        id: orphan.shortCode,
        namespace: orphan.id,
        type: "layout",
        index: 0,
        component,
        params,
        belongsToRoute,
        layoutName: orphan.id,
        loading: orphan.loading === false ? null : orphan.loading,
        ...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
    });
    return segments;
}
/**
 * Resolve parallel EntryData with its loaders and slot handlers.
 */
export async function resolveParallelEntry(parallelEntry, params, context, belongsToRoute, parentShortCode, deps) {
    invariant(parallelEntry.type === "parallel", `Expected parallel entry, got: ${parallelEntry.type}`);
    const segments = [];
    const slots = parallelEntry.handler;
    for (const [slot, handler] of Object.entries(slots)) {
        let component;
        const hasLoadingFallback = parallelEntry.loading !== undefined && parallelEntry.loading !== false;
        if (hasLoadingFallback) {
            const result = typeof handler === "function" ? handler(context) : handler;
            component = result;
        }
        else {
            component =
                typeof handler === "function" ? await handler(context) : handler;
        }
        segments.push({
            id: `${parentShortCode}.${slot}`,
            namespace: parallelEntry.id,
            type: "parallel",
            index: 0,
            component,
            loading: parallelEntry.loading === false ? null : parallelEntry.loading,
            params,
            slot,
            belongsToRoute,
            parallelName: `${parallelEntry.id}.${slot}`,
            ...(parallelEntry.mountPath
                ? { mountPath: parallelEntry.mountPath }
                : {}),
        });
    }
    if (!parallelEntry.loading) {
        const loaderSegments = await resolveLoaders(parallelEntry, context, belongsToRoute, deps, parentShortCode);
        segments.push(...loaderSegments);
    }
    return segments;
}
/**
 * Wrapper that adds error boundary handling to segment resolution.
 */
export async function resolveWithErrorHandling(entry, routeKey, params, context, loaderPromises, resolveFn, deps, errorContext) {
    try {
        return await resolveFn();
    }
    catch (error) {
        if (error instanceof Response) {
            throw error;
        }
        if (error instanceof DataNotFoundError) {
            const notFoundFallback = deps.findNearestNotFoundBoundary(entry);
            if (notFoundFallback) {
                const notFoundInfo = createNotFoundInfo(error, entry.shortCode, entry.type, context.pathname);
                deps.callOnError(error, "handler", {
                    request: context.request,
                    url: context.url,
                    routeKey,
                    params,
                    segmentId: entry.shortCode,
                    segmentType: entry.type,
                    env: errorContext?.env,
                    isPartial: errorContext?.isPartial,
                    handledByBoundary: true,
                    metadata: { notFound: true, message: notFoundInfo.message },
                    requestStartTime: errorContext?.requestStartTime,
                });
                debugLog("segment", "notFound boundary handled error", {
                    segmentId: entry.shortCode,
                    message: notFoundInfo.message,
                });
                const reqCtx = getRequestContext();
                if (reqCtx) {
                    reqCtx.res = new Response(null, {
                        status: 404,
                        headers: reqCtx.res.headers,
                    });
                }
                const notFoundSegment = createNotFoundSegment(notFoundInfo, notFoundFallback, entry, params);
                return [notFoundSegment];
            }
        }
        const fallback = deps.findNearestErrorBoundary(entry);
        const segmentType = entry.type;
        const errorInfo = createErrorInfo(error, entry.shortCode, segmentType);
        const effectiveFallback = fallback ?? DefaultErrorFallback;
        deps.callOnError(error, "handler", {
            request: context.request,
            url: context.url,
            routeKey,
            params,
            segmentId: entry.shortCode,
            segmentType: entry.type,
            env: errorContext?.env,
            isPartial: errorContext?.isPartial,
            handledByBoundary: !!fallback,
            requestStartTime: errorContext?.requestStartTime,
        });
        debugLog("segment", "error boundary handled error", {
            segmentId: entry.shortCode,
            boundary: fallback ? "custom" : "default",
            message: errorInfo.message,
        });
        {
            const reqCtx = getRequestContext();
            if (reqCtx) {
                reqCtx.res = new Response(null, {
                    status: 500,
                    headers: reqCtx.res.headers,
                });
            }
        }
        const errorSegment = createErrorSegment(errorInfo, effectiveFallback, entry, params);
        return [errorSegment];
    }
}
/**
 * Resolve all segments for a route (used for single-cache-per-request pattern).
 */
export async function resolveAllSegments(entries, routeKey, params, context, loaderPromises, deps, options) {
    const allSegments = [];
    const seenIds = new Set();
    for (const entry of entries) {
        const resolvedSegments = await resolveWithErrorHandling(entry, routeKey, params, context, loaderPromises, () => resolveSegment(entry, routeKey, params, context, loaderPromises, deps, false, options), deps);
        // Deduplicate by segment ID. include() scopes can produce entries that
        // resolve the same shared layout/loader segment. Duplicates in the segment
        // array propagate to the client's matched[] and change the React tree depth.
        for (const seg of resolvedSegments) {
            if (!seenIds.has(seg.id)) {
                seenIds.add(seg.id);
                allSegments.push(seg);
            }
        }
    }
    return allSegments;
}
/**
 * Resolve only loader segments for all entries (used when serving cached non-loader segments).
 */
export async function resolveLoadersOnly(entries, context, deps) {
    const loaderSegments = [];
    for (const entry of entries) {
        const belongsToRoute = entry.type === "route";
        const segments = await resolveLoaders(entry, context, belongsToRoute, deps);
        loaderSegments.push(...segments);
    }
    return loaderSegments;
}
// ---------------------------------------------------------------------------
// Revalidation path (partial match)
// ---------------------------------------------------------------------------
/**
 * Resolve loaders with revalidation awareness (for partial rendering).
 * Returns both segments to render AND all matched segment IDs.
 */
export async function resolveLoadersWithRevalidation(entry, ctx, belongsToRoute, clientSegmentIds, prevParams, request, prevUrl, nextUrl, routeKey, deps, actionContext, shortCodeOverride, stale) {
    const loaderEntries = entry.loader ?? [];
    if (loaderEntries.length === 0)
        return { segments: [], matchedIds: [] };
    const shortCode = shortCodeOverride ?? entry.shortCode;
    const loaderMeta = loaderEntries.map(({ loader, revalidate: loaderRevalidateFns }, i) => ({
        loader,
        loaderRevalidateFns,
        segmentId: `${shortCode}D${i}.${loader.$$id}`,
        index: i,
    }));
    const matchedIds = loaderMeta.map((m) => m.segmentId);
    const revalidationChecks = await Promise.all(loaderMeta.map(async ({ loader, loaderRevalidateFns, segmentId, index }) => {
        const shouldRun = await revalidate(async () => {
            if (!clientSegmentIds.has(segmentId))
                return true;
            const dummySegment = {
                id: segmentId,
                namespace: entry.id,
                type: "loader",
                index,
                component: null,
                params: ctx.params,
                loaderId: loader.$$id,
                belongsToRoute,
            };
            return await evaluateRevalidation({
                segment: dummySegment,
                prevParams,
                getPrevSegment: null,
                request,
                prevUrl,
                nextUrl,
                revalidations: loaderRevalidateFns.map((fn, j) => ({
                    name: `loader-revalidate${j}`,
                    fn,
                })),
                routeKey,
                context: ctx,
                actionContext,
                stale,
            });
        }, async () => true, () => false);
        return { shouldRun, loader, segmentId, index };
    }));
    const loadersToRun = revalidationChecks.filter((c) => c.shouldRun);
    const segments = loadersToRun.map(({ loader, segmentId, index }) => ({
        id: segmentId,
        namespace: entry.id,
        type: "loader",
        index,
        component: null,
        params: ctx.params,
        loaderId: loader.$$id,
        loaderData: deps.wrapLoaderPromise(ctx.use(loader), entry, segmentId, ctx.pathname),
        belongsToRoute,
    }));
    return { segments, matchedIds };
}
/**
 * Resolve only loader segments for all entries with revalidation logic.
 */
export async function resolveLoadersOnlyWithRevalidation(entries, context, clientSegmentIds, prevParams, request, prevUrl, nextUrl, routeKey, deps, actionContext) {
    const allLoaderSegments = [];
    const allMatchedIds = [];
    for (const entry of entries) {
        const belongsToRoute = entry.type === "route";
        const { segments, matchedIds } = await resolveLoadersWithRevalidation(entry, context, belongsToRoute, clientSegmentIds, prevParams, request, prevUrl, nextUrl, routeKey, deps, actionContext);
        allLoaderSegments.push(...segments);
        allMatchedIds.push(...matchedIds);
    }
    return { segments: allLoaderSegments, matchedIds: allMatchedIds };
}
/**
 * Build a map of segment shortCode -> entry with revalidate functions.
 */
export function buildEntryRevalidateMap(entries) {
    const map = new Map();
    function processEntry(entry, parentShortCode) {
        map.set(entry.shortCode, { entry, revalidate: entry.revalidate });
        if (entry.type !== "parallel") {
            for (const parallelEntry of entry.parallel) {
                if (parallelEntry.type === "parallel") {
                    const slots = Object.keys(parallelEntry.handler);
                    for (const slot of slots) {
                        const parallelId = `${parallelEntry.shortCode}.${slot}`;
                        map.set(parallelId, {
                            entry: parallelEntry,
                            revalidate: parallelEntry.revalidate,
                        });
                    }
                }
            }
        }
        for (const layoutEntry of entry.layout) {
            processEntry(layoutEntry);
        }
    }
    for (const entry of entries) {
        processEntry(entry);
    }
    return map;
}
/**
 * Resolve parallel segments with revalidation.
 */
export async function resolveParallelSegmentsWithRevalidation(entry, params, context, belongsToRoute, clientSegmentIds, prevParams, request, prevUrl, nextUrl, routeKey, deps, actionContext, stale) {
    const segments = [];
    const matchedIds = [];
    for (const parallelEntry of entry.parallel) {
        invariant(parallelEntry.type === "parallel", `Expected parallel entry, got: ${parallelEntry.type}`);
        const slots = parallelEntry.handler;
        for (const [slot, handler] of Object.entries(slots)) {
            const parallelId = `${entry.shortCode}.${slot}`;
            const isFullRefetch = clientSegmentIds.size === 0;
            // When the parent layout is new (not in client's segment set),
            // all its parallel children must be resolved and tracked.
            // Without this, navigating to a new layout with parallels
            // (e.g., BlogLayout with @sidebar) from a different route
            // would silently drop those parallel segments.
            const isNewParent = !clientSegmentIds.has(entry.shortCode);
            if (isFullRefetch ||
                clientSegmentIds.has(parallelId) ||
                belongsToRoute ||
                isNewParent) {
                matchedIds.push(parallelId);
            }
            const shouldResolve = await (async () => {
                if (isFullRefetch)
                    return true;
                if (!clientSegmentIds.has(parallelId))
                    return belongsToRoute || isNewParent;
                const dummySegment = {
                    id: parallelId,
                    namespace: parallelEntry.id,
                    type: "parallel",
                    index: 0,
                    component: null,
                    params,
                    slot,
                    belongsToRoute,
                    parallelName: `${parallelEntry.id}.${slot}`,
                    ...(parallelEntry.mountPath
                        ? { mountPath: parallelEntry.mountPath }
                        : {}),
                };
                return await evaluateRevalidation({
                    segment: dummySegment,
                    prevParams,
                    getPrevSegment: null,
                    request,
                    prevUrl,
                    nextUrl,
                    revalidations: parallelEntry.revalidate.map((fn, i) => ({
                        name: `revalidate${i}`,
                        fn,
                    })),
                    routeKey,
                    context,
                    actionContext,
                    stale,
                });
            })();
            let component;
            const hasLoadingFallback = parallelEntry.loading !== undefined && parallelEntry.loading !== false;
            if (!shouldResolve) {
                component = null;
            }
            else if (hasLoadingFallback) {
                component =
                    (typeof handler === "function"
                        ? handler(context)
                        : handler);
            }
            else {
                component =
                    typeof handler === "function"
                        ? await handler(context)
                        : handler;
            }
            segments.push({
                id: parallelId,
                namespace: parallelEntry.id,
                type: "parallel",
                index: 0,
                component,
                loading: parallelEntry.loading === false ? null : parallelEntry.loading,
                params,
                slot,
                belongsToRoute,
                parallelName: `${parallelEntry.id}.${slot}`,
                ...(parallelEntry.mountPath
                    ? { mountPath: parallelEntry.mountPath }
                    : {}),
            });
        }
        if (!parallelEntry.loading) {
            const loaderResult = await resolveLoadersWithRevalidation(parallelEntry, context, belongsToRoute, clientSegmentIds, prevParams, request, prevUrl, nextUrl, routeKey, deps, actionContext, entry.shortCode, stale);
            segments.push(...loaderResult.segments);
            matchedIds.push(...loaderResult.matchedIds);
        }
    }
    return { segments, matchedIds };
}
/**
 * Resolve entry handler (layout, cache, or route) with revalidation.
 */
export async function resolveEntryHandlerWithRevalidation(entry, params, context, belongsToRoute, clientSegmentIds, prevParams, request, prevUrl, nextUrl, routeKey, deps, actionContext, stale) {
    const matchedId = entry.shortCode;
    const component = await revalidate(async () => {
        const hasSegment = clientSegmentIds.has(entry.shortCode);
        debugLog("segment.revalidate", "entry presence check", {
            segmentId: entry.shortCode,
            entryType: entry.type,
            clientHasSegment: hasSegment,
            belongsToRoute,
        });
        if (!hasSegment)
            return true;
        const dummySegment = {
            id: entry.shortCode,
            namespace: entry.id,
            type: entry.type === "cache"
                ? "layout"
                : entry.type,
            index: 0,
            component: null,
            params,
            belongsToRoute,
            ...(entry.type === "layout" || entry.type === "cache"
                ? { layoutName: entry.id }
                : {}),
            ...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
        };
        const shouldRevalidate = await evaluateRevalidation({
            segment: dummySegment,
            prevParams,
            getPrevSegment: null,
            request,
            prevUrl,
            nextUrl,
            revalidations: entry.revalidate.map((fn, i) => ({
                name: `revalidate${i}`,
                fn,
            })),
            routeKey,
            context,
            actionContext,
            stale,
        });
        debugLog("segment.revalidate", "entry revalidation decision", {
            segmentId: entry.shortCode,
            shouldRevalidate,
        });
        return shouldRevalidate;
    }, async () => {
        context._currentSegmentId = entry.shortCode;
        if (entry.type === "layout" || entry.type === "cache") {
            return typeof entry.handler === "function"
                ? handleHandlerResult(await entry.handler(context))
                : entry.handler;
        }
        const routeEntry = entry;
        if (!routeEntry.loading) {
            return handleHandlerResult(await routeEntry.handler(context));
        }
        if (!actionContext) {
            const result = handleHandlerResult(routeEntry.handler(context));
            return {
                content: result instanceof Promise ? deps.trackHandler(result) : result,
            };
        }
        debugLog("segment.action", "resolving action route with awaited value", {
            entryId: entry.id,
        });
        return {
            content: Promise.resolve(handleHandlerResult(await routeEntry.handler(context))),
        };
    }, () => null);
    const resolvedComponent = component && typeof component === "object" && "content" in component
        ? component.content
        : component;
    const segment = {
        id: entry.shortCode,
        namespace: entry.id,
        type: entry.type === "cache" ? "layout" : entry.type,
        index: 0,
        component: resolvedComponent,
        loading: entry.loading === false ? null : entry.loading,
        params,
        belongsToRoute,
        ...(entry.type === "layout" || entry.type === "cache"
            ? { layoutName: entry.id }
            : {}),
        ...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
    };
    return { segment, matchedId };
}
/**
 * Resolve segments with revalidation awareness (for partial rendering).
 */
export async function resolveSegmentWithRevalidation(entry, routeKey, params, context, clientSegmentIds, prevParams, request, prevUrl, nextUrl, loaderPromises, deps, actionContext, stale) {
    const segments = [];
    const matchedIds = [];
    const belongsToRoute = entry.type === "route";
    const loaderResult = await resolveLoadersWithRevalidation(entry, context, belongsToRoute, clientSegmentIds, prevParams, request, prevUrl, nextUrl, routeKey, deps, actionContext, undefined, stale);
    segments.push(...loaderResult.segments);
    matchedIds.push(...loaderResult.matchedIds);
    if (entry.type === "route") {
        for (const orphan of entry.layout) {
            const orphanResult = await resolveOrphanLayoutWithRevalidation(orphan, params, context, clientSegmentIds, prevParams, request, prevUrl, nextUrl, routeKey, loaderPromises, true, deps, actionContext, stale);
            segments.push(...orphanResult.segments);
            matchedIds.push(...orphanResult.matchedIds);
        }
    }
    const parallelResult = await resolveParallelSegmentsWithRevalidation(entry, params, context, belongsToRoute, clientSegmentIds, prevParams, request, prevUrl, nextUrl, routeKey, deps, actionContext, stale);
    segments.push(...parallelResult.segments);
    matchedIds.push(...parallelResult.matchedIds);
    if (entry.type === "layout" || entry.type === "cache") {
        for (const orphan of entry.layout) {
            const orphanResult = await resolveOrphanLayoutWithRevalidation(orphan, params, context, clientSegmentIds, prevParams, request, prevUrl, nextUrl, routeKey, loaderPromises, false, deps, actionContext, stale);
            segments.push(...orphanResult.segments);
            matchedIds.push(...orphanResult.matchedIds);
        }
    }
    const handlerResult = await resolveEntryHandlerWithRevalidation(entry, params, context, belongsToRoute, clientSegmentIds, prevParams, request, prevUrl, nextUrl, routeKey, deps, actionContext, stale);
    segments.push(handlerResult.segment);
    matchedIds.push(handlerResult.matchedId);
    return { segments, matchedIds };
}
/**
 * Resolve orphan layout with revalidation.
 */
export async function resolveOrphanLayoutWithRevalidation(orphan, params, context, clientSegmentIds, prevParams, request, prevUrl, nextUrl, routeKey, loaderPromises, belongsToRoute, deps, actionContext, stale) {
    invariant(orphan.type === "layout" || orphan.type === "cache", `Expected orphan to be a layout or cache, got: ${orphan.type}`);
    const segments = [];
    const matchedIds = [];
    const loaderResult = await resolveLoadersWithRevalidation(orphan, context, belongsToRoute, clientSegmentIds, prevParams, request, prevUrl, nextUrl, routeKey, deps, actionContext, undefined, stale);
    segments.push(...loaderResult.segments);
    matchedIds.push(...loaderResult.matchedIds);
    for (const parallelEntry of orphan.parallel) {
        invariant(parallelEntry.type === "parallel", `Expected parallel entry, got: ${parallelEntry.type}`);
        const loaderResult = await resolveLoadersWithRevalidation(parallelEntry, context, belongsToRoute, clientSegmentIds, prevParams, request, prevUrl, nextUrl, routeKey, deps, actionContext, undefined, stale);
        segments.push(...loaderResult.segments);
        matchedIds.push(...loaderResult.matchedIds);
        const slots = parallelEntry.handler;
        for (const [slot, handler] of Object.entries(slots)) {
            // Use orphan.shortCode (the parent layout) to match the SSR path
            // (resolveParallelEntry receives parentShortCode = orphan.shortCode).
            // Using parallelEntry.shortCode would generate IDs the client doesn't know about.
            const parallelId = `${orphan.shortCode}.${slot}`;
            matchedIds.push(parallelId);
            const shouldResolve = await (async () => {
                if (!clientSegmentIds.has(parallelId))
                    return true;
                const dummySegment = {
                    id: parallelId,
                    namespace: parallelEntry.id,
                    type: "parallel",
                    index: 0,
                    component: null,
                    params,
                    slot,
                    belongsToRoute,
                    parallelName: `${parallelEntry.id}.${slot}`,
                    ...(parallelEntry.mountPath
                        ? { mountPath: parallelEntry.mountPath }
                        : {}),
                };
                return await evaluateRevalidation({
                    segment: dummySegment,
                    prevParams,
                    getPrevSegment: null,
                    request,
                    prevUrl,
                    nextUrl,
                    revalidations: parallelEntry.revalidate.map((fn, i) => ({
                        name: `revalidate${i}`,
                        fn,
                    })),
                    routeKey,
                    context,
                    actionContext,
                    stale,
                });
            })();
            let component;
            const hasLoadingFallback = parallelEntry.loading !== undefined && parallelEntry.loading !== false;
            if (!shouldResolve) {
                component = null;
            }
            else if (hasLoadingFallback) {
                component =
                    (typeof handler === "function"
                        ? handler(context)
                        : handler);
            }
            else {
                component =
                    typeof handler === "function"
                        ? await handler(context)
                        : handler;
            }
            segments.push({
                id: parallelId,
                namespace: parallelEntry.id,
                type: "parallel",
                index: 0,
                component,
                loading: parallelEntry.loading === false ? null : parallelEntry.loading,
                params,
                slot,
                belongsToRoute,
                parallelName: `${parallelEntry.id}.${slot}`,
                ...(parallelEntry.mountPath
                    ? { mountPath: parallelEntry.mountPath }
                    : {}),
            });
        }
    }
    matchedIds.push(orphan.shortCode);
    const component = await revalidate(async () => {
        if (!clientSegmentIds.has(orphan.shortCode))
            return true;
        const dummySegment = {
            id: orphan.shortCode,
            namespace: orphan.id,
            type: "layout",
            index: 0,
            component: null,
            params,
            belongsToRoute,
            layoutName: orphan.id,
            ...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
        };
        return await evaluateRevalidation({
            segment: dummySegment,
            prevParams,
            getPrevSegment: null,
            request,
            prevUrl,
            nextUrl,
            revalidations: orphan.revalidate.map((fn, i) => ({
                name: `revalidate${i}`,
                fn,
            })),
            routeKey,
            context,
            actionContext,
            stale,
        });
    }, async () => typeof orphan.handler === "function"
        ? handleHandlerResult(await orphan.handler(context))
        : orphan.handler, () => null);
    segments.push({
        id: orphan.shortCode,
        namespace: orphan.id,
        type: "layout",
        index: 0,
        component,
        params,
        belongsToRoute,
        layoutName: orphan.id,
        loading: orphan.loading === false ? null : orphan.loading,
        ...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
    });
    return { segments, matchedIds };
}
/**
 * Wrapper for segment resolution with revalidation that adds error boundary handling.
 */
export async function resolveWithRevalidationErrorHandling(entry, params, resolveFn, deps, pathname, errorContext) {
    try {
        return await resolveFn();
    }
    catch (error) {
        if (error instanceof Response) {
            throw error;
        }
        if (error instanceof DataNotFoundError) {
            const notFoundFallback = deps.findNearestNotFoundBoundary(entry);
            if (notFoundFallback) {
                const notFoundInfo = createNotFoundInfo(error, entry.shortCode, entry.type, pathname);
                if (errorContext) {
                    deps.callOnError(error, "handler", {
                        request: errorContext.request,
                        url: errorContext.url,
                        routeKey: errorContext.routeKey,
                        params,
                        segmentId: entry.shortCode,
                        segmentType: entry.type,
                        env: errorContext.env,
                        isPartial: errorContext.isPartial,
                        handledByBoundary: true,
                        metadata: { notFound: true, message: notFoundInfo.message },
                        requestStartTime: errorContext.requestStartTime,
                    });
                }
                debugLog("segment", "notFound boundary handled error", {
                    segmentId: entry.shortCode,
                    message: notFoundInfo.message,
                });
                const reqCtx = getRequestContext();
                if (reqCtx) {
                    reqCtx.res = new Response(null, {
                        status: 404,
                        headers: reqCtx.res.headers,
                    });
                }
                const notFoundSegment = createNotFoundSegment(notFoundInfo, notFoundFallback, entry, params);
                return {
                    segments: [notFoundSegment],
                    matchedIds: [notFoundSegment.id],
                };
            }
        }
        const fallback = deps.findNearestErrorBoundary(entry);
        const segmentType = entry.type;
        const errorInfo = createErrorInfo(error, entry.shortCode, segmentType);
        const effectiveFallback = fallback ?? DefaultErrorFallback;
        if (errorContext) {
            deps.callOnError(error, "handler", {
                request: errorContext.request,
                url: errorContext.url,
                routeKey: errorContext.routeKey,
                params,
                segmentId: entry.shortCode,
                segmentType: entry.type,
                env: errorContext.env,
                isPartial: errorContext.isPartial,
                handledByBoundary: !!fallback,
                requestStartTime: errorContext.requestStartTime,
            });
        }
        debugLog("segment", "error boundary handled error", {
            segmentId: entry.shortCode,
            boundary: fallback ? "custom" : "default",
            message: errorInfo.message,
        });
        {
            const reqCtx = getRequestContext();
            if (reqCtx) {
                reqCtx.res = new Response(null, {
                    status: 500,
                    headers: reqCtx.res.headers,
                });
            }
        }
        const errorSegment = createErrorSegment(errorInfo, effectiveFallback, entry, params);
        return {
            segments: [errorSegment],
            matchedIds: [errorSegment.id],
        };
    }
}
/**
 * Resolve all segments for a route with revalidation logic (for matchPartial).
 */
export async function resolveAllSegmentsWithRevalidation(entries, routeKey, params, context, clientSegmentSet, prevParams, request, prevUrl, nextUrl, loaderPromises, actionContext, interceptResult, localRouteName, pathname, deps) {
    const allSegments = [];
    const matchedIds = [];
    const seenSegIds = new Set();
    const seenMatchIds = new Set();
    for (const entry of entries) {
        if (entry.type === "route" && interceptResult) {
            debugLog("matchPartial.intercept", "skipping route handler during intercept", {
                localRouteName,
                segmentId: entry.shortCode,
            });
            if (!seenMatchIds.has(entry.shortCode)) {
                seenMatchIds.add(entry.shortCode);
                matchedIds.push(entry.shortCode);
            }
            continue;
        }
        const nonParallelEntry = entry;
        const resolved = await resolveWithRevalidationErrorHandling(nonParallelEntry, params, () => resolveSegmentWithRevalidation(nonParallelEntry, routeKey, params, context, clientSegmentSet, prevParams, request, prevUrl, nextUrl, loaderPromises, deps, actionContext, false), deps, pathname);
        // Deduplicate segments and matchedIds by ID, matching resolveAllSegments.
        // include() scopes can produce entries that resolve the same shared
        // layout/loader segment. Duplicates cause React tree depth changes.
        for (const seg of resolved.segments) {
            if (!seenSegIds.has(seg.id)) {
                seenSegIds.add(seg.id);
                allSegments.push(seg);
            }
        }
        for (const id of resolved.matchedIds) {
            if (!seenMatchIds.has(id)) {
                seenMatchIds.add(id);
                matchedIds.push(id);
            }
        }
    }
    return { segments: allSegments, matchedIds };
}
//# sourceMappingURL=segment-resolution.js.map