import { By, WebElement, error } from 'selenium-webdriver';
import { slotProxy, SlotProxy } from '../../';
import { OjNavigationListBase } from './OjNavigationListBase';
import { executeWithModules } from '../../lib/oj-module-proxy';

/**
 * The component WebElement for [oj-navigation-list](../../jsdocs/oj.ojNavigationList.html).
 * Do not instantiate this class directly, instead, use
 * [ojNavigationList](../functions/elements.ojNavigationList-1.html).
 */
export class OjNavigationList extends OjNavigationListBase {
  // Put overrides here
  /**
   * Sets the value of "expanded" property.
   * Specifies the key set containing the keys of the items that should be expanded.
   * Use the ExpandedKeySet class to specify items to expand.
   * Use the ExpandAllKeySet class to expand all items.
   * @param expanded The value to set for "expanded"
   * @override
   */
  async changeExpanded(expanded: Array<any>) {
    await this.whenReady();
    return executeWithModules(
      this.getDriver(),
      ['KeySet'],
      [this, expanded],
      `
      (({ ExpandedKeySet }, ele, expanded) => {
        const expandedKeySet = new ExpandedKeySet(expanded);
        ele.expanded = expandedKeySet;
      })
      `
    );
  }
  /*
   * Expands items specified by index path. Note: This expands all intermediate items too.
   * This also collapses all other nodes if any expanded already.
   * @param index array of index path to the element to be expanded
   */
  async changeExpandedByIndex(indexes: Array<Array<number>>) {
    this.whenReady();
    let expansionTree: any = this._getExpansionTree(indexes);
    await this.getDriver().executeScript(
      [
        /* Reference to the current nav list */
        'var ele = arguments[0]',

        /* Expansiontree(type object) of given index array */
        'let expansionTree = arguments[1];',

        '_traverseListToExpand(null, expansionTree);',

        /* Check it has collapsed class */
        ' function _isCollapsed(item) {',
        '  return _hasClass(item,"oj-collapsed");',
        ' }',
        /* Check it has expanded class */
        ' function _isExpanded(item) {',
        '  return _hasClass(item,"oj-expanded");',
        ' }',
        /* Expand the item if it is not exapanded */
        ' function  expandItem(item) {',
        '  let isExpanded = _isExpanded(item);',
        '  if (!isExpanded) {',
        '   var collapsedIcon = item.querySelector(".oj-navigationlist-collapse-icon");',
        '   collapsedIcon.click();',
        '  }',
        ' }',
        /* Collapse the item if it is not collapsed */
        ' function collapseItem(item) {',
        '  let isCollapsed = _isCollapsed(item);',
        '  if (!isCollapsed) {',
        '   var expandedIcon = item.querySelector(".oj-navigationlist-expand-icon");',
        '   expandedIcon.click();',
        '  }',
        ' }',
        /* Check given class name in element */
        ' function _hasClass(element, className) {',
        ' let classes =  element.getAttribute("class");',
        ' let hasClass = false;',
        ' classes.split(" ").forEach(c => {',
        '  if (c === className) {',
        '   hasClass = true;',
        '  }',
        ' });',
        ' return hasClass;',
        ' }',
        /* Get the element by its index */
        ' function getItemByIndex(index, parent){',
        ' let item;',
        '  if (!parent)',
        '   item = ele.querySelector(".oj-navigationlist-element > .oj-navigationlist-item-element:nth-child(" + index + ")")',
        '  else {',
        '   item = parent.querySelector(".oj-navigationlist-group > .oj-navigationlist-item-element:nth-child(" + index + ")")',
        '  }',
        ' return item;',
        ' }',
        /* This will check should item be exapanded only if present in exapasion tree*/
        ' function  shouldBeExpanded(expansionTree, indexPath){',
        '  if (indexPath.length === 0) {',
        '   return false;',
        '  }',
        '  for (let i=0; i < indexPath.length; i++) {',
        '  expansionTree = expansionTree[indexPath[i]];',
        '   if (!expansionTree) {',
        '    return false;',
        '   }',
        '  }',
        ' return true;',
        ' }',
        /* Expands items specified by index path
         This also collapses all other nodes if any expanded already */
        ' function _traverseListToExpand(parent, expansionTree, parentIndexPath) {',
        '  let item, indexPath, index = 1;',
        '  item =  getItemByIndex(index, parent);',
        '  parentIndexPath = parentIndexPath && parentIndexPath.length > 0 ? parentIndexPath : []',
        '  while (item) {',
        '   let isExpanded =  _isExpanded(item);',
        '   let isCollapsed = _isCollapsed(item);',
        '     if (isCollapsed || isExpanded) {',
        '       indexPath = parentIndexPath.concat([index])',
        '       if (isCollapsed && shouldBeExpanded(expansionTree, indexPath)) {',
        '           expandItem(item);',
        '       }',
        '     _traverseListToExpand(item, expansionTree, indexPath);',
        '     if (isExpanded && !shouldBeExpanded(expansionTree, indexPath)) {',
        '         collapseItem(item);',
        '     }',
        '   }',
        '   index = index + 1;',
        '   item =  getItemByIndex(index, parent);',
        '  }',
        ' }'
      ].join('\n'),
      this,
      expansionTree
    );
  }
  /**
   * Create a object of given index array
   * @param pathsToBeExpanded array of index path to the element to be selected
   */
  private _getExpansionTree(pathsToBeExpanded: Array<Array<number>> = []): any {
    let children: any,
      expansionTree: any = {};
    for (let i = 0; i < pathsToBeExpanded.length; i++) {
      children = expansionTree;
      for (let j = 0; j < pathsToBeExpanded[i].length; j++) {
        let index = pathsToBeExpanded[i][j];
        if (!children[index]) {
          children[index] = {};
        }
        children = children[index];
      }
    }
    return expansionTree;
  }
  /**
   * Selets item specified by index path. Note: This expands all intermediate items too.
   * @param index array of index path to the element to be selected
   */
  async changeSelectionByIndex(index: number | Array<number>) {
    await this.whenReady();
    let indexArray: Array<number>, parent: WebElement | undefined;
    if (!Array.isArray(index)) {
      indexArray = [index];
    } else {
      indexArray = index;
    }

    for (let i = 0; i < indexArray.length; i++) {
      parent = await this._getItemByIndex(indexArray[i], parent);
      if (parent && i < indexArray.length - 1) {
        await this._expandItem(parent);
      } else {
        await parent?.click();
      }
    }
  }
  /**
   * Gets the value of "expanded" property.
   * Specifies the key set containing the keys of the items that should be expanded.

Use the ExpandedKeySet class to specify items to expand.
Use the ExpandAllKeySet class to expand all items.
   * @return The value of "expanded" property.
   * @override
   */
  async getExpanded(): Promise<Array<any>> {
    await this.whenReady();
    let expanded: any = await this.getDriver().executeScript(
      ['return Array.from(arguments[0].expanded.values());'].join('\n'),
      this
    );
    return expanded;
  }
  /**
   * Returns item node specified by index path.
   * @param index array of index path to the element to be returned
   */
  async getItemByIndex(indexPath: Array<number>): Promise<WebElement | undefined> {
    let item: WebElement | undefined;
    for (let i = 0; i < indexPath.length; i++) {
      item = item
        ? await this._getItemByIndex(indexPath[i], item)
        : await this._getItemByIndex(indexPath[i]);
    }
    return item;
  }

