import {
  Component,
  OnInit,
  OnDestroy,
  Input,
  ViewChild,
  ContentChild,
  TemplateRef,
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Directive
} from '@angular/core';

import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { FlatTreeControl } from '@angular/cdk/tree';

import { MatTreeFlattener, MatTree } from '@angular/material/tree';

import { VirtualScrollTreeFlatDataSource } from '@ucap/ng-ui';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { PerfectScrollbarDirective } from 'ngx-perfect-scrollbar';
import { RoomInfo } from '@ucap/protocol-room';

export interface ChatGroupNode {
  nodeType: string;
  roomInfo?: RoomInfo;
  children?: ChatGroupNode[];
}

export interface FlatNode {
  expandable: boolean;
  level: number;
  node: ChatGroupNode;
}

@Directive({
  selector: '[ucapChatExpansionNode]'
})
export class ExpansionNodeDirective {}

@Directive({
  selector: '[ucapChatExpansionHeader]'
})
export class ExpansionHeaderDirective {}

@Component({
  selector: 'ucap-chat-expansion',
  templateUrl: './expansion.component.html',
  styleUrls: ['./expansion.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ExpansionComponent implements OnInit, OnDestroy {
  @Input()
  set chatGroup(list: { division: string; roomList: RoomInfo[] }[]) {
    if (!list || 0 === list.length) {
    } else {
      list.sort((a, b) =>
        a.division < b.division ? 1 : a.division > b.division ? -1 : 0
      );

      for (const item of list) {
        const nodeType = item.division;
        const node: ChatGroupNode = {
          nodeType,
          children: []
        };

        item.roomList.sort((a, b) =>
          a.finalEventDate < b.finalEventDate
            ? 1
            : a.finalEventDate > b.finalEventDate
            ? -1
            : 0
        );

        item.roomList.forEach((roomInfo) => {
          node.children.push({
            nodeType,
            roomInfo
          });
        });

        if (!!this.nodeMap.get(item.division)) {
          this.nodeMap[item.division].push(node);
        } else {
          this.nodeMap.set(item.division, [node]);
        }
      }
    }
    this.refreshNodes();
  }

  @ViewChild('treeList', { static: false })
  treeList: MatTree<FlatNode>;

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

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

  @ContentChild(ExpansionNodeDirective, {
    read: TemplateRef,
    static: false
  })
  nodeTemplate: TemplateRef<ExpansionNodeDirective>;

  @ContentChild(ExpansionHeaderDirective, {
    read: TemplateRef,
    static: false
  })
  headerTemplate: TemplateRef<ExpansionHeaderDirective>;

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

  private nodeMap: Map<string, ChatGroupNode[]> = new Map();
  // tslint:disable-next-line: variable-name
  private _ngOnDestroySubject: Subject<void>;

  constructor(private changeDetectorRef: ChangeDetectorRef) {
    this.treeControl = new FlatTreeControl<FlatNode>(
      (node) => node.level,
      (node) => node.expandable
    );

    this.treeFlattener = new MatTreeFlattener<ChatGroupNode, FlatNode>(
      (node: ChatGroupNode, level: number) => {
        return {
          expandable: !!node.children && node.children.length > 0,
          level,
          nodeType: node.nodeType,
          node
        };
      },
      (node) => node.level,
      (node) => node.expandable,
      (node) => node.children
    );

    this.dataSource = new VirtualScrollTreeFlatDataSource<
      ChatGroupNode,
      FlatNode
    >(this.treeControl, this.treeFlattener);
  }

  ngOnInit(): void {
    this._ngOnDestroySubject = new Subject();

    this.dataSource.cdkVirtualScrollViewport = this.cvsvList;
    this.treeControl.expansionModel.changed
      .pipe(takeUntil(this._ngOnDestroySubject))
      .subscribe(() => {
        this.cvsvList.checkViewportSize();
        this.psDirectiveRef.update();
      });
  }

  ngOnDestroy(): void {
    if (!!this._ngOnDestroySubject) {
      this._ngOnDestroySubject.next();
      this._ngOnDestroySubject.complete();
    }
  }

  isHeader = (_: number, node: FlatNode) => 0 === node.level;

  private refreshNodes() {
    const rootNode: ChatGroupNode[] = [];
    this.nodeMap.forEach((node) => rootNode.push(...node));

    this.dataSource.data = rootNode;
    this.changeDetectorRef.detectChanges();
  }
}
