import {
  DestroyRef,
  EnvironmentProviders,
  inject,
  Provider,
} from '@angular/core';
import { QuerySettings } from '../types';
import { FilterService, QUERY_SETTINGS } from './filter.service';
import { NavigationEnd, NavigationExtras, Router } from '@angular/router';
import { FILTER_ON_COMMIT, FILTER_ON_INIT } from './tokens';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

export function provideFilter(...features: FilterFeature[]): Provider[] {
  return [FilterService, features.map((feature) => feature.ɵproviders)];
}

const enum ProvideFilterKind {
  QuerySettings,
  QueryParamsSync,
}

export interface FilterFeature {
  ɵkind: ProvideFilterKind;
  ɵproviders: Array<Provider> | EnvironmentProviders;
}

function filterFeature<FeatureKind extends ProvideFilterKind>(
  kind: FeatureKind,
  providers: Array<Provider> | EnvironmentProviders,
): FilterFeature {
  return {
    ɵkind: kind,
    ɵproviders: providers,
  };
}

export function withQuerySettings(querySettings: QuerySettings): FilterFeature {
  return filterFeature(ProvideFilterKind.QuerySettings, [
    { provide: QUERY_SETTINGS, useValue: querySettings },
  ]);
}

export function withQuerySettingsFactory(
  querySettingsFactory: () => QuerySettings,
): FilterFeature {
  return filterFeature(ProvideFilterKind.QuerySettings, [
    { provide: QUERY_SETTINGS, useFactory: querySettingsFactory },
  ]);
}

export function withQueryParamsSync({
  replaceUrl = true,
  queryParamsHandling = 'merge',
}: Pick<
  NavigationExtras,
  'replaceUrl' | 'queryParamsHandling'
> = {}): FilterFeature {
  function onCommitFactory() {
    const router = inject(Router);
    return (filterService: FilterService) => () => {
      const queryParams = router.routerState.root.snapshot.queryParams;
      if (!filterService.isQueryParamsEqual(queryParams)) {
        router.navigate([], {
          queryParams: filterService.queryParams(),
          queryParamsHandling,
          replaceUrl,
        });
      }
    };
  }

  function onInitFactory() {
    const router = inject(Router);
    const destroyRef = inject(DestroyRef);
    return (filterService: FilterService) => () => {
      function parseQueryParams() {
        const queryParams = router.routerState.root.snapshot.queryParams;
        if (!filterService.isQueryParamsEqual(queryParams)) {
          filterService.parseQueryParams(queryParams, { commit: true });
        }
      }

      parseQueryParams();
      router.events.pipe(takeUntilDestroyed(destroyRef)).subscribe((event) => {
        if (event instanceof NavigationEnd) {
          parseQueryParams();
        }
      });
    };
  }

  return filterFeature(ProvideFilterKind.QueryParamsSync, [
    {
      provide: FILTER_ON_COMMIT,
      useFactory: onCommitFactory,
      multi: true,
    },
    {
      provide: FILTER_ON_INIT,
      useFactory: onInitFactory,
      multi: true,
    },
  ]);
}
