import { PermissionsSee } from './../../../../interfaces/permissions-see.interface';
import { MatSnackBar } from '@angular/material/snack-bar';
import { EncService } from './../../../../services/enc/enc.service';
import { ProfilePermissionsInterface } from './../../../../interfaces/profile-permissions.interface';
import { BehaviorSubject, lastValueFrom } from 'rxjs';
import { UsersProfilesService } from './../../../../services/users-profiles.service';
import { ModuleInterface } from './../../../../interfaces/modules.interface';
import { ActivatedRoute } from '@angular/router';
import { Component, OnInit, Injectable } from '@angular/core';
import { Location } from '@angular/common';
import { MatTreeFlatDataSource, MatTreeFlattener, MatTreeNestedDataSource } from '@angular/material/tree';
import { ProfileInterface } from './../../../../interfaces/profile.interface';
import { FlatTreeControl, NestedTreeControl } from '@angular/cdk/tree';
import { SelectionModel } from '@angular/cdk/collections';
import { animate, state, style, transition, trigger } from '@angular/animations';

export class TodoItemNode{
  children?: TodoItemNode[];
  item?: string;
}

export class TodoItemFlatNode{
  item?: string;
  level?: number;
  expandable?: boolean;
}

interface SeeNode{
  name: string;
  children?: SeeNode[];
}

interface SeeFlatNode{
  expandible: boolean;
  name: string;
  level: number;
}

@Component({
  selector: 'app-permissions',
  templateUrl: './permissions.component.html',
  styleUrls: ['./permissions.component.css'],
  animations: [
    trigger('slideVertical', [
      state(
        '*',
        style({
          height: 0
        })
      ),
      state(
        'show',
        style({
          height: '*'
        })
      ),
      transition('* => *', [animate('400ms cubic-bezier(0.25, .08, 0.25, 1)')])
    ])
  ]
})
export class PermissionsComponent implements OnInit {
  flatNodeMap = new Map<TodoItemFlatNode, TodoItemNode>();
  nestedNodeMap = new Map<TodoItemNode, TodoItemFlatNode>();
  selectedParent: TodoItemFlatNode | null = null;
  newItemName = '';
  treeControl: FlatTreeControl<TodoItemFlatNode>;
  treeFlattener: MatTreeFlattener<TodoItemNode, TodoItemFlatNode>;
  dataSource: MatTreeFlatDataSource<TodoItemNode, TodoItemFlatNode>;
  checklistSelection = new SelectionModel<TodoItemFlatNode>(true);

  dataChange = new BehaviorSubject<TodoItemNode[]>([]);
  permissions?: ProfilePermissionsInterface;

  profileName: string;
  hasError: boolean;
  errorStr: string;
  isLoading: boolean;

  isNew: boolean;
  isWatching: boolean;

  map: Map<string, boolean>;
  profileNum: number;

  permissionsSee: Array<PermissionsSee>;
  private _transformer = (node: SeeNode, level: number) => {
    return {
      expandible: !!node.children && node.children.length > 0,
      name: node.name,
      level: level
    }
  }

  /*treeControlSee = new FlatTreeControl<SeeFlatNode>(
    node => node.level,
    node => node.expandible
  );

  treeFlattenerSee = new MatTreeFlattener(
    this._transformer,
    node => node.level,
    node => node.expandible,
    node => node.children
  );

  dataSourceSee = new MatTreeFlatDataSource(this.treeControlSee, this.treeFlattenerSee);*/

  treeControlSee = new NestedTreeControl<SeeNode>(node => node.children);
  dataSourceSee = new MatTreeNestedDataSource<SeeNode>();

  constructor(
    private route: ActivatedRoute, 
    private _location: Location,
    private usersProfilesService: UsersProfilesService,
    private _encService: EncService,
    private _snackBar: MatSnackBar
  ) { 
    this.treeFlattener = new MatTreeFlattener(
      this.transformer,
      this.getLevel,
      this.isExpandable,
      this.getChildren,
    );

    this.treeControl = new FlatTreeControl<TodoItemFlatNode>(this.getLevel, this.isExpandable);
    this.dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);

    this.profileName = '';
    this.hasError = false;
    this.errorStr = "";
    this.isLoading = true;

    this.isNew = false;
    this.isWatching = false;

    this.map = new Map();
    this.profileNum = 0;

