/*
 * Copyright (C) 2018 Robert Bosch GmbH Copyright (C) 2018 Robert Bosch Manufacturing Solutions GmbH, Germany. All rights reserved.
 */

import { Location } from '@angular/common';
import { AfterContentInit, ContentChildren, Directive, OnDestroy, QueryList } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { CommandbarSidebarSlideComponent } from './commandbar-sidebar-slide.component';
import { CommandbarSidebarComponent } from './commandbar-sidebar.component';

/**
 * Directive to incorporate browser history into sidebar behavior, e.g. to work with mobile's back button.
 * The slides 'back' and 'apply' button are handled in a way compatible with the browser history of navigating a tree.
 *
 * It is exported as 'navigator' to get a reference to it as a template variable like '#sidebarNavigator="navigator"'.
 */
@Directive({
  selector: '[bciCommandbarSidebarRoutingNavigator]',
  exportAs: 'navigator',
})
export class CommandbarSidebarRoutingNavigatorDirective implements AfterContentInit, OnDestroy {
  @ContentChildren(CommandbarSidebarSlideComponent) slides: QueryList<CommandbarSidebarSlideComponent>;
  private componentDestroyed$: Subject<void> = new Subject();
  private slidesChanged$: Subject<void> = new Subject();
  private readonly mainSlideKey = 'main';
  private hasHistoryOnMainSlide = false;
  private hasHistoryWithoutSidebar = false;
  private currentSlideKey: string;

  constructor(
    private route: ActivatedRoute,
    private router: Router,
    private location: Location,
    private sidebar: CommandbarSidebarComponent
  ) {}

  ngAfterContentInit(): void {
    this.slides.changes.pipe(takeUntil(this.componentDestroyed$)).subscribe(() => {
      this.initSlideInteractions();
    });
    this.initSlideInteractions();

    this.route.params.pipe(takeUntil(this.componentDestroyed$)).subscribe((params) => {
      const shouldBeOpen = !!params.filterSidebar;
      const shouldBeOnMainSlide = params.filterSidebar === this.mainSlideKey;

      // remember if opened on main slide
      const isOpening = shouldBeOpen && !this.sidebar.opened;
      if (isOpening) {
        this.hasHistoryOnMainSlide = shouldBeOnMainSlide;
      }

      // remember if there is a browser history entry without and open sidebar
      if (!shouldBeOpen && !this.sidebar.opened) {
        this.hasHistoryWithoutSidebar = true;
      }

      this.currentSlideKey = params.filterSidebar;
      this.sidebar.opened = shouldBeOpen;
      this.sidebar.activeSlide = shouldBeOnMainSlide ? undefined : params.filterSidebar;
    });
  }

  ngOnDestroy(): void {
    this.componentDestroyed$.next();
    this.componentDestroyed$.complete();
  }

  /**
   * Open sidebar on main slide.
   */
  open() {
    this.goToMain();
  }

  /**
   * Close sidebar.
   */
  close() {
    if (!this.sidebar.opened) {
      return;
    }

    // two levels up?
    if (!this.isOnMainSlide() && this.hasHistoryOnMainSlide) {
      this.location.back();
    }

    if (this.hasHistoryWithoutSidebar) {
      this.location.back();
    } else {
      const params = { ...this.route.snapshot.params };
      delete params.filterSidebar;
      return this.router.navigate([params], { relativeTo: this.route, replaceUrl: true });
    }
  }

  /**
   * Go to main slide, potentially opening the sidebar.
   */
  goToMain() {
    this.goTo(this.mainSlideKey);
  }

  /**
   * Go to the slide with the given key, potentially opening the sidebar.
   */
  goTo(slideKey: string) {
    if (!this.sidebar.opened) {
      this.navigateTo(slideKey, false);
      return;
    }

    const shouldBeOnMainSlide = slideKey === this.mainSlideKey;

    if (this.isOnMainSlide()) {
      if (!shouldBeOnMainSlide) {
        this.navigateTo(slideKey, false);
      }
    } else {
      if (shouldBeOnMainSlide && this.hasHistoryOnMainSlide) {
        this.location.back();
      } else {
        this.navigateTo(slideKey, true);
      }
    }
  }

  private initSlideInteractions() {
    this.slidesChanged$.next();
    this.slides.forEach((slide) => {
      if (!!slide.key) {
        slide.back.pipe(takeUntil(this.slidesChanged$)).subscribe(() => this.goToMain());
      } else {
        slide.confirm.pipe(takeUntil(this.slidesChanged$)).subscribe(() => this.close());
      }
    });
  }

  private isOnMainSlide() {
    return this.currentSlideKey === this.mainSlideKey;
  }

  private navigateTo(slideKey: string, replace = false) {
    const params = { ...this.route.snapshot.params };
    params.filterSidebar = slideKey;
    return this.router.navigate([params], { relativeTo: this.route, replaceUrl: replace });
  }
}
