import { StringUtils } from "@sankhyalabs/core";
import { Node } from "./Node";
export class Tree extends Node {
  constructor(changeCallback) {
    super(undefined);
    this._disabledValues = new Map();
    this._changeCallback = changeCallback;
  }
  async addChildAt(parentId, item) {
    const parent = this.getNode(parentId);
    if (parent == undefined) {
      this.addChild(this, item);
      this._changeCallback();
      return;
    }
    parent.addChild(this, item);
    this._changeCallback();
  }
  setDisabled(id, value) {
    this._disabledValues.set(id, value);
    this._changeCallback();
  }
  isNodeDisabled(id, defaultValue) {
    if (!this._disabledValues.has(id)) {
      return defaultValue;
    }
    return this._disabledValues.get(id);
  }
  load(items) {
    if (items === this._currentItems) {
      return;
    }
    this._currentItems = items;
    const oldChildren = this.children;
    this.children = new Map();
    this._disabledValues.clear();
    items.forEach(item => {
      const node = oldChildren.get(item.id);
      if (node) {
        node.refresh(item);
        this.children.set(item.id, node);
      }
      else {
        this.addChild(this, Object.assign({}, item));
      }
    });
  }
  async setFilterPattern(pattern, enableHierarchicalFilter = true) {
    if (pattern && enableHierarchicalFilter && pattern.includes('.')) {
      const layers = pattern.split('.').map(p => StringUtils.replaceAccentuatedCharsKeepSymbols(p.trim()));
      await this.applyLayeredFilter(this, layers, 0);
    }
    else {
      this._filterPattern = StringUtils.replaceAccentuatedCharsKeepSymbols(pattern);
      this.setAllChildrenExpanded(this, false);
      this.applyFilter(this);
    }
    this._changeCallback();
  }
  updateItem(item) {
    if (item == undefined) {
      return;
    }
    const node = this.getNode(item.id);
    if (node == undefined) {
      return;
    }
    Object.keys(item).forEach(attrib => node.item[attrib] = item[attrib]);
    if (item.children == undefined) {
      node.children.clear();
    }
    else if (Array.isArray(item.children)) {
      node.updateChildren(item.children);
    }
    this._changeCallback();
  }
  removeItem(id) {
    if (!id) {
      return;
    }
    const node = this.getNode(id);
    if (!node) {
      return;
    }
    if (node.parent) {
      node.parent.removeChild(id);
    }
    else {
      this.children.delete(id);
    }
    this._disabledValues.delete(id);
    this._changeCallback();
  }
  async open(path) {
    return new Promise(async (resolve) => {
      await this.walkPath(this, path, node => node.item.expanded = true);
      resolve();
    });
  }
  setAllChildrenVisible(node) {
    node.children.forEach(child => {
      child.visible = true;
      this.setAllChildrenVisible(child);
    });
  }
  applyFilter(node) {
    if (!this._filterPattern) {
      node.visible = true;
      this.setAllChildrenVisible(node);
      return;
    }
    node.children.forEach((value) => {
      this.applyFilter(value);
    });
    if (node.item == undefined) {
      return;
    }
    const normalizedLabel = StringUtils.replaceAccentuatedCharsKeepSymbols(node.item.label);
    let isVisible = false;
    if (normalizedLabel.includes(this._filterPattern)) {
      isVisible = true;
      node.item.expanded = true;
      this.setAllChildrenVisible(node);
    }
    else {
      const childrenArray = Array.from(node.children.values());
      for (let i = 0; i < childrenArray.length; i++) {
        if (childrenArray[i].visible) {
          isVisible = true;
          node.item.expanded = true;
          break;
        }
      }
    }
    node.visible = isVisible;
  }
  async applyLayeredFilter(node, layers, level) {
    if (!node.item) {
      const results = await Promise.all(Array.from(node.children.values()).map(child => this.applyLayeredFilter(child, layers, level)));
      const childFound = results.some(Boolean);
      node.visible = childFound;
      return childFound;
    }
    const normalizedLabel = StringUtils.replaceAccentuatedCharsKeepSymbols(node.item.label);
    if (!normalizedLabel.includes(layers[level])) {
      const results = await Promise.all(Array.from(node.children.values()).map(child => this.applyLayeredFilter(child, layers, level)));
      const childFound = results.some(Boolean);
      if (childFound)
        node.item.expanded = true;
      node.visible = childFound;
      return childFound;
    }
    if (level === layers.length - 1) {
      node.visible = true;
      this.setAllChildrenVisible(node);
      return true;
    }
    const results = await Promise.all(Array.from(node.children.values()).map(child => this.applyLayeredFilter(child, layers, level + 1)));
    const childFound = results.some(Boolean);
    if (childFound)
      node.item.expanded = true;
    node.visible = childFound;
    return childFound;
  }
  async walkPath(parent, path, callback, currentLevel = 0) {
    return new Promise(async (resolve) => {
      const levels = path.split(">>").map(item => item.trim());
      if (levels.length > currentLevel) {
        const node = parent.getNode(levels[currentLevel]);
        if (node) {
          if (node.needLoad()) {
            await this.loadLevel(node);
          }
          callback(node);
          await this.walkPath(node, path, callback, currentLevel + 1);
        }
      }
      resolve();
    });
  }
  async loadChildren(node) {
    return new Promise(resolve => {
      this.loadLevel(node).then(() => {
        resolve();
        this._changeCallback();
      });
    });
  }
  expandAll() {
    this.setAllChildrenExpanded(this, true);
    this._changeCallback();
  }
  collapseAll() {
    this.setAllChildrenExpanded(this, false);
    this._changeCallback();
  }
  setAllChildrenExpanded(node, expanded) {
    if (node.item && !node.isPlaceHolder) {
      node.item.expanded = expanded;
    }
    node.children.forEach(child => {
      this.setAllChildrenExpanded(child, expanded);
    });
  }
  async loadLevel(node) {
    return new Promise(resolve => {
      node.addPlaceHolder();
      const loader = node.item.children;
      loader(node.item).then(result => {
        node.updateChildren(result);
        resolve();
      });
    });
  }
}
