throws an error when forRoot() is used in a lazy context', fakeAsync(
        inject([Router, Location], (router: Router, location: Location) => {
          @Component({
            selector: 'lazy',
            template: 'should not show',
            standalone: false,
          })
          class LazyLoadedComponent {}

          @NgModule({
            declarations: [LazyLoadedComponent],
            imports: [RouterModule.forRoot([{path: 'loaded', component: LazyLoadedComponent}])],
          })
          class LoadedModule {}

          const fixture = createRoot(router, RootCmp);

          router.resetConfig([{path: 'lazy', loadChildren: () => LoadedModule}]);

          let recordedError: any = null;
          router.navigateByUrl('/lazy/loaded')!.catch((err) => (recordedError = err));
          advance(fixture);
          expect(recordedError.message).toContain(`NG04007`);
        }),
      ));

      it('should combine routes from multiple modules into a single configuration', fakeAsync(
        inject([Router, Location], (router: Router, location: Location) => {
          @Component({
            selector: 'lazy',
            template: 'lazy-loaded-2',
            standalone: false,
          })
          class LazyComponent2 {}

          @NgModule({
            declarations: [LazyComponent2],
            imports: [RouterModule.forChild([{path: 'loaded', component: LazyComponent2}])],
          })
          class SiblingOfLoadedModule {}

          @Component({
            selector: 'lazy',
            template: 'lazy-loaded-1',
            standalone: false,
          })
          class LazyComponent1 {}

          @NgModule({
            declarations: [LazyComponent1],
            imports: [
              RouterModule.forChild([{path: 'loaded', component: LazyComponent1}]),
              SiblingOfLoadedModule,
            ],
          })
          class LoadedModule {}

          const fixture = createRoot(router, RootCmp);

          router.resetConfig([
            {path: 'lazy1', loadChildren: () => LoadedModule},
            {path: 'lazy2', loadChildren: () => SiblingOfLoadedModule},
          ]);

          router.navigateByUrl('/lazy1/loaded');
          advance(fixture);
          expect(location.path()).toEqual('/lazy1/loaded');

          router.navigateByUrl('/lazy2/loaded');
          advance(fixture);
          expect(location.path()).toEqual('/lazy2/loaded');
        }),
      ));

      it('should allow lazy loaded module in named outlet', fakeAsync(
        inject([Router], (router: Router) => {
          @Component({
            selector: 'lazy',
            template: 'lazy-loaded',
            standalone: false,
          })
          class LazyComponent {}

          @NgModule({
            declarations: [LazyComponent],
            imports: [RouterModule.forChild([{path: '', component: LazyComponent}])],
          })
          class LazyLoadedModule {}

          const fixture = createRoot(router, RootCmp);

          router.resetConfig([
            {
              path: 'team/:id',
              component: TeamCmp,
              children: [
                {path: 'user/:name', component: UserCmp},
                {path: 'lazy', loadChildren: () => LazyLoadedModule, outlet: 'right'},
              ],
            },
          ]);

          router.navigateByUrl('/team/22/user/john');
          advance(fixture);

          expect(fixture.nativeElement).toHaveText('team 22 [ user john, right:  ]');

          router.navigateByUrl('/team/22/(user/john//right:lazy)');
          advance(fixture);

          expect(fixture.nativeElement).toHaveText('team 22 [ user john, right: lazy-loaded ]');
        }),
      ));

      it('should allow componentless named outlet to render children', fakeAsync(
        inject([Router], (router: Router) => {
          const fixture = createRoot(router, RootCmp);

          router.resetConfig([
            {
              path: 'team/:id',
              component: TeamCmp,
              children: [
                {path: 'user/:name', component: UserCmp},
                {path: 'simple', outlet: 'right', children: [{path: '', component: SimpleCmp}]},
              ],
            },
          ]);

          router.navigateByUrl('/team/22/user/john');
          advance(fixture);

          expect(fixture.nativeElement).toHaveText('team 22 [ user john, right:  ]');

          router.navigateByUrl('/team/22/(user/john//right:simple)');
          advance(fixture);

          expect(fixture.nativeElement).toHaveText('team 22 [ user john, right: simple ]');
        }),
      ));

      it('should render loadComponent named outlet with children', fakeAsync(() => {
        const router = TestBed.inject(Router);
        const fixture = createRoot(router, RootCmp);

        @Component({
          standalone: true,
          imports: [RouterModule],
          template: '[right outlet component: <router-outlet></router-outlet>]',
        })
        class RightComponent {
          constructor(readonly route: ActivatedRoute) {}
        }

        const loadSpy = jasmine.createSpy();
        loadSpy.and.returnValue(RightComponent);

        router.resetConfig([
          {
            path: 'team/:id',
            component: TeamCmp,
            children: [
              {path: 'user/:name', component: UserCmp},
              {
                path: 'simple',
                loadComponent: loadSpy,
                outlet: 'right',
                children: [{path: '', component: SimpleCmp}],
              },
            ],
          },
          {path: '', component: SimpleCmp},
        ]);

        router.navigateByUrl('/team/22/(user/john//right:simple)');
        advance(fixture);

        expect(fixture.nativeElement).toHaveText(
          'team 22 [ user john, right: [right outlet component: simple] ]',
        );
        const rightCmp: RightComponent = fixture.debugElement.query(
          By.directive(RightComponent),
        ).componentInstance;
        // Ensure we don't accidentally add `EmptyOutletComponent` via `standardizeConfig`
        expect(rightCmp.route.routeConfig?.component).not.toBeDefined();

        // Ensure we can navigate away and come back
        router.navigateByUrl('/');
        advance(fixture);
        router.navigateByUrl('/team/22/(user/john//right:simple)');
        advance(fixture);
        expect(fixture.nativeElement).toHaveText(
          'team 22 [ user john, right: [right outlet component: simple] ]',
        );
        expect(loadSpy.calls.count()).toEqual(1);
      }));

      describe('should use the injector of the lazily-loaded configuration', () => {
        class LazyLoadedServiceDefinedInModule {}

        @Component({
          selector: 'eager-parent',
          template: 'eager-parent <router-outlet></router-outlet>',
          standalone: false,
        })
        class EagerParentComponent {}

        @Component({
          selector: 'lazy-parent',
          template: 'lazy-parent <router-outlet></router-outlet>',
          standalone: false,
        })
        class LazyParentComponent {}

        @Component({
          selector: 'lazy-child',
          template: 'lazy-child',
          standalone: false,
        })
        class LazyChildComponent {
          constructor(
            lazy: LazyParentComponent, // should be able to inject lazy/direct parent
            lazyService: LazyLoadedServiceDefinedInModule, // should be able to inject lazy service
            eager: EagerParentComponent, // should use the injector of the location to create a
            // parent
          ) {}
        }

        @NgModule({
          declarations: [LazyParentComponent, LazyChildComponent],
          imports: [
            RouterModule.forChild([
              {
                path: '',
                children: [
                  {
                    path: 'lazy-parent',
                    component: LazyParentComponent,
                    children: [{path: 'lazy-child', component: LazyChildComponent}],
                  },
                ],
              },
            ]),
          ],
          providers: [LazyLoadedServiceDefinedInModule],
        })
        class LoadedModule {}

        @NgModule({declarations: [EagerParentComponent], imports: [RouterModule.forRoot([])]})
        class TestModule {}

        beforeEach(() => {
          TestBed.configureTestingModule({
            imports: [TestModule],
          });
        });

        it('should use the injector of the lazily-loaded configuration', fakeAsync(
          inject([Router, Location], (router: Router, location: Location) => {
            const fixture = createRoot(router, RootCmp);

            router.resetConfig([
              {
                path: 'eager-parent',
                component: EagerParentComponent,
                children: [{path: 'lazy', loadChildren: () => LoadedModule}],
              },
            ]);

            router.navigateByUrl('/eager-parent/lazy/lazy-parent/lazy-child');
            advance(fixture);

            expect(location.path()).toEqual('/eager-parent/lazy/lazy-parent/lazy-child');
            expect(fixture.nativeElement).toHaveText('eager-parent lazy-parent lazy-child');
          }),
        ));
      });


works when given a callback', fakeAsync(
        inject([Router, Location], (router: Router, location: Location) => {
          @Component({
            selector: 'lazy',
            template: 'lazy-loaded',
            standalone: false,
          })
          class LazyLoadedComponent {}

          @NgModule({
            declarations: [LazyLoadedComponent],
            imports: [RouterModule.forChild([{path: 'loaded', component: LazyLoadedComponent}])],
          })
          class LoadedModule {}

          const fixture = createRoot(router, RootCmp);

          router.resetConfig([{path: 'lazy', loadChildren: () => LoadedModule}]);

          router.navigateByUrl('/lazy/loaded');
          advance(fixture);

          expect(location.path()).toEqual('/lazy/loaded');
          expect(fixture.nativeElement).toHaveText('lazy-loaded');
        }),
      ));

      it('error emit an error when cannot load a config', fakeAsync(
        inject([Router, Location], (router: Router, location: Location) => {
          const fixture = createRoot(router, RootCmp);

          router.resetConfig([
            {
              path: 'lazy',
              loadChildren: () => {
                throw new Error('invalid');
              },
            },
          ]);

          const recordedEvents: Event[] = [];
          router.events.forEach((e) => recordedEvents.push(e));

          router.navigateByUrl('/lazy/loaded')!.catch((s) => {});
          advance(fixture);

          expect(location.path()).toEqual('');

          expectEvents(recordedEvents, [
            [NavigationStart, '/lazy/loaded'],
            [RouteConfigLoadStart],
            [NavigationError, '/lazy/loaded'],
          ]);
        }),
      ));

      it('should emit an error when the lazily-loaded config is not valid', fakeAsync(() => {
        const router = TestBed.inject(Router);
        @NgModule({imports: [RouterModule.forChild([{path: 'loaded'}])]})
        class LoadedModule {}

        const fixture = createRoot(router, RootCmp);
        router.resetConfig([{path: 'lazy', loadChildren: () => LoadedModule}]);

        let recordedError: any = null;
        router.navigateByUrl('/lazy/loaded').catch((err) => (recordedError = err));
        advance(fixture);

        expect(recordedError.message).toContain(
          `Invalid configuration of route 'lazy/loaded'. One of the following must be provided: component, loadComponent, redirectTo, children or loadChildren`,
        );
      }));

      it('should work with complex redirect rules', fakeAsync(
        inject([Router, Location], (router: Router, location: Location) => {
          @Component({
            selector: 'lazy',
            template: 'lazy-loaded',
            standalone: false,
          })
          class LazyLoadedComponent {}

          @NgModule({
            declarations: [LazyLoadedComponent],
            imports: [RouterModule.forChild([{path: 'loaded', component: LazyLoadedComponent}])],
          })
          class LoadedModule {}

          const fixture = createRoot(router, RootCmp);

          router.resetConfig([
            {path: 'lazy', loadChildren: () => LoadedModule},
            {path: '**', redirectTo: 'lazy'},
          ]);

          router.navigateByUrl('/lazy/loaded');
          advance(fixture);

          expect(location.path()).toEqual('/lazy/loaded');
        }),
      ));

      it('should work with wildcard route', fakeAsync(
        inject([Router, Location], (router: Router, location: Location) => {
          @Component({
            selector: 'lazy',
            template: 'lazy-loaded',
            standalone: false,
          })
          class LazyLoadedComponent {}

          @NgModule({
            declarations: [LazyLoadedComponent],
            imports: [RouterModule.forChild([{path: '', component: LazyLoadedComponent}])],
          })
          class LazyLoadedModule {}

          const fixture = createRoot(router, RootCmp);

          router.resetConfig([{path: '**', loadChildren: () => LazyLoadedModule}]);

          router.navigateByUrl('/lazy');
          advance(fixture);

          expect(location.path()).toEqual('/lazy');
          expect(fixture.nativeElement).toHaveText('lazy-loaded');
        }),
      ));

      describe('preloading', () => {
        let log: string[] = [];
        @Component({
          selector: 'lazy',
          template: 'should not show',
          standalone: false,
        })
        class LazyLoadedComponent {}

        @NgModule({
          declarations: [LazyLoadedComponent],
          imports: [
            RouterModule.forChild([{path: 'LoadedModule2', component: LazyLoadedComponent}]),
          ],
        })
        class LoadedModule2 {}

        @NgModule({
          imports: [
            RouterModule.forChild([{path: 'LoadedModule1', loadChildren: () => LoadedModule2}]),
          ],
        })
        class LoadedModule1 {}

        @NgModule({})
        class EmptyModule {}

        beforeEach(() => {
          log.length = 0;
          TestBed.configureTestingModule({
            providers: [
              {provide: PreloadingStrategy, useExisting: PreloadAllModules},
              {
                provide: 'loggingReturnsTrue',
                useValue: () => {
                  log.push('loggingReturnsTrue');
                  return true;
                },
              },
            ],
          });
          const preloader = TestBed.inject(RouterPreloader);
          preloader.setUpPreloading();
        });

        it('should work', fakeAsync(() => {
          const router = TestBed.inject(Router);
          const fixture = createRoot(router, RootCmp);

          router.resetConfig([
            {path: 'blank', component: BlankCmp},
            {path: 'lazy', loadChildren: () => LoadedModule1},
          ]);

          router.navigateByUrl('/blank');
          advance(fixture);

          const config = router.config;
          const firstRoutes = getLoadedRoutes(config[1])!;

          expect(firstRoutes).toBeDefined();
          expect(firstRoutes[0].path).toEqual('LoadedModule1');

          const secondRoutes = getLoadedRoutes(firstRoutes[0])!;
          expect(secondRoutes).toBeDefined();
          expect(secondRoutes[0].path).toEqual('LoadedModule2');
        }));

        it('should not preload when canLoad is present and does not execute guard', fakeAsync(() => {
          const router = TestBed.inject(Router);
          const fixture = createRoot(router, RootCmp);

          router.resetConfig([
            {path: 'blank', component: BlankCmp},
            {path: 'lazy', loadChildren: () => LoadedModule1, canLoad: ['loggingReturnsTrue']},
          ]);

          router.navigateByUrl('/blank');
          advance(fixture);

          const config = router.config;
          const firstRoutes = getLoadedRoutes(config[1])!;

          expect(firstRoutes).toBeUndefined();
          expect(log.length).toBe(0);
        }));

        it('should allow navigation to modules with no routes', fakeAsync(() => {
          const router = TestBed.inject(Router);
          const fixture = createRoot(router, RootCmp);

          router.resetConfig([{path: 'lazy', loadChildren: () => EmptyModule}]);

          router.navigateByUrl('/lazy');
          advance(fixture);
        }));
      });


