import TreeListNode from './TreeListNode.js';
'use strict';


class TreeList {


  /** @summary Create a tree list view with interactive nodes
   * @author Arthur Beaulieu
   * @since October 2020
   * @description <blockquote>This component will build a hierarchical tree with given model. The model must fit the format
   * described in the <code>README.md</code>. Each node can then be expanded or collapsed if it contains children. Each node
   * will also fire a given callback when clicked, sending its information so further handling can be done on the caller side.
   * It finally expose some global method to update the tree by code (expand or collapse all).</blockquote>
   * @param {object} options - The TreeList global options
   * @param {object} options.renderTo - The DOM element to render the TreeList in
   * @param {object[]} options.model - The model to build the TreeList with. Must match the format described on this component's repository 
   * @param {function} [options.nodeClicked] - The callback function to provided that will be called each time a node is clicked */
  constructor(options = {}) {
    // Prevent wrong type for arguments, fallback according to attribute utility
    if (typeof options.renderTo !== 'object' || (options.renderTo && !options.renderTo.appendChild)) {
      options.renderTo = document.body;
    }
    if (typeof options.model !== 'object') {
      options.model = [];
    }
    if (typeof options.nodeClicked !== 'function') {
      options.nodeClicked = undefined;
    }    
    /** @private
     * @member {array} - Nodes that have a depth of 0. Child nodes are available and hierarchical */
   	this._nodes = [];
    /** @private
     * @member {object} - The DOM element to render the TreeList in */
   	this._container = options.renderTo;
    /** @private
     * @member {function} - The callback function to provided that will be called each time a node is clicked */
    this._nodeClicked = options.nodeClicked;
    // Build model (ie build hierarchical nodes) and build ui DOM elements
   	this._buildModel(options.model);
   	this._buildUI();
  }


  /** @method
   * @name destroy
   * @public
   * @memberof TreeList
   * @description <blockquote>TreeList destructor. Will delete each nodes, their events and their properties.</blockquote> */
  destroy() {
    // Destroy model children, they will recursively destroy any existing nodes and listeners
    for (let i = 0; i < this._nodes.length; ++i) {
      this._nodes[i].destroy();
    }
    // Delete object attributes
    Object.keys(this).forEach(key => {
      delete this[key];
    });     
  }


  /*  --------------------------------------------------------------------------------------------------------------- */
  /*  --------------------------------------  TREELIST JS INTERN METHODS  ------------------------------------------  */
  /*                                                                                                                  */
  /*  The following methods are made to build nodes according to the given model, store them and display them.        */
  /*  --------------------------------------------------------------------------------------------------------------- */  


  /** @method
   * @name _buildModel
   * @private
   * @memberof TreeList
   * @description <blockquote>This method will build the TreeList using the provided model. It build the tree nodes recursively,
   * so they match the hierarchical format given as input model.</blockquote>
   * @param {object} model - The model to build the TreeList with. Must match the format described on this component's repository */
	_buildModel(model) {
		for (let i = 0; i < model.length; ++i) {
			this._nodes.push(this._buildNode(model[i], 0));
		}
	}


  /** @method
   * @name _buildNode
   * @private
   * @memberof TreeList
   * @description <blockquote>This method will build the TreeList using the provided model. It build the tree nodes recursively,
   * so they match the hierarchical format given as input model.</blockquote>
   * @param {object} nodeModel - The node model that contains its information
   * @param {number} [nodeModel.id=-1] - The node ID
   * @param {number} nodeModel.name - The node name to display in TreeList
   * @param {array} nodeModel.children - The node children
   * @param {number} depth - The node depth in graph
   * @return {object} The TreeListNode object */
  _buildNode(nodeModel, depth) {
    // No need to pass children since we need full model generation before assigning descendants
    const node = new TreeListNode({
      id: nodeModel.id,
      name: nodeModel.name,
      depth: depth,
      clicked: this._nodeClicked
    });
    // Construct child node if any
    if (nodeModel.children && nodeModel.children.length > 0) {
      for (let i = 0; i < nodeModel.children.length; ++i) {
        node.children.push(this._buildNode(nodeModel.children[i], depth + 1));
      }
    }
    // Set expander only when children have been recursively constructed
    node.setExpander();
    return node;    
  }


  /** @method
   * @name _buildUI
   * @private
   * @memberof TreeList
   * @description <blockquote>This method will attach root nodes (depth 0) to the TreeList UI.</blockquote> */
	_buildUI() {
		this._container.classList.add('tree-list');

		for (let i = 0; i < this._nodes.length; ++i) {
			this._container.appendChild(this._nodes[i].container);
		}
	}


  /*  --------------------------------------------------------------------------------------------------------------- */
  /*  --------------------------------------  TREELIST JS PUBLIC METHOD  -------------------------------------------  */
  /*                                                                                                                  */
  /*  The following methods are made expand all or collapse all nodes in the tree list.                               */
  /*  --------------------------------------------------------------------------------------------------------------- */  


  /** @method
   * @name expandAll
   * @public
   * @memberof TreeList
   * @description <blockquote>This method will expand all nodes in the TreeList recursively.</blockquote> */
  expandAll() {
    for (let i = 0; i < this._nodes.length; ++i) {
      this._nodes[i].expand(true);
    }
  }


  /** @method
   * @name collapseAll
   * @public
   * @memberof TreeList
   * @description <blockquote>This method will collapse all nodes in the TreeList recursively.</blockquote> */
  collapseAll() {
    for (let i = 0; i < this._nodes.length; ++i) {
      this._nodes[i].collapse(true);
    }
  }


};


export default TreeList;