import { Router, NavModel } from 'aurelia-router';
import { singleton, autoinject, BindingEngine } from 'aurelia-framework';
import { bindable, customElement } from "aurelia-templating";
import { AuthService } from '../../security/auth-service';
import { bindingMode, computedFrom, Disposable } from 'aurelia-binding';
import { UIMenuGroupConfig } from '../../model/common-model';
import { Config, RouteSettings } from '../../config';
import { UIInternal } from '../../utils/ui-internal';

@customElement("ui-navbar-menu")
@singleton()
@autoinject
export class UINavbarMenu {
  @bindable
  public router: Router;
  @bindable
  public groupIcons: Map<string, UIMenuGroupConfig>;

  @bindable({ defaultBindingMode: bindingMode.oneTime })
  public autoFold: boolean = false;
  @bindable
  public viewBurger: boolean = true;
  @bindable
  public keepCondensed = false;

  @bindable public onSelected: (menu: MenuGroup) => void;
  @bindable public onViewChildren: (menu: MenuGroup) => void;

  @bindable({ defaultBindingMode: bindingMode.twoWay })
  public isCondensed: boolean = false;

  @bindable({ defaultBindingMode: bindingMode.twoWay })
  private newAuthorizedRoutes: Array<MenuGroupInstance>;
  private isHoverNavbar = false;

  @computedFrom("newAuthorizedRoutes.length")
  public get authorizedTopRoutes() {
    return this.newAuthorizedRoutes.filter(x => !x.isBottom);
  }

  @computedFrom("newAuthorizedRoutes.length")
  public get authorizedBottomRoutes() {
    return this.newAuthorizedRoutes.filter(x => x.isBottom);
  }

  public disposable: Disposable;

  private maxLevel: number;
  private navbar: HTMLElement;
  private navbarMenu: HTMLElement;
  private removeSmallDeviceTimeout: any;

  constructor(private authService: AuthService, private bindingEngine: BindingEngine, private config: Config) {
  }

  public bind() {
    this.maxLevel = this.config.menuConfig.maxLevelVerticalNavbar;
    this.refreshRoutes();
    this.disposable = this.bindingEngine.propertyObserver(this.authService, 'currentUser').subscribe(
      (newVal, oldVal) => {
        this.refreshRoutes();
      }
    )
    UIInternal.subscribe(UIInternal.EVT_ROUTE_CHANGE, () => {
      this.router.refreshNavigation();
      this.refreshRoutes();
      this.selectedCurrentRoute();
    });
    this.isCondensed = this.asCondensed();

    UIInternal.subscribe('router:navigation:complete', () => {
      this.selectedCurrentRoute();
    });
    this.selectedCurrentRoute();

    UIInternal.subscribe(UIInternal.EVT_VIEWPORT_SMALL_DEVICE_ACTIVATED, () => {
      if (!this.isCondensed) {
        this.toggleMenu()
      }
    });
  }

  private selectedCurrentRoute() {
    this.newAuthorizedRoutes.forEach(menu => {
      let name: string;
      if (this.router.currentInstruction.config.nav == true) {
        name = this.router.currentInstruction.config.name;
      } else {
        name = this.router.currentInstruction.config.settings.parentName;
      }

      if (menu.children == null || menu.children.length == 0) {
        if (this.isSameRoute(menu, name)) {
          this.newAuthorizedRoutes.find(x => x.isSelected)?.setSelected(false);
          menu.setSelected();
        }
      } else {
        menu.children.forEach(subMenu => {
          if (subMenu.children == null || subMenu.children.length == 0) {
            if (this.isSameRoute(subMenu, name)) {
              this.newAuthorizedRoutes.find(x => x.isSelected)?.setSelected(false);
              subMenu.setSelected();
              menu.viewChildren = true;
            }
          } else {
            subMenu.children.forEach(child => {
              if (this.isSameRoute(child, name)) {
                this.newAuthorizedRoutes.find(x => x.isSelected)?.setSelected(false);
                child.setSelected();
                menu.viewChildren = true;
                subMenu.viewChildren = true;
              }
            })
          }
        })
      }
    });
  }

  private isSameRoute(menu: MenuGroupInstance, name: string): boolean {
    return menu.route == this.router.currentInstruction.fragment || menu.route == "/" + this.router.currentInstruction.config.href || (name != null && menu.routeName == name);
  }