describe('component input binding', () => {
  it('sets component inputs from matching query params', async () => {
    @Component({
      template: '',
      standalone: false,
    })
    class MyComponent {
      @Input() language?: string;
    }

    TestBed.configureTestingModule({
      providers: [
        provideRouter([{path: '**', component: MyComponent}], withComponentInputBinding()),
      ],
    });
    const harness = await RouterTestingHarness.create();

    const instance = await harness.navigateByUrl('/?language=english', MyComponent);
    expect(instance.language).toEqual('english');

    await harness.navigateByUrl('/?language=french');
    expect(instance.language).toEqual('french');

    // Should set the input to undefined when the matching router data is removed
    await harness.navigateByUrl('/');
    expect(instance.language).toEqual(undefined);
    await harness.navigateByUrl('/?notlanguage=doubletalk');
    expect(instance.language).toEqual(undefined);
  });

  it('sets component inputs from resolved and static data', async () => {
    @Component({
      template: '',
      standalone: false,
    })
    class MyComponent {
      @Input() resolveA?: string;
      @Input() dataA?: string;
    }

    TestBed.configureTestingModule({
      providers: [
        provideRouter(
          [
            {
              path: '**',
              component: MyComponent,
              data: {'dataA': 'My static data'},
              resolve: {'resolveA': () => 'My resolved data'},
            },
          ],
          withComponentInputBinding(),
        ),
      ],
    });
    const harness = await RouterTestingHarness.create();

    const instance = await harness.navigateByUrl('/', MyComponent);
    expect(instance.resolveA).toEqual('My resolved data');
    expect(instance.dataA).toEqual('My static data');
  });

  it('sets component inputs from path params', async () => {
    @Component({
      template: '',
      standalone: false,
    })
    class MyComponent {
      @Input() language?: string;
    }

    TestBed.configureTestingModule({
      providers: [
        provideRouter([{path: '**', component: MyComponent}], withComponentInputBinding()),
      ],
    });
    const harness = await RouterTestingHarness.create();

    const instance = await harness.navigateByUrl('/x;language=english', MyComponent);
    expect(instance.language).toEqual('english');
  });

  it('when keys conflict, sets inputs based on priority: data > path params > query params', async () => {
    @Component({
      template: '',
      standalone: false,
    })
    class MyComponent {
      @Input() result?: string;
    }

    TestBed.configureTestingModule({
      providers: [
        provideRouter(
          [
            {
              path: 'withData',
              component: MyComponent,
              data: {'result': 'from data'},
            },
            {
              path: 'withoutData',
              component: MyComponent,
            },
          ],
          withComponentInputBinding(),
        ),
      ],
    });
    const harness = await RouterTestingHarness.create();

    let instance = await harness.navigateByUrl(
      '/withData;result=from path param?result=from query params',
      MyComponent,
    );
    expect(instance.result).toEqual('from data');

    // Same component, different instance because it's a different route
    instance = await harness.navigateByUrl(
      '/withoutData;result=from path param?result=from query params',
      MyComponent,
    );
    expect(instance.result).toEqual('from path param');
    instance = await harness.navigateByUrl('/withoutData?result=from query params', MyComponent);
    expect(instance.result).toEqual('from query params');
  });

  it('does not write multiple times if two sources of conflicting keys both update', async () => {
    let resultLog: Array<string | undefined> = [];
    @Component({
      template: '',
      standalone: false,
    })
    class MyComponent {
      @Input()
      set result(v: string | undefined) {
        resultLog.push(v);
      }
    }

    TestBed.configureTestingModule({
      providers: [
        provideRouter([{path: '**', component: MyComponent}], withComponentInputBinding()),
      ],
    });
    const harness = await RouterTestingHarness.create();

    await harness.navigateByUrl('/x', MyComponent);
    expect(resultLog).toEqual([undefined]);

    await harness.navigateByUrl('/x;result=from path param?result=from query params', MyComponent);
    expect(resultLog).toEqual([undefined, 'from path param']);
  });

  it('Should have inputs available to all outlets after navigation', async () => {
    @Component({
      template: '{{myInput}}',
      standalone: true,
    })
    class MyComponent {
      @Input() myInput?: string;
    }

    @Component({
      template: '<router-outlet/>',
      imports: [RouterOutlet],
      standalone: true,
    })
    class OutletWrapper {}

    TestBed.configureTestingModule({
      providers: [
        provideRouter(
          [
            {
              path: 'root',
              component: OutletWrapper,
              children: [{path: '**', component: MyComponent}],
            },
          ],
          withComponentInputBinding(),
        ),
      ],
    });
    const harness = await RouterTestingHarness.create('/root/child?myInput=1');
    expect(harness.routeNativeElement!.innerText).toBe('1');
    await harness.navigateByUrl('/root/child?myInput=2');
    expect(harness.routeNativeElement!.innerText).toBe('2');
  });
});

describe('injectors', () => {
  it('should always use environment injector from route hierarchy and not inherit from outlet', async () => {
    let childTokenValue: any = null;
    const TOKEN = new InjectionToken<any>('');

    @Component({
      template: '',
      standalone: true,
    })
    class Child {
      constructor() {
        childTokenValue = inject(TOKEN as any, {optional: true});
      }
    }

    @NgModule({
      providers: [{provide: TOKEN, useValue: 'some value'}],
    })
    class ModWithProviders {}

    @Component({
      template: '<router-outlet/>',
      imports: [RouterOutlet, ModWithProviders],
      standalone: true,
    })
    class App {}

    TestBed.configureTestingModule({
      providers: [provideRouter([{path: 'a', component: Child}])],
    });
    const fixture = TestBed.createComponent(App);
    fixture.detectChanges();
    await TestBed.inject(Router).navigateByUrl('/a');
    fixture.detectChanges();
    expect(childTokenValue).toEqual(null);
  });

  it('should not get sibling providers', async () => {
    let childTokenValue: any = null;
    const TOKEN = new InjectionToken<any>('');
    @Component({
      template: '',
      standalone: true,
    })
    class Child {
      constructor() {
        childTokenValue = inject(TOKEN, {optional: true});
      }
    }

    @Component({
      template: '<router-outlet/>',
      imports: [RouterOutlet],
      standalone: true,
    })
    class App {}

    TestBed.configureTestingModule({
      providers: [
        provideRouter([
          {path: 'a', providers: [{provide: TOKEN, useValue: 'a value'}], component: Child},
          {path: 'b', component: Child},
        ]),
      ],
    });
    const fixture = TestBed.createComponent(App);
    fixture.detectChanges();
    await TestBed.inject(Router).navigateByUrl('/a');
    fixture.detectChanges();
    expect(childTokenValue).toEqual('a value');
    await TestBed.inject(Router).navigateByUrl('/b');
    fixture.detectChanges();
    expect(childTokenValue).toEqual(null);
  });
});


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {
  Compiler,
  createEnvironmentInjector,
  EnvironmentInjector,
  Injectable,
  OnDestroy,
} from '@angular/core';
import {from, Observable, of, Subscription} from 'rxjs';
import {catchError, concatMap, filter, mergeAll, mergeMap} from 'rxjs/operators';

import {Event, NavigationEnd} from './events';
import {LoadedRouterConfig, Route, Routes} from './models';
import {Router} from './router';
import {RouterConfigLoader} from './router_config_loader';

/**
 * @description
 *
 * Provides a preloading strategy.
 *
 * @publicApi
 */
export abstract class PreloadingStrategy {
  abstract preload(route: Route, fn: () => Observable<any>): Observable<any>;
}

/**
 * @description
 *
 * Provides a preloading strategy that preloads all modules as quickly as possible.
 *
 * ```
 * RouterModule.forRoot(ROUTES, {preloadingStrategy: PreloadAllModules})
 * ```
 *
 * @publicApi
 */
@Injectable({providedIn: 'root'})
export class PreloadAllModules implements PreloadingStrategy {
  preload(route: Route, fn: () => Observable<any>): Observable<any> {
    return fn().pipe(catchError(() => of(null)));
  }
}

/**
 * @description
 *
 * Provides a preloading strategy that does not preload any modules.
 *
 * This strategy is enabled by default.
 *
 * @publicApi
 */
@Injectable({providedIn: 'root'})
export class NoPreloading implements PreloadingStrategy {
  preload(route: Route, fn: () => Observable<any>): Observable<any> {
    return of(null);
  }
}

