// Keep this filename free of a secondary extension so declaration generation
// can rewrite relative imports for both ESM and CJS.
import { isNotFound } from './not-found'
import { isRedirect } from './redirect'
import { getLocationChangeInfo, runRouteLifecycle } from './router'
import { hydrateSsrMatchId } from './ssr/ssr-match-id'
import type { GLOBAL_SEROVAL, GLOBAL_TSR } from './ssr/constants'
import type { AnySerializationAdapter } from './ssr/serializer/transformer'
import type { TsrSsrGlobal } from './ssr/types'
import type { ParsedLocation } from './location'
import type { AnyRouteMatch } from './Matches'
import type { NotFoundError } from './not-found'
import type {
  AnyRoute,
  BeforeLoadContextOptions,
  LoaderFnContext,
  RouteContextOptions,
  RouteLoaderFn,
} from './route'
import type { AnyRedirect } from './redirect'
import type { AnyRouter } from './router'

type RouteComponentType =
  | 'component'
  | 'pendingComponent'
  | 'errorComponent'
  | 'notFoundComponent'

export function replaceRouteChunk(
  route: AnyRoute,
  lazyFn: AnyRoute['lazyFn'],
): void {
  route.lazyFn = lazyFn ?? route.lazyFn
  route._lazy = undefined
}

function preloadComponent(
  route: AnyRoute,
  type: RouteComponentType,
): Promise<void> | undefined {
  return (route.options[type] as any)?.preload?.()
}

function loadComponents(
  route: AnyRoute,
  onPendingReady?: () => void,
): Promise<void> | undefined {
  const component = preloadComponent(route, 'component')
  const pending = preloadComponent(route, 'pendingComponent')
  const pendingReady =
    onPendingReady && pending ? pending.then(onPendingReady) : pending
  if (onPendingReady && !pending) {
    onPendingReady()
  }
  if (component && pendingReady) {
    return Promise.all([component, pendingReady]).then(() => {})
  }
  return component ?? pendingReady
}

export function loadRouteChunk(
  route: AnyRoute,
  // `false` waits only for lazy route options, before a boundary is selected.
  componentType?: 'errorComponent' | 'notFoundComponent' | false,
  onPendingReady?: () => void,
): Promise<void> | undefined {
  const afterLazy = () =>
    componentType === false
      ? undefined
      : componentType
        ? preloadComponent(route, componentType)
        : loadComponents(route, onPendingReady)
  const current = route._lazy
  if (current) {
    return current === true ? afterLazy() : current.then(afterLazy)
  }
  if (!route.lazyFn) {
    return afterLazy()
  }

  const promise = route.lazyFn().then(
    (lazyRoute) => {
      // HMR clears the owner before an obsolete import can settle.
      if (process.env.NODE_ENV === 'production' || route._lazy === promise) {
        const { id: _id, ...options } = lazyRoute.options
        Object.assign(route.options, options)
        route._lazy = true
      }
    },
    (error) => {
      if (process.env.NODE_ENV === 'production' || route._lazy === promise) {
        route._lazy = undefined
      }
      throw error
    },
  )
  route._lazy = promise
  return promise.then(afterLazy)
}

/** Return the structural lane through the first terminal render boundary. */
export function _getRenderedMatches(
  matches: Array<AnyRouteMatch>,
): Array<AnyRouteMatch> {
  const end =
    matches.findIndex(
      (match) => match.status !== 'success' || match._notFound,
    ) + 1
  return end && end < matches.length ? matches.slice(0, end) : matches
}

/** Return the lane whose document assets belong to the current presentation. */
export function _getAssetMatches(
  matches: Array<AnyRouteMatch>,
): Array<AnyRouteMatch> {
  let end = matches.length
  for (let index = 0; index < end; index++) {
    const match = matches[index]!
    // `_assetEnd` is only ever set on hydration presentation clones that are
    // `status: 'pending'`, `ssr: 'data-only'`, error-free, and not not-found
    // (see hydrate.ts), and commits clear it — so its presence alone is the guard.
    if (match._assetEnd !== undefined) {
      end = Math.min(end, Math.max(index + 1, match._assetEnd))
      continue
    }
    if (match.status !== 'success' || match._notFound) {
      end = index + 1
      break
    }
  }
  // `end` only ever shrinks to `index + 1 >= 1`, so no zero guard is needed.
  return end < matches.length ? matches.slice(0, end) : matches
}

declare const lanePhase: unique symbol

type LanePhase = 'matched' | 'contextualized' | 'reduced' | 'projected'

/**
 * Lane matches carry their lane's phase so functions can demand evidence of
 * pipeline position (e.g. `commitMatches` only accepts a projected lane's
 * matches). The brand is phantom — it never exists at runtime.
 */
type LaneMatches<TPhase extends LanePhase> = Array<WorkMatch> & {
  readonly [lanePhase]?: TPhase
}

type Lane<TPhase extends LanePhase> = [
  location: ParsedLocation,
  matches: LaneMatches<TPhase>,
  background?: Array<BackgroundLoaderTask>,
  backgroundSettlement?: Promise<IndexedOutcome | undefined>,
] & { readonly [lanePhase]?: TPhase }

type MatchedLane = Lane<'matched'>
type ContextualizedLane = Lane<'contextualized'>
type ReducedLane = Lane<'reduced'>
type ProjectedLane = Lane<'projected'>

const SUCCESS = 0
const ERROR = 1
const NOT_FOUND = 2
// Control outcomes stay contiguous so the hot path can test them together.
const REDIRECTED = 3
const CANCELED = 4
const CANCELED_OUTCOME: [kind: typeof CANCELED] = [CANCELED]

type RedirectOutcome = [
  kind: typeof REDIRECTED,
  redirect: AnyRedirect,
  location?: ParsedLocation,
]

type NonRedirectOutcome =
  | [kind: typeof SUCCESS, data: unknown]
  | [kind: typeof ERROR, error: unknown]
  | [kind: typeof NOT_FOUND, error: NotFoundError]
  | [kind: typeof CANCELED]

type RawLoaderOutcome =
  | NonRedirectOutcome
  | [kind: typeof REDIRECTED, redirect: AnyRedirect]

type LoaderOutcome = NonRedirectOutcome | RedirectOutcome

type IndexedOutcome = [index: number, outcome: LoaderOutcome, boundary?: number]

export type LoaderFlight = [
  outcome: Promise<RawLoaderOutcome>,
  controller: AbortController,
  leases: number,
]

type WorkMatch = AnyRouteMatch & {
  _flight?: LoaderFlight
}

declare const matchPhase: unique symbol

/**
 * A match whose loader outcome has been applied by `settleInto`, which is the
 * sole granter of this brand (phantom, zero-runtime). Consumers that require
 * it — e.g. `cacheLoaderMatch` — can only be reached after settlement, so the
 * compiler enforces the loader→settle→cache ordering. Sources that arrive
 * already settled (dehydrated server data) must cast at a named boundary.
 */
type SettledMatch = WorkMatch & { readonly [matchPhase]: 'settled' }

export type LoadTransaction = [
  controller: AbortController,
  redirects: number,
  location: ParsedLocation,
  matches: Array<AnyRouteMatch>,
  startedAt: number,
  done: Promise<void>,
  /**
   * Dev-only HMR refresh mode. Presence forces successor rematerialization
   * until this publication is acknowledged. The optional hydration handoff is
   * retired when the refresh publishes.
   */
  refresh?: [handoff: NonNullable<AnyRouter['_handoff']> | undefined],
]

export type PendingSession = [
  generation: LoadTransaction,
  boundaryId: string,
  /** Pending reveal time until acknowledged, then minimum-visible-until time. */
  deadline: number,
  revealTimer?: ReturnType<typeof setTimeout>,
  ack?: Promise<boolean> | true,
  component?: unknown,
]

type CoordinatorRouter = AnyRouter & {
  /** Active speculative lanes retained for cancellation, invalidation, and cache clearing. */
  _preloads?: Map<AbortController, Array<AnyRouteMatch>>
  _refreshNextLoad?: boolean
}

type LoaderTask = [
  index: number,
  outcome: Promise<LoaderOutcome>,
  chunkFailure: Promise<IndexedOutcome | undefined>,
  candidate?: WorkMatch,
]

type BackgroundLoaderTask = [
  index: number,
  outcome: Promise<LoaderOutcome>,
  chunkFailure: Promise<IndexedOutcome | undefined>,
  candidate: WorkMatch,
]

type ExecuteLaneOptions = [
  controller: AbortController,
  redirects: number,
  base: Array<AnyRouteMatch>,
  preload?: boolean,
  sync?: boolean,
  forceStaleReload?: boolean,
  resolvedPrefix?: number,
  onReady?: () => void,
]

type ControlOutcome = RedirectOutcome | [kind: typeof CANCELED]

type LaneResult = ProjectedLane | ControlOutcome

function isControl(
  result: Lane<any> | ControlOutcome,
): result is ControlOutcome {
  return typeof result[0 /* location or kind */] === 'number'
}