  public attached() {
    if (this.autoFold) {
      this.navbarMenu.onmouseenter = () => {
        this.toggleClassHover(true);
      }
      this.navbarMenu.onmouseleave = () => {
        this.toggleClassHover(false);
      }

      if (!this.isCondensed) {
        this.closeMenu();
      }
    }
  }

  private toggleClassHover(hover: boolean) {
    if (hover) {
      document.body.classList.add('ui-navbar-menu-hover');
      this.isHoverNavbar = true;
    } else {
      document.body.classList.remove('ui-navbar-menu-hover');
      this.isHoverNavbar = false;
    }
  }

  public detached() {
    this.disposable.dispose();
  }

  public refreshRoutes() {
    this.getAvailableRoutes();
  }

  private getAvailableRoutes() {
    if (this.router == null) throw "You must pass a router parameter when using ui-menu element";
    let authorizedRoutes: NavModel[] = [];
    if (this.newAuthorizedRoutes == null) {
      this.newAuthorizedRoutes = [];
    }
    this.newAuthorizedRoutes.splice(0);
    authorizedRoutes = this.router.navigation;
    if (this.authService != null) {
      authorizedRoutes = authorizedRoutes.filter((r) => this.authService.checkRouteAccess(r.settings));
    }
    let newAuthorizedRoutes: Array<MenuGroupInstance> = new Array<MenuGroupInstance>();
    let routes: Map<string, Array<NavModel>> = authorizedRoutes.filter(x => x.config.nav == true).groupBy('config.settings.group') as Map<string, Array<NavModel>>;
    routes.forEach((groupValue, groupKey) => {
      let newGroup = new MenuGroupInstance(1, {
        group: groupKey,
        subGroup: null,
        title: groupKey,
        route: null,
        isDefault: false,
        classes: groupValue.length > 1 ? '' : groupValue[0].config.settings.classes,
      });
      if (groupKey != null) {
        let groupConfig: UIMenuGroupConfig = this.groupIcons.get(groupKey);
        let defaultSubGroup: string = null;
        if (groupConfig != null) {
          newGroup.icon = groupConfig.icon;
          newGroup.isBottom = groupConfig.isBottom;
          newGroup.isDefault = groupConfig.isDefault;
          newGroup.classes = groupConfig.classes;
          defaultSubGroup = groupConfig.defaultSubGroup;
        }

        let subGroup: Map<string, Array<NavModel>> = groupValue.groupBy('config.settings.subGroup') as Map<string, Array<NavModel>>;
        if (subGroup.size == 1) {
          subGroup.forEach((subValue, subKey) => {
            if (subValue.length == 1 && groupConfig?.alwaysGroup !== true) {
              newGroup.title = subValue[0].config.title;
              newGroup.route = subValue[0].href ?? subValue[0].relativeHref;
              newGroup.routeName = subValue[0].config.name;
              newGroup.isDefault = subValue[0].config.isDefault;
              newGroup.classes = subValue[0].config.settings.classes;
              newGroup.children = null;
            } else {
              subValue.forEach(child => {
                let children = new MenuGroupInstance(2, {
                  group: groupKey,
                  subGroup: subKey,
                  title: child.config.title,
                  route: child.href ?? child.relativeHref,
                  routeName: child.config.name,
                  isDefault: child.config.isDefault,
                  classes: child.config.settings.classes
                });
                newGroup.children.push(children);
              });
            }
          });
        } else {
          subGroup.forEach((subValue, subKey) => {
            let newSubGroup = new MenuGroupInstance(2, {
              group: groupKey,
              subGroup: subKey,
              title: subKey,
              route: null,
              isDefault: (defaultSubGroup == subKey)
            })
            if (subValue != null) {
              subValue.forEach((childrenValue, childrenKey) => {
                let children = new MenuGroupInstance(3, {
                  group: groupKey,
                  subGroup: subKey,
                  title: childrenValue.config.title,
                  route: childrenValue.href ?? childrenValue.relativeHref,
                  routeName: childrenValue.config.name,
                  isDefault: childrenValue.config.isDefault,
                  classes: childrenValue.config.settings.classes
                })
                newSubGroup.children.push(children);
              });
            } else {
              console.log("subValue value: ", subValue);
            }
            newGroup.children.push(newSubGroup);
          });
        }
      } else if (groupValue.length == 1) {
        newGroup.route = groupValue[0]?.href ?? groupValue[0]?.relativeHref;
        newGroup.routeName = groupValue[0]?.config?.name;
      }
      newAuthorizedRoutes.push(newGroup);
    });
    this.newAuthorizedRoutes.push(...newAuthorizedRoutes);
  }

