import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { getNumberId } from '@utils/id';

import { Observable } from 'rxjs';
import { first, map, take } from 'rxjs/operators';
import { Breadcrumb } from './defs';

import * as actions from './store/breadcrumb.actions';
import * as selectors from './store/breadcrumb.selectors';

@Injectable()
export class BreadcrumbService {
  constructor(private store: Store<any>) {}

  getAll$() {
    return this.store.select(selectors.selectBreadcrumbs);
  }

  getByKey$(key: string): Observable<Breadcrumb[]> {
    return this.store.select(selectors.selectBreadcrumbsByKey, key);
  }

  getBreadcrumbById$(id: number): Observable<Breadcrumb> {
    return this.store.select(selectors.selectBreadcrumbById, id);
  }

  getBreadcrumbByKey$(key: string | number): Observable<Breadcrumb[]> {
    return this.store.select(selectors.selectBreadcrumbsByKey, key);
  }

  getLastActivatedBreadcrumbByKey$(key: string | number): Observable<Breadcrumb> {
    return this.getBreadcrumbByKey$(key).pipe(
      map((crumbs) =>
        crumbs.reduce((latest, current) => {
          if (!latest) {
            return current;
          }
          return latest.timestamp > current.timestamp ? latest : current;
        }, undefined)
      )
    );
  }

  addBreadcrumb(breadcrumb: Breadcrumb): Breadcrumb {
    const newBreadcrumb: Breadcrumb = { ...breadcrumb, id: getNumberId(), timestamp: Date.now(), changed: Date.now() };
    this.store.dispatch(actions.addBreadcrumb({ breadcrumb: newBreadcrumb }));
    return newBreadcrumb;
  }

  patchBreadcrumb(breadcrumbId: number, changes: Partial<Breadcrumb>) {
    this.store.dispatch(actions.updateBreadcrumb({ id: breadcrumbId, changes: { ...changes, changed: Date.now() } }));
  }

  async patchBreadcrumbByKeyAndTags(key: string | number, tags: string[], changes: Partial<Breadcrumb>) {
    const crumbs = await this.getBreadcrumbsByKeyAndTags$(key, tags).pipe(first()).toPromise();
    crumbs.forEach((crumb) => this.patchBreadcrumb(crumb.id, changes));
  }

  async addBreadcrumbIfNotExists(breadcrumb: Breadcrumb) {
    const crumbs = await this.getBreadcrumbsByKeyAndTags$(breadcrumb.key, breadcrumb.tags).pipe(take(1)).toPromise();
    if (crumbs.length === 0) {
      return this.addBreadcrumb(breadcrumb);
    }
    return crumbs[0];
  }

  async addOrUpdateBreadcrumbIfNotExists(breadcrumb: Breadcrumb) {
    const crumbs = await this.getBreadcrumbsByKeyAndTags$(breadcrumb.key, breadcrumb.tags).pipe(take(1)).toPromise();
    if (crumbs.length === 0) {
      return this.addBreadcrumb(breadcrumb);
    }
    return this.patchBreadcrumb(crumbs[0].id, breadcrumb);
  }

  getBreadcrumbsByKeyAndTag$(key: string | number, tag: string): Observable<Breadcrumb[]> {
    return this.store.select(selectors.selectBreadcrumbsByKeyAndTag, { key, tag });
  }

  getBreadcrumbsByKeyAndTags$(key: string | number, tags: string[]): Observable<Breadcrumb[]> {
    return this.store.select(selectors.selectBreadcrumbsByKeyAndTags, { key, tags });
  }

  async removeBreadcrumbsAfter(breadcrumbId: number, withTags: string[] = []) {
    const breadcrumb = await this.getBreadcrumbById$(breadcrumbId).pipe(take(1)).toPromise();

    if (!breadcrumb) {
      return;
    }

    let breadcrumbs: Breadcrumb[];

    if (withTags?.length > 0) {
      breadcrumbs = await this.getBreadcrumbsByKeyAndTags$(breadcrumb.key, withTags).pipe(take(1)).toPromise();
    } else {
      breadcrumbs = await this.getBreadcrumbByKey$(breadcrumb.key).pipe(take(1)).toPromise();
    }

    if (!breadcrumbs?.length) {
      return;
    }

    const breadcrumbsToRemove = breadcrumbs.filter((crumb) => crumb.timestamp > breadcrumb.timestamp);

    if (!breadcrumbsToRemove.length) {
      return;
    }

    this.store.dispatch(actions.removeManyBreadcrumb({ ids: breadcrumbsToRemove.map((crumb) => crumb.id) }));
  }

  async removeBreadcrumb(breadcrumbId: number, recursive: boolean = true) {
    const breadcrumb = await this.getBreadcrumbById$(breadcrumbId).pipe(take(1)).toPromise();

    if (!breadcrumb) {
      return;
    }

    let breadcrumbsToRemove = [breadcrumb];

    if (recursive) {
      const breadcrumbs = await this.getBreadcrumbByKey$(breadcrumb.key).pipe(take(1)).toPromise();
      breadcrumbsToRemove = [...breadcrumbsToRemove, ...breadcrumbs.filter((crumb) => crumb.timestamp > breadcrumb.timestamp)];
    }

    if (!breadcrumbsToRemove.length) {
      return;
    }

    this.store.dispatch(actions.removeManyBreadcrumb({ ids: breadcrumbsToRemove.map((crumb) => crumb.id) }));
  }

  async removeBreadcrumbsByKeyAndTags(key: number | string, tags: string[]) {
    const crumbs = await this.getBreadcrumbsByKeyAndTags$(key, tags).pipe(first()).toPromise();
    crumbs.forEach((crumb) => this.removeBreadcrumb(crumb.id));
  }

  getLatestBreadcrumbForSection(section: 'customer' | 'branch') {
    return this.store
      .select(selectors.selectBreadcrumbsBySection, { section })
      .pipe(map((crumbs) => crumbs.sort((a, b) => b.changed - a.changed).find((f) => true)));
  }
}