/**
 * The preloader optimistically loads all router configurations to
 * make navigations into lazily-loaded sections of the application faster.
 *
 * The preloader runs in the background. When the router bootstraps, the preloader
 * starts listening to all navigation events. After every such event, the preloader
 * will check if any configurations can be loaded lazily.
 *
 * If a route is protected by `canLoad` guards, the preloaded will not load it.
 *
 * @publicApi
 */
@Injectable({providedIn: 'root'})
export class RouterPreloader implements OnDestroy {
  private subscription?: Subscription;

  constructor(
    private router: Router,
    compiler: Compiler,
    private injector: EnvironmentInjector,
    private preloadingStrategy: PreloadingStrategy,
    private loader: RouterConfigLoader,
  ) {}

  setUpPreloading(): void {
    this.subscription = this.router.events
      .pipe(
        filter((e: Event) => e instanceof NavigationEnd),
        concatMap(() => this.preload()),
      )
      .subscribe(() => {});
  }

  preload(): Observable<any> {
    return this.processRoutes(this.injector, this.router.config);
  }

  /** @nodoc */
  ngOnDestroy(): void {
    if (this.subscription) {
      this.subscription.unsubscribe();
    }
  }

  private processRoutes(injector: EnvironmentInjector, routes: Routes): Observable<void> {
    const res: Observable<any>[] = [];
    for (const route of routes) {
      if (route.providers && !route._injector) {
        route._injector = createEnvironmentInjector(
          route.providers,
          injector,
          `Route: ${route.path}`,
        );
      }

      const injectorForCurrentRoute = route._injector ?? injector;
      const injectorForChildren = route._loadedInjector ?? injectorForCurrentRoute;

      // Note that `canLoad` is only checked as a condition that prevents `loadChildren` and not
      // `loadComponent`. `canLoad` guards only block loading of child routes by design. This
      // happens as a consequence of needing to descend into children for route matching immediately
      // while component loading is deferred until route activation. Because `canLoad` guards can
      // have side effects, we cannot execute them here so we instead skip preloading altogether
      // when present. Lastly, it remains to be decided whether `canLoad` should behave this way
      // at all. Code splitting and lazy loading is separate from client-side authorization checks
      // and should not be used as a security measure to prevent loading of code.
      if (
        (route.loadChildren && !route._loadedRoutes && route.canLoad === undefined) ||
        (route.loadComponent && !route._loadedComponent)
      ) {
        res.push(this.preloadConfig(injectorForCurrentRoute, route));
      }
      if (route.children || route._loadedRoutes) {
        res.push(this.processRoutes(injectorForChildren, (route.children ?? route._loadedRoutes)!));
      }
    }
    return from(res).pipe(mergeAll());
  }

  private preloadConfig(injector: EnvironmentInjector, route: Route): Observable<void> {
    return this.preloadingStrategy.preload(route, () => {
      let loadedChildren$: Observable<LoadedRouterConfig | null>;
      if (route.loadChildren && route.canLoad === undefined) {
        loadedChildren$ = this.loader.loadChildren(injector, route);
      } else {
        loadedChildren$ = of(null);
      }

      const recursiveLoadChildren$ = loadedChildren$.pipe(
        mergeMap((config: LoadedRouterConfig | null) => {
          if (config === null) {
            return of(void 0);
          }
          route._loadedRoutes = config.routes;
          route._loadedInjector = config.injector;
          // If the loaded config was a module, use that as the module/module injector going
          // forward. Otherwise, continue using the current module/module injector.
          return this.processRoutes(config.injector ?? injector, config.routes);
        }),
      );
      if (route.loadComponent && !route._loadedComponent) {
        const loadComponent$ = this.loader.loadComponent(route);
        return from([recursiveLoadChildren$, loadComponent$]).pipe(mergeAll());
      } else {
        return recursiveLoadChildren$;
      }
    });
  }
}


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {
  Compiler,
  EnvironmentInjector,
  inject,
  Injectable,
  InjectionToken,
  Injector,
  NgModuleFactory,
  Type,
} from '@angular/core';
import {ConnectableObservable, from, Observable, of, Subject} from 'rxjs';
import {finalize, map, mergeMap, refCount, tap} from 'rxjs/operators';

import {DefaultExport, LoadedRouterConfig, Route, Routes} from './models';
import {wrapIntoObservable} from './utils/collection';
import {assertStandalone, validateConfig} from './utils/config';
import {standardizeConfig} from './components/empty_outlet';

/**
 * The DI token for a router configuration.
 *
 * `ROUTES` is a low level API for router configuration via dependency injection.
 *
 * We recommend that in almost all cases to use higher level APIs such as `RouterModule.forRoot()`,
 * `provideRouter`, or `Router.resetConfig()`.
 *
 * @publicApi
 */
export const ROUTES = new InjectionToken<Route[][]>(ngDevMode ? 'ROUTES' : '');

type ComponentLoader = Observable<Type<unknown>>;

@Injectable({providedIn: 'root'})
export class RouterConfigLoader {
  private componentLoaders = new WeakMap<Route, ComponentLoader>();
  private childrenLoaders = new WeakMap<Route, Observable<LoadedRouterConfig>>();
  onLoadStartListener?: (r: Route) => void;
  onLoadEndListener?: (r: Route) => void;
  private readonly compiler = inject(Compiler);

  loadComponent(route: Route): Observable<Type<unknown>> {
    if (this.componentLoaders.get(route)) {
      return this.componentLoaders.get(route)!;
    } else if (route._loadedComponent) {
      return of(route._loadedComponent);
    }

    if (this.onLoadStartListener) {
      this.onLoadStartListener(route);
    }
    const loadRunner = wrapIntoObservable(route.loadComponent!()).pipe(
      map(maybeUnwrapDefaultExport),
      tap((component) => {
        if (this.onLoadEndListener) {
          this.onLoadEndListener(route);
        }
        (typeof ngDevMode === 'undefined' || ngDevMode) &&
          assertStandalone(route.path ?? '', component);
        route._loadedComponent = component;
      }),
      finalize(() => {
        this.componentLoaders.delete(route);
      }),
    );
    // Use custom ConnectableObservable as share in runners pipe increasing the bundle size too much
    const loader = new ConnectableObservable(loadRunner, () => new Subject<Type<unknown>>()).pipe(
      refCount(),
    );
    this.componentLoaders.set(route, loader);
    return loader;
  }

  loadChildren(parentInjector: Injector, route: Route): Observable<LoadedRouterConfig> {
    if (this.childrenLoaders.get(route)) {
      return this.childrenLoaders.get(route)!;
    } else if (route._loadedRoutes) {
      return of({routes: route._loadedRoutes, injector: route._loadedInjector});
    }

    if (this.onLoadStartListener) {
      this.onLoadStartListener(route);
    }
    const moduleFactoryOrRoutes$ = loadChildren(
      route,
      this.compiler,
      parentInjector,
      this.onLoadEndListener,
    );
    const loadRunner = moduleFactoryOrRoutes$.pipe(
      finalize(() => {
        this.childrenLoaders.delete(route);
      }),
    );
    // Use custom ConnectableObservable as share in runners pipe increasing the bundle size too much
    const loader = new ConnectableObservable(
      loadRunner,
      () => new Subject<LoadedRouterConfig>(),
    ).pipe(refCount());
    this.childrenLoaders.set(route, loader);
    return loader;
  }
}

/**
 * Executes a `route.loadChildren` callback and converts the result to an array of child routes and
 * an injector if that callback returned a module.
 *
 * This function is used for the route discovery during prerendering
 * in @angular-devkit/build-angular. If there are any updates to the contract here, it will require
 * an update to the extractor.
 */
export function loadChildren(
  route: Route,
  compiler: Compiler,
  parentInjector: Injector,
  onLoadEndListener?: (r: Route) => void,
): Observable<LoadedRouterConfig> {
  return wrapIntoObservable(route.loadChildren!()).pipe(
    map(maybeUnwrapDefaultExport),
    mergeMap((t) => {
      if (t instanceof NgModuleFactory || Array.isArray(t)) {
        return of(t);
      } else {
        return from(compiler.compileModuleAsync(t));
      }
    }),
    map((factoryOrRoutes: NgModuleFactory<any> | Routes) => {
      if (onLoadEndListener) {
        onLoadEndListener(route);
      }
      // This injector comes from the `NgModuleRef` when lazy loading an `NgModule`. There is
      // no injector associated with lazy loading a `Route` array.
      let injector: EnvironmentInjector | undefined;
      let rawRoutes: Route[];
      let requireStandaloneComponents = false;
      if (Array.isArray(factoryOrRoutes)) {
        rawRoutes = factoryOrRoutes;
        requireStandaloneComponents = true;
      } else {
        injector = factoryOrRoutes.create(parentInjector).injector;
        // When loading a module that doesn't provide `RouterModule.forChild()` preloader
        // will get stuck in an infinite loop. The child module's Injector will look to
        // its parent `Injector` when it doesn't find any ROUTES so it will return routes
        // for it's parent module instead.
        rawRoutes = injector.get(ROUTES, [], {optional: true, self: true}).flat();
      }
      const routes = rawRoutes.map(standardizeConfig);
      (typeof ngDevMode === 'undefined' || ngDevMode) &&
        validateConfig(routes, route.path, requireStandaloneComponents);
      return {routes, injector};
    }),
  );
}

function isWrappedDefaultExport<T>(value: T | DefaultExport<T>): value is DefaultExport<T> {
  // We use `in` here with a string key `'default'`, because we expect `DefaultExport` objects to be
  // dynamically imported ES modules with a spec-mandated `default` key. Thus we don't expect that
  // `default` will be a renamed property.
  return value && typeof value === 'object' && 'default' in value;
}

function maybeUnwrapDefaultExport<T>(input: T | DefaultExport<T>): T {
  // As per `isWrappedDefaultExport`, the `default` key here is generated by the browser and not
  // subject to property renaming, so we reference it with bracket access.
  return isWrappedDefaultExport(input) ? input['default'] : input;
}


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {
  HashLocationStrategy,
  LOCATION_INITIALIZED,
  LocationStrategy,
  ViewportScroller,
} from '@angular/common';
import {
  APP_BOOTSTRAP_LISTENER,
  APP_INITIALIZER,
  ApplicationRef,
  ComponentRef,
  ENVIRONMENT_INITIALIZER,
  EnvironmentProviders,
  inject,
  InjectFlags,
  InjectionToken,
  Injector,
  makeEnvironmentProviders,
  NgZone,
  Provider,
  runInInjectionContext,
  Type,
} from '@angular/core';
import {of, Subject} from 'rxjs';

import {INPUT_BINDER, RoutedComponentInputBinder} from './directives/router_outlet';
import {Event, NavigationError, stringifyEvent} from './events';
import {RedirectCommand, Routes} from './models';
import {NAVIGATION_ERROR_HANDLER, NavigationTransitions} from './navigation_transition';
import {Router} from './router';
import {InMemoryScrollingOptions, ROUTER_CONFIGURATION, RouterConfigOptions} from './router_config';
import {ROUTES} from './router_config_loader';
import {PreloadingStrategy, RouterPreloader} from './router_preloader';
import {ROUTER_SCROLLER, RouterScroller} from './router_scroller';
import {ActivatedRoute} from './router_state';
import {UrlSerializer} from './url_tree';
import {afterNextNavigation} from './utils/navigations';
import {
  CREATE_VIEW_TRANSITION,
  createViewTransition,
  VIEW_TRANSITION_OPTIONS,
  ViewTransitionsFeatureOptions,
} from './utils/view_transition';

