import { ElementRef, Injectable } from '@angular/core';
import { UUID } from 'angular2-uuid';

export interface TreeNode {
  label: string;
  isSelected: boolean;
  value: unknown;
  childItemType?: string;
  grandChildrenItemType?: string;
  grandChildrenInfo?: string;
  toggleAllChildrenLabel?: string;
  children?: Array<TreeNode>;
}

export interface TreeNodeModel extends TreeNode {
  uuid?: string;
  expandedChildUuid?: string;
  children?: Array<TreeNodeModel>;
  hasFocus?: boolean;
  toggleAllHasFocus?: boolean;
  showGeneralInfoAboutGrandChildren?: boolean;
  hideTreeNode?: boolean;
}

export interface FilterTreeNodeData {
  text: string;
  treeNode: TreeNodeModel;
}

@Injectable({
  providedIn: 'root',
})
export class TreeNodesSelectorService {
  getTreeNodeModelFromTreeNode(treeNode: TreeNode): TreeNodeModel | null {
    let children: Array<TreeNode> = [];

    if (!treeNode) {
      return null;
    }

    children = treeNode.children?.map(childNode => this.getTreeNodeModelFromTreeNode(childNode));

    return {
      ...treeNode,
      uuid: UUID.UUID(),
      hasFocus: false,
      children,
      hideTreeNode: false,
    };
  }

  getBasicTreeNodeFromModel(treeNodeModel: TreeNodeModel): TreeNode {
    const children = treeNodeModel?.children?.map(childNode => this.getBasicTreeNodeFromModel(childNode));

    const treeNode: TreeNode = {
      isSelected: treeNodeModel.isSelected,
      value: treeNodeModel.value,
      label: treeNodeModel.label,
      grandChildrenItemType: treeNodeModel.grandChildrenItemType,
      grandChildrenInfo: treeNodeModel.grandChildrenInfo,
      childItemType: treeNodeModel.childItemType,
      toggleAllChildrenLabel: treeNodeModel.toggleAllChildrenLabel,
      children,
    };

    return treeNode;
  }

  hasSelectedLeafNodeDescendant(treeNode: TreeNodeModel): boolean {
    if (!treeNode || !treeNode.children) {
      return false;
    }

    return treeNode.children.some(
      childNode => (this.isLeafNode(childNode) && childNode.isSelected) || this.hasSelectedLeafNodeDescendant(childNode)
    );
  }

  getAriaLabelTextForExpansionButton(parentTreeNode: TreeNodeModel, treeNode: TreeNodeModel): string {
    const isActiveNode = parentTreeNode && treeNode ? parentTreeNode.expandedChildUuid === treeNode.uuid : false;

    return `${isActiveNode ? 'Döljer' : 'Visar'} ${treeNode.grandChildrenItemType} för ${treeNode.label}`;
  }

  isFullyVisibleElement(element: HTMLElement, container: HTMLElement): boolean {
    const { bottom, top, left, right } = element.getBoundingClientRect();
    const containerRect = container.getBoundingClientRect();

    if (top <= containerRect.top && containerRect.top - top > 0) {
      return false;
    }

    if (bottom - containerRect.bottom > 0) {
      return false;
    }

    if (left <= containerRect.left && containerRect.left - left > 0) {
      return false;
    }

    if (right - containerRect.right > 0) {
      return false;
    }

    return true;
  }

  hasChildLeafNodes(node: TreeNodeModel): boolean {
    if (!node || !node.children || node.children.length === 0) {
      return false;
    }

    return node.children.some(child => this.isLeafNode(child));
  }

  isLeafNode(node: TreeNodeModel): boolean {
    return node && (!node.children || node.children.length === 0);
  }

  allChildLeafNodesAreSelected(node: TreeNodeModel): boolean {
    if (!node || !node.children || node.children.length === 0) {
      return true;
    }

    return !node.children.some(child => this.isLeafNode(child) && !child.isSelected);
  }

  getElementByQuerySelector(elementRef: ElementRef, selector: string): HTMLElement {
    const element = elementRef?.nativeElement as HTMLElement;

    return element?.querySelector(selector);
  }