export function waitFor<T>(
  value: T | PromiseLike<T>,
  signal: AbortSignal,
): Promise<T> {
  if (signal.aborted) {
    return Promise.race([Promise.reject(signal), value])
  }
  return new Promise<T>((resolve, reject) => {
    const abort = () => reject(signal)
    signal.addEventListener('abort', abort, { once: true })
    Promise.resolve(value)
      .then(resolve, reject)
      .finally(() => signal.removeEventListener('abort', abort))
  })
}

export function getRoute(router: AnyRouter, match: WorkMatch): AnyRoute {
  return (router.routesById as Record<string, AnyRoute>)[match.routeId]!
}

function normalize(
  value: unknown,
  rejected: boolean,
  routeId?: string,
): RawLoaderOutcome {
  if (isRedirect(value)) {
    return [REDIRECTED, value]
  }
  if (isNotFound(value)) {
    value.routeId ||= routeId
    return [NOT_FOUND, value]
  }
  if (rejected && typeof (value as any)?.then === 'function') {
    value = new Error('A Promise was thrown', { cause: value })
  }
  return rejected ? [ERROR, value] : [SUCCESS, value]
}

function normalizeError(route: AnyRoute, cause: unknown): RawLoaderOutcome {
  let outcome = normalize(cause, true, route.id)
  if (outcome[0 /* kind */] !== ERROR) {
    return outcome
  }
  try {
    route.options.onError?.(outcome[1 /* error */])
  } catch (onErrorCause) {
    outcome = normalize(onErrorCause, true, route.id)
  }
  return outcome
}

function normalizeLaneError(
  router: AnyRouter,
  lane: Lane<any>,
  route: AnyRoute,
  cause: unknown,
  options: ExecuteLaneOptions,
): LoaderOutcome {
  if (options[0 /* controller */].signal.aborted) {
    return CANCELED_OUTCOME
  }
  return materializeRedirect(
    router,
    lane,
    route,
    normalizeError(route, cause),
    options,
  )
}

async function contextualize(
  router: AnyRouter,
  lane: MatchedLane,
  options: ExecuteLaneOptions,
  end: number,
  planSuccessfulLane: () => void,
  retainedEnd: number,
): Promise<IndexedOutcome | undefined> {
  const [location, matches] = lane
  const signal = options[0 /* controller */].signal
  const preload = !!options[3 /* preload */]
  for (let index = options[6 /* resolvedPrefix */] ?? 0; index < end; index++) {
    const match = matches[index]!
    const route = getRoute(router, match)

    match.abortController = options[0 /* controller */]
    // Contextualization is serial, so the previous match already contains the
    // complete parent context for this route.
    const parentContext =
      matches[index - 1]?.context ?? router.options.context ?? {}
    const common = {
      params: match.params,
      location,
      navigate: (opts: any) =>
        router.navigate({
          ...opts,
          _fromLocation: location,
        }),
      buildLocation: router.buildLocation,
      cause: preload ? ('preload' as const) : match.cause,
      abortController: options[0 /* controller */],
      preload,
      matches,
      routeId: route.id,
    }
    let context = parentContext
    try {
      let routeContext = match._ctx
      if (!routeContext && route.options.context) {
        routeContext = match._ctx =
          route.options.context({
            ...common,
            deps: match.loaderDeps,
            context: parentContext,
          } satisfies RouteContextOptions<any, any, any, any, any>) || {}
      }
      context = {
        ...parentContext,
        ...routeContext,
      }
      match.context = context
    } catch (cause) {
      releaseFlight(router, match)
      return [index, normalizeLaneError(router, lane, route, cause, options)]
    }
    if (signal.aborted) {
      return [index, CANCELED_OUTCOME]
    }
    const validationError = match.paramsError ?? match.searchError
    if (validationError !== undefined) {
      releaseFlight(router, match)
      return [
        index,
        normalizeLaneError(router, lane, route, validationError, options),
      ]
    }
    const beforeLoad = route.options.beforeLoad
    if (!beforeLoad) {
      continue
    }

    const beforeLoadContext: BeforeLoadContextOptions<
      any,
      any,
      any,
      any,
      any,
      any,
      any,
      any,
      any
    > = {
      ...common,
      search: match.search,
      context,
      ...router.options.additionalContext,
    }

    const previousStatus = match.status
    if (index >= retainedEnd) {
      match.status = 'pending'
      options[7 /* onReady */]?.()
    }
    try {
      setFetching(router, match, 'beforeLoad', options[0 /* controller */])
      const result = await waitFor(beforeLoad(beforeLoadContext), signal)
      if (signal.aborted) {
        return [index, CANCELED_OUTCOME]
      }
      const outcome = materializeRedirect(
        router,
        lane,
        route,
        normalize(result, false, route.id),
        options,
      )
      if (outcome[0 /* kind */] !== SUCCESS) {
        releaseFlight(router, match)
        return [index, outcome]
      }
      match.context = {
        ...context,
        ...result,
      }
    } catch (cause) {
      releaseFlight(router, match)
      return [index, normalizeLaneError(router, lane, route, cause, options)]
    } finally {
      if (match.status === 'pending') {
        match.status = previousStatus
      }
      setFetching(router, match, false, options[0 /* controller */])
    }
  }

  // Let a synchronous lane claim predecessor flights before this frame yields.
  planSuccessfulLane()
  return
}

function releaseOwnedFlight(
  router: AnyRouter,
  match: WorkMatch,
  flight?: LoaderFlight,
): AbortController | undefined {
  if (!flight || --flight[2 /* leases */]) {
    return
  }
  if (router._flights?.get(match.id) === flight) {
    const current = router._tx
    if (
      current &&
      !current[0 /* controller */].signal.aborted &&
      !current[3 /* matches */].includes(match) &&
      current[3 /* matches */].some((candidate) => candidate.id === match.id) &&
      current[3 /* matches */].some(
        (candidate) => candidate.isFetching === 'beforeLoad',
      )
    ) {
      // Keep work discoverable only while the current lane is still running
      // beforeLoad. Loader planning performs the matching zero-owner sweep.
      return
    }
    router._flights.delete(match.id)
  }
  return flight[1 /* controller */]
}

function releaseFlight(router: AnyRouter, match: WorkMatch): void {
  const flight = match._flight
  match._flight = undefined
  releaseOwnedFlight(router, match, flight)?.abort()
}
/**
 * Not passing in a `next` ownership recipient
 * is equivalent to discarding the match resources
 */
function transferMatchResources(
  router: AnyRouter,
  previous: Array<AnyRouteMatch>,
  next?: Array<AnyRouteMatch>,
  deferSameIdFlight?: true,
): void {
  const abort: Array<AbortController> = []
  for (const match of previous as Array<WorkMatch>) {
    if (!next?.includes(match)) {
      const flight = match._flight
      match._flight = undefined
      if (
        deferSameIdFlight &&
        flight?.[2 /* leases */] === 1 &&
        router._flights?.get(match.id) === flight &&
        next?.some((candidate) => candidate.id === match.id)
      ) {
        // The successor has not made its same-ID reload decision yet.
        flight[2 /* leases */] = 0
      } else {
        const controller = releaseOwnedFlight(router, match, flight)
        if (controller) {
          abort.push(controller)
        }
      }
    }
  }
  for (const controller of abort) {
    controller.abort()
  }
}

function acquireMatchResources(matches: Array<AnyRouteMatch>): void {
  for (const match of matches as Array<WorkMatch>) {
    const flight = match._flight
    if (flight) {
      flight[2 /* leases */]++
    }
  }
}

function setFetching(
  router: AnyRouter,
  match: WorkMatch,
  value: AnyRouteMatch['isFetching'],
  owner?: AbortController,
): void {
  match.isFetching = value
  if (owner && router._tx?.[0 /* controller */] !== owner) {
    return
  }
  const store = router.stores.byRoute.get(match.routeId)
  const presented = store?.get()
  if (presented?.id === match.id) {
    store!.set({ ...presented, isFetching: value })
  }
}

function getLoaderContext(
  router: AnyRouter,
  lane: ContextualizedLane,
  match: WorkMatch,
  route: AnyRoute,
  controller: AbortController,
  parentMatchPromise: Promise<WorkMatch> | undefined,
  preload: boolean,
): LoaderFnContext {
  const location = lane[0 /* location */]
  return {
    params: match.params,
    location,
    navigate: (opts: any) =>
      router.navigate({
        ...opts,
        _fromLocation: location,
      }),
    cause: preload ? ('preload' as const) : match.cause,
    abortController: controller,
    preload,
    deps: match.loaderDeps,
    parentMatchPromise: parentMatchPromise as any,
    context: match.context,
    route,
    ...router.options.additionalContext,
  }
}