/**
 * Sets up providers necessary to enable `Router` functionality for the application.
 * Allows to configure a set of routes as well as extra features that should be enabled.
 *
 * @usageNotes
 *
 * Basic example of how you can add a Router to your application:
 * ```
 * const appRoutes: Routes = [];
 * bootstrapApplication(AppComponent, {
 *   providers: [provideRouter(appRoutes)]
 * });
 * ```
 *
 * You can also enable optional features in the Router by adding functions from the `RouterFeatures`
 * type:
 * ```
 * const appRoutes: Routes = [];
 * bootstrapApplication(AppComponent,
 *   {
 *     providers: [
 *       provideRouter(appRoutes,
 *         withDebugTracing(),
 *         withRouterConfig({paramsInheritanceStrategy: 'always'}))
 *     ]
 *   }
 * );
 * ```
 *
 * @see {@link RouterFeatures}
 *
 * @publicApi
 * @param routes A set of `Route`s to use for the application routing table.
 * @param features Optional features to configure additional router behaviors.
 * @returns A set of providers to setup a Router.
 */
export function provideRouter(routes: Routes, ...features: RouterFeatures[]): EnvironmentProviders {
  return makeEnvironmentProviders([
    {provide: ROUTES, multi: true, useValue: routes},
    typeof ngDevMode === 'undefined' || ngDevMode
      ? {provide: ROUTER_IS_PROVIDED, useValue: true}
      : [],
    {provide: ActivatedRoute, useFactory: rootRoute, deps: [Router]},
    {provide: APP_BOOTSTRAP_LISTENER, multi: true, useFactory: getBootstrapListener},
    features.map((feature) => feature.ɵproviders),
  ]);
}

export function rootRoute(router: Router): ActivatedRoute {
  return router.routerState.root;
}

/**
 * Helper type to represent a Router feature.
 *
 * @publicApi
 */
export interface RouterFeature<FeatureKind extends RouterFeatureKind> {
  ɵkind: FeatureKind;
  ɵproviders: Provider[];
}

/**
 * Helper function to create an object that represents a Router feature.
 */
function routerFeature<FeatureKind extends RouterFeatureKind>(
  kind: FeatureKind,
  providers: Provider[],
): RouterFeature<FeatureKind> {
  return {ɵkind: kind, ɵproviders: providers};
}

/**
 * An Injection token used to indicate whether `provideRouter` or `RouterModule.forRoot` was ever
 * called.
 */
export const ROUTER_IS_PROVIDED = new InjectionToken<boolean>('', {
  providedIn: 'root',
  factory: () => false,
});

const routerIsProvidedDevModeCheck = {
  provide: ENVIRONMENT_INITIALIZER,
  multi: true,
  useFactory() {
    return () => {
      if (!inject(ROUTER_IS_PROVIDED)) {
        console.warn(
          '`provideRoutes` was called without `provideRouter` or `RouterModule.forRoot`. ' +
            'This is likely a mistake.',
        );
      }
    };
  },
};

/**
 * Registers a DI provider for a set of routes.
 * @param routes The route configuration to provide.
 *
 * @usageNotes
 *
 * ```
 * @NgModule({
 *   providers: [provideRoutes(ROUTES)]
 * })
 * class LazyLoadedChildModule {}
 * ```
 *
 * @deprecated If necessary, provide routes using the `ROUTES` `InjectionToken`.
 * @see {@link ROUTES}
 * @publicApi
 */
export function provideRoutes(routes: Routes): Provider[] {
  return [
    {provide: ROUTES, multi: true, useValue: routes},
    typeof ngDevMode === 'undefined' || ngDevMode ? routerIsProvidedDevModeCheck : [],
  ];
}

/**
 * A type alias for providers returned by `withInMemoryScrolling` for use with `provideRouter`.
 *
 * @see {@link withInMemoryScrolling}
 * @see {@link provideRouter}
 *
 * @publicApi
 */
export type InMemoryScrollingFeature = RouterFeature<RouterFeatureKind.InMemoryScrollingFeature>;

/**
 * Enables customizable scrolling behavior for router navigations.
 *
 * @usageNotes
 *
 * Basic example of how you can enable scrolling feature:
 * ```
 * const appRoutes: Routes = [];
 * bootstrapApplication(AppComponent,
 *   {
 *     providers: [
 *       provideRouter(appRoutes, withInMemoryScrolling())
 *     ]
 *   }
 * );
 * ```
 *
 * @see {@link provideRouter}
 * @see {@link ViewportScroller}
 *
 * @publicApi
 * @param options Set of configuration parameters to customize scrolling behavior, see
 *     `InMemoryScrollingOptions` for additional information.
 * @returns A set of providers for use with `provideRouter`.
 */
export function withInMemoryScrolling(
  options: InMemoryScrollingOptions = {},
): InMemoryScrollingFeature {
  const providers = [
    {
      provide: ROUTER_SCROLLER,
      useFactory: () => {
        const viewportScroller = inject(ViewportScroller);
        const zone = inject(NgZone);
        const transitions = inject(NavigationTransitions);
        const urlSerializer = inject(UrlSerializer);
        return new RouterScroller(urlSerializer, transitions, viewportScroller, zone, options);
      },
    },
  ];
  return routerFeature(RouterFeatureKind.InMemoryScrollingFeature, providers);
}

export function getBootstrapListener() {
  const injector = inject(Injector);
  return (bootstrappedComponentRef: ComponentRef<unknown>) => {
    const ref = injector.get(ApplicationRef);

    if (bootstrappedComponentRef !== ref.components[0]) {
      return;
    }

    const router = injector.get(Router);
    const bootstrapDone = injector.get(BOOTSTRAP_DONE);

    if (injector.get(INITIAL_NAVIGATION) === InitialNavigation.EnabledNonBlocking) {
      router.initialNavigation();
    }

    injector.get(ROUTER_PRELOADER, null, InjectFlags.Optional)?.setUpPreloading();
    injector.get(ROUTER_SCROLLER, null, InjectFlags.Optional)?.init();
    router.resetRootComponentType(ref.componentTypes[0]);
    if (!bootstrapDone.closed) {
      bootstrapDone.next();
      bootstrapDone.complete();
      bootstrapDone.unsubscribe();
    }
  };
}

/**
 * A subject used to indicate that the bootstrapping phase is done. When initial navigation is
 * `enabledBlocking`, the first navigation waits until bootstrapping is finished before continuing
 * to the activation phase.
 */
const BOOTSTRAP_DONE = new InjectionToken<Subject<void>>(
  typeof ngDevMode === 'undefined' || ngDevMode ? 'bootstrap done indicator' : '',
  {
    factory: () => {
      return new Subject<void>();
    },
  },
);

/**
 * This and the INITIAL_NAVIGATION token are used internally only. The public API side of this is
 * configured through the `ExtraOptions`.
 *
 * When set to `EnabledBlocking`, the initial navigation starts before the root
 * component is created. The bootstrap is blocked until the initial navigation is complete. This
 * value should be set in case you use [server-side rendering](guide/ssr), but do not enable
 * [hydration](guide/hydration) for your application.
 *
 * When set to `EnabledNonBlocking`, the initial navigation starts after the root component has been
 * created. The bootstrap is not blocked on the completion of the initial navigation.
 *
 * When set to `Disabled`, the initial navigation is not performed. The location listener is set up
 * before the root component gets created. Use if there is a reason to have more control over when
 * the router starts its initial navigation due to some complex initialization logic.
 *
 * @see {@link ExtraOptions}
 */
const enum InitialNavigation {
  EnabledBlocking,
  EnabledNonBlocking,
  Disabled,
}

con


* Provides the location strategy that uses the URL fragment instead of the history API.
 *
 * @usageNotes
 *
 * Basic example of how you can use the hash location option:
 * ```
 * const appRoutes: Routes = [];
 * bootstrapApplication(AppComponent,
 *   {
 *     providers: [
 *       provideRouter(appRoutes, withHashLocation())
 *     ]
 *   }
 * );
 * ```
 *
 * @see {@link provideRouter}
 * @see {@link HashLocationStrategy}
 *
 * @returns A set of providers for use with `provideRouter`.
 *
 * @publicApi
 */
export function withHashLocation(): RouterHashLocationFeature {
  const providers = [{provide: LocationStrategy, useClass: HashLocationStrategy}];
  return routerFeature(RouterFeatureKind.RouterHashLocationFeature, providers);
}

/**
 * A type alias for providers returned by `withNavigationErrorHandler` for use with `provideRouter`.
 *
 * @see {@link withNavigationErrorHandler}
 * @see {@link provideRouter}
 *
 * @publicApi
 */
export type NavigationErrorHandlerFeature =
  RouterFeature<RouterFeatureKind.NavigationErrorHandlerFeature>;

/**
 * Provides a function which is called when a navigation error occurs.
 *
 * This function is run inside application's [injection context](guide/di/dependency-injection-context)
 * so you can use the [`inject`](api/core/inject) function.
 *
 * This function can return a `RedirectCommand` to convert the error to a redirect, similar to returning
 * a `UrlTree` or `RedirectCommand` from a guard. This will also prevent the `Router` from emitting
 * `NavigationError`; it will instead emit `NavigationCancel` with code NavigationCancellationCode.Redirect.
 * Return values other than `RedirectCommand` are ignored and do not change any behavior with respect to
 * how the `Router` handles the error.
 *
 * @usageNotes
 *
 * Basic example of how you can use the error handler option:
 * ```
 * const appRoutes: Routes = [];
 * bootstrapApplication(AppComponent,
 *   {
 *     providers: [
 *       provideRouter(appRoutes, withNavigationErrorHandler((e: NavigationError) =>
 * inject(MyErrorTracker).trackError(e)))
 *     ]
 *   }
 * );
 * ```
 *
 * @see {@link NavigationError}
 * @see {@link core/inject}
 * @see {@link runInInjectionContext}
 *
 * @returns A set of providers for use with `provideRouter`.
 *
 * @publicApi
 */
export function withNavigationErrorHandler(
  handler: (error: NavigationError) => unknown | RedirectCommand,
): NavigationErrorHandlerFeature {
  const providers = [
    {
      provide: NAVIGATION_ERROR_HANDLER,
      useValue: handler,
    },
  ];
  return routerFeature(RouterFeatureKind.NavigationErrorHandlerFeature, providers);
}

/**
 * A type alias for providers returned by `withComponentInputBinding` for use with `provideRouter`.
 *
 * @see {@link withComponentInputBinding}
 * @see {@link provideRouter}
 *
 * @publicApi
 */
export type ComponentInputBindingFeature =
  RouterFeature<RouterFeatureKind.ComponentInputBindingFeature>;

/**
 * A type alias for providers returned by `withViewTransitions` for use with `provideRouter`.
 *
 * @see {@link withViewTransitions}
 * @see {@link provideRouter}
 *
 * @publicApi
 */
export type ViewTransitionsFeature = RouterFeature<RouterFeatureKind.ViewTransitionsFeature>;