  getPresentationItemElement(elementRef: ElementRef, treeNode: TreeNodeModel): HTMLElement {
    return this.getElementByQuerySelector(elementRef, `#${this.getPresentationItemId(treeNode)}`);
  }

  getPresentationToggleAllElement(elementRef: ElementRef, treeNode: TreeNodeModel): HTMLElement {
    return this.getElementByQuerySelector(elementRef, `#${this.getPresentationToggleAllId(treeNode)}`);
  }

  getExpansionColumnElement(element: HTMLElement): HTMLElement {
    return element?.closest('.tree-nodes-selector-panel__expanded-node');
  }

  getExpandedTreeNodes(treeNode: TreeNodeModel): Array<TreeNodeModel> {
    let expandedChildNode: TreeNodeModel | null = null;

    if (!treeNode) {
      return [];
    }

    expandedChildNode = treeNode.children?.find(childNode => this.isExpandedNode(treeNode, childNode));

    if (expandedChildNode) {
      return [treeNode].concat(this.getExpandedTreeNodes(expandedChildNode));
    }

    return treeNode.children && treeNode.children.some(child => child.children && child.children.length > 0)
      ? [treeNode, { ...treeNode, children: undefined, showGeneralInfoAboutGrandChildren: true }]
      : [treeNode];
  }

  isExpandedNode(parentTreeNode: TreeNodeModel, treeNode: TreeNodeModel): boolean {
    if (!parentTreeNode || !treeNode) {
      return false;
    }

    return !parentTreeNode.hideTreeNode && !treeNode.hideTreeNode && parentTreeNode.expandedChildUuid === treeNode.uuid;
  }

  getVisibleChildren(treeNode: TreeNodeModel): Array<TreeNodeModel> | null {
    if (!treeNode || !treeNode.children) {
      return null;
    }

    return treeNode.children.filter(childNode => !childNode.hideTreeNode);
  }

  getTreeNodeWithNoNodesSelected(treeNode: TreeNodeModel): TreeNodeModel {
    return this.getTreeNodeWithAllOrNoNodesSelected(treeNode, false);
  }

  getTreeNodeWithAllNodesSelected(treeNode: TreeNodeModel): TreeNodeModel {
    return this.getTreeNodeWithAllOrNoNodesSelected(treeNode, true);
  }

  private getTreeNodeWithAllOrNoNodesSelected(treeNode: TreeNodeModel, selectingAll: boolean): TreeNodeModel {
    if (!treeNode) {
      return null;
    }

    return {
      ...treeNode,
      isSelected: selectingAll,
      children: treeNode.children
        ? treeNode.children.map(child => this.getTreeNodeWithAllOrNoNodesSelected(child, selectingAll))
        : undefined,
    };
  }

  getAriaLabelForToggleAllButton(treeNode: TreeNodeModel): string {
    const allChildNodesAreSelected = this.allChildLeafNodesAreSelected(treeNode);

    return `${allChildNodesAreSelected ? 'Tar bort' : 'Väljer'} samtliga alternativ under: ${treeNode.label}`;
  }

  getFilterButtonAriaLabelText(treeNode: TreeNodeModel): string {
    if (!treeNode) {
      return '';
    }

    return `Filtrera ${treeNode.showGeneralInfoAboutGrandChildren ? treeNode.grandChildrenItemType : treeNode.label}`;
  }

  getTreeNodeHeadingText(treeNode: TreeNodeModel): string {
    if (!treeNode) {
      return '';
    }

    return treeNode.showGeneralInfoAboutGrandChildren ? treeNode.grandChildrenItemType : treeNode.label;
  }

  getToggleAllButtonId(treeNode: TreeNodeModel): string {
    return `toggle-all-${treeNode.uuid}`;
  }

  getItemButtonId(treeNode: TreeNodeModel): string {
    return `item-${treeNode.uuid}`;
  }

  getPresentationItemId(treeNode: TreeNodeModel): string {
    return `presentation-item-${treeNode?.uuid}`;
  }

  getPresentationToggleAllId(treeNode: TreeNodeModel): string {
    return `presentation-toggle-all-${treeNode?.uuid}`;
  }

  getFilterDescriptionId(treeNode: TreeNodeModel): string {
    return `filter-description-${treeNode?.uuid}`;
  }