async function loadResource(
  router: AnyRouter,
  lane: ContextualizedLane,
  match: WorkMatch,
  route: AnyRoute,
  loader: RouteLoaderFn<any> | undefined,
  parentMatchPromise: Promise<WorkMatch> | undefined,
  options: ExecuteLaneOptions,
): Promise<LoaderOutcome> {
  const owner = options[0 /* controller */]
  const signal = owner.signal
  if (signal.aborted) {
    return CANCELED_OUTCOME
  }
  if (!loader) {
    return [SUCCESS, undefined]
  }

  let flight = match._flight
  setFetching(router, match, 'loader', owner)
  try {
    if (!flight) {
      const controller = new AbortController()
      flight = [
        Promise.resolve()
          .then(() =>
            loader(
              getLoaderContext(
                router,
                lane,
                match,
                route,
                controller,
                parentMatchPromise,
                !!options[3 /* preload */],
              ),
            ),
          )
          .then(
            (value) => normalize(value, false, route.id),
            (cause) => normalize(cause, true, route.id),
          )
          .then((result): RawLoaderOutcome => {
            // The registry controls discovery; leases keep current consumers
            // sharing the same terminal outcome.
            if (
              result[0 /* kind */] !== SUCCESS &&
              router._flights?.get(match.id) === flight
            ) {
              router._flights!.delete(match.id)
              if (!flight![2 /* leases */]) {
                controller.abort()
              }
            }
            return result[0 /* kind */] === ERROR && flight![2 /* leases */]
              ? normalizeError(route, result[1 /* error */])
              : result
          }),
        controller,
        1,
      ]
      ;(router._flights ??= new Map()).set(match.id, flight)
    }
    match._flight = flight
    match.abortController = flight[1 /* controller */]
    return materializeRedirect(
      router,
      lane,
      route,
      await waitFor(flight[0 /* outcome */], signal),
      options,
    )
  } catch (cause) {
    if (cause !== signal || !signal.aborted) {
      throw cause
    }
    releaseFlight(router, match)
    return CANCELED_OUTCOME
  } finally {
    setFetching(router, match, false, owner)
  }
}

function settleInto(
  match: WorkMatch,
  result: LoaderOutcome,
  preload: boolean,
): asserts match is SettledMatch {
  if (result[0 /* kind */] === SUCCESS) {
    match.loaderData = result[1 /* data */]
    match.error = undefined
    match.status = 'success'
    match.invalid = false
    match.updatedAt = Date.now()
    match.preload = preload
  } else if (result[0 /* kind */] !== REDIRECTED) {
    // Reduction installs only the selected terminal failure. Every other
    // settled attempt remains a renderable, stale match in that lane.
    match.status = 'success'
    match.error = undefined
    match.invalid = true
  }
}

export function cacheLoaderMatch(
  router: CoordinatorRouter,
  match: SettledMatch,
  planned: AnyRouteMatch | undefined,
): void {
  const current = router._cache.get(match.id) as WorkMatch | undefined
  if (
    current !== planned ||
    router._committed.some(
      (candidate) =>
        candidate.id === match.id &&
        (candidate as WorkMatch)._flight === match._flight,
    )
  ) {
    return
  }
  const cached = {
    ...match,
    _notFound: undefined,
    context: {},
  } as WorkMatch
  if (cached._flight) {
    cached._flight[2 /* leases */]++
  }
  router._cache.set(match.id, cached)
  if (current) {
    releaseFlight(router, current)
  }
}

function getParentSnapshot(
  match: WorkMatch,
  outcome: LoaderOutcome,
): WorkMatch {
  if (outcome[0 /* kind */] === ERROR || outcome[0 /* kind */] === NOT_FOUND) {
    return {
      ...match,
      status: outcome[0 /* kind */] === ERROR ? 'error' : 'notFound',
      error: outcome[1 /* error */],
      _flight: undefined,
    }
  }
  return match
}

function createLoaderTask(
  router: AnyRouter,
  lane: ContextualizedLane,
  index: number,
  tasks: Array<LoaderTask>,
  semanticParent: Promise<WorkMatch> | undefined,
  options: ExecuteLaneOptions,
  retainedEnd: number,
): Promise<WorkMatch> {
  const match = lane[1 /* matches */][index]!
  const route = getRoute(router, match)
  const preload = !!options[3 /* preload */]
  const plannedCacheMatch = router._cache.get(match.id)
  let configured
  let reload = false
  let reloadFailure: LoaderOutcome | undefined
  try {
    if (match.status === 'success') {
      configured = route.options.shouldReload
      if (typeof configured === 'function') {
        configured = configured(
          getLoaderContext(
            router,
            lane,
            match,
            route,
            options[0 /* controller */],
            semanticParent,
            preload,
          ),
        )
      }
      if (options[0 /* controller */].signal.aborted) {
        reloadFailure = CANCELED_OUTCOME
      }
    }
    if (!reloadFailure) {
      if (match.status !== 'success') {
        reload = true
      } else {
        const staleAge =
          options[3 /* preload */] || match.preload
            ? (route.options.preloadStaleTime ??
              router.options.defaultPreloadStaleTime ??
              30_000)
            : (route.options.staleTime ?? router.options.defaultStaleTime ?? 0)
        reload = !!(
          match.invalid ||
          configured ||
          (configured === undefined &&
            Date.now() - match.updatedAt >= staleAge &&
            (options[5 /* forceStaleReload */] ||
              match.cause === 'enter' ||
              options[2 /* base */].some(
                (candidate) =>
                  candidate.routeId === match.routeId &&
                  candidate.id !== match.id,
              )))
        )
      }
    }
  } catch (cause) {
    match.invalid = true
    releaseFlight(router, match)
    reloadFailure = normalizeLaneError(router, lane, route, cause, options)
  }
  const routeLoader = route.options.loader
  const loader =
    typeof routeLoader === 'function' ? routeLoader : routeLoader?.handler
  let donor =
    (!preload || route.options.preload !== false) &&
    routeLoader &&
    !(process.env.NODE_ENV !== 'production' && router._tx?.[6 /* refresh */])
      ? router._flights?.get(match.id)
      : undefined
  if (donor === match._flight || reloadFailure) {
    donor = undefined
  } else if (donor && !reload && !preload && configured === undefined) {
    // Normal cache policy accepts an already-running generation even when this
    // lane itself would not have started another loader.
    reload = true
  } else if (!reload) {
    donor = undefined
  }
  const background = !!(
    routeLoader &&
    reload &&
    match.status === 'success' &&
    !preload &&
    !options[4 /* sync */] &&
    ((typeof routeLoader === 'function'
      ? undefined
      : routeLoader?.staleReloadMode) ??
      router.options.defaultStaleReloadMode) !== 'blocking'
  )
  const loaded = reload && (!preload || route.options.preload !== false)
  const blocking =
    loaded && !background && (match.status !== 'success' || !!routeLoader)
  const onReady = index >= retainedEnd ? options[7 /* onReady */] : undefined
  const onLazyReady = route.lazyFn && route._lazy !== true ? onReady : undefined
  if (loaded && !routeLoader) {
    match.invalid = false
    match.updatedAt = Date.now()
  }
  if (donor) {
    donor[2 /* leases */]++
  }
  if (blocking) {
    const acceptedFlight = match._flight
    match._flight = donor
    releaseOwnedFlight(router, match, acceptedFlight)?.abort()
    // A mounted success remains renderable while its loader revalidates. Every
    // non-retained blocking generation presents pending state.
    if (index >= retainedEnd) {
      match.status = 'pending'
    }
    onReady?.()
  }
  if (!loaded) {
    match.isFetching = false
  }
  const loaderOutcome = reloadFailure
    ? Promise.resolve(reloadFailure)
    : !blocking
      ? Promise.resolve<LoaderOutcome>([SUCCESS, match.loaderData])
      : loadResource(
          router,
          lane,
          match,
          route,
          loader,
          semanticParent,
          options,
        )
  const outcome = loaderOutcome.then((result) => {
    if (blocking) {
      settleInto(match, result, preload)
      if (result[0 /* kind */] === SUCCESS) {
        // A settled generation can outlive its lane without keeping unresolved
        // navigation work alive.
        if (routeLoader && !options[0 /* controller */].signal.aborted) {
          cacheLoaderMatch(router, match, plannedCacheMatch)
        }
        // A route is renderable only after both its data and normal component
        // chunk are ready. Its loader data is already available to descendants.
        if (index >= retainedEnd) {
          match.status = 'pending'
        }
      }
    }
    return result
  })

  const chunkOutcome = waitFor(
    Promise.resolve().then(() => loadRouteChunk(route, undefined, onLazyReady)),
    options[0 /* controller */].signal,
  ).then(
    () => undefined,
    (cause): IndexedOutcome | undefined =>
      lane[1 /* matches */].some(
        (candidate, candidateIndex) =>
          candidateIndex <= index &&
          (candidate.status === 'error' ||
            candidate.status === 'notFound' ||
            candidate._notFound),
      )
        ? undefined
        : [index, normalizeLaneError(router, lane, route, cause, options)],
  )
  const chunkFailure = chunkOutcome.then((failure) =>
    outcome.then((result) => {
      if (
        blocking &&
        !failure &&
        result[0 /* kind */] === SUCCESS &&
        match.status === 'pending' &&
        !options[0 /* controller */].signal.aborted
      ) {
        match.status = 'success'
        onReady?.()
      }
      return failure
    }),
  )
  tasks.push([index, outcome, chunkFailure])
  if (!background) {
    return outcome.then((result) => getParentSnapshot(match, result))
  }
  const candidate: WorkMatch = {
    ...match,
    status: 'pending',
    preload: false,
    _flight: donor,
  }
  match.invalid = false
  match.isFetching = 'loader'
  const backgroundOutcome = loadResource(
    router,
    lane,
    candidate,
    route,
    loader,
    semanticParent,
    options,
  ).then((result) => {
    match.isFetching = false
    settleInto(candidate, result, false)
    return result
  })
  ;(lane[2 /* background */] ??= []).push([
    index,
    backgroundOutcome,
    chunkFailure,
    candidate,
  ])
  return backgroundOutcome.then((result) =>
    getParentSnapshot(candidate, result),
  )
}

async function getNotFoundBoundary(
  router: AnyRouter,
  matches: Array<WorkMatch>,
  indexed: IndexedOutcome | undefined,
  signal: AbortSignal,
  fallback = 0,
): Promise<number> {
  const cause = indexed?.[1 /* outcome */][1 /* error or redirect */] as
    | NotFoundError
    | undefined
  let index = cause?.routeId
    ? matches.findIndex((match) => match.routeId === cause.routeId)
    : (indexed?.[0 /* index */] ?? matches.length - 1)
  if (index < 0) {
    index = 0
  }
  for (let i = index; i >= 0; i--) {
    const route = getRoute(router, matches[i]!)
    try {
      const loading = loadRouteChunk(route, false)
      if (loading) {
        await waitFor(loading, signal)
      }
    } catch (cause) {
      if (cause === signal && signal.aborted) {
        throw cause
      }
    }
    if (route.options.notFoundComponent) {
      return i
    }
  }
  return cause?.routeId ? index : fallback
}

function discardBackground(router: AnyRouter, lane: Lane<any>): void {
  if (lane[2 /* background */]) {
    transferMatchResources(
      router,
      lane[2 /* background */].map((task) => task[3 /* candidate */]),
    )
    lane[2 /* background */] = undefined
  }
}

async function settleTasks(
  tasks: Array<LoaderTask>,
  serialFailure?: IndexedOutcome,
  redirectTasks?: Array<BackgroundLoaderTask>,
  gate?: number | Promise<number>,
): Promise<IndexedOutcome | undefined> {
  let loaderFailure: IndexedOutcome | undefined

  try {
    await Promise.all(
      tasks.map((task) =>
        task[1 /* outcome */].then(async (outcome) => {
          const taskIndex = task[0 /* index */]
          if (gate && taskIndex >= (await gate)) {
            return
          }
          if (outcome[0 /* kind */] >= REDIRECTED) {
            throw [taskIndex, outcome] as IndexedOutcome
          }
          if (!loaderFailure && outcome[0 /* kind */] !== SUCCESS) {
            loaderFailure = [taskIndex, outcome]
            // Every started descendant must settle before an ordinary failure
            // wins because a redirect from any of them remains control flow.
            await Promise.all(
              (redirectTasks ?? []).map((nextTask) => {
                if (nextTask[0 /* index */] <= taskIndex) {
                  return
                }
                return nextTask[1 /* outcome */].then((nextOutcome) => {
                  if (nextOutcome[0 /* kind */] === REDIRECTED) {
                    throw [
                      nextTask[0 /* index */],
                      nextOutcome,
                    ] as IndexedOutcome
                  }
                })
              }),
            )
          }
        }),
      ),
    )
  } catch (cause) {
    return cause as IndexedOutcome
  }
  return serialFailure ?? loaderFailure
}

function materializeRedirect(
  router: AnyRouter,
  lane: Lane<any>,
  route: AnyRoute,
  outcome: RawLoaderOutcome,
  options: ExecuteLaneOptions,
  failed?: true,
): LoaderOutcome {
  while (outcome[0 /* kind */] === REDIRECTED) {
    const redirect = outcome[1 /* redirect */]
    if (
      redirect.options.reloadDocument
        ? options[3 /* preload */]
        : options[1 /* redirects */] >= 20
    ) {
      return outcome
    }
    try {
      if (redirect.options.href && redirect.options.reloadDocument) {
        router.resolveRedirect(redirect)
        return outcome
      }
      return [
        REDIRECTED,
        redirect,
        router.buildLocation({
          ...redirect.options,
          _fromLocation: lane[0 /* location */],
          _includeValidateSearch: true,
        }),
      ]
    } catch (cause) {
      outcome = failed ? [ERROR, cause] : normalizeError(route, cause)
      failed = true
    }
  }
  return outcome
}

async function reduceLane(
  router: AnyRouter,
  lane: ContextualizedLane,
  tasks: Array<LoaderTask>,
  controller: AbortController,
  settlement: Promise<IndexedOutcome | undefined>,
  onReady?: () => void,
): Promise<ReducedLane | ControlOutcome> {
  const matches = lane[1 /* matches */]
  let failure = await settlement
  let redirectLimitExceeded = false
  const plannedBoundary = matches.findIndex((match) => match._notFound)
  const boundaryOf = (found: IndexedOutcome) =>
    found[1 /* outcome */][0 /* kind */] === NOT_FOUND
      ? getNotFoundBoundary(router, matches, found, controller.signal)
      : found[0 /* index */]
  let readinessEnd = plannedBoundary < 0 ? matches.length : plannedBoundary

  if ((failure?.[1 /* outcome */][0 /* kind */] ?? 0) >= REDIRECTED) {
    readinessEnd = 0
  } else if (failure) {
    readinessEnd = failure[2 /* boundary */] ??= await boundaryOf(failure)
    for (const task of tasks) {
      if (task[0 /* index */] >= readinessEnd) {
        break
      }
      const outcome = await task[1 /* outcome */]
      // Presence means a loader previously succeeded, even with `undefined`.
      if (
        outcome[0 /* kind */] !== SUCCESS &&
        outcome[0 /* kind */] < REDIRECTED &&
        !('loaderData' in matches[task[0 /* index */]]!)
      ) {
        failure = [task[0 /* index */], outcome]
        readinessEnd = failure[2 /* boundary */] = await boundaryOf(failure)
        break
      }
    }
  }

  for (const task of tasks) {
    if (task[0 /* index */] >= readinessEnd) {
      break
    }
    const chunkFailure = await task[2 /* chunkFailure */]
    if (!chunkFailure) {
      continue
    }
    failure = chunkFailure
    break
  }

  if ((failure?.[1 /* outcome */][0 /* kind */] ?? 0) >= REDIRECTED) {
    const outcome = failure![1 /* outcome */]
    if (
      outcome[0 /* kind */] !== REDIRECTED ||
      outcome[1 /* redirect */].options.reloadDocument ||
      outcome[2 /* location */]
    ) {
      discardBackground(router, lane)
      return outcome as ControlOutcome
    }
    redirectLimitExceeded = true
    failure = [0, [ERROR, new Error('Too many redirects')]]
  }

  const boundary = failure
    ? (failure[2 /* boundary */] ?? (await boundaryOf(failure)))
    : plannedBoundary
  if (boundary >= 0) {
    const outcome = failure?.[1 /* outcome */]
    const kind = outcome?.[0 /* kind */]
    const match = matches[boundary]!
    const cause = outcome?.[1 /* error or redirect */]
    const install = () => {
      if (outcome) {
        match._notFound = undefined
        if (kind === ERROR) {
          match.status = 'error'
        } else {
          ;(cause as NotFoundError).routeId = match.routeId
          if (match.routeId === router.routeTree.id) {
            match.status = 'success'
            match._notFound = true
          } else {
            match.status = 'notFound'
          }
        }
        match.error = cause
        match.isFetching = false
      }
    }
    install()
    if (!outcome) {
      onReady?.()
    }
    const route = getRoute(router, match)
    try {
      await waitFor<unknown>(
        outcome
          ? Promise.resolve().then(() =>
              loadRouteChunk(
                route,
                kind === ERROR ? 'errorComponent' : 'notFoundComponent',
              ),
            )
          : Promise.all([
              loadRouteChunk(route),
              loadRouteChunk(route, 'notFoundComponent'),
            ]),
        controller.signal,
      )
    } catch (cause) {
      if (cause === controller.signal && controller.signal.aborted) {
        discardBackground(router, lane)
        return CANCELED_OUTCOME
      }
    }
    if (!outcome) {
      match.status = 'success'
    } else if (redirectLimitExceeded) {
      controller.abort()
      await Promise.all([
        ...tasks.map((task) => task[1 /* outcome */]),
        ...tasks.map((task) => task[2 /* chunkFailure */]),
        ...(lane[2 /* background */] ?? []).map(
          (task) => task[1 /* outcome */],
        ),
      ])
      discardBackground(router, lane)
      transferMatchResources(router, matches)
      install()
    }
  }

  return lane as ReducedLane
}