/**
 * Enables binding information from the `Router` state directly to the inputs of the component in
 * `Route` configurations.
 *
 * @usageNotes
 *
 * Basic example of how you can enable the feature:
 * ```
 * const appRoutes: Routes = [];
 * bootstrapApplication(AppComponent,
 *   {
 *     providers: [
 *       provideRouter(appRoutes, withComponentInputBinding())
 *     ]
 *   }
 * );
 * ```
 *
 * The router bindings information from any of the following sources:
 *
 *  - query parameters
 *  - path and matrix parameters
 *  - static route data
 *  - data from resolvers
 *
 * Duplicate keys are resolved in the same order from above, from least to greatest,
 * meaning that resolvers have the highest precedence and override any of the other information
 * from the route.
 *
 * Importantly, when an input does not have an item in the route data with a matching key, this
 * input is set to `undefined`. This prevents previous information from being
 * retained if the data got removed from the route (i.e. if a query parameter is removed).
 * Default values can be provided with a resolver on the route to ensure the value is always present
 * or an input and use an input transform in the component.
 *
 * @see {@link guide/components/inputs#input-transforms input transforms}
 * @returns A set of providers for use with `provideRouter`.
 */
export function withComponentInputBinding(): ComponentInputBindingFeature {
  const providers = [
    RoutedComponentInputBinder,
    {provide: INPUT_BINDER, useExisting: RoutedComponentInputBinder},
  ];

  return routerFeature(RouterFeatureKind.ComponentInputBindingFeature, providers);
}

/**
 * Enables view transitions in the Router by running the route activation and deactivation inside of
 * `document.startViewTransition`.
 *
 * Note: The View Transitions API is not available in all browsers. If the browser does not support
 * view transitions, the Router will not attempt to start a view transition and continue processing
 * the navigation as usual.
 *
 * @usageNotes
 *
 * Basic example of how you can enable the feature:
 * ```
 * const appRoutes: Routes = [];
 * bootstrapApplication(AppComponent,
 *   {
 *     providers: [
 *       provideRouter(appRoutes, withViewTransitions())
 *     ]
 *   }
 * );
 * ```
 *
 * @returns A set of providers for use with `provideRouter`.
 * @see https://developer.chrome.com/docs/web-platform/view-transitions/
 * @see https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API
 * @developerPreview
 */
export function withViewTransitions(
  options?: ViewTransitionsFeatureOptions,
): ViewTransitionsFeature {
  const providers = [
    {provide: CREATE_VIEW_TRANSITION, useValue: createViewTransition},
    {
      provide: VIEW_TRANSITION_OPTIONS,
      useValue: {skipNextTransition: !!options?.skipInitialTransition, ...options},
    },
  ];
  return routerFeature(RouterFeatureKind.ViewTransitionsFeature, providers);
}

/**
 * A type alias that represents all Router features available for use with `provideRouter`.
 * Features can be enabled by adding special functions to the `provideRouter` call.
 * See documentation for each symbol to find corresponding function name. See also `provideRouter`
 * documentation on how to use those functions.
 *
 * @see {@link provideRouter}
 *
 * @publicApi
 */
export type RouterFeatures =
  | PreloadingFeature
  | DebugTracingFeature
  | InitialNavigationFeature
  | InMemoryScrollingFeature
  | RouterConfigurationFeature
  | NavigationErrorHandlerFeature
  | ComponentInputBindingFeature
  | ViewTransitionsFeature
  | RouterHashLocationFeature;

/**
 * The list of features as an enum to uniquely type each feature.
 */
export const enum RouterFeatureKind {
  PreloadingFeature,
  DebugTracingFeature,
  EnabledBlockingInitialNavigationFeature,
  DisabledInitialNavigationFeature,
  InMemoryScrollingFeature,
  RouterConfigurationFeature,
  RouterHashLocationFeature,
  NavigationErrorHandlerFeature,
  ComponentInputBindingFeature,
  ViewTransitionsFeature,
}


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {NavigationBehaviorOptions, Route} from './models';
import {ActivatedRouteSnapshot, RouterStateSnapshot} from './router_state';
import {UrlTree} from './url_tree';

/**
 * Identifies the call or event that triggered a navigation.
 *
 * * 'imperative': Triggered by `router.navigateByUrl()` or `router.navigate()`.
 * * 'popstate' : Triggered by a `popstate` event.
 * * 'hashchange'-: Triggered by a `hashchange` event.
 *
 * @publicApi
 */
export type NavigationTrigger = 'imperative' | 'popstate' | 'hashchange';
export const IMPERATIVE_NAVIGATION = 'imperative';

/**
 * Identifies the type of a router event.
 *
 * @publicApi
 */
export enum EventType {
  NavigationStart,
  NavigationEnd,
  NavigationCancel,
  NavigationError,
  RoutesRecognized,
  ResolveStart,
  ResolveEnd,
  GuardsCheckStart,
  GuardsCheckEnd,
  RouteConfigLoadStart,
  RouteConfigLoadEnd,
  ChildActivationStart,
  ChildActivationEnd,
  ActivationStart,
  ActivationEnd,
  Scroll,
  NavigationSkipped,
}

/**
 * Base for events the router goes through, as opposed to events tied to a specific
 * route. Fired one time for any given navigation.
 *
 * The following code shows how a class subscribes to router events.
 *
 * ```ts
 * import {Event, RouterEvent, Router} from '@angular/router';
 *
 * class MyService {
 *   constructor(public router: Router) {
 *     router.events.pipe(
 *        filter((e: Event | RouterEvent): e is RouterEvent => e instanceof RouterEvent)
 *     ).subscribe((e: RouterEvent) => {
 *       // Do something
 *     });
 *   }
 * }
 * ```
 *
 * @see {@link Event}
 * @see [Router events summary](guide/routing/router-reference#router-events)
 * @publicApi
 */
export class RouterEvent {
  constructor(
    /** A unique ID that the router assigns to every router navigation. */
    public id: number,
    /** The URL that is the destination for this navigation. */
    public url: string,
  ) {}
}

/**
 * An event triggered when a navigation starts.
 *
 * @publicApi
 */
export class NavigationStart extends RouterEvent {
  readonly type = EventType.NavigationStart;

  /**
   * Identifies the call or event that triggered the navigation.
   * An `imperative` trigger is a call to `router.navigateByUrl()` or `router.navigate()`.
   *
   * @see {@link NavigationEnd}
   * @see {@link NavigationCancel}
   * @see {@link NavigationError}
   */
  navigationTrigger?: NavigationTrigger;

  /**
   * The navigation state that was previously supplied to the `pushState` call,
   * when the navigation is triggered by a `popstate` event. Otherwise null.
   *
   * The state object is defined by `NavigationExtras`, and contains any
   * developer-defined state value, as well as a unique ID that
   * the router assigns to every router transition/navigation.
   *
   * From the perspective of the router, the router never "goes back".
   * When the user clicks on the back button in the browser,
   * a new navigation ID is created.
   *
   * Use the ID in this previous-state object to differentiate between a newly created
   * state and one returned to by a `popstate` event, so that you can restore some
   * remembered state, such as scroll position.
   *
   */
  restoredState?: {[k: string]: any; navigationId: number} | null;

  constructor(
    /** @docsNotRequired */
    id: number,
    /** @docsNotRequired */
    url: string,
    /** @docsNotRequired */
    navigationTrigger: NavigationTrigger = 'imperative',
    /** @docsNotRequired */
    restoredState: {[k: string]: any; navigationId: number} | null = null,
  ) {
    super(id, url);
    this.navigationTrigger = navigationTrigger;
    this.restoredState = restoredState;
  }

  /** @docsNotRequired */
  override toString(): string {
    return `NavigationStart(id: ${this.id}, url: '${this.url}')`;
  }
}

/**
 * An event triggered when a navigation ends successfully.
 *
 * @see {@link NavigationStart}
 * @see {@link NavigationCancel}
 * @see {@link NavigationError}
 *
 * @publicApi
 */
export class NavigationEnd extends RouterEvent {
  readonly type = EventType.NavigationEnd;

  constructor(
    /** @docsNotRequired */
    id: number,
    /** @docsNotRequired */
    url: string,
    /** @docsNotRequired */
    public urlAfterRedirects: string,
  ) {
    super(id, url);
  }

  /** @docsNotRequired */
  override toString(): string {
    return `NavigationEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}')`;
  }
}

/**
 * A code for the `NavigationCancel` event of the `Router` to indicate the
 * reason a navigation failed.
 *
 * @publicApi
 */
export enum NavigationCancellationCode {
  /**
   * A navigation failed because a guard returned a `UrlTree` to redirect.
   */
  Redirect,
  /**
   * A navigation failed because a more recent navigation started.
   */
  SupersededByNewNavigation,
  /**
   * A navigation failed because one of the resolvers completed without emitting a value.
   */
  NoDataFromResolver,
  /**
   * A navigation failed because a guard returned `false`.
   */
  GuardRejected,
}

/**
 * A code for the `NavigationSkipped` event of the `Router` to indicate the
 * reason a navigation was skipped.
 *
 * @publicApi
 */
export enum NavigationSkippedCode {
  /**
   * A navigation was skipped because the navigation URL was the same as the current Router URL.
   */
  IgnoredSameUrlNavigation,
  /**
   * A navigation was skipped because the configured `UrlHandlingStrategy` return `false` for both
   * the current Router URL and the target of the navigation.
   *
   * @see {@link UrlHandlingStrategy}
   */
  IgnoredByUrlHandlingStrategy,
}

/**
 * An event triggered when a navigation is canceled, directly or indirectly.
 * This can happen for several reasons including when a route guard
 * returns `false` or initiates a redirect by returning a `UrlTree`.
 *
 * @see {@link NavigationStart}
 * @see {@link NavigationEnd}
 * @see {@link NavigationError}
 *
 * @publicApi
 */
export class NavigationCancel extends RouterEvent {
  readonly type = EventType.NavigationCancel;

  constructor(
    /** @docsNotRequired */
    id: number,
    /** @docsNotRequired */
    url: string,
    /**
     * A description of why the navigation was cancelled. For debug purposes only. Use `code`
     * instead for a stable cancellation reason that can be used in production.
     */
    public reason: string,
    /**
     * A code to indicate why the navigation was canceled. This cancellation code is stable for
     * the reason and can be relied on whereas the `reason` string could change and should not be
     * used in production.
     */
    readonly code?: NavigationCancellationCode,
  ) {
    super(id, url);
  }

  /** @docsNotRequired */
  override toString(): string {
    return `NavigationCancel(id: ${this.id}, url: '${this.url}')`;
  }
}

/**
 * An event triggered when a navigation is skipped.
 * This can happen for a couple reasons including onSameUrlHandling
 * is set to `ignore` and the navigation URL is not different than the
 * current state.
 *
 * @publicApi
 */
export class NavigationSkipped extends RouterEvent {
  readonly type = EventType.NavigationSkipped;

  constructor(
    /** @docsNotRequired */
    id: number,
    /** @docsNotRequired */
    url: string,
    /**
     * A description of why the navigation was skipped. For debug purposes only. Use `code`
     * instead for a stable skipped reason that can be used in production.
     */
    public reason: string,
    /**
     * A code to indicate why the navigation was skipped. This code is stable for
     * the reason and can be relied on whereas the `reason` string could change and should not be
     * used in production.
     */
    readonly code?: NavigationSkippedCode,
  ) {
    super(id, url);
  }
}

/**
 * An event triggered when a navigation fails due to an unexpected error.
 *
 * @see {@link NavigationStart}
 * @see {@link NavigationEnd}
 * @see {@link NavigationCancel}
 *
 * @publicApi
 */