  viewChildren(menu: MenuGroup) {
    if (menu.viewChildren) {
      let group = this.newAuthorizedRoutes.find(x => x.group == menu.group);
      let subGroup = group?.children?.find(x => x.subGroup == menu.subGroup);
      if (group.viewChildren) {
        if (subGroup?.viewChildren) {
          subGroup.viewChildren = false;
        } else {
          group.viewChildren = false;
        }
      }
    } else {
      let group = this.newAuthorizedRoutes.find(x => x.viewChildren);
      if (group != null) {
        let subViewChild = group.children?.find(x => x.viewChildren);
        if (subViewChild != null) {
          subViewChild.viewChildren = false;
        }
        if (group.group != menu.group) {
          group.viewChildren = false;
        }
      }
      menu.viewChildren = true;
    }
    if (this.onViewChildren) {
      this.onViewChildren(menu);
    }
  }

  routeClick($event: MouseEvent, menu: MenuGroupInstance) {
    if (this.asCondensed() && !this.keepCondensed) {
      this.openMenu();
    }
    if (menu.route != null) {
      if ($event.ctrlKey) {
        window.open(menu.route, "_blank");
      }
      else {
        let otherGroup = this.newAuthorizedRoutes.filter(x => x.route != menu.route && (x.children == null || x.children.every(c =>
          c.route != menu.route && (c.children == null || c.children?.every(sc => sc.route != menu.route)))))

        otherGroup.forEach(x => {
          this.changeViewChildren(x, false);
        });
        this.router.navigate(menu.route);
      }
    }
    if (this.onSelected && menu.isSelected && menu.children?.length == 0) {
      this.onSelected(menu);
    }

    if (menu.children?.length > 0) {
      this.viewChildren(menu)
    }

  }

  changeViewChildren(menu: MenuGroup, viewChildren: boolean) {
    menu.children?.forEach(children => this.changeViewChildren(children, viewChildren));
    menu.viewChildren = viewChildren;
  }

  menuIsSelected(menu: MenuGroup) {
    if (menu.children?.length > 0) {
      menu.children.forEach((x) => {
        this.menuIsSelected(x);
      });
      return false;
    } else {
      return menu.isSelected;
    }
  }

  public toggleMenu() {
    if (this.asCondensed()) {
      this.openMenu();
    }
    else {
      this.closeMenu();
    }
  }

  private asCondensed(): boolean {
    return document.body.classList.contains("condensed");
  }

  private openMenu() {
    document.body.classList.remove("condensed");
    this.isCondensed = !this.isCondensed;
  }

  private closeMenu() {
    document.body.classList.add("condensed");
    this.closeCollapsible();
    this.isCondensed = !this.isCondensed;
  }

  private closeCollapsible() {
    let route = this.newAuthorizedRoutes.find(x => x.isSelected);
    if (route != null) {
      route.viewChildren = false;
    }
  }
}

export interface MenuGroup {
  icon: string;
  group: string;
  subGroup: string;
  title: string;
  routeName: string;
  route: string;
  children: Array<MenuGroup>;
  isSelected: boolean;
  viewChildren: boolean;
  isBottom: boolean;
  isDefault: boolean;
  level: number;
}

export class MenuGroupInstance implements MenuGroup {
  icon: string;
  group: string;
  subGroup: string;
  title: string;
  routeName: string;
  route: string;
  children: MenuGroupInstance[] = [];
  _isSelected: boolean = false;
  viewChildren: boolean = false;
  isBottom: boolean = false;
  isDefault: boolean = false;
  _level: number;
  classes: string | null;

  constructor(level: number, data: Partial<MenuGroupInstance>) {
    Object.assign(this, data);
    this._level = level;
  }

  public get isSelected(): boolean {
    if (this.children?.length > 0) {
      return this.children.find(child => child.isSelected) != null;
    }
    return this._isSelected;
  }

  public setSelected(isSelected: boolean = true) {
    if (this.children?.length > 0) {
      if (this.isSelected && isSelected == false) {
        this.children.find(x => x.isSelected)?.setSelected(false);
      }
    } else {
      this._isSelected = isSelected;
    }
    if (isSelected == true) {
      this.viewChildren = true;
    }
  }

  public get level(): number {
    return this._level;
  }

  public hasChildren(maxLevel: number): boolean {
    return this.children != null && this.children.length > 0 && this.level != maxLevel;
  }
}