/**
 * @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 {DOCUMENT} from '@angular/common';
import {provideLocationMocks} from '@angular/common/testing';
import {Component, inject, Inject, Injectable, NgModule} from '@angular/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {fakeAsync, TestBed, tick} from '@angular/core/testing';
import {
  ActivatedRoute,
  provideRouter,
  ResolveFn,
  Router,
  RouterModule,
  RouterStateSnapshot,
  TitleStrategy,
  withRouterConfig,
} from '@angular/router';
import {RouterTestingHarness} from '@angular/router/testing';

describe('title strategy', () => {
  describe('DefaultTitleStrategy', () => {
    let router: Router;
    let document: Document;

    beforeEach(() => {
      TestBed.configureTestingModule({
        imports: [TestModule],
        providers: [
          provideLocationMocks(),
          provideRouter([], withRouterConfig({paramsInheritanceStrategy: 'always'})),
        ],
      });
      router = TestBed.inject(Router);
      document = TestBed.inject(DOCUMENT);
    });

    it('sets page title from data', fakeAsync(() => {
      router.resetConfig([{path: 'home', title: 'My Application', component: BlankCmp}]);
      router.navigateByUrl('home');
      tick();
      expect(document.title).toBe('My Application');
    }));

    it('sets page title from resolved data', fakeAsync(() => {
      router.resetConfig([{path: 'home', title: TitleResolver, component: BlankCmp}]);
      router.navigateByUrl('home');
      tick();
      expect(document.title).toBe('resolved title');
    }));

    it('sets page title from resolved data function', fakeAsync(() => {
      router.resetConfig([{path: 'home', title: () => 'resolved title', component: BlankCmp}]);
      router.navigateByUrl('home');
      tick();
      expect(document.title).toBe('resolved title');
    }));

    it('sets title with child routes', fakeAsync(() => {
      router.resetConfig([
        {
          path: 'home',
          title: 'My Application',
          children: [{path: '', title: 'child title', component: BlankCmp}],
        },
      ]);
      router.navigateByUrl('home');
      tick();
      expect(document.title).toBe('child title');
    }));

    it('sets title with child routes and named outlets', fakeAsync(() => {
      router.resetConfig([
        {
          path: 'home',
          title: 'My Application',
          children: [
            {path: '', title: 'child title', component: BlankCmp},
            {path: '', outlet: 'childaux', title: 'child aux title', component: BlankCmp},
          ],
        },
        {path: 'compose', component: BlankCmp, outlet: 'aux', title: 'compose'},
      ]);
      router.navigateByUrl('home(aux:compose)');
      tick();
      expect(document.title).toBe('child title');
    }));

    it('sets page title with inherited params', fakeAsync(() => {
      router.resetConfig([
        {
          path: 'home',
          title: 'My Application',
          children: [
            {
              path: '',
              title: TitleResolver,
              component: BlankCmp,
            },
          ],
        },
      ]);
      router.navigateByUrl('home');
      tick();
      expect(document.title).toBe('resolved title');
    }));

    it('can get the title from the ActivatedRouteSnapshot', async () => {
      router.resetConfig([
        {
          path: 'home',
          title: 'My Application',
          component: BlankCmp,
        },
      ]);
      await router.navigateByUrl('home');
      expect(router.routerState.snapshot.root.firstChild!.title).toEqual('My Application');
    });

    it('pushes updates through the title observable', async () => {
      @Component({template: '', standalone: true})
      class HomeCmp {
        private readonly title$ = inject(ActivatedRoute).title.pipe(takeUntilDestroyed());
        title?: string;

        constructor() {
          this.title$.subscribe((v) => (this.title = v));
        }
      }
      const titleResolver: ResolveFn<string> = (route) => route.queryParams['id'];
      router.resetConfig([
        {
          path: 'home',
          title: titleResolver,
          component: HomeCmp,
          runGuardsAndResolvers: 'paramsOrQueryParamsChange',
        },
      ]);

      const harness = await RouterTestingHarness.create();
      const homeCmp = await harness.navigateByUrl('/home?id=1', HomeCmp);
      expect(homeCmp.title).toEqual('1');
      await harness.navigateByUrl('home?id=2');
      expect(homeCmp.title).toEqual('2');
    });
  });

  describe('custom strategies', () => {
    it('overriding the setTitle method', fakeAsync(() => {
      @Injectable({providedIn: 'root'})
      class TemplatePageTitleStrategy extends TitleStrategy {
        constructor(@Inject(DOCUMENT) private readonly document: Document) {
          super();
        }

        // Example of how setTitle could implement a template for the title
        override updateTitle(state: RouterStateSnapshot) {
          const title = this.buildTitle(state);
          this.document.title = `My Application | ${title}`;
        }
      }

      TestBed.configureTestingModule({
        imports: [TestModule],
        providers: [
          provideLocationMocks(),
          provideRouter([]),
          {provide: TitleStrategy, useClass: TemplatePageTitleStrategy},
        ],
      });
      const router = TestBed.inject(Router);
      const document = TestBed.inject(DOCUMENT);
      router.resetConfig([
        {
          path: 'home',
          title: 'Home',
          children: [{path: '', title: 'Child', component: BlankCmp}],
        },
      ]);

      router.navigateByUrl('home');
      tick();
      expect(document.title).toEqual('My Application | Child');
    }));
  });
});

@Component({template: ''})
export class BlankCmp {}

@Component({
  template: `
<router-outlet></router-outlet>
<router-outlet name="aux"></router-outlet>
`,
})
export class RootCmp {}

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

@Injectable({providedIn: 'root'})
export class TitleResolver {
  resolve() {
    return 'resolved title';
  }
}