  getCleanedText(text: string): string {
    return text?.trim().toLowerCase();
  }

  getFilteredTreeNodeChildren(filterTreeNodeData: FilterTreeNodeData): Array<TreeNodeModel> {
    let cleanedFilterText: string = null;

    if (!filterTreeNodeData || !filterTreeNodeData.treeNode || !filterTreeNodeData.treeNode.children) {
      return [];
    }

    cleanedFilterText = this.getCleanedText(filterTreeNodeData.text);

    if (!cleanedFilterText || cleanedFilterText.length === 0) {
      return filterTreeNodeData.treeNode.children.map(childNode => {
        return { ...childNode, hideTreeNode: false };
      });
    }

    return filterTreeNodeData.treeNode.children.map(childNode => {
      return { ...childNode, hideTreeNode: this.getCleanedText(childNode?.label).indexOf(cleanedFilterText) === -1 };
    });
  }

  getTreeNodeFilteredBySelectedLeafOrDecsendants(treeNode: TreeNode): TreeNode {
    const filteredTreeNode: TreeNode = {
      ...treeNode,
      children: treeNode.children
        ? treeNode.children
            .filter(
              childNode =>
                this.hasSelectedLeafNodeDescendant(childNode) || (this.isLeafNode(childNode) && childNode.isSelected)
            )
            .map(childNode => this.getTreeNodeFilteredBySelectedLeafOrDecsendants(childNode))
        : undefined,
    };

    return filteredTreeNode;
  }

  getTreeNodeSelectorLabel(treeNode: TreeNode): string {
    const levelInfoList: Array<{ levelItemType: string; sumOfItems: number }> = [];
    let selectorLabel = '';

    if (!treeNode) {
      return selectorLabel;
    }

    selectorLabel = `Välj ${this.getCleanedText(treeNode.label)}`;

    if (!treeNode.children || treeNode.children.length === 0) {
      return selectorLabel;
    }

    this.updateLevelInfoList(treeNode, 0, levelInfoList);

    if (!levelInfoList || levelInfoList.length === 0 || levelInfoList[0].sumOfItems === 0) {
      return selectorLabel;
    }

    selectorLabel = levelInfoList.map(levelInfo => `${levelInfo.sumOfItems} ${levelInfo.levelItemType}`).join(', ');

    return selectorLabel;
  }

  private updateLevelInfoList(
    treeNode: TreeNode,
    level: number,
    levelInfoList: Array<{ levelItemType: string; sumOfItems: number }>
  ): void {
    let selectedDescendants: Array<TreeNode> = [];
    let nodeSum = 0;
    let i: number;

    if (!treeNode || !treeNode.children || treeNode.children.length === 0) {
      return;
    }

    selectedDescendants = treeNode.children.filter(
      childNode => this.hasSelectedLeafNodeDescendant(childNode) || (this.isLeafNode(childNode) && childNode.isSelected)
    );
    nodeSum = selectedDescendants.length;

    if (levelInfoList.length > level && levelInfoList[level]) {
      levelInfoList[level].sumOfItems += nodeSum;
    } else {
      levelInfoList[level] = { levelItemType: treeNode.childItemType, sumOfItems: nodeSum };
    }

    if (this.isLeafNode(treeNode)) {
      return;
    }

    for (i = 0; i < selectedDescendants.length; i++) {
      this.updateLevelInfoList(selectedDescendants[i], level + 1, levelInfoList);
    }
  }

  getClonedTreeNode(treeNode: TreeNodeModel): TreeNodeModel | null {
    let clonedTreeNode: TreeNodeModel | null;

    try {
      clonedTreeNode = JSON.parse(JSON.stringify(treeNode)) as TreeNode;
    } catch (error) {
      console.error(error, 'Failed to clone tree node');
    }

    return clonedTreeNode;
  }

  hasSelectedAllLeafNodes(treeNode: TreeNode): boolean {
    if (!treeNode) {
      return false;
    }

    return this.isLeafNode(treeNode)
      ? treeNode.isSelected
      : !treeNode.children.some(childNode => !this.hasSelectedAllLeafNodes(childNode));
  }
}
