import {
  Component,
  OnInit,
  Input,
  ChangeDetectorRef,
  ViewChild,
  Output,
  EventEmitter,
  AfterViewInit,
  OnDestroy
} from '@angular/core';
import { MatTreeFlattener, MatTree } from '@angular/material';
import { NGXLogger } from 'ngx-logger';

import { DeptInfo } from '@ucap-webmessenger/protocol-query';
import { LoginResponse } from '@ucap-webmessenger/protocol-authentication';
import { FlatTreeControl } from '@angular/cdk/tree';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { VirtualScrollTreeFlatDataSource } from '@ucap-webmessenger/ui';
import { ucapAnimations } from '@ucap-webmessenger/ui';
import { trigger, transition, style, animate } from '@angular/animations';
import { PerfectScrollbarDirective } from 'ngx-perfect-scrollbar';
import { Subscription } from 'rxjs';

interface OrganizationNode {
  deptInfo: DeptInfo;
  name: string;
  children?: OrganizationNode[];
}

/** Flat node with expandable and level information */
interface FlatNode {
  expandable: boolean;
  name: string;
  level: number;
  deptInfo: DeptInfo;
}

@Component({
  selector: 'ucap-organization-tree',
  templateUrl: './tree.component.html',
  styleUrls: ['./tree.component.scss'],
  animations: [
    ucapAnimations,
    trigger('romvoeAdd', [
      transition('remove <=> add', [
        style({
          transform: `rotate(45deg)`,
          opacity: 0
        }),
        animate('.2s 0s ease-out')
      ])
    ])
  ]
})
export class TreeComponent implements OnInit, OnDestroy, AfterViewInit {
  @Input()
  loginRes: LoginResponse;

  @Input()
  set oraganizationList(deptInfoList: DeptInfo[]) {
    if (!deptInfoList || 0 === deptInfoList.length) {
      return;
    }
    const nodeMap = new Map<number, OrganizationNode>();
    const rootNodeList: OrganizationNode[] = [];
    const remainChildNodeList: OrganizationNode[] = [];
    let myNode: OrganizationNode;

    deptInfoList.forEach(deptInfo => {
      const node: OrganizationNode = {
        deptInfo,
        name: deptInfo.name,
        children: []
      };
      if (nodeMap.has(deptInfo.seq)) {
        this.logger.warn('duplicate seq', deptInfo.seq);
        return;
      }
      nodeMap.set(deptInfo.seq, node);

      if (deptInfo.seq === this.loginRes.departmentCode) {
        myNode = node;
      }

      if (0 === deptInfo.parentSeq) {
        rootNodeList.push(node);
        return;
      }

      if (nodeMap.has(deptInfo.parentSeq)) {
        nodeMap.get(deptInfo.parentSeq).children.push(node);
      } else {
        remainChildNodeList.push(node);
      }
    });

    remainChildNodeList.forEach(node => {
      if (nodeMap.has(node.deptInfo.parentSeq)) {
        nodeMap.get(node.deptInfo.parentSeq).children.push(node);
      }
    });
    this.dataSource.data = rootNodeList;
  }

  @Output()
  selected = new EventEmitter<DeptInfo>();

  @ViewChild('cvsvOrganization', { static: false })
  cvsvOrganization: CdkVirtualScrollViewport;

  @ViewChild('orgranizationTree', { static: false })
  orgranizationTree: MatTree<OrganizationNode>;

  @ViewChild(PerfectScrollbarDirective, { static: false })
  psDirectiveRef?: PerfectScrollbarDirective;

  treeControl: FlatTreeControl<FlatNode>;
  treeFlattener: MatTreeFlattener<OrganizationNode, FlatNode>;
  dataSource: VirtualScrollTreeFlatDataSource<OrganizationNode, FlatNode>;

  treeControlExpansionChangeSubscription: Subscription;

  constructor(
    private changeDetectorRef: ChangeDetectorRef,
    private logger: NGXLogger
  ) {
    this.treeControl = new FlatTreeControl<FlatNode>(
      node => node.level,
      node => node.expandable
    );
    this.treeFlattener = new MatTreeFlattener<OrganizationNode, FlatNode>(
      (node: OrganizationNode, level: number) => {
        return {
          expandable: !!node.children && node.children.length > 0,
          name: node.name,
          level,
          deptInfo: node.deptInfo
        };
      },
      node => node.level,
      node => node.expandable,
      node => node.children
    );
    this.dataSource = new VirtualScrollTreeFlatDataSource<
      OrganizationNode,
      FlatNode
    >(this.treeControl, this.treeFlattener);
  }

  ngOnInit() {
    this.treeControlExpansionChangeSubscription = this.treeControl.expansionModel.changed.subscribe(
      () => {
        this.psDirectiveRef.update();
      }
    );
  }

  ngOnDestroy(): void {
    this.logger.debug('-----------------------TreeComponent ngOnDestroy');
    if (!!this.treeControlExpansionChangeSubscription) {
      this.treeControlExpansionChangeSubscription.unsubscribe();
    }
  }

  ngAfterViewInit(): void {
    this.dataSource.cdkVirtualScrollViewport = this.cvsvOrganization;
  }

  hasChild = (_: number, node: FlatNode) => node.expandable;

  isLastNode(node: FlatNode, depth: number): boolean {
    const i = this.findNodeIndex(node, depth);

    if (-1 === i) {
      return false;
    }

    if (i === this.dataSource.expandedDataSubject.value.length - 1) {
      return true;
    }

    const n = this.dataSource.expandedDataSubject.value[i];

    for (
      let idx = i + 1;
      idx < this.dataSource.expandedDataSubject.value.length;
      idx++
    ) {
      const element = this.dataSource.expandedDataSubject.value[idx];
      if (n.level === element.level) {
        return false;
      }
      if (n.level > element.level) {
        return true;
      }
    }
    return true;
  }

  appendDivArray(level: number): number[] {
    if (0 === level) {
      return [];
    }

    return Array.from({ length: level }, (_, i: number) => i);
  }

  onClickNode(node: OrganizationNode) {
    this.selected.emit(node.deptInfo);
  }

  private findNodeIndex(node: FlatNode, depth: number): number {
    if (!node) {
      return -1;
    }
    const i = this.dataSource.expandedDataSubject.value.findIndex(n => {
      return node.deptInfo.seq === n.deptInfo.seq;
    });

    if (0 === depth) {
      return i;
    }
    return this.findNodeIndex(this.findParentNode(i), depth - 1);
  }

  private findParentNode(index: number): FlatNode {
    const node = this.dataSource.expandedDataSubject.value[index];

    for (let idx = index - 1; idx >= 0; idx--) {
      const n = this.dataSource.expandedDataSubject.value[idx];
      if (n.level === node.level - 1) {
        return n;
      }
    }
    return undefined;
  }
}