    this.permissionsSee = [];
  }

  hasChildSee = (_: number, node: SeeNode) => !!node.children && node.children.length > 0;

  async initialize(profile: number){
    try{
      let profilePermissions: ProfileInterface = await lastValueFrom(this.usersProfilesService.getProfile("A", "A", 1));
      let permissionsObj: ProfilePermissionsInterface = JSON.parse("{}");
      let permissionsStr = "{";
      let seeNodes: SeeNode[] = [];

      permissionsObj.permissionsPerModule.forEach(i0 => {
        permissionsStr += `\"${i0.id} - ${i0.name}\": { `;
        this.map.set(`${i0.id} - ${i0.name}`, i0.enabled);

        let enabled0 = "Deshabilitado";
        let children0: SeeNode[] = [];

        i0.permissionsPerSubmodule.forEach(i1 => {
          permissionsStr += `\"${i0.id} - ${i1.id} - ${i1.name}\": { `;
          this.map.set(`${i0.id} - ${i1.id} - ${i1.name}`, i1.enabled);

          if(i1.enabled) enabled0 = "Habilitado parcialmente";
          let enabled1 = "Deshabilitado";
          let children1: Array<SeeNode> = [];

          i1.permissionsPerAction.forEach(i2 => {
            permissionsStr += `\"${i0.id} - ${i1.id} - ${i2.id} - ${i2.name}\": null, `;
            this.map.set(`${i0.id} - ${i1.id} - ${i2.id} - ${i2.name}`, i2.enabled);

            if(i2.enabled){
              enabled0 = "Habilitado parcialmente";
              enabled1 = "Habilitado parcialmente";
            }

            let enabled2 = i2.enabled ? "Habilitado" : "Deshabilitado";
            let permissions2: SeeNode = {
              name: `${i0.id} - ${i1.id} - ${i2.id} - ${i2.name} - Permisos: ${enabled2}`,
            };

            children1.push(permissions2);
          });

          if(i1.enabled) enabled1 = "Habilitado";
          let permissions1: SeeNode = {
            name: `${i0.id} - ${i1.id} - ${i1.name} - Permisos: ${enabled1}`,
            children: children1
          }

          children0.push(permissions1);

          permissionsStr = permissionsStr.substring(0, permissionsStr.length - 2);
          permissionsStr += "}, ";
        });

        if(i0.enabled) enabled0 = "Habilitado";
        let permissions0: SeeNode = {
          name: `${i0.id} - ${i0.name} - Permisos: ${enabled0}`,
          children: children0
        }
        
        seeNodes.push(permissions0);

        permissionsStr = permissionsStr.substring(0, permissionsStr.length - 2);
        permissionsStr += "}, ";
      });
      
      permissionsStr = permissionsStr.substring(0, permissionsStr.length - 2);
      permissionsStr += "}";

      this.dataSourceSee.data = seeNodes;
      
      const data =this.buildFileTree(JSON.parse(permissionsStr), 0);
      this.dataChange.next(data);
    }catch(error: any){
      this.isLoading = false;
      this.errorStr = error.error.msg;
      this.hasError = true;
    }
  }
  
  checkEnabledNodes(){
    for(var i = 0; i < this.treeControl.dataNodes.length; i++){
      let item = this.treeControl.dataNodes[i].item;
      let enabled = this.map.get(item!);
      let selected = this.checklistSelection.selected.includes(this.treeControl.dataNodes[i]);
      if(enabled && !selected) this.todoItemSelectionToggle(this.treeControl.dataNodes[i]);
    }

    this.isLoading = false;
  }

  getLevel = (node: TodoItemFlatNode): number => node.level!;

  isExpandable = (node: TodoItemFlatNode) => node.expandable!;

  getChildren = (node: TodoItemNode): TodoItemNode[] => node.children!;

  hasChild = (_: number, _nodeData: TodoItemFlatNode) => _nodeData.expandable;

  hasNoContent = (_: number, _nodeData: TodoItemFlatNode) => _nodeData.item === '';

  transformer = (node: TodoItemNode, level: number) => {
    const existingNode = this.nestedNodeMap.get(node);
    const flatNode =
      existingNode && existingNode.item === node.item ? existingNode : new TodoItemFlatNode();
    flatNode.item = node.item;
    flatNode.level = level;
    flatNode.expandable = !!node.children?.length;
    this.flatNodeMap.set(flatNode, node);
    this.nestedNodeMap.set(node, flatNode);
    return flatNode;
  };

  descendantsAllSelected(node: TodoItemFlatNode): boolean {
    const descendants = this.treeControl.getDescendants(node);
    const descAllSelected =
      descendants.length > 0 &&
      descendants.every(child => {
        return this.checklistSelection.isSelected(child);
      });
    return descAllSelected;
  }

  descendantsPartiallySelected(node: TodoItemFlatNode): boolean{
    const descendants = this.treeControl.getDescendants(node);
    const result = descendants.some(child => this.checklistSelection.isSelected(child));
    return result && !this.descendantsAllSelected(node);
  }

  todoItemSelectionToggle(node: TodoItemFlatNode): void {
    this.checklistSelection.toggle(node);
    const descendants = this.treeControl.getDescendants(node);
    this.checklistSelection.isSelected(node)
      ? this.checklistSelection.select(...descendants)
      : this.checklistSelection.deselect(...descendants);

    // Force update for the parent
    descendants.forEach(child => this.checklistSelection.isSelected(child));
    this.checkAllParentsSelection(node);
  }

  todoLeafItemSelectionToggle(node: TodoItemFlatNode): void {
    this.checklistSelection.toggle(node);
    this.checkAllParentsSelection(node);
  }

  checkAllParentsSelection(node: TodoItemFlatNode): void {
    let parent: TodoItemFlatNode | null = this.getParentNode(node);
    while (parent) {
      this.checkRootNodeSelection(parent);
      parent = this.getParentNode(parent);
    }
  }

  checkRootNodeSelection(node: TodoItemFlatNode): void {
    const nodeSelected = this.checklistSelection.isSelected(node);
    const descendants = this.treeControl.getDescendants(node);
    const descAllSelected =
      descendants.length > 0 &&
      descendants.every(child => {
        return this.checklistSelection.isSelected(child);
      });
    if (nodeSelected && !descAllSelected) {
      this.checklistSelection.deselect(node);
    } else if (!nodeSelected && descAllSelected) {
      this.checklistSelection.select(node);
    }
  }

  getParentNode(node: TodoItemFlatNode): TodoItemFlatNode | null {
    const currentLevel = this.getLevel(node);

    if (currentLevel < 1) {
      return null;
    }

    const startIndex = this.treeControl.dataNodes.indexOf(node) - 1;

    for (let i = startIndex; i >= 0; i--) {
      const currentNode = this.treeControl.dataNodes[i];

      if (this.getLevel(currentNode) < currentLevel) {
        return currentNode;
      }
    }
    return null;
  }

  buildFileTree(obj: {[key: string]: any}, level: number): TodoItemNode[]{
    return Object.keys(obj).reduce<TodoItemNode[]>((accumulator, key) => {
      const value = obj[key];
      const node = new TodoItemNode();
      node.item = key;

      if(value != null){
        if(typeof value === 'object'){
          node.children = this.buildFileTree(value, level + 1);
        }else{
          node.item = value;
        }
      }

      return accumulator.concat(node);
    }, []);
  }

  ngOnInit(): void {
    this.route.queryParams.subscribe(params => {
      let data = params['data'];
      this._encService.desencriptar(data).then((dataDec) => {
        let dataJSON = JSON.parse(dataDec);
        let objKeys = Object.keys(dataJSON);

        if(!objKeys.includes("profile") || !objKeys.includes("profileName") || !objKeys.includes("action")){
          this.isLoading = false;
          this.hasError = true;
          this.errorStr = "No se recibió ningún parámetro";
        }else{
          this.profileName = dataJSON.profileName;
          this.isNew = dataJSON.action == 'create';
          this.isWatching = dataJSON.action == 'see';
          this.profileNum = dataJSON.profile;

          this.initialize(dataJSON.profile);

          this.dataChange.subscribe((a) => {
            this.dataSource.data = this.dataChange.value;
            if(a.length > 0){
              this.checkEnabledNodes();
            };
          });
        }
      });
    });
  }

  return(){
    this._location.back();
  }

  openSnackBar(message: string){
    this._snackBar.open(message, undefined, {
      duration: 5000
    });
  }

  async save(){
    let selected = this.checklistSelection.selected;
    let selectedArr: string[] = [];
    
    selected.forEach(item => selectedArr.push(item.item!));
    let permissionsStr = "{";
    permissionsStr += "\"permissionsPerModule\": [";

    this.dataSource.data.forEach(item => {
      permissionsStr += "{";

      let itemArr = item.item?.split(" - ");
      permissionsStr += `\"id\": \"${itemArr![0]}\", `;
      permissionsStr += `\"name\": \"${itemArr![1]}\", `;

      let enabled = selectedArr.includes(item.item!);
      permissionsStr += `\"enabled\": ${enabled}, `;
      permissionsStr += `\"permissionsPerSubmodule\": [ `;

      item.children!.forEach(item0 => {
        permissionsStr += "{";

        let itemArr0 = item0.item?.split(" - ");
        permissionsStr += `\"id\": \"${itemArr0![1]}\", `;
        permissionsStr += `\"name\": \"${itemArr0![2]}\", `;

        let enabled0 = selectedArr.includes(item0.item!);
        permissionsStr += `\"enabled\": ${enabled0}, `;
        permissionsStr += `\"permissionsPerAction\": [ `;
        
        item0.children?.forEach(item1 => {
          permissionsStr += "{";

          let itemArr1 = item1.item?.split(" - ");
          permissionsStr += `\"id\": \"${itemArr1![2]}\", `;
          permissionsStr += `\"name\": \"${itemArr1![3]}\", `;

          let enabled1 = selectedArr.includes(item1.item!);
          permissionsStr += `\"enabled\": ${enabled1}}, `;
        });

        permissionsStr = permissionsStr.substring(0, permissionsStr.length - 2);
        permissionsStr += "]}, ";
        
      });

      permissionsStr = permissionsStr.substring(0, permissionsStr.length - 2);
      permissionsStr += "]}, ";
      
    });

    permissionsStr = permissionsStr.substring(0, permissionsStr.length - 2);
    permissionsStr += "]}";
    
    try{
      await lastValueFrom(this.usersProfilesService.updateProfile({}));
      this.openSnackBar("La modificación se realizó correctamente.");
      this.return();
    }catch(error: any){
      let msg = "";
      if(error.error.msg != undefined){
        msg = error.error.msg;
      }else{
        msg = "Ocurrió un error inesperado.";
      }

      this.openSnackBar(msg);
    }
  }
}