export async function projectLane(
  router: AnyRouter,
  lane: ReducedLane,
  signal: AbortSignal,
  start = 0,
  end = lane[1 /* matches */].length,
): Promise<ProjectedLane> {
  const matches = lane[1 /* matches */]
  for (let index = start; index < end; index++) {
    const match = matches[index]!
    const routeOptions = getRoute(router, match).options
    if (routeOptions.head || routeOptions.scripts) {
      try {
        const context = {
          ssr: router.options.ssr,
          matches,
          match,
          params: match.params,
          loaderData: match.loaderData,
        }
        const [head, scripts] = await waitFor(
          Promise.all([
            routeOptions.head?.(context),
            routeOptions.scripts?.(context),
          ]),
          signal,
        )
        match.meta = head?.meta
        match.links = head?.links
        match.headScripts = head?.scripts
        match.styles = head?.styles
        match.scripts = scripts
      } catch (cause) {
        if (cause === signal && signal.aborted) {
          break
        }
        console.error(cause)
      }
    }
    if (match.status !== 'success' || match._notFound) {
      break
    }
  }
  return lane as ProjectedLane
}

async function executeClientLane(
  router: AnyRouter,
  location: ParsedLocation,
  matches: Array<AnyRouteMatch>,
  options: ExecuteLaneOptions,
): Promise<LaneResult> {
  const matched = [location, matches as Array<WorkMatch>] as MatchedLane
  const signal = options[0 /* controller */].signal
  let reduced: ReducedLane | ControlOutcome
  try {
    const presented = router.stores.matches.get()
    let plannedBoundary = matches.findIndex((match) => match._notFound)
    if (router.options.notFoundMode !== 'root' && plannedBoundary >= 0) {
      const boundary = await getNotFoundBoundary(
        router,
        matched[1 /* matches */],
        undefined,
        signal,
        plannedBoundary,
      )
      if (boundary !== plannedBoundary) {
        matches[plannedBoundary]!._notFound = undefined
        matches[boundary]!._notFound = true
      }
      plannedBoundary = boundary
    }
    let end = plannedBoundary < 0 ? matches.length : plannedBoundary + 1
    let retainedEnd = 0
    while (retainedEnd < end && retainedEnd !== plannedBoundary) {
      const match = matches[retainedEnd]!
      const committed = options[2 /* base */][retainedEnd]
      const visible = presented[retainedEnd]
      if (
        committed?.id !== match.id ||
        committed.status !== 'success' ||
        committed._notFound ||
        match.preload ||
        visible?.id !== match.id ||
        visible.status !== 'success' ||
        visible._notFound
      ) {
        break
      }
      retainedEnd++
    }
    const tasks: Array<LoaderTask> = []
    const start = options[6 /* resolvedPrefix */] ?? 0
    let semanticParent = start
      ? Promise.resolve(matched[1 /* matches */][start - 1]!)
      : undefined
    const planSuccessfulLane = () => {
      for (let index = start; index < end; index++) {
        if (signal.aborted) {
          break
        }
        semanticParent = createLoaderTask(
          router,
          matched as ContextualizedLane,
          index,
          tasks,
          semanticParent,
          options,
          retainedEnd,
        )
      }
    }
    // From here on `matched` is contextualized: `contextualize` communicates
    // through mutation plus a failure return, so the phase brand is asserted at
    // the two use sites below rather than granted by a (byte-costing) return.
    const failure = await contextualize(
      router,
      matched,
      options,
      end,
      planSuccessfulLane,
      retainedEnd,
    )
    if (failure) {
      options[4 /* sync */] = true
      end = failure[0 /* index */]
      if (failure[1 /* outcome */][0 /* kind */] === NOT_FOUND) {
        const boundary = await getNotFoundBoundary(
          router,
          matched[1 /* matches */],
          failure,
          signal,
        )
        failure[2 /* boundary */] = boundary
        end = Math.min(end, boundary + 1)
      } else if (failure[1 /* outcome */][0 /* kind */] >= REDIRECTED) {
        end = 0
      }
      planSuccessfulLane()
    }
    if (!signal.aborted && !options[3 /* preload */]) {
      const abort: Array<AbortController> = []
      for (const [id, flight] of router._flights ?? []) {
        if (!flight[2 /* leases */]) {
          router._flights!.delete(id)
          abort.push(flight[1 /* controller */])
        }
      }
      for (const controller of abort) {
        controller.abort()
      }
    }
    const reduction = reduceLane(
      router,
      matched as ContextualizedLane,
      tasks,
      options[0 /* controller */],
      settleTasks(tasks, failure, matched[2 /* background */]),
      options[7 /* onReady */],
    )
    if (matched[2 /* background */]?.length) {
      matched[3 /* backgroundSettlement */] = settleTasks(
        matched[2 /* background */],
        undefined,
        undefined,
        reduction.then(
          (foreground) =>
            isControl(foreground)
              ? 0
              : _getRenderedMatches(foreground[1 /* matches */]).length,
          () => 0,
        ),
      )
    }
    reduced = await reduction
  } catch (cause) {
    discardBackground(router, matched)
    if (cause === signal && signal.aborted) {
      return CANCELED_OUTCOME
    }
    throw cause
  }
  if (isControl(reduced)) {
    return reduced
  }
  return projectLane(
    router,
    reduced,
    signal,
    options[6 /* resolvedPrefix */] === reduced[1 /* matches */].length
      ? options[6 /* resolvedPrefix */]
      : 0,
  )
}

/**
 * Waits for `pendingMs`, then presents the complete lane. Rendering applies the
 * selected boundary cutoff while retaining every match's structural state.
 * A replacement load for the same match keeps the timer; choosing a different
 * match resets it. `pendingMinMs` starts after the fallback renders.
 */
function offerPending(router: CoordinatorRouter, tx: LoadTransaction): void {
  if (router._tx !== tx) {
    return
  }
  const matches = tx[3 /* matches */]
  const presented = router.stores.matches.get()
  let session = router._pending
  for (let index = 0; index < matches.length; index++) {
    const match = matches[index]!
    const success = match.status === 'success' && !match._notFound
    const presentedPending =
      presented[index]?.id === match.id &&
      presented[index]?.status === 'pending'
    if (success && !presentedPending) {
      continue
    }
    const route = getRoute(router, match as WorkMatch)
    const delay =
      (success && presentedPending) || match.invalid
        ? 0
        : (route.options.pendingMs ?? router.options.defaultPendingMs)
    const component =
      route.options.pendingComponent ??
      (router.options as any).defaultPendingComponent
    if (!component || typeof delay !== 'number' || delay === Infinity) {
      if (session) {
        session[0 /* generation */] = tx
        session[2 /* deadline */] = 0
        session[4 /* ack */] = true
      }
      return
    }
    const min =
      route.options.pendingMinMs ?? router.options.defaultPendingMinMs ?? 0
    let tookOver = false
    if (session?.[1 /* boundaryId */] === match.id) {
      tookOver = session[0 /* generation */] !== tx
      session[0 /* generation */] = tx
    } else {
      clearTimeout(session?.[3 /* revealTimer */])
      router._pending = session = undefined
    }
    if (!session) {
      // Hydration and redirects can preserve pending presentation without a session.
      // Do not delay it again; conservatively start pendingMinMs from now.
      router._pending = session = [
        tx,
        match.id,
        presentedPending ? Date.now() + min : tx[4 /* startedAt */] + delay,
        undefined,
        presentedPending || undefined,
        component,
      ]
    }
    if (
      session[4 /* ack */] &&
      !tookOver &&
      session[5 /* component */] === component
    ) {
      return
    }
    session[5 /* component */] = component
    if (!session[4 /* ack */]) {
      clearTimeout(session[3 /* revealTimer */])
      const remaining = session[2 /* deadline */] - Date.now()
      if (remaining > 0) {
        session[3 /* revealTimer */] = setTimeout(
          () => offerPending(router, tx),
          remaining,
        )
        return
      }
      session[2 /* deadline */] = 0
    }
    const offered = matches.map((match) => ({
      ...match,
      _flight: undefined,
    }))
    offered[index]!.status = 'pending'
    const ack = (session[4 /* ack */] = router
      .startTransition(() => router.stores.setMatches(offered), offered)
      .then((rendered) => {
        if (
          rendered &&
          router._pending === session &&
          session![4 /* ack */] === ack &&
          !session![2 /* deadline */]
        ) {
          session![2 /* deadline */] = Date.now() + min
        }
        return rendered
      }))
    return
  }
}

/**
 * Cancels pending UI timing unless the current successor can take over the
 * same boundary that remains painted.
 */
function finishPending(router: CoordinatorRouter, tx: LoadTransaction): void {
  const session = router._pending
  if (
    router._tx === tx ||
    !router._tx?.[3 /* matches */].some(
      (match) => match.id === session?.[1 /* boundaryId */],
    )
  ) {
    clearTimeout(session?.[3 /* revealTimer */])
    router._pending = undefined
  }
}

