import { By, Key, WebElement } from 'selenium-webdriver';
import { OjDataGridBase } from './OjDataGridBase';
import { slotProxy, SlotProxy } from '../../';

/**
 * The component WebElement for [oj-data-grid](../../jsdocs/oj.ojDataGrid.html).
 * Do not instantiate this class directly, instead, use
 * [ojDataGrid](../functions/elements.ojDataGrid-1.html).
 */
export class OjDataGrid extends OjDataGridBase {
  // Put overrides here

  /**
   * This method exists to get content that is intended to interact with. This is not a method to verify data or that template rendering is correct.
   * For those cases unit test the data, template, style function etc.
   *
   * @param {cellSubId} subId information about the cell to get contents of
   * @param {By} by The lookup to pass into findElements
   * @returns {Promise<Array.<WebElement>|null>} If no cell is found matching the id, returns null.
   *          Otherwise: On Zero Match return an empty list. On One Match returns list of one WebElement only. On One+ Match returns list with all matching instance.
   *          If cell renderer/template/slot is not specified the promise will be rejected with an error message.
   * @deprecated {since:"10.0.0", description:"This api is deprecated. Please use findCell"}
   */
  async getCellContents(itemLocator: {
    key: {
      rowKey?: any;
      columnKey?: any;
      rowIndex?: number | null;
      columnIndex?: number | null;
    };
  }): Promise<WebElement | null> {
    let datagridCell = null;
    let key = itemLocator.key;
    let rowIndex = key.rowIndex;
    let columnIndex = key.columnIndex;
    let rowKey = key.rowKey;
    let columnKey = key.columnKey;
    let cellMatch = null;

    let cells = await this.findElements(By.css('.oj-datagrid-cell'));
    for (let i = 0; i < cells.length; i++) {
      let cell = cells[i];

      cellMatch = await this.getDriver().executeScript(
        [
          'var cellContext = arguments[0]["data-oj-cellContext"];',
          'var cellRowIndex  = cellContext.indexes.row;',
          'var cellColumnIndex  = cellContext.indexes.column;',
          'var cellRowKey  = cellContext.keys.row;',
          'var cellColumnKey = cellContext.keys.column;',
          'if ((cellRowIndex === arguments[1] || cellRowKey === arguments[3]) &&',
          '(cellColumnIndex === arguments[2] || cellColumnKey === arguments[4])) {',
          '  return true;',
          '}',
          'return false;'
        ].join('\n'),
        cell,
        rowIndex,
        columnIndex,
        rowKey,
        columnKey
      );

      if (cellMatch) {
        datagridCell = cell;
      }
    }
    return datagridCell;
  }
  /**
   * Retrieve a SlotProxy which represents a single datagrid cell.
   * @param itemLocator.rowKey The rowKey within the Collection's dataset associated with the item. (Optional if rowIndex is provided)
   * @param itemLocator.columnKey The columnKey within the Collection's dataset associated with the item. (Optional if columnIndex is provided).
   * @param itemLocator.rowIndex The rowIndex within the Collection's dataset associated with the item. (Optional if rowKey is provided)
   * @param itemLocator.columnIndex The columnIndex within the Collection's dataset associated with the item. (Optional if columnKey is provided).
   * @override
   */
  async findCell<K = any>(itemLocator: {
    rowKey?: K;
    columnKey?: K;
    rowIndex?: number | null;
    columnIndex?: number | null;
  }): Promise<SlotProxy> {
    await this.whenReady();
    const datagridCell = await this.getDriver().executeScript<WebElement>(
      `
      const element = arguments[0];
      const rowIndex = arguments[1];
      const columnIndex = arguments[2];
      const rowKey = arguments[3];
      const columnKey = arguments[4];
      const cells = element.querySelectorAll('.oj-datagrid-cell');
      for (let i = 0; i < cells.length; i++) {
        const cellContext = cells[i]["data-oj-cellContext"];
        const cellRowIndex  = cellContext.indexes.row;
        const cellColumnIndex  = cellContext.indexes.column;
        const cellRowKey  = cellContext.keys.row;
        const cellColumnKey = cellContext.keys.column;
        if ((cellRowIndex === rowIndex || cellRowKey === rowKey) &&
        (cellColumnIndex === columnIndex || cellColumnKey === columnKey)) {
          return cells[i];
        }
      }`,
      this,
      itemLocator.rowIndex,
      itemLocator.columnIndex,
      itemLocator.rowKey,
      itemLocator.columnKey
    );
    if (datagridCell) {
      return slotProxy(datagridCell, this);
    } else {
      throw Error(`No corresponding cell found for the itemLocator`);
    }
  }
  /**
   * Forces grid to ender editMode on a particular cell.
   * @param itemLocator.rowKey The rowKey within the Collection's dataset associated with the item. (Optional if rowIndex is provided)
   * @param itemLocator.columnKey The columnKey within the Collection's dataset associated with the item. (Optional if columnIndex is provided).
   * @param itemLocator.rowIndex The rowIndex within the Collection's dataset associated with the item. (Optional if rowKey is provided)
   * @param itemLocator.columnIndex The columnIndex within the Collection's dataset associated with the item. (Optional if columnKey is provided)
   * @override
   */
  async enterEditMode<K = any>(itemLocator: {
    rowKey?: K;
    columnKey?: K;
    rowIndex?: number;
    columnIndex?: number;
  }) {
    await this.whenReady();
    const datagridCell = await this.getDriver().executeScript<WebElement>(
      `
      const element = arguments[0];
      const rowIndex = arguments[1];
      const columnIndex = arguments[2];
      const rowKey = arguments[3];
      const columnKey = arguments[4];
      const cells = element.querySelectorAll('.oj-datagrid-cell');
      for (let i = 0; i < cells.length; i++) {
        const cellContext = cells[i]["data-oj-cellContext"];
        const cellRowIndex  = cellContext.indexes.row;
        const cellColumnIndex  = cellContext.indexes.column;
        const cellRowKey  = cellContext.keys.row;
        const cellColumnKey = cellContext.keys.column;
        if ((cellRowIndex === rowIndex || cellRowKey === rowKey) &&
        (cellColumnIndex === columnIndex || cellColumnKey === columnKey)) {
          return cells[i];
        }
      }`,
      this,
      itemLocator.rowIndex,
      itemLocator.columnIndex,
      itemLocator.rowKey,
      itemLocator.columnKey
    );
    if (datagridCell) {
      const actions = this.getDriver().actions({ bridge: true });
      await actions.doubleClick(datagridCell).perform();
      await this.whenBusyContextReady();
    } else {
      throw Error(`No corresponding cell found for the itemLocator`);
    }
  }
  /**
   * Forces grid to exit editMode.
   * @param exitParams.cancel If set to true datagrid will cancel edit.
   * @override
   */
  async exitEditMode(exitParams?: { cancel: boolean }) {
    await this.whenReady();
    const actions = this.getDriver().actions({ bridge: true });
    if (!exitParams || !exitParams.cancel) {
      await actions.sendKeys(Key.ENTER).perform();
    } else {
      await actions.sendKeys(Key.ESCAPE).perform();
    }
    await this.whenBusyContextReady();
  }
  /**
   * This method exists to get content that is intended to interact with. This is not a method to verify data or that template rendering is correct.
   * For those cases unit test the data, template, style function etc.
   *
   * @param {headerSubId} subId information about the header to get contents of, if level not specified will default to 0
   * @param {By} by The lookup to pass into findElements
   * @returns {Promise<Array.<WebElement>|null>} If no header is found matching the id, returns null.
   *          Otherwise: On Zero Match return an empty list. On One Match returns list of one WebElement only. On One+ Match returns list with all matching instance.
   *          If header renderer/template/slot is not specified the promise will be rejected with an error message.
   * @deprecated {since:"10.0.0", description:"This api is deprecated. Please use findHeader"}
   */
  async getHeaderContents(
    subId: {
      axis: 'row' | 'rowEnd' | 'column' | 'columnEnd';
      key?: any;
      index?: number;
      level?: number;
    },
    by: By
  ): Promise<Array<WebElement> | null> {
    await this.whenReady();
    let axis = subId.axis;
    let key = subId.key;
    let index = subId.index;
    let level = subId.level != null ? subId.level : 0;

    let headerProperty = await this.getHeader();

    // getProperty should change all members that are functions to string representation not null
    if (headerProperty[axis].renderer === null) {
      return Promise.reject(
        new Error('No renderer to inspect content of, please see the method documentation.')
      );
    }

    let headerMatch = null;
    let header = null;

    let headers = await this.findElements(
      By.css('.oj-datagrid-' + axis.replace('End', '-end') + '-header-cell')
    );
    for (let i = 0; i < headers.length; i++) {
      header = headers[i];

      headerMatch = await this.getDriver().executeScript(
        [
          'var headerContext = arguments[0]["data-oj-cellContext"];',
          'var headerIndex = headerContext.index;',
          'var headerKey = headerContext.key;',
          'var headerLevel = headerContext.level;',
          'if (headerKey === arguments[1] || ',
          '(headerIndex === arguments[2] && headerLevel === arguments[3])) {',
          '  return true;',
          '}',
          'return false;'
        ].join('\n'),
        header,
        key,
        index,
        level
      );

      if (headerMatch) {
        return header.findElements(by);
      }
    }

    return null;
  }
  /**
   * Retrieve a SlotProxy which represents a single datagrid header.
   * @param itemLocator.axis The axis where the header is located.
   * @param itemLocator.key The key within the Collection's dataset associated with the header.
   * @param itemLocator.index The index within the Collection's dataset associated with the header.
   * @param itemLocator.level The level of the header requested.
   * @override
   */
  async findHeader<K = any>(itemLocator: {
    axis: 'row' | 'rowEnd' | 'column' | 'columnEnd';
    key?: K;
    index?: number;
    level?: number;
  }): Promise<SlotProxy> {
    await this.whenReady();
    const header = await this.getDriver().executeScript<WebElement>(
      `
      const element = arguments[0];
      const axis = arguments[1];
      const key = arguments[2];
      const index = arguments[3];
      const level = arguments[4];
      const headers = element.querySelectorAll('.oj-datagrid-' + axis.replace('End', '-end') + '-header-cell');
      for (let i = 0; i < headers.length; i++) {
        const headerContext = headers[i]["data-oj-cellContext"];
        const headerIndex = headerContext.index;
        const headerKey = headerContext.key;
        const headerLevel = headerContext.level;
        if (headerKey === key || (headerIndex === index && headerLevel === level)) {
          return headers[i];
        }
      } `,
      this,
      itemLocator.axis,
      itemLocator.key,
      itemLocator.index,
      itemLocator.level != null ? itemLocator.level : 0
    );
    if (header) {
      return slotProxy(header, this);
    } else {
      throw Error(`No corresponding header found for the itemLocator`);
    }
  }