  private async _hasClass(element: WebElement, className: string) {
    let classes = await element.getAttribute('class');
    let hasClass = false;
    classes.split(' ').forEach((c) => {
      if (c === className) {
        hasClass = true;
      }
    });
    return hasClass;
  }

  private async _getItemByIndex(
    index: number,
    parent?: WebElement
  ): Promise<WebElement | undefined> {
    let item;
    try {
      if (!parent)
        item = await this.findElement(
          By.css(
            '.oj-navigationlist-element > .oj-navigationlist-item-element:nth-child(' + index + ')'
          )
        );
      else {
        item = await parent.findElement(
          By.css(
            '.oj-navigationlist-group > .oj-navigationlist-item-element:nth-child(' + index + ')'
          )
        );
      }
    } catch (e) {
      item = undefined; //element not found
    }
    return item;
  }

  private async _isExpanded(item: WebElement) {
    return await this._hasClass(item, 'oj-expanded');
  }

  private async _expandItem(item: WebElement) {
    let isExpanded = await this._isExpanded(item);
    if (!isExpanded) {
      var collapsedIcon = await item.findElement(By.css('.oj-navigationlist-collapse-icon'));
      await collapsedIcon.click();
      await this.whenBusyContextReady();
    }
  }
  /**
   * Retrieve a SlotProxy which represents a single navlist item.
   * @param key The key within the Collection's dataset associated with the item.
   */
  async findItem<T>(itemLocator: { key: T }): Promise<SlotProxy> {
    // rendere in DOM
    const el = await this.getDriver().executeScript<WebElement>(
      `
    return arguments[0].getNodeBySubId({ subId: 'oj-navigationlist-item', key: ${JSON.stringify(
      itemLocator.key
    )} })
    `,
      this
    );
    return slotProxy(el, this);
  }

  /**
   * Selects item specified by key.
   * Triggers ojSelectionAction if the key passed is same as the current selection value.
   * @param key key of the item to be selected
   * @override
   */
  async doSelection<K>(key: K): Promise<void> {
    await this.whenReady();
    const item = await this.findItem({ key: key });
    if (item != null) {
      const itemElement = await item.findElement(By.css('a'));
      if (itemElement != null) {
        await itemElement?.click();
      } else {
        return Promise.reject(
          new error.InvalidArgumentError(`Clickable content not found in item`)
        );
      }
    } else {
      return Promise.reject(
        new error.InvalidArgumentError(`Item with specified key cannot be found`)
      );
    }
  }

  /**
   * Sets the value of <code>selection</code> property.
   * Specifies the key of the selected item. See the Help documentation for more information.
   * @param selection The value to set for <code>selection</code>
   * @deprecated Since 16.1.0. Instead use doSelection to change selection.
   */
  async changeSelection(selection: any): Promise<void> {
    return super.changeSelection(selection);
  }
}
