import { I18N } from 'aurelia-i18n';
import { EventAggregator } from 'aurelia-event-aggregator';
import { NavigationInstructionInit, Router, RouterEvent, NavigationInstruction, PipelineResult } from 'aurelia-router';
import { bindable, bindingMode, computedFrom, customElement, TaskQueue } from "aurelia-framework";
import { Various } from '../../../model/common-model';

// @containerless()
@customElement("ui-tab-router-view")
export class UITabRouterView {

  /**
   * The router viewport name.
   *
   * @type {string}
   * @memberof UITabRouterView
   */
  @bindable({ defaultBindingMode: bindingMode.oneTime }) public name: string = "default";

  /**
   * If tab must show a tooltip, with a delay of 500ms.
   *
   * @type {boolean}
   * @memberof UITabRouterView
   */
  @bindable({ defaultBindingMode: bindingMode.oneTime }) public tooltip: boolean = true;

  private routesTabs: Array<UIRouteTab> = [];
  private routesTabsStorage: Array<UIRouteTabStorage> = [];
  private currentResource: string;
  private show = true;

  @computedFrom("routesTabs", "routesTabs.length")
  private get routesTabDisplay() {
    return this.routesTabs;
  }

  @computedFrom("routesTabs", "routesTabs.length")
  public get classes() {
    if (this.routesTabs != null && this.routesTabs.length == 0) {
      return 'ui-tab-router-view-empty';
    }
    return '';
  }

  constructor(private taskQueue: TaskQueue, public router: Router, public ea: EventAggregator, private i18n: I18N) {
    this.restoreRoutesTabs();
    ea.subscribe(RouterEvent.Success, (event: { instruction: NavigationInstruction; result: PipelineResult }) => {
      if (event?.instruction != null) {
        this.run(event.instruction);
      }
    });
  }

  attached() {
    this.routesTabsStorage = this.clearUnknowRoutes(this.routesTabsStorage);
  }

  private run(navigationInstruction: NavigationInstruction): void {
    if (navigationInstruction?.config == null || navigationInstruction.config?.settings?.tab == false) {
      this.show = false;
      return;
    }

    if (navigationInstruction.config.route.indexOf('*childRoute') > -1) {
      this.manageChildRoute(navigationInstruction);
      return;
    }

    if (navigationInstruction.config.name == 'callback' || navigationInstruction.config.name == 'login') return;

    // Show tab router view
    this.show = true;

    // Extract route
    let route = this.extractRoute(navigationInstruction);
    if (route == null) {
      this.routesTabs = [];
      throw ('UITabRouterView :: route is null !');
    }

    // Extract resource
    let resource = this.extractResource(route);

    // Set current routeTabs
    this.setCurrentRouteTabsGroup(resource);

    // Extract route unique title
    let routeUniqueTitle = this.extractRouteUniqueTitle(navigationInstruction);
    if (routeUniqueTitle.toLowerCase() == 'login') {
      this.show = false;
      return;
    }

    // Reset all tab.active
    this.routesTabsStorage.forEach(rts => rts.routesTabs.forEach(rt => rt.active = false));

    // Find routeTab and active it
    let rt = this.getRouteTab(navigationInstruction, resource);
    rt.active = true;
    rt.name = routeUniqueTitle;
    rt.childRoute = null;

    // Sort tabs
    this.routesTabs = this.routesTabs.sort((a, b) => a.first == b.first ? 0 : a.first ? -1 : 1);

    // Store in local storage
    this.storeRoutesTabs(resource, !rt.first);
  }

