import { OjCheckboxsetBase } from './OjCheckboxsetBase';
import { error, Key, WebElement } from 'selenium-webdriver';

/**
 * The component WebElement for [oj-checkboxset](../../jsdocs/oj.ojCheckboxset.html).
 * Do not instantiate this class directly, instead, use
 * [ojCheckboxset](../functions/elements.ojCheckboxset-1.html).
 */
export class OjCheckboxset extends OjCheckboxsetBase {
  /**
   * Sets the value of "value" property.
   * An array that represents the value of the component. See the Help documentation for more information.
   * @param value The value to set for "value"
   * @throws {ElementNotInteractableError} if the API is called when the control is readonly or disabled
   * @throws {InvalidArgumentError} if the value parameter contains non-existent values
   */
  async changeValue(value: any[]): Promise<void> {
    // Check if the control is interactable
    if (!(await this.isInteractable())) {
      // Cannot interact when the control is disabled or readonly
      return Promise.reject(
        new error.ElementNotInteractableError(
          'OjCheckboxset: Value cannot be changed when the control is disabled or readonly'
        )
      );
    }

    // Do selection using SPACE key
    // Clear all selected
    // JET-42517: Use executeScript to locate <input> elements to de-select.
    // OjWebElement.whenReady expects elements to be visible (isDisplayed) before
    // they can be acted upon, but oj-checkboxset <input> elements are opacity:0
    // Get the selected checkboxes, that are not present in the new value.
    const selected = await this.getCheckboxesToUnselect(value);

    // unselect the checked checkboxes that are not present in the new value array
    for (let i = 0, len = selected.length; i < len; i++) {
      await selected[i].sendKeys(Key.SPACE);
    }

    await this.whenBusyContextReady();
    for (let i = 0, len = value.length; i < len; i++) {
      const [input, isSelected] = await this.getCheckbox(value[i]);
      if (input == null) {
        // If the input is not found, then the provided value does not exist in the
        // oj-checkboxset element
        return Promise.reject(
          new error.InvalidArgumentError(`OjCheckboxset: ${value[i]} is not a valid value.`)
        );
      }

      // Make selection, if it is not already selected
      if (!isSelected) {
        await input.sendKeys(Key.SPACE);
      }
    }
  }

  /**
   * Checks if the Checkboxset is in interactable state
   */
  private async isInteractable(): Promise<boolean> {
    const isReadonly = await this.getReadonly();
    const isDisabled = await this.getDisabled();
    return !(isReadonly || isDisabled);
  }

  /**
   * Finds the input element for the provided value if it exists
   *
   * @param value The value for which the checkbox element has to be fetched
   * @returns The corresponding input element if it exists along with its selected state, null otherwise
   */
  private async getCheckbox(value: any): Promise<[WebElement, boolean]> {
    const subId = { subId: 'oj-checkboxset-checkbox', value: value };
    return await this.getDriver().executeScript<[WebElement, boolean]>(
      `
      var element = arguments[0];
      var subId = arguments[1];

      var checkbox = element.getNodeBySubId(subId);

      if (checkbox == null) {
        return [null, false];
      }

      return [checkbox, checkbox.classList.contains('oj-selected')];
    `,
      this,
      subId
    );
  }

  /**
   * Finds the selected checkboxes and filter it using the excludeList
   *
   * @param excludeList List of values to be excluded from the current selected list
   * @returns The array of filtered selected checkboxes
   */
  private async getCheckboxesToUnselect(excludeList: any[] = []): Promise<WebElement[]> {
    return await this.getDriver().executeScript<WebElement[]>(
      `
      var element = arguments[0];
      var excludeList = arguments[1].slice();
      
      var oj = require('ojs/ojcore-base');

      // get the selected checkboxes
      var selected = element.querySelectorAll("input.oj-selected:enabled");

      // If either selected checkboxes or exclude list is empty,
      // return the selected array
      if (selected.length === 0 || excludeList.length === 0) {
        return Array.from(selected);
      }

      var filteredSelectedCheckboxes = [];
      // Filter the selected array using the excludeList
      selected.forEach(function (node) {
        var subId = element.getSubIdByNode(node);
        var shouldExclude = false;

        // Check if current selected item is in the exclude list
        for (var i = 0; i < excludeList.length; i++) {
          // If a match is found, set the flag and break the loop
          if (oj.Object.compareValues(subId.value, excludeList[i])) {
            shouldExclude = true;
            break;
          }
        }

        if (!shouldExclude) {
          // If no match is found, push the item to the return array and continue
          filteredSelectedCheckboxes.push(node);
        }
      });

      return filteredSelectedCheckboxes;
    `,
      this,
      excludeList
    );
  }
}