/**
 * Router events that allow you to track the lifecycle of the router.
 *
 * The events occur in the following sequence:
 *
 * * [NavigationStart](api/router/NavigationStart): Navigation starts.
 * * [RouteConfigLoadStart](api/router/RouteConfigLoadStart): Before
 * the router [lazy loads](guide/routing/common-router-tasks#lazy-loading) a route configuration.
 * * [RouteConfigLoadEnd](api/router/RouteConfigLoadEnd): After a route has been lazy loaded.
 * * [RoutesRecognized](api/router/RoutesRecognized): When the router parses the URL
 * and the routes are recognized.
 * * [GuardsCheckStart](api/router/GuardsCheckStart): When the router begins the *guards*
 * phase of routing.
 * * [ChildActivationStart](api/router/ChildActivationStart): When the router
 * begins activating a route's children.
 * * [ActivationStart](api/router/ActivationStart): When the router begins activating a route.
 * * [GuardsCheckEnd](api/router/GuardsCheckEnd): When the router finishes the *guards*
 * phase of routing successfully.
 * * [ResolveStart](api/router/ResolveStart): When the router begins the *resolve*
 * phase of routing.
 * * [ResolveEnd](api/router/ResolveEnd): When the router finishes the *resolve*
 * phase of routing successfully.
 * * [ChildActivationEnd](api/router/ChildActivationEnd): When the router finishes
 * activating a route's children.
 * * [ActivationEnd](api/router/ActivationEnd): When the router finishes activating a route.
 * * [NavigationEnd](api/router/NavigationEnd): When navigation ends successfully.
 * * [NavigationCancel](api/router/NavigationCancel): When navigation is canceled.
 * * [NavigationError](api/router/NavigationError): When navigation fails
 * due to an unexpected error.
 * * [Scroll](api/router/Scroll): When the user scrolls.
 *
 * @publicApi
 */
export type Event =
  | NavigationStart
  | NavigationEnd
  | NavigationCancel
  | NavigationError
  | RoutesRecognized
  | GuardsCheckStart
  | GuardsCheckEnd
  | RouteConfigLoadStart
  | RouteConfigLoadEnd
  | ChildActivationStart
  | ChildActivationEnd
  | ActivationStart
  | ActivationEnd
  | Scroll
  | ResolveStart
  | ResolveEnd
  | NavigationSkipped;

export function stringifyEvent(routerEvent: Event): string {
  switch (routerEvent.type) {
    case EventType.ActivationEnd:
      return `ActivationEnd(path: '${routerEvent.snapshot.routeConfig?.path || ''}')`;
    case EventType.ActivationStart:
      return `ActivationStart(path: '${routerEvent.snapshot.routeConfig?.path || ''}')`;
    case EventType.ChildActivationEnd:
      return `ChildActivationEnd(path: '${routerEvent.snapshot.routeConfig?.path || ''}')`;
    case EventType.ChildActivationStart:
      return `ChildActivationStart(path: '${routerEvent.snapshot.routeConfig?.path || ''}')`;
    case EventType.GuardsCheckEnd:
      return `GuardsCheckEnd(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${routerEvent.state}, shouldActivate: ${routerEvent.shouldActivate})`;
    case EventType.GuardsCheckStart:
      return `GuardsCheckStart(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${routerEvent.state})`;
    case EventType.NavigationCancel:
      return `NavigationCancel(id: ${routerEvent.id}, url: '${routerEvent.url}')`;
    case EventType.NavigationSkipped:
      return `NavigationSkipped(id: ${routerEvent.id}, url: '${routerEvent.url}')`;
    case EventType.NavigationEnd:
      return `NavigationEnd(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}')`;
    case EventType.NavigationError:
      return `NavigationError(id: ${routerEvent.id}, url: '${routerEvent.url}', error: ${routerEvent.error})`;
    case EventType.NavigationStart:
      return `NavigationStart(id: ${routerEvent.id}, url: '${routerEvent.url}')`;
    case EventType.ResolveEnd:
      return `ResolveEnd(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${routerEvent.state})`;
    case EventType.ResolveStart:
      return `ResolveStart(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${routerEvent.state})`;
    case EventType.RouteConfigLoadEnd:
      return `RouteConfigLoadEnd(path: ${routerEvent.route.path})`;
    case EventType.RouteConfigLoadStart:
      return `RouteConfigLoadStart(path: ${routerEvent.route.path})`;
    case EventType.RoutesRecognized:
      return `RoutesRecognized(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${routerEvent.state})`;
    case EventType.Scroll:
      const pos = routerEvent.position
        ? `${routerEvent.position[0]}, ${routerEvent.position[1]}`
        : null;
      return `Scroll(anchor: '${routerEvent.anchor}', position: '${pos}')`;
  }
}


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {
  EnvironmentInjector,
  EnvironmentProviders,
  NgModuleFactory,
  Provider,
  ProviderToken,
  Type,
} from '@angular/core';
import {Observable} from 'rxjs';

import {ActivatedRouteSnapshot, RouterStateSnapshot} from './router_state';
import {UrlSegment, UrlSegmentGroup, UrlTree} from './url_tree';

/**
 * How to handle a navigation request to the current URL. One of:
 *
 * - `'ignore'` :  The router ignores the request it is the same as the current state.
 * - `'reload'` : The router processes the URL even if it is not different from the current state.
 * One example of when you might want this option is if a `canMatch` guard depends on
 * application state and initially rejects navigation to a route. After fixing the state, you want
 * to re-navigate to the same URL so the route with the `canMatch` guard can activate.
 *
 * Note that this only configures whether the Route reprocesses the URL and triggers related
 * action and events like redirects, guards, and resolvers. By default, the router re-uses a
 * component instance when it re-navigates to the same component type without visiting a different
 * component first. This behavior is configured by the `RouteReuseStrategy`. In order to reload
 * routed components on same url navigation, you need to set `onSameUrlNavigation` to `'reload'`
 * _and_ provide a `RouteReuseStrategy` which returns `false` for `shouldReuseRoute`. Additionally,
 * resolvers and most guards for routes do not run unless the path or path params changed
 * (configured by `runGuardsAndResolvers`).
 *
 * @publicApi
 * @see {@link RouteReuseStrategy}
 * @see {@link RunGuardsAndResolvers}
 * @see {@link NavigationBehaviorOptions}
 * @see {@link RouterConfigOptions}
 */
export type OnSameUrlNavigation = 'reload' | 'ignore';

/**
 * The `InjectionToken` and `@Injectable` classes for guards and resolvers are deprecated in favor
 * of plain JavaScript functions instead.. Dependency injection can still be achieved using the
 * [`inject`](api/core/inject) function from `@angular/core` and an injectable class can be used as
 * a functional guard using [`inject`](api/core/inject): `canActivate: [() =>
 * inject(myGuard).canActivate()]`.
 *
 * @deprecated
 * @see {@link CanMatchFn}
 * @see {@link CanLoadFn}
 * @see {@link CanActivateFn}
 * @see {@link CanActivateChildFn}
 * @see {@link CanDeactivateFn}
 * @see {@link ResolveFn}
 * @see {@link core/inject}
 * @publicApi
 */
export type DeprecatedGuard = ProviderToken<any> | any;

/**
 * The supported types that can be returned from a `Router` guard.
 *
 * @see [Routing guide](guide/routing/common-router-tasks#preventing-unauthorized-access)
 * @publicApi
 */
export type GuardResult = boolean | UrlTree | RedirectCommand;

/**
 * Can be returned by a `Router` guard to instruct the `Router` to redirect rather than continue
 * processing the path of the in-flight navigation. The `redirectTo` indicates _where_ the new
 * navigation should go to and the optional `navigationBehaviorOptions` can provide more information
 * about _how_ to perform the navigation.
 *
 * ```ts
 * const route: Route = {
 *   path: "user/:userId",
 *   component: User,
 *   canActivate: [
 *     () => {
 *       const router = inject(Router);
 *       const authService = inject(AuthenticationService);
 *
 *       if (!authService.isLoggedIn()) {
 *         const loginPath = router.parseUrl("/login");
 *         return new RedirectCommand(loginPath, {
 *           skipLocationChange: "true",
 *         });
 *       }
 *
 *       return true;
 *     },
 *   ],
 * };
 * ```
 * @see [Routing guide](guide/routing/common-router-tasks#preventing-unauthorized-access)
 *
 * @publicApi
 */
export class RedirectCommand {
  constructor(
    readonly redirectTo: UrlTree,
    readonly navigationBehaviorOptions?: NavigationBehaviorOptions,
  ) {}
}

/**
 * Type used to represent a value which may be synchronous or async.
 *
 * @publicApi
 */
export type MaybeAsync<T> = T | Observable<T> | Promise<T>;

/**
 * Represents a route configuration for the Router service.
 * An array of `Route` objects, used in `Router.config` and for nested route configurations
 * in `Route.children`.
 *
 * @see {@link Route}
 * @see {@link Router}
 * @see [Router configuration guide](guide/routing/router-reference#configuration)
 * @publicApi
 */
export type Routes = Route[];

/**
 * Represents the result of matching URLs with a custom matching function.
 *
 * * `consumed` is an array of the consumed URL segments.
 * * `posParams` is a map of positional parameters.
 *
 * @see {@link UrlMatcher}
 * @publicApi
 */
export type UrlMatchResult = {
  consumed: UrlSegment[];
  posParams?: {[name: string]: UrlSegment};
};

/**
 * A function for matching a route against URLs. Implement a custom URL matcher
 * for `Route.matcher` when a combination of `path` and `pathMatch`
 * is not expressive enough. Cannot be used together with `path` and `pathMatch`.
 *
 * The function takes the following arguments and returns a `UrlMatchResult` object.
 * * *segments* : An array of URL segments.
 * * *group* : A segment group.
 * * *route* : The route to match against.
 *
 * The following example implementation matches HTML files.
 *
 * ```
 * export function htmlFiles(url: UrlSegment[]) {
 *   return url.length === 1 && url[0].path.endsWith('.html') ? ({consumed: url}) : null;
 * }
 *
 * export const routes = [{ matcher: htmlFiles, component: AnyComponent }];
 * ```
 *
 * @publicApi
 */
export type UrlMatcher = (
  segments: UrlSegment[],
  group: UrlSegmentGroup,
  route: Route,
) => UrlMatchResult | null;

/**
 *
 * Represents static data associated with a particular route.
 *
 * @see {@link Route#data}
 *
 * @publicApi
 */
export type Data = {
  [key: string | symbol]: any;
};

/**
 *
 * Represents the resolved data associated with a particular route.
 *
 * Returning a `RedirectCommand` directs the router to cancel the current navigation and redirect to
 * the location provided in the `RedirectCommand`. Note that there are no ordering guarantees when
 * resolvers execute. If multiple resolvers would return a `RedirectCommand`, only the first one
 * returned will be used.
 *
 * @see {@link Route#resolve}
 *
 * @publicApi
 */
export type ResolveData = {
  [key: string | symbol]: ResolveFn<unknown> | DeprecatedGuard;
};

/**
 * An ES Module object with a default export of the given type.
 *
 * @see {@link Route#loadComponent}
 * @see {@link LoadChildrenCallback}
 *
 * @publicApi
 */
export interface DefaultExport<T> {
  /**
   * Default exports are bound under the name `"default"`, per the ES Module spec:
   * https://tc39.es/ecma262/#table-export-forms-mapping-to-exportentry-records
   */
  default: T;
}