  /**
   * Manage child route.
   *
   * @private
   * @param {NavigationInstruction} navigationInstruction
   * @return {*} 
   * @memberof UITabRouterView
   */
  private manageChildRoute(navigationInstruction: NavigationInstruction) {
    if (navigationInstruction.config.route.indexOf('*childRoute') == -1) return;

    let route = this.extractRoute(navigationInstruction);
    let childRouteIndex = route.indexOf('/*childRoute');
    route = route.slice(0, childRouteIndex);

    let resource = this.extractResource(route);
    this.setCurrentRouteTabsGroup(resource);

    let fragment = navigationInstruction.fragment;
    childRouteIndex = fragment.indexOf('/' + navigationInstruction.params.childRoute);
    fragment = fragment.slice(0, childRouteIndex);

    let rt = this.routesTabs.find(r => r.fragment == fragment && r.resource == resource);
    if (rt != null) {
      // Reset all tab.active
      this.routesTabsStorage.forEach(rts => rts.routesTabs.forEach(rt => rt.active = false));

      rt.active = true;
      rt.childRoute = navigationInstruction.fragment.slice(1) + (navigationInstruction.queryString != null ? '?' + navigationInstruction.queryString : '');
      this.storeRoutesTabs(resource, !rt.first);
    }
  }

  /**
   * Check if there already are tabs from the same resource as the given resource. 
   * If not, create a new group of tabs, and set the current routeTabs with it.
   *
   * @param {string} resource
   * @memberof UITabRouterView
   */
  setCurrentRouteTabsGroup(resource: string) {
    if (this.currentResource != resource) {
      this.currentResource = resource;
      let rts = this.routesTabsStorage.find(rts => rts.resource == resource);
      this.routesTabs = rts ? rts.routesTabs : [];
    }
  }

  /**
   * Find the routeTab of given insctruction, or create it if not found.
   *
   * @param {NavigationInstruction} navigationInstruction
   * @return {*}  {UIRouteTab}
   * @memberof UITabRouterView
   */
  getRouteTab(navigationInstruction: NavigationInstruction, resource: string): UIRouteTab {
    let routeUniqueTitle = this.extractRouteUniqueTitle(navigationInstruction);
    let rt: UIRouteTab;
    if (navigationInstruction.fragment == '/')// the app default route
      rt = this.routesTabs.find(r => r.fragment == navigationInstruction.fragment && r.resource == resource);
    else
      rt = this.routesTabs.find(r => r.name == routeUniqueTitle && r.resource == resource);

    if (rt == null) {
      if (!navigationInstruction.fragment.includes(Various.NewId.toString())) {
        rt = new UIRouteTab(routeUniqueTitle, navigationInstruction, resource);
        if (!this.routesTabs.some(r => r.first)) {
          rt.first = navigationInstruction.config.nav == true;
        }
        this.routesTabs.push(rt);
      }
    } else {
      rt.name = routeUniqueTitle;
    }
    return rt;
  }

  /**
   * Return the resource of the given route.
   *
   * @private
   * @param {string} route
   * @return {*} 
   * @memberof UITabRouterView
   */
  private extractResource(route: string) {
    let resource: string;
    let paramIndex = route.indexOf('/:');
    if (paramIndex > -1) {
      resource = route.slice(0, paramIndex);
    } else {
      let allIndex = route.toLowerCase().indexOf('/all');
      if (allIndex > - 1) {
        resource = route.slice(0, allIndex);
      } else {
        let starIndex = route.toLowerCase().indexOf('/*');
        if (starIndex > - 1) {
          resource = route.slice(0, starIndex);
        } else {
          resource = route;
        }
      }
    }
    return resource;
  }

  /**
   * Return the route of the given instruction.
   *
   * @private
   * @param {NavigationInstruction} navigationInstruction
   * @return {*}  {string}
   * @memberof UITabRouterView
   */
  private extractRoute(navigationInstruction: NavigationInstruction): string {
    if (navigationInstruction?.config == null) return;

    if (navigationInstruction.config.redirect != null)
      return navigationInstruction.config.redirect;

    if (Array.isArray(navigationInstruction.config.route))
      return navigationInstruction.config.route[navigationInstruction.config.route.length - 1];

    if (navigationInstruction.config.route.trim().length > 0)
      return navigationInstruction.config.route;

    return navigationInstruction.config.href;
  }