async function awaitPendingMinimum(
  router: CoordinatorRouter,
  tx: LoadTransaction,
): Promise<void> {
  const session = router._pending
  if (!session) {
    return
  }
  clearTimeout(session[3 /* revealTimer */])
  const remaining = session[2 /* deadline */] - Date.now()
  if (
    !session[4 /* ack */] ||
    remaining <= 0 ||
    !_getRenderedMatches(tx[3 /* matches */]).some(
      (match) => match.id === session[1 /* boundaryId */],
    )
  ) {
    return
  }
  let timer: ReturnType<typeof setTimeout> | undefined
  try {
    await waitFor(
      new Promise<void>((resolve) => {
        timer = setTimeout(resolve, remaining)
      }),
      tx[0 /* controller */].signal,
    )
  } catch {}
  clearTimeout(timer)
}

function publishMatches(
  router: CoordinatorRouter,
  matches: Array<AnyRouteMatch>,
): void {
  router._committed = matches
  router.stores.setMatches(matches)
}

function commitMatches(
  router: CoordinatorRouter,
  tx: LoadTransaction,
  matches: LaneMatches<'projected'>,
  resolvedPrefix?: number,
): void {
  const previous = router._committed
  const previousCached = router._cache
  for (const match of matches) {
    match.preload = false
    if (resolvedPrefix) {
      match._assetEnd = undefined
    }
  }
  const cut = _getRenderedMatches(matches).length
  const cached = new Map<string, AnyRouteMatch>()
  if (process.env.NODE_ENV === 'production' || !tx[6 /* refresh */]) {
    const now = Date.now()
    for (const match of [...previous, ...previousCached.values()]) {
      // Rendered-prefix ids and settled successes anywhere in the lane are
      // authoritative: retaining an older same-id generation would shadow them
      // at the next planning pass. Unsettled beyond-boundary matches are not —
      // they must not evict a newer same-id preload.
      if (
        match.status !== 'success' ||
        matches.some(
          (candidate, index) =>
            candidate.id === match.id &&
            (index < cut || candidate.status === 'success'),
        )
      ) {
        continue
      }
      const work = match as WorkMatch
      const route = getRoute(router, work)
      if (
        !route.options.loader ||
        now - match.updatedAt >=
          (match.preload
            ? (route.options.preloadGcTime ??
              router.options.defaultPreloadGcTime ??
              300_000)
            : (route.options.gcTime ?? router.options.defaultGcTime ?? 300_000))
      ) {
        continue
      }
      cached.set(
        match.id,
        previousCached.get(match.id) === match
          ? match
          : ({
              ...match,
              _flight: undefined,
              isFetching: false,
              context: {},
            } as WorkMatch),
      )
    }
  }
  // The lane becomes committed before publication can synchronously reenter.
  tx[3 /* matches */] = []
  router._cache = cached
  publishMatches(router, matches)
  transferMatchResources(
    router,
    [...previousCached.values(), ...previous],
    [...matches, ...cached.values()],
  )
  if (process.env.NODE_ENV !== 'production') {
    const handoff = tx[6 /* refresh */]?.[0 /* handoff */]
    if (handoff && router._handoff === handoff) {
      handoff[1 /* finish */]()
    }
  }
  runRouteLifecycle(router, previous, matches, tx)
}

async function awaitCurrent(
  router: CoordinatorRouter,
  owner?: LoadTransaction,
): Promise<void> {
  let current = router._tx
  while (current && current !== owner) {
    await current[5 /* done */]
    if (router._tx === current) {
      return
    }
    current = router._tx
  }
}

async function followRedirect(
  router: CoordinatorRouter,
  tx: LoadTransaction,
  outcome: RedirectOutcome,
): Promise<void> {
  const redirect = outcome[1 /* redirect */]
  const location = outcome[2 /* location */]
  if (!location) {
    await router.navigate({
      ...redirect.options,
      replace: true,
      ignoreBlocker: true,
    } as any)
    return
  }
  if (redirect.options.reloadDocument) {
    await router.navigate({
      href: location.publicHref,
      reloadDocument: true,
      replace: true,
      ignoreBlocker: true,
    } as any)
    return
  }
  ;(location as ParsedLocation & { _redirects?: number })._redirects =
    tx[1 /* redirects */] + 1
  router._pendingLocation = location
  const committed = router.commitLocation({
    ...location,
    viewTransition: redirect.options.viewTransition,
    replace: true,
    resetScroll: redirect.options.resetScroll,
    hashScrollIntoView: redirect.options.hashScrollIntoView,
    ignoreBlocker: true,
  })
  queueMicrotask(() => {
    if (router._pendingLocation === location) {
      router._pendingLocation = undefined
    }
  })
  await committed
}

async function runBackground(
  router: CoordinatorRouter,
  tx: LoadTransaction,
  base: Array<AnyRouteMatch>,
  tasks: Array<BackgroundLoaderTask>,
  settlement: Promise<IndexedOutcome | undefined>,
): Promise<void> {
  const next = base.map((match) => ({ ...match }))
  acquireMatchResources(next)
  for (const task of tasks) {
    releaseFlight(router, next[task[0 /* index */]]!)
    next[task[0 /* index */]] = task[3 /* candidate */]
  }
  // Phase jump: the clones inherit beforeLoad context from the committed
  // foreground lane, which already ran `contextualize` for these matches.
  const lane = [tx[2 /* location */], next] as ContextualizedLane
  let reduced: ReducedLane | ControlOutcome
  try {
    reduced = await reduceLane(
      router,
      lane,
      tasks,
      tx[0 /* controller */],
      settlement,
    )
  } catch (cause) {
    transferMatchResources(router, next)
    throw cause
  }
  if (isControl(reduced)) {
    transferMatchResources(router, next)
    if (
      reduced[0 /* kind */] === REDIRECTED &&
      router._tx === tx &&
      router._committed === base
    ) {
      await followRedirect(router, tx, reduced)
    }
    return
  }
  const projected = await projectLane(
    router,
    reduced,
    tx[0 /* controller */].signal,
  )
  if (router._tx !== tx || router._committed !== base) {
    transferMatchResources(router, projected[1 /* matches */])
    return
  }
  for (const match of projected[1 /* matches */] as Array<WorkMatch>) {
    const cached = router._cache.get(match.id) as WorkMatch | undefined
    if (cached?._flight && cached._flight === match._flight) {
      router._cache.delete(match.id)
      releaseFlight(router, cached)
    }
  }
  publishMatches(router, projected[1 /* matches */])
  transferMatchResources(router, base, projected[1 /* matches */])
}

async function runClientTransaction(
  router: CoordinatorRouter,
  tx: LoadTransaction,
  forceStaleReload: boolean,
  onReady?: () => void,
  sync?: boolean,
  resolvedPrefix?: number,
): Promise<void> {
  const options: ExecuteLaneOptions = [
    tx[0 /* controller */],
    tx[1 /* redirects */],
    router._committed,
    undefined,
    sync,
    forceStaleReload,
    resolvedPrefix,
    onReady,
  ]
  const result = await executeClientLane(
    router,
    tx[2 /* location */],
    tx[3 /* matches */],
    options,
  )

  if (isControl(result)) {
    const follow = result[0 /* kind */] === REDIRECTED && router._tx === tx
    if (!follow || result[1 /* redirect */].options.reloadDocument) {
      finishPending(router, tx)
    }
    transferMatchResources(router, tx[3 /* matches */])
    tx[3 /* matches */] = []
    if (!follow) {
      return
    }
    if (router._tx !== tx) {
      finishPending(router, tx)
      return
    }
    if (process.env.NODE_ENV !== 'production' && tx[6 /* refresh */]) {
      router._refreshNextLoad = true
    }
    await followRedirect(router, tx, result)
    return
  }
  if (router._tx !== tx) {
    finishPending(router, tx)
    transferMatchResources(router, result[1 /* matches */])
    discardBackground(router, result)
    return
  }
  // Only an acknowledged fallback owns a minimum. Recheck at the commit
  // boundary because native view transitions can defer their update callback.
  await awaitPendingMinimum(router, tx)
  if (router._tx !== tx) {
    finishPending(router, tx)
    transferMatchResources(router, result[1 /* matches */])
    discardBackground(router, result)
    return
  }
  const toLocation = tx[2 /* location */]
  const changeInfo = getLocationChangeInfo(
    toLocation,
    router.stores.resolvedLocation.get(),
  )
  const background = result[2 /* background */]
  await router.startViewTransition(async () => {
    if (router._tx !== tx) {
      finishPending(router, tx)
      transferMatchResources(router, result[1 /* matches */])
      discardBackground(router, result)
      return
    }
    await awaitPendingMinimum(router, tx)
    if (router._tx !== tx) {
      finishPending(router, tx)
      transferMatchResources(router, result[1 /* matches */])
      discardBackground(router, result)
      return
    }
    const commit = () => {
      finishPending(router, tx)
      commitMatches(router, tx, result[1 /* matches */], resolvedPrefix)
      if (router._tx !== tx) {
        return
      }
      router.emit({ type: 'onLoad', ...changeInfo })
      if (router._tx === tx) {
        router.emit({ type: 'onBeforeRouteMount', ...changeInfo })
      }
    }
    const rendered = await router.startTransition(
      commit,
      result[1 /* matches */],
    )
    if (process.env.NODE_ENV !== 'production' && tx[6 /* refresh */]) {
      tx[6 /* refresh */] = undefined
    }
    if (router._tx !== tx) {
      discardBackground(router, result)
      return
    }
    if (background?.length) {
      // Publish background matches only after the foreground acknowledgement.
      // Otherwise fast work can replace the acknowledged generation
      // before the framework commits it and strand the navigation.
      runBackground(
        router,
        tx,
        result[1 /* matches */],
        background,
        result[3 /* backgroundSettlement */]!,
      ).catch(console.error)
    }
    router.batch(() => {
      router.stores.resolvedLocation.set(toLocation)
      router.stores.status.set('idle')
      if (router._tx === tx) {
        router.emit({ type: 'onResolved', ...changeInfo })
      }
      if (rendered && router._tx === tx) {
        router.emit({ type: 'onRendered', ...changeInfo })
      }
    })
    if (router._tx !== tx) {
      return
    }
    router._commitPromise?.resolve()
    router._commitPromise = undefined
  })
}

