/**
 * @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.io/license
 */

import {CommonModule, NgForOf} from '@angular/common';
import {Component, Type} from '@angular/core';
import {ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
import {Router, RouterModule, RouterOutlet} from '@angular/router/src';
import {RouterTestingModule} from '@angular/router/testing';


describe('router outlet name', () => {
  it('should support name binding', fakeAsync(() => {
       @Component({
         standalone: true,
         template: '<router-outlet [name]="name"></router-outlet>',
         imports: [RouterOutlet],
       })
       class RootCmp {
         name = 'popup';
       }

       @Component({
         template: 'popup component',
         standalone: true,
       })
       class PopupCmp {
       }

       TestBed.configureTestingModule({
         imports:
             [RouterTestingModule.withRoutes([{path: '', outlet: 'popup', component: PopupCmp}])]
       });
       const router = TestBed.inject(Router);
       const fixture = createRoot(router, RootCmp);
       expect(fixture.nativeElement.innerHTML).toContain('popup component');
     }));

  it('should be able to change the name of the outlet', fakeAsync(() => {
       @Component({
         standalone: true,
         template: '<router-outlet [name]="name"></router-outlet>',
         imports: [RouterOutlet],
       })
       class RootCmp {
         name = '';
       }

       @Component({
         template: 'hello world',
         standalone: true,
       })
       class GreetingCmp {
       }

       @Component({
         template: 'goodbye cruel world',
         standalone: true,
       })
       class FarewellCmp {
       }

       TestBed.configureTestingModule({
         imports: [RouterTestingModule.withRoutes([
           {path: '', outlet: 'greeting', component: GreetingCmp},
           {path: '', outlet: 'farewell', component: FarewellCmp},
         ])]
       });
       const router = TestBed.inject(Router);
       const fixture = createRoot(router, RootCmp);

       expect(fixture.nativeElement.innerHTML).not.toContain('goodbye');
       expect(fixture.nativeElement.innerHTML).not.toContain('hello');

       fixture.componentInstance.name = 'greeting';
       advance(fixture);
       expect(fixture.nativeElement.innerHTML).toContain('hello');
       expect(fixture.nativeElement.innerHTML).not.toContain('goodbye');

       fixture.componentInstance.name = 'goodbye';
       advance(fixture);
       expect(fixture.nativeElement.innerHTML).toContain('goodbye');
       expect(fixture.nativeElement.innerHTML).not.toContain('hello');
     }));

  it('should support outlets in ngFor', fakeAsync(() => {
       @Component({
         standalone: true,
         template: `
            <div *ngFor="let outlet of outlets">
                <router-outlet [name]="outlet"></router-outlet>
            </div>
            `,
         imports: [RouterOutlet, NgForOf],
       })
       class RootCmp {
         outlets = ['outlet1', 'outlet2', 'outlet3'];
       }

       @Component({
         template: 'component 1',
         standalone: true,
       })
       class Cmp1 {
       }

       @Component({
         template: 'component 2',
         standalone: true,
       })
       class Cmp2 {
       }

       @Component({
         template: 'component 3',
         standalone: true,
       })
       class Cmp3 {
       }

       TestBed.configureTestingModule({
         imports: [RouterTestingModule.withRoutes([
           {path: '1', outlet: 'outlet1', component: Cmp1},
           {path: '2', outlet: 'outlet2', component: Cmp2},
           {path: '3', outlet: 'outlet3', component: Cmp3},
         ])]
       });
       const router = TestBed.inject(Router);
       const fixture = createRoot(router, RootCmp);

       router.navigate([{outlets: {'outlet1': '1'}}]);
       advance(fixture);
       expect(fixture.nativeElement.innerHTML).toContain('component 1');
       expect(fixture.nativeElement.innerHTML).not.toContain('component 2');
       expect(fixture.nativeElement.innerHTML).not.toContain('component 3');

       router.navigate([{outlets: {'outlet1': null, 'outlet2': '2', 'outlet3': '3'}}]);
       advance(fixture);
       expect(fixture.nativeElement.innerHTML).not.toContain('component 1');
       expect(fixture.nativeElement.innerHTML).toMatch('.*component 2.*component 3');

       // reverse the outlets
       fixture.componentInstance.outlets = ['outlet3', 'outlet2', 'outlet1'];
       router.navigate([{outlets: {'outlet1': '1', 'outlet2': '2', 'outlet3': '3'}}]);
       advance(fixture);
       expect(fixture.nativeElement.innerHTML).toMatch('.*component 3.*component 2.*component 1');
     }));

  it('should not activate if route is changed', fakeAsync(() => {
       @Component({
         standalone: true,
         template: '<div *ngIf="initDone"><router-outlet></router-outlet></div>',
         imports: [RouterOutlet, CommonModule],
       })
       class ParentCmp {
         initDone = false;
         constructor() {
           setTimeout(() => this.initDone = true, 1000);
         }
       }

       @Component({
         template: 'child component',
         standalone: true,
       })
       class ChildCmp {
       }

       TestBed.configureTestingModule({
         imports: [RouterTestingModule.withRoutes([
           {path: 'parent', component: ParentCmp, children: [{path: 'child', component: ChildCmp}]}
         ])]
       });
       const router = TestBed.inject(Router);
       const fixture = createRoot(router, ParentCmp);

       advance(fixture, 250);
       router.navigate(['parent/child']);
       advance(fixture, 250);
       // Not contain because initDone is still false
       expect(fixture.nativeElement.innerHTML).not.toContain('child component');

       advance(fixture, 1500);
       router.navigate(['parent']);
       advance(fixture, 1500);
       // Not contain because route was changed back to parent
       expect(fixture.nativeElement.innerHTML).not.toContain('child component');
     }));
});

function advance(fixture: ComponentFixture<unknown>, millis?: number): void {
  tick(millis);
  fixture.detectChanges();
}

function createRoot<T>(router: Router, type: Type<T>): ComponentFixture<T> {
  const f = TestBed.createComponent(type);
  advance(f);
  router.initialNavigation();
  advance(f);
  return f;
}