  /**
   * This method exists to get content that is intended to interact with. This is not a method to verify data or that template rendering is correct.
   * For those cases unit test the data, template, style function etc.
   *
   * @param {labelSubId} subId information about the label to get contents of, if level not specified will default to 0
   * @param {By} by The lookup to pass into findElements
   * @returns {Promise<Array.<WebElement>|null>} If no label is found matching the id, returns null.
   *          Otherwise: On Zero Match return an empty list. On One Match returns list of one WebElement only. On One+ Match returns list with all matching instance.
   *          If label renderer/template/slot is not specified the promise will be rejected with an error message.
   * @deprecated {since:"10.0.0", description:"This api is deprecated. Please use findLabel"}
   */
  async getLabelContents(
    subId: {
      axis: 'row' | 'rowEnd' | 'column' | 'columnEnd';
      level: number;
    },
    by: By
  ): Promise<Array<WebElement> | null> {
    await this.whenReady();
    let axis = subId.axis;
    let level = subId.level != null ? subId.level : 0;

    let headerProperty = await this.getHeader();

    // getProperty should change all members that are functions to string representation not null
    if (headerProperty[axis].label.renderer === null) {
      return Promise.reject(
        new Error('No renderer to inspect content of, please see the method documentation.')
      );
    }

    let labelMatch = null;
    let label = null;

    let labels = await this.findElements(
      By.css('.oj-datagrid-' + axis.replace('End', '-end') + '-label')
    );
    for (let i = 0; i < labels.length; i++) {
      label = labels[i];

      labelMatch = await this.getDriver().executeScript(
        [
          'var labelContext = arguments[0]["data-oj-cellContext"];',
          'var labelLevel = labelContext.level;',
          'if (labelLevel === arguments[1]) {',
          '  return true;',
          '}',
          'return false;'
        ].join('\n'),
        label,
        level
      );

      if (labelMatch) {
        return label.findElements(by);
      }
    }

    return null;
  }
  /**
   * Retrieve a SlotProxy which represents a single datagrid label.
   * @param itemLocator.axis The axis where the label is located.
   * @param itemLocator.level The level of the label requested.
   * @override
   */
  async findLabel(itemLocator: {
    axis: 'row' | 'rowEnd' | 'column' | 'columnEnd';
    level: number;
  }): Promise<SlotProxy> {
    await this.whenReady();
    const headerLabel = await this.getDriver().executeScript<WebElement>(
      `
      const element = arguments[0];
      const axis = arguments[1];
      const level = arguments[2];
      const labels = element.querySelectorAll('.oj-datagrid-' + axis.replace('End', '-end') + '-header-label');
      for (let i = 0; i < labels.length; i++) {  
        const labelContext = labels[i]["data-oj-cellContext"];
        const labelLevel = labelContext.level;
        if (labelLevel === level) {
          return labels[i];
        }
      }`,
      this,
      itemLocator.axis,
      itemLocator.level != null ? itemLocator.level : 0
    );
    if (headerLabel) {
      return slotProxy(headerLabel, this);
    } else {
      throw Error(`No corresponding headerLabel found for the itemLocator`);
    }
  }
}