export async function loadClientRoute(
  router: CoordinatorRouter,
  opts?: { sync?: boolean },
): Promise<void> {
  let rematerialize = false
  if (process.env.NODE_ENV !== 'production') {
    rematerialize = !!router._refreshNextLoad || !!router._tx?.[6 /* refresh */]
  }
  const previousOwner = router._tx
  const resolvedLocation = router.stores.resolvedLocation.get()
  const previousLocation = resolvedLocation ?? router.stores.location.get()
  const location = router.latestLocation
  const pendingLocation = router._pendingLocation as
    | (ParsedLocation & { _redirects?: number })
    | undefined
  const redirects =
    pendingLocation?.href === location.href
      ? (pendingLocation._redirects ?? 0)
      : 0
  const handoff = router._handoff
  const hydrationController = rematerialize
    ? undefined
    : handoff?.[0 /* claim */]()
  const preflight = new AbortController()
  const previousPreflight = router._preflight
  router._preflight = preflight
  if (!rematerialize && !hydrationController) {
    handoff?.[1 /* finish */]()
  }
  previousPreflight?.abort()
  // The preflight controller is not exposed to route hooks. Every replacement
  // aborts its predecessor, so a live signal is the sole authority here.
  if (preflight.signal.aborted) {
    await awaitCurrent(router, previousOwner)
    return
  }

  const changeInfo = getLocationChangeInfo(location, resolvedLocation)
  router.emit({ type: 'onBeforeNavigate', ...changeInfo })
  if (!preflight.signal.aborted) {
    router.emit({ type: 'onBeforeLoad', ...changeInfo })
  }
  if (preflight.signal.aborted) {
    await awaitCurrent(router, previousOwner)
    return
  }
  const sameHref = previousLocation.href === location.href
  let controller = preflight
  const matches =
    process.env.NODE_ENV !== 'production' && rematerialize
      ? router.matchRoutes(location, {
          _controller: preflight,
          _rematerialize: true,
        })
      : router.matchRoutes(location, { _controller: preflight })
  acquireMatchResources(matches)
  const resolvedPrefix = hydrationController
    ? handoff![1 /* finish */](matches)
    : undefined
  if (resolvedPrefix) {
    controller = hydrationController!
  } else {
    hydrationController?.abort()
  }
  if (preflight.signal.aborted) {
    transferMatchResources(router, matches)
    await awaitCurrent(router, previousOwner)
    return
  }
  router._preflight = undefined

  const tx: LoadTransaction = [
    controller,
    redirects,
    location,
    matches,
    Date.now(),
    Promise.resolve()
      .then(() =>
        runClientTransaction(
          router,
          tx,
          sameHref,
          () => offerPending(router, tx),
          opts?.sync,
          resolvedPrefix,
        ),
      )
      // Preserve the settlement turn in which immediately completed background
      // work can publish before callers resume from `load`.
      .then(),
  ]
  if (process.env.NODE_ENV !== 'production' && rematerialize) {
    tx[6 /* refresh */] = [handoff]
    router._refreshNextLoad = undefined
  }
  router._tx = tx
  if (previousOwner) {
    for (const match of router.stores.matches.get() as Array<WorkMatch>) {
      if (router._tx !== tx) {
        break
      }
      if (match.isFetching) {
        setFetching(router, match, false)
      }
    }
    previousOwner[0 /* controller */].abort()
    transferMatchResources(
      router,
      previousOwner[3 /* matches */],
      tx[3 /* matches */],
      true,
    )
  }
  if (router._tx !== tx) {
    transferMatchResources(router, tx[3 /* matches */])
    tx[3 /* matches */] = []
    await awaitCurrent(router, tx)
    return
  }
  router.batch(() => {
    router.stores.status.set('pending')
    router.stores.location.set(location)
  })
  // Cold loads have no committed UI to retain, but provisional not-found
  // matches must wait for lazy routes to place the final boundary.
  if (
    resolvedPrefix ||
    (!router._committed.length && !matches.some((match) => match._notFound))
  ) {
    offerPending(router, tx)
  }
  await tx[5 /* done */]
  await awaitCurrent(router, tx)
}

export async function refreshClientRoute(
  router: CoordinatorRouter,
): Promise<void> {
  const pending = router._tx
  if (
    pending &&
    !pending[6 /* refresh */] &&
    router.stores.status.get() === 'pending'
  ) {
    await pending[5 /* done */]
    if (router._tx !== pending) {
      await awaitCurrent(router, pending)
    }
  }
  // Existing owners remain presented but cannot donate stale work.
  router._flights?.clear()
  router.clearCache()
  router._refreshNextLoad = true
  await loadClientRoute(router, { sync: true })
}

export async function preloadClientRoute(
  router: CoordinatorRouter,
  opts: any,
  redirects = 0,
  builtLocation?: ParsedLocation,
): Promise<Array<AnyRouteMatch> | undefined> {
  if (
    process.env.NODE_ENV !== 'production' &&
    (router._refreshNextLoad || router._tx?.[6 /* refresh */])
  ) {
    return
  }
  const location = builtLocation ?? router.buildLocation(opts)
  const base = router._committed
  const controller = new AbortController()
  let matches: Array<AnyRouteMatch>
  try {
    matches = router.matchRoutes(location, {
      _controller: controller,
    })
    acquireMatchResources(matches)
  } catch (cause) {
    controller.abort()
    if (!isNotFound(cause)) {
      console.error(cause)
    }
    return
  }
  ;(router._preloads ??= new Map()).set(controller, matches)
  let active: boolean
  try {
    let result: LaneResult
    try {
      result = await executeClientLane(router, location, matches, [
        controller,
        redirects,
        base,
        true,
      ])
    } finally {
      active = router._preloads.delete(controller)
      transferMatchResources(router, matches)
      controller.abort()
    }
    if (!isControl(result)) {
      return result[1 /* matches */]
    }
    if (
      active &&
      result[0 /* kind */] === REDIRECTED &&
      !result[1 /* redirect */].options.reloadDocument
    ) {
      return preloadClientRoute(
        router,
        result[1 /* redirect */].options,
        redirects + 1,
        result[2 /* location */],
      )
    }
  } catch (cause) {
    if (!isNotFound(cause)) {
      console.error(cause)
    }
  }
  return
}

// --- SSR hydration (client entry via @tanstack/router-core/ssr/client) ---

declare global {
  interface Window {
    [GLOBAL_TSR]?: TsrSsrGlobal
    [GLOBAL_SEROVAL]?: any
  }
}