  /**
   * 
   *
   * @private
   * @param {NavigationInstruction} navigationInstruction
   * @return {*}  {string}
   * @memberof UITabRouterView
   */
  private extractRouteUniqueTitle(navigationInstruction: NavigationInstruction): string {
    let routeUniqueTitle: string;
    let defaultViewport: any;
    if (navigationInstruction?.viewPortInstructions != null && this.name in navigationInstruction?.viewPortInstructions)
      defaultViewport = navigationInstruction?.viewPortInstructions[this.name];

    if (defaultViewport?.controller?.view?.bindingContext?.ribbonHeaderText != null)
      routeUniqueTitle = defaultViewport.controller.view.bindingContext.ribbonHeaderText;
    else if (defaultViewport?.component?.viewModel?.ribbonHeaderText != null)
      routeUniqueTitle = defaultViewport.component.viewModel.ribbonHeaderText;
    else if (navigationInstruction.config?.name != null)
      routeUniqueTitle = this.i18n.tr(navigationInstruction.config.name);
    else
      routeUniqueTitle = Array.isArray(navigationInstruction.config.route) ? navigationInstruction.config.route[0] : navigationInstruction.config.route;

    return routeUniqueTitle;
  }



  private navigate(tab: UIRouteTab): void {

    if (tab.childRoute != null) {
      this.router.navigate(tab.childRoute);
      return;
    }

    if (tab?.fragment == null) return;
    this.router.navigate(tab.fragment, tab.options);
  }

  private removeTab(event: Event, tab: UIRouteTab) {
    if (tab?.fragment == null) return;
    if (event?.stopPropagation != null)
      event.stopPropagation();

    let i = this.routesTabs.findIndex(r => r.fragment == tab.fragment);
    if (i < 0) return;

    this.routesTabs.splice(i, 1);

    if (tab.active) {
      let firstTab = this.routesTabs[0];
      if (firstTab != null) {
        this.taskQueue.queueTask(() => {
          this.navigate(firstTab);
        });
      }
    }
  }

  private storeRoutesTabs(resource: string, isVisible: boolean = true): void {
    let storage = this.routesTabsStorage.find(s => s.resource == resource);
    if (storage == null) {
      storage = new UIRouteTabStorage(resource, isVisible);
      this.routesTabsStorage.push(storage);
    }
    storage.routesTabs = this.routesTabs;
    window.localStorage.setItem('DigiRouteTabs', JSON.stringify(this.routesTabsStorage));
  }

  private restoreRoutesTabs(): void {
    let ls = window.localStorage.getItem('DigiRouteTabs');
    if (ls != null) {
      this.routesTabsStorage = JSON.parse(ls);
    }
  }

  /**
   * Clear routes who are not found in the app routes config, due to changing route config during test or development.
   *
   * @private
   * @param {UIRouteTabStorage[]} routesTabs
   * @return {*}  {UIRouteTabStorage[]}
   * @memberof UITabRouterView
   */
  private clearUnknowRoutes(routesTabs: UIRouteTabStorage[]): UIRouteTabStorage[] {
    routesTabs.forEach(routes => {
      routes.routesTabs = routes.routesTabs.filter(tab =>
        this.router.routes.findIndex(r => r.navModel?.relativeHref == tab.relativeHref) > -1
        && tab.name.toLowerCase() != 'login');
      let firstSingleRoute = routes.routesTabs.filter(x => x.first);
      if (firstSingleRoute.length > 1) {
        let routeToRemove = firstSingleRoute.filter(x => x.fragment != '/');
        routeToRemove.forEach(x => {
          routes.routesTabs.remove(x);
        });
      }
    });
    return routesTabs;
  }
}

class UIRouteTab {
  public name: string;
  public fragment: string;
  public relativeHref: string;
  public options: any;
  public active = false;
  public resource: string;
  public first: boolean;
  // public last: boolean;
  public childRoute: string;

  constructor(name: string, navigationInstruction: NavigationInstructionInit, resource: string) {
    this.name = name;
    this.fragment = navigationInstruction.fragment;
    this.relativeHref = navigationInstruction.config?.navModel?.relativeHref;
    this.options = navigationInstruction.options;
    this.resource = resource;
  }
}

class UIRouteTabStorage {
  resource: string;
  routesTabs: Array<UIRouteTab>;
  isVisible: boolean;

  constructor(resource: string, isVisible: boolean = true) {
    this.resource = resource;
    this.isVisible = isVisible
  }
}