/**
 *
 * A function that is called to resolve a collection of lazy-loaded routes.
 * Must be an arrow function of the following form:
 * `() => import('...').then(mod => mod.MODULE)`
 * or
 * `() => import('...').then(mod => mod.ROUTES)`
 *
 * For example:
 *
 * ```
 * [{
 *   path: 'lazy',
 *   loadChildren: () => import('./lazy-route/lazy.module').then(mod => mod.LazyModule),
 * }];
 * ```
 * or
 * ```
 * [{
 *   path: 'lazy',
 *   loadChildren: () => import('./lazy-route/lazy.routes').then(mod => mod.ROUTES),
 * }];
 * ```
 *
 * If the lazy-loaded routes are exported via a `default` export, the `.then` can be omitted:
 * ```
 * [{
 *   path: 'lazy',
 *   loadChildren: () => import('./lazy-route/lazy.routes'),
 * }];
 * ```
 *
 * @see {@link Route#loadChildren}
 * @publicApi
 */
export type LoadChildrenCallback = () =>
  | Type<any>
  | NgModuleFactory<any>
  | Routes
  | Observable<Type<any> | Routes | DefaultExport<Type<any>> | DefaultExport<Routes>>
  | Promise<
      NgModuleFactory<any> | Type<any> | Routes | DefaultExport<Type<any>> | DefaultExport<Routes>
    >;

/**
 *
 * A function that returns a set of routes to load.
 *
 * @see {@link LoadChildrenCallback}
 * @publicApi
 */
export type LoadChildren = LoadChildrenCallback;


/**
 * A configuration object that defines a single route.
 * A set of routes are collected in a `Routes` array to define a `Router` configuration.
 * The router attempts to match segments of a given URL against each route,
 * using the configuration options defined in this object.
 *
 * Supports static, parameterized, redirect, and wildcard routes, as well as
 * custom route data and resolve methods.
 *
 * For detailed usage information, see the [Routing Guide](guide/routing/common-router-tasks).
 *
 * @usageNotes
 *
 * ### Simple Configuration
 *
 * The following route specifies that when navigating to, for example,
 * `/team/11/user/bob`, the router creates the 'Team' component
 * with the 'User' child component in it.
 *
 * ```
 * [{
 *   path: 'team/:id',
 *  component: Team,
 *   children: [{
 *     path: 'user/:name',
 *     component: User
 *   }]
 * }]
 * ```
 *
 * ### Multiple Outlets
 *
 * The following route creates sibling components with multiple outlets.
 * When navigating to `/team/11(aux:chat/jim)`, the router creates the 'Team' component next to
 * the 'Chat' component. The 'Chat' component is placed into the 'aux' outlet.
 *
 * ```
 * [{
 *   path: 'team/:id',
 *   component: Team
 * }, {
 *   path: 'chat/:user',
 *   component: Chat
 *   outlet: 'aux'
 * }]
 * ```
 *
 * ### Wild Cards
 *
 * The following route uses wild-card notation to specify a component
 * that is always instantiated regardless of where you navigate to.
 *
 * ```
 * [{
 *   path: '**',
 *   component: WildcardComponent
 * }]
 * ```
 *
 * ### Redirects
 *
 * The following route uses the `redirectTo` property to ignore a segment of
 * a given URL when looking for a child path.
 *
 * When navigating to '/team/11/legacy/user/jim', the router changes the URL segment
 * '/team/11/legacy/user/jim' to '/team/11/user/jim', and then instantiates
 * the Team component with the User child component in it.
 *
 * ```
 * [{
 *   path: 'team/:id',
 *   component: Team,
 *   children: [{
 *     path: 'legacy/user/:name',
 *     redirectTo: 'user/:name'
 *   }, {
 *     path: 'user/:name',
 *     component: User
 *   }]
 * }]
 * ```
 *
 * The redirect path can be relative, as shown in this example, or absolute.
 * If we change the `redirectTo` value in the example to the absolute URL segment '/user/:name',
 * the result URL is also absolute, '/user/jim'.

 * ### Empty Path
 *
 * Empty-path route configurations can be used to instantiate components that do not 'consume'
 * any URL segments.
 *
 * In the following configuration, when navigating to
 * `/team/11`, the router instantiates the 'AllUsers' component.
 *
 * ```
 * [{
 *   path: 'team/:id',
 *   component: Team,
 *   children: [{
 *     path: '',
 *     component: AllUsers
 *   }, {
 *     path: 'user/:name',
 *     component: User
 *   }]
 * }]
 * ```
 *
 * Empty-path routes can have children. In the following example, when navigating
 * to `/team/11/user/jim`, the router instantiates the wrapper component with
 * the user component in it.
 *
 * Note that an empty path route inherits its parent's parameters and data.
 *
 * ```
 * [{
 *   path: 'team/:id',
 *   component: Team,
 *   children: [{
 *     path: '',
 *     component: WrapperCmp,
 *     children: [{
 *       path: 'user/:name',
 *       component: User
 *     }]
 *   }]
 * }]
 * ```
 *
 * ### Matching Strategy
 *
 * The default path-match strategy is 'prefix', which means that the router
 * checks URL elements from the left to see if the URL matches a specified path.
 * For example, '/team/11/user' matches 'team/:id'.
 *
 * ```
 * [{
 *   path: '',
 *   pathMatch: 'prefix', //default
 *   redirectTo: 'main'
 * }, {
 *   path: 'main',
 *   component: Main
 * }]
 * ```
 *
 * You can specify the path-match strategy 'full' to make sure that the path
 * covers the whole unconsumed URL. It is important to do this when redirecting
 * empty-path routes. Otherwise, because an empty path is a prefix of any URL,
 * the router would apply the redirect even when navigating to the redirect destination,
 * creating an endless loop.
 *
 * In the following example, supplying the 'full' `pathMatch` strategy ensures
 * that the router applies the redirect if and only if navigating to '/'.
 *
 * ```
 * [{
 *   path: '',
 *   pathMatch: 'full',
 *   redirectTo: 'main'
 * }, {
 *   path: 'main',
 *   component: Main
 * }]
 * ```
 *
 * ### Componentless Routes
 *
 * You can share parameters between sibling components.
 * For example, suppose that two sibling components should go next to each other,
 * and both of them require an ID parameter. You can accomplish this using a route
 * that does not specify a component at the top level.
 *
 * In the following example, 'MainChild' and 'AuxChild' are siblings.
 * When navigating to 'parent/10/(a//aux:b)', the route instantiates
 * the main child and aux child components next to each other.
 * For this to work, the application component must have the primary and aux outlets defined.
 *
 * ```
 * [{
 *    path: 'parent/:id',
 *    children: [
 *      { path: 'a', component: MainChild },
 *      { path: 'b', component: AuxChild, outlet: 'aux' }
 *    ]
 * }]
 * ```
 *
 * The router merges the parameters, data, and resolve of the componentless
 * parent into the parameters, data, and resolve of the children.
 *
 * This is especially useful when child components are defined
 * with an empty path string, as in the following example.
 * With this configuration, navigating to '/parent/10' creates
 * the main child and aux components.
 *
 * ```
 * [{
 *    path: 'parent/:id',
 *    children: [
 *      { path: '', component: MainChild },
 *      { path: '', component: AuxChild, outlet: 'aux' }
 *    ]
 * }]
 * ```
 *
 * ### Lazy Loading
 *
 * Lazy loading speeds up application load time by splitting the application
 * into multiple bundles and loading them on demand.
 * To use lazy loading, provide the `loadChildren` property in the `Route` object,
 * instead of the `children` property.
 *
 * Given the following example route, the router will lazy load
 * the associated module on demand using the browser native import system.
 *
 * ```
 * [{
 *   path: 'lazy',
 *   loadChildren: () => import('./lazy-route/lazy.module').then(mod => mod.LazyModule),
 * }];
 * ```
 *
 * @publicApi
 */


export interface Route {
  /**
   * Used to define a page title for the route. This can be a static string or an `Injectable` that
   * implements `Resolve`.
   *
   * @see {@link TitleStrategy}
   */
  title?: string | Type<Resolve<string>> | ResolveFn<string>;

  /**
   * The path to match against. Cannot be used together with a custom `matcher` function.
   * A URL string that uses router matching notation.
   * Can be a wild card (`**`) that matches any URL (see Usage Notes below).
   * Default is "/" (the root path).
   *
   */
  path?: string;
  /**
   * The path-matching strategy, one of 'prefix' or 'full'.
   * Default is 'prefix'.
   *
   * By default, the router checks URL elements from the left to see if the URL
   * matches a given path and stops when there is a config match. Importantly there must still be a
   * config match for each segment of the URL. For example, '/team/11/user' matches the prefix
   * 'team/:id' if one of the route's children matches the segment 'user'. That is, the URL
   * '/team/11/user' matches the config
   * `{path: 'team/:id', children: [{path: ':user', component: User}]}`
   * but does not match when there are no children as in `{path: 'team/:id', component: Team}`.
   *
   * The path-match strategy 'full' matches against the entire URL.
   * It is important to do this when redirecting empty-path routes.
   * Otherwise, because an empty path is a prefix of any URL,
   * the router would apply the redirect even when navigating
   * to the redirect destination, creating an endless loop.
   *
   */
  pathMatch?: 'prefix' | 'full';
  /**
   * A custom URL-matching function. Cannot be used together with `path`.
   */
  matcher?: UrlMatcher;
  /**
   * The component to instantiate when the path matches.
   * Can be empty if child routes specify components.
   */
  component?: Type<any>;

  /**
   * An object specifying a lazy-loaded component.
   */
  loadComponent?: () =>
    | Type<unknown>
    | Observable<Type<unknown> | DefaultExport<Type<unknown>>>
    | Promise<Type<unknown> | DefaultExport<Type<unknown>>>;
  /**
   * Filled for routes `loadComponent` once the component is loaded.
   * @internal
   */
  _loadedComponent?: Type<unknown>;

  /**
   * A URL or function that returns a URL to redirect to when the path matches.
   *
   * Absolute if the URL begins with a slash (/) or the function returns a `UrlTree`, otherwise
   * relative to the path URL.
   *
   * The `RedirectFunction` is run in an injection context so it can call `inject` to get any
   * required dependencies.
   *
   * When not present, router does not redirect.
   */
  redirectTo?: string | RedirectFunction;
  /**
   * Name of a `RouterOutlet` object where the component can be placed
   * when the path matches.
   */
  outlet?: string;
  /**
   * An array of `CanActivateFn` or DI tokens used to look up `CanActivate()`
   * handlers, in order to determine if the current user is allowed to
   * activate the component. By default, any user can activate.
   *
   * When using a function rather than DI tokens, the function can call `inject` to get any required
   * dependencies. This `inject` call must be done in a synchronous context.
   */
  canActivate?: Array<CanActivateFn | DeprecatedGuard>;
  /**
   * An array of `CanMatchFn` or DI tokens used to look up `CanMatch()`
   * handlers, in order to determine if the current user is allowed to
   * match the `Route`. By default, any route can match.
   *
   * When using a function rather than DI tokens, the function can call `inject` to get any required
   * dependencies. This `inject` call must be done in a synchronous context.
   */
  canMatch?: Array<CanMatchFn | DeprecatedGuard>;
  /**
   * An array of `CanActivateChildFn` or DI tokens used to look up `CanActivateChild()` handlers,
   * in order to determine if the current user is allowed to activate
   * a child of the component. By default, any user can activate a child.
   *
   * When using a function rather than DI tokens, the function can call `inject` to get any required
   * dependencies. This `inject` call must be done in a synchronous context.
   */
  canActivateChild?: Array<CanActivateChildFn | DeprecatedGuard>;
  /**
   * An array of `CanDeactivateFn` or DI tokens used to look up `CanDeactivate()`
   * handlers, in order to determine if the current user is allowed to
   * deactivate the component. By default, any user can deactivate.
   *
   * When using a function rather than DI tokens, the function can call `inject` to get any required
   * dependencies. This `inject` call must be done in a synchronous context.
   */
  canDeactivate?: Array<CanDeactivateFn<any> | DeprecatedGuard>;
  /**
   * An array of `CanLoadFn` or DI tokens used to look up `CanLoad()`
   * handlers, in order to determine if the current user is allowed to
   * load the component. By default, any user can load.
   *
   * When using a function rather than DI tokens, the function can call `inject` to get any required
   * dependencies. This `inject` call must be done in a synchronous context.
   * @deprecated Use `canMatch` instead
   */
  canLoad?: Array<CanLoadFn | DeprecatedGuard>;
  /**
   * Additional developer-defined data provided to the component via
   * `ActivatedRoute`. By default, no additional data is passed.
   */
  data?: Data;
  /**
   * A map of DI tokens used to look up data resolvers. See `Resolve`.
   */
  resolve?: ResolveData;
  /**
   * An array of child `Route` objects that specifies a nested route
   * configuration.
   */
  children?: Routes;
  /**
   * An object specifying lazy-loaded child routes.
   */
  loadChildren?: LoadChildren;