export async function hydrate(router: AnyRouter): Promise<void> {
  if (process.env.NODE_ENV !== 'production' && !window.$_TSR) {
    throw new Error(
      'Invariant failed: Expected to find bootstrap data on window.$_TSR, but we did not. Please file an issue!',
    )
  }
  const tsr = window.$_TSR!

  const adapters = router.options.serializationAdapters as
    | Array<AnySerializationAdapter>
    | undefined
  if (adapters?.length) {
    tsr.t = new Map(
      adapters.map((adapter) => [adapter.key, adapter.fromSerializable]),
    )
    tsr.buffer.forEach((script) => script())
  }
  tsr.initialized = true

  const dehydratedRouter = tsr.router
  if (process.env.NODE_ENV !== 'production' && !dehydratedRouter) {
    throw new Error(
      'Invariant failed: Expected to find a dehydrated data on window.$_TSR.router, but we did not. Please file an issue!',
    )
  }
  router.ssr = { manifest: dehydratedRouter!.manifest }
  router.options.ssr = {
    nonce: (
      document.querySelector('meta[property="csp-nonce"]') as
        | HTMLMetaElement
        | undefined
    )?.content,
  }

  const dehydratedMatches = dehydratedRouter!.matches

  const controller = new AbortController()
  const previousPreflight = router._preflight
  router._preflight = controller
  previousPreflight?.abort()
  // Only a new slot owner supersedes hydration.
  const isCurrent = () => router._preflight === controller

  let location!: AnyRouter['latestLocation']
  let candidates!: Array<AnyRouteMatch>
  let handoffHistoryHref!: string
  let handoffHistoryState: unknown
  try {
    await waitFor(
      router.options.hydrate?.(dehydratedRouter!.dehydratedData),
      controller.signal,
    )
    if (!isCurrent()) {
      return
    }
    // Hydration trusts transported context and beforeLoad. The raw history
    // entry owns the handoff; route structure is verified after rematching.
    const historyLocation = router.history.location
    handoffHistoryHref = historyLocation.href
    handoffHistoryState = historyLocation.state
    router.updateLatestLocation()
    location = router.latestLocation
    router.stores.location.set(location)
    candidates = router.matchRoutes(location, {
      _controller: controller,
    })
  } catch (cause) {
    if (isCurrent()) {
      router._preflight = undefined
    }
    controller.abort(cause)
    if (cause !== controller.signal) {
      throw cause
    }
  }
  if (!isCurrent()) {
    return
  }
  const committed: Array<AnyRouteMatch> = []
  let pendingBoundary: number | undefined
  let verifiedAssetEnd = 0
  const retryFrom = (index: number) => {
    // The failing route's identity is still verified, but no descendant is.
    verifiedAssetEnd = Math.min(verifiedAssetEnd, index + 1)
    const removed = committed.splice(index)
    for (const match of removed) {
      if (
        getRoute(router, match).options.loader &&
        (match.status === 'success' ||
          (!match.invalid && 'loaderData' in match))
      ) {
        cacheLoaderMatch(
          router,
          // Phase jump: dehydrated server data is already past the loader
          // phase — the guard above verified a settled success (or transported
          // loaderData), so this clone is settled without a client settleInto.
          {
            ...match,
            status: 'success',
            error: undefined,
            preload: true,
          } as SettledMatch,
          router._cache.get(match.id),
        )
      }
    }
    transferMatchResources(router, removed)
  }

  // A longer server lane is valid only when the local match already caps the
  // branch at a global not-found boundary. Otherwise no transported work is
  // safe to attach to the shorter client lane.
  const shared =
    dehydratedMatches.length > candidates.length
      ? candidates.findIndex((match) => match._notFound) + 1
      : dehydratedMatches.length
  let isTerminal = false
  for (let index = 0; index < shared; index++) {
    const candidate = candidates[index]!
    const dehydrated = dehydratedMatches[index]!
    if (
      typeof dehydrated.i !== 'string' ||
      hydrateSsrMatchId(dehydrated.i) !== candidate.id
    ) {
      pendingBoundary ??= index
      break
    }
    verifiedAssetEnd = index + 1
    const route = getRoute(router, candidate)
    if (
      'l' in dehydrated ||
      (dehydrated.s === 'success' &&
        dehydrated.e === undefined &&
        route.options.loader)
    ) {
      candidate.loaderData = dehydrated.l
    }
    candidate.status = dehydrated.s
    candidate.ssr = dehydrated.ssr
    route.options.ssr = candidate.ssr
    candidate.updatedAt = dehydrated.u
    candidate.error = dehydrated.e
    candidate._notFound ||= dehydrated.g
    const terminal =
      candidate.status === 'error' ||
      candidate.status === 'notFound' ||
      candidate._notFound
    if (terminal) {
      isTerminal = true
      committed.push(candidate)
      if (candidate.ssr === false || candidate.ssr === 'data-only') {
        pendingBoundary ??= index
      }
      break
    }
    if (candidate.status === 'pending') {
      pendingBoundary ??= index
      break
    }

    committed.push(candidate)
    if (candidate.ssr === 'data-only') {
      pendingBoundary ??= index
    }
  }
  if (
    !isTerminal &&
    committed.length === shared &&
    shared < candidates.length
  ) {
    pendingBoundary = shared
  }

  // Hooks observe structural membership. Execution remains limited to
  // `committed`, the accepted server prefix.
  const chunks = committed.map(async (match) => {
    try {
      const route = getRoute(router, match)
      if (match._notFound) {
        await Promise.all([
          loadRouteChunk(route),
          loadRouteChunk(route, 'notFoundComponent'),
        ])
      } else {
        await loadRouteChunk(
          route,
          match.status === 'error'
            ? 'errorComponent'
            : match.status === 'notFound'
              ? 'notFoundComponent'
              : undefined,
        )
      }
      return true
    } catch {
      return false
    }
  })
  let chunkFailure = 0
  try {
    while (
      chunkFailure < chunks.length &&
      (await waitFor(chunks[chunkFailure]!, controller.signal))
    ) {
      chunkFailure++
    }
  } catch {
    return
  }
  if (!isCurrent()) {
    return
  }
  if (chunkFailure < committed.length) {
    retryFrom(chunkFailure)
  }

  // The first pending match is already visible, so prepare its route context
  // without granting its beforeLoad or loader any hydration authority.
  const contextEnd = Math.max(
    pendingBoundary === committed.length
      ? committed.length + 1
      : committed.length,
    // `chunks.length` keeps the pre-retry committed length, so a smaller
    // `chunkFailure` is the exclusive bound of the verified context prefix.
    chunkFailure < chunks.length ? chunkFailure : verifiedAssetEnd,
  )
  for (let index = 0; index < contextEnd; index++) {
    const match = candidates[index]!
    const route = getRoute(router, match)
    const parentContext =
      candidates[index - 1]?.context ?? router.options.context ?? {}
    let routeContext
    if (route.options.context) {
      try {
        routeContext = match._ctx =
          route.options.context({
            deps: match.loaderDeps,
            params: match.params,
            context: parentContext,
            location,
            navigate: (opts: any) =>
              router.navigate({
                ...opts,
                _fromLocation: location,
              }),
            buildLocation: router.buildLocation,
            cause: match.cause,
            abortController: controller,
            preload: false,
            matches: candidates,
            routeId: route.id,
          }) || {}
      } catch {
        if (!isCurrent()) {
          return
        }
        if (
          match.status !== 'error' &&
          match.status !== 'notFound' &&
          !match._notFound
        ) {
          retryFrom(index)
          break
        }
      }
      if (!isCurrent()) {
        return
      }
    }
    match.context = {
      ...parentContext,
      ...routeContext,
      ...(committed[index] && dehydratedMatches[index]!.b),
    }
  }

  await projectLane(
    router,
    [location, candidates] as any,
    controller.signal,
    0,
    verifiedAssetEnd,
  )
  if (!isCurrent()) {
    return
  }
  const needsClientLoad =
    pendingBoundary !== undefined || committed.length < shared
  const committedMatches =
    isTerminal && committed.length === shared ? candidates : committed
  let presented = needsClientLoad ? candidates : committedMatches
  let dataOnlyAssetEnd: number | undefined
  if (needsClientLoad && pendingBoundary !== undefined) {
    const boundary = presented[pendingBoundary]!
    // A verified descendant proves this data-only boundary was nonterminal.
    dataOnlyAssetEnd =
      boundary.ssr === 'data-only' && verifiedAssetEnd > pendingBoundary + 1
        ? verifiedAssetEnd
        : undefined
    presented = presented.slice()
    presented[pendingBoundary] = {
      ...boundary,
      status: 'pending',
      ssr: boundary.ssr === 'data-only' ? 'data-only' : false,
      _assetEnd: dataOnlyAssetEnd,
    }
  }

  const claim = () => {
    const historyLocation = router.history.location
    return needsClientLoad &&
      !router._tx &&
      historyLocation.href === handoffHistoryHref &&
      historyLocation.state === handoffHistoryState &&
      router._committed === committedMatches &&
      committedMatches.length &&
      !controller.signal.aborted
      ? controller
      : undefined
  }
  const handoff: NonNullable<AnyRouter['_handoff']> = [
    claim,
    (matches) => {
      if (router._handoff !== handoff) {
        return
      }
      // `finish` is single-use. Consume the slot before validating or moving
      // resources so reentrant work cannot claim the same handoff.
      router._handoff = undefined
      const prefix = committedMatches.length
      if (
        !matches ||
        !claim() ||
        committedMatches.some((match, index) => match.id !== matches[index]?.id)
      ) {
        controller.abort()
        return
      }
      let handoffAssetEnd = dataOnlyAssetEnd
      if (handoffAssetEnd !== undefined) {
        for (let index = prefix; index < handoffAssetEnd; index++) {
          if (candidates[index]?.id !== matches[index]?.id) {
            handoffAssetEnd = index > pendingBoundary! + 1 ? index : undefined
            break
          }
        }
      }
      const clones = committedMatches.map((match) => ({ ...match }))
      if (handoffAssetEnd !== undefined) {
        clones[pendingBoundary!]!._assetEnd = handoffAssetEnd
      }
      transferMatchResources(router, matches.splice(0, prefix, ...clones))
      for (let index = prefix; index < matches.length; index++) {
        const match = matches[index]!
        const hydrated = candidates[index]
        if (hydrated?.id === match.id && hydrated._ctx) {
          match._ctx = hydrated._ctx
        }
        match.abortController = controller
      }
      return prefix
    },
  ]
  router._committed = committedMatches
  router._handoff = handoff
  router._preflight = undefined
  router.batch(() => {
    router.stores.setMatches(presented)
    router.stores.status.set('idle')
    if (!needsClientLoad) {
      router.stores.resolvedLocation.set(router.stores.location.get())
    }
  })
}