  /**
   * A policy for when to run guards and resolvers on a route.
   *
   * Guards and/or resolvers will always run when a route is activated or deactivated. When a route
   * is unchanged, the default behavior is the same as `paramsChange`.
   *
   * `paramsChange` : Rerun the guards and resolvers when path or
   * path param changes. This does not include query parameters. This option is the default.
   * - `always` : Run on every execution.
   * - `pathParamsChange` : Rerun guards and resolvers when the path params
   * change. This does not compare matrix or query parameters.
   * - `paramsOrQueryParamsChange` : Run when path, matrix, or query parameters change.
   * - `pathParamsOrQueryParamsChange` : Rerun guards and resolvers when the path params
   * change or query params have changed. This does not include matrix parameters.
   *
   * @see {@link RunGuardsAndResolvers}
   */
  runGuardsAndResolvers?: RunGuardsAndResolvers;

  /**
   * A `Provider` array to use for this `Route` and its `children`.
   *
   * The `Router` will create a new `EnvironmentInjector` for this
   * `Route` and use it for this `Route` and its `children`. If this
   * route also has a `loadChildren` function which returns an `NgModuleRef`, this injector will be
   * used as the parent of the lazy loaded module.
   */
  providers?: Array<Provider | EnvironmentProviders>;

  /**
   * Injector created from the static route providers
   * @internal
   */
  _injector?: EnvironmentInjector;

  /**
   * Filled for routes with `loadChildren` once the routes are loaded.
   * @internal
   */
  _loadedRoutes?: Route[];

  /**
   * Filled for routes with `loadChildren` once the routes are loaded
   * @internal
   */
  _loadedInjector?: EnvironmentInjector;
}

export interface LoadedRouterConfig {
  routes: Route[];
  injector: EnvironmentInjector | undefined;
}


/**
 * @description
 *
 * Interface that a class can implement to be a guard deciding if a route can be activated.
 * If all guards return `true`, navigation continues. If any guard returns `false`,
 * navigation is cancelled. If any guard returns a `UrlTree`, the current navigation
 * is cancelled and a new navigation begins to the `UrlTree` returned from the guard.
 *
 * The following example implements a `CanActivate` function that checks whether the
 * current user has permission to activate the requested route.
 *
 * ```
 * class UserToken {}
 * class Permissions {
 *   canActivate(): boolean {
 *     return true;
 *   }
 * }
 *
 * @Injectable()
 * class CanActivateTeam implements CanActivate {
 *   constructor(private permissions: Permissions, private currentUser: UserToken) {}
 *
 *   canActivate(
 *     route: ActivatedRouteSnapshot,
 *     state: RouterStateSnapshot
 *   ): MaybeAsync<GuardResult> {
 *     return this.permissions.canActivate(this.currentUser, route.params.id);
 *   }
 * }
 * ```
 *
 * Here, the defined guard function is provided as part of the `Route` object
 * in the router configuration:
 *
 * ```
 * @NgModule({
 *   imports: [
 *     RouterModule.forRoot([
 *       {
 *         path: 'team/:id',
 *         component: TeamComponent,
 *         canActivate: [CanActivateTeam]
 *       }
 *     ])
 *   ],
 *   providers: [CanActivateTeam, UserToken, Permissions]
 * })
 * class AppModule {}
 * ```
 *
 * @publicApi
 */
export interface CanActivate {
  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): MaybeAsync<GuardResult>;
}

/**
 * The signature of a function used as a `canActivate` guard on a `Route`.
 *
 * If all guards return `true`, navigation continues. If any guard returns `false`,
 * navigation is cancelled. If any guard returns a `UrlTree`, the current navigation
 * is cancelled and a new navigation begins to the `UrlTree` returned from the guard.
 *
 * The following example implements and uses a `CanActivateFn` that checks whether the
 * current user has permission to activate the requested route.
 *
 * ```ts
 * @Injectable()
 * class UserToken {}
 *
 * @Injectable()
 * class PermissionsService {
 *   canActivate(currentUser: UserToken, userId: string): boolean {
 *     return true;
 *   }
 *   canMatch(currentUser: UserToken): boolean {
 *     return true;
 *   }
 * }
 *
 * const canActivateTeam: CanActivateFn = (
 *   route: ActivatedRouteSnapshot,
 *   state: RouterStateSnapshot,
 * ) => {
 *   return inject(PermissionsService).canActivate(inject(UserToken), route.params['id']);
 * };
 * ```
 *
 * Here, the defined guard function is provided as part of the `Route` object
 * in the router configuration:
 *
 * ```ts
 * bootstrapApplication(App, {
 *    providers: [
 *      provideRouter([
 *        {
 *          path: 'team/:id',
 *          component: TeamComponent,
 *          canActivate: [canActivateTeam],
 *        },
 *      ]),
 *    ],
 *  });
 * ```
 *
 * @publicApi
 * @see {@link Route}
 */
export type CanActivateFn = (
  route: ActivatedRouteSnapshot,
  state: RouterStateSnapshot,
) => MaybeAsync<GuardResult>;

/**
 * @description
 *
 * Interface that a class can implement to be a guard deciding if a child route can be activated.
 * If all guards return `true`, navigation continues. If any guard returns `false`,
 * navigation is cancelled. If any guard returns a `UrlTree`, current navigation
 * is cancelled and a new navigation begins to the `UrlTree` returned from the guard.
 *
 * The following example implements a `CanActivateChild` function that checks whether the
 * current user has permission to activate the requested child route.
 *
 * ```
 * class UserToken {}
 * class Permissions {
 *   canActivate(user: UserToken, id: string): boolean {
 *     return true;
 *   }
 * }
 *
 * @Injectable()
 * class CanActivateTeam implements CanActivateChild {
 *   constructor(private permissions: Permissions, private currentUser: UserToken) {}
 *
 *   canActivateChild(
 *     route: ActivatedRouteSnapshot,
 *     state: RouterStateSnapshot
 *   ): MaybeAsync<GuardResult> {
 *     return this.permissions.canActivate(this.currentUser, route.params.id);
 *   }
 * }
 * ```
 *
 * Here, the defined guard function is provided as part of the `Route` object
 * in the router configuration:
 *
 * ```
 * @NgModule({
 *   imports: [
 *     RouterModule.forRoot([
 *       {
 *         path: 'root',
 *         canActivateChild: [CanActivateTeam],
 *         children: [
 *           {
 *              path: 'team/:id',
 *              component: TeamComponent
 *           }
 *         ]
 *       }
 *     ])
 *   ],
 *   providers: [CanActivateTeam, UserToken, Permissions]
 * })
 * class AppModule {}
 * ```
 *
 * @publicApi
 */
export interface CanActivateChild {
  canActivateChild(
    childRoute: ActivatedRouteSnapshot,
    state: RouterStateSnapshot,
  ): MaybeAsync<GuardResult>;
}

/**
 * The signature of a function used as a `canActivateChild` guard on a `Route`.
 *
 * If all guards return `true`, navigation continues. If any guard returns `false`,
 * navigation is cancelled. If any guard returns a `UrlTree`, the current navigation
 * is cancelled and a new navigation begins to the `UrlTree` returned from the guard.
 *
 * The following example implements a `canActivate` function that checks whether the
 * current user has permission to activate the requested route.
 *
 * {@example router/route_functional_guards.ts region="CanActivateChildFn"}
 *
 * @publicApi
 * @see {@link Route}
 */
export type CanActivateChildFn = (
  childRoute: ActivatedRouteSnapshot,
  state: RouterStateSnapshot,
) => MaybeAsync<GuardResult>;

/**
 * @description
 *
 * Interface that a class can implement to be a guard deciding if a route can be deactivated.
 * If all guards return `true`, navigation continues. If any guard returns `false`,
 * navigation is cancelled. If any guard returns a `UrlTree`, current navigation
 * is cancelled and a new navigation begins to the `UrlTree` returned from the guard.
 *
 * The following example implements a `CanDeactivate` function that checks whether the
 * current user has permission to deactivate the requested route.
 *
 * ```
 * class UserToken {}
 * class Permissions {
 *   canDeactivate(user: UserToken, id: string): boolean {
 *     return true;
 *   }
 * }
 * ```
 *
 * Here, the defined guard function is provided as part of the `Route` object
 * in the router configuration:
 *
 * ```
 *
 * @Injectable()
 * class CanDeactivateTeam implements CanDeactivate<TeamComponent> {
 *   constructor(private permissions: Permissions, private currentUser: UserToken) {}
 *
 *   canDeactivate(
 *     component: TeamComponent,
 *     currentRoute: ActivatedRouteSnapshot,
 *     currentState: RouterStateSnapshot,
 *     nextState: RouterStateSnapshot
 *   ): MaybeAsync<GuardResult> {
 *     return this.permissions.canDeactivate(this.currentUser, route.params.id);
 *   }
 * }
 *
 * @NgModule({
 *   imports: [
 *     RouterModule.forRoot([
 *       {
 *         path: 'team/:id',
 *         component: TeamComponent,
 *         canDeactivate: [CanDeactivateTeam]
 *       }
 *     ])
 *   ],
 *   providers: [CanDeactivateTeam, UserToken, Permissions]
 * })
 * class AppModule {}
 * ```
 *
 * @publicApi
 */
export interface CanDeactivate<T> {
  canDeactivate(
    component: T,
    currentRoute: ActivatedRouteSnapshot,
    currentState: RouterStateSnapshot,
    nextState: RouterStateSnapshot,
  ): MaybeAsync<GuardResult>;
}

/**
 * The signature of a function used as a `canDeactivate` guard on a `Route`.
 *
 * If all guards return `true`, navigation continues. If any guard returns `false`,
 * navigation is cancelled. If any guard returns a `UrlTree`, the current navigation
 * is cancelled and a new navigation begins to the `UrlTree` returned from the guard.
 *
 * The following example implements and uses a `CanDeactivateFn` that checks whether the
 * user component has unsaved changes before navigating away from the route.
 *
 * {@example router/route_functional_guards.ts region="CanDeactivateFn"}
 *
 * @publicApi
 * @see {@link Route}
 */
export type CanDeactivateFn<T> = (
  component: T,
  currentRoute: ActivatedRouteSnapshot,
  currentState: RouterStateSnapshot,
  nextState: RouterStateSnapshot,
) => MaybeAsync<GuardResult>;