// declare var moment: any;
import moment from 'moment-timezone';
// declare module 'hot-formula-parser' {
//   function Parser(): any;
//   export = Parser;
// }
import Emitter from './Emitter';
import Helpers, { ICellRange, IHelpers } from './Helpers';
import { IPageContentCell, IPageContent } from './Page/Content';

import { Parser } from 'hot-formula-parser';
import { ITask, ITaskActionSet, ITaskActionSetAction } from './Actions/Tasks';
import { IActionsButton } from './Actions/Buttons';
import { IActionsStyle } from './Actions/Styles';
import { IGridCell } from './Grid3/GridCell';

// interface IData {
//   value: any;
//   formatted_value?: any;
//   reset_value?: any;
//   reset_time?: any;
//   style: any;
// }

// interface IAction {
//   cell?: string;
//   offset?: string;
//   data: IData;
// }

// interface ITask {
//   condition?: string;
//   cell?: string;
//   operator?: string;
//   value?: string;
//   actions: IAction[];
// }

// interface ITaskGroup {
//   ref?: string;
//   range?: string;
// }

interface IFunctionsCellUpdate {
  row: number;
  col: number;
  data: IPageContentCell;
}

export interface IFunctionsVar {
  id?: any;
  username?: string;
  lastname?: string;
  firstname?: string;
  email?: string;
  pageid?: string;
  pagename?: string;
  pagedescription?: string;
  folderid?: string;
  foldername?: string;
  folderdescription?: string;
  [key: string]: any;
}

export interface IFunctionsTable {
  values: any;
  headers: string[];
}

// interface IStyleCondition {
//   exp?: string;
//   operator?: string;
//   cell?: string;
//   value?: string;
//   style: any;
//   columns: any[];
// }

// interface IStyle {
//   ref?: string;
//   range?: string;
//   conditions: IStyleCondition[];
// }

// interface IButton {
//   name: string;
//   range: string;
// }

export interface IFunctions {
  cell: IPageContentCell;
  content: IPageContent;
  vars: IFunctionsVar;
  selectedRows: number[];
  updateVars(vars: IFunctionsVar): void;
  updateContentDelta(content: IPageContent) : void;
  parse(str: string, joinWith?: string): string;
  getCellReference(str: string): IPageContentCell;
  setCellReference(cell: IPageContentCell): void;
  isButtonWithinTaskRange(buttonCell: IGridCell, taskGroup: ITask): boolean;
  isTaskValid(task: ITaskActionSet): boolean;
  getCellUpdate(action: ITaskActionSetAction): IFunctionsCellUpdate | null;
  updateCell(action: ITaskActionSetAction): IFunctionsCellUpdate | null;
  getCellFormatting(styles: IActionsStyle[], buttons?: IActionsButton[], contentDiff?: IPageContent): IActionsStyle[];
  setCellFormatting(styles: IActionsStyle[], buttons?: IActionsButton[], contentDiff?: IPageContent): IActionsStyle[];
  toUppercaseFunctions(expression: string): string;
  getTable(config: IFunctionsTable): string
}

export class Functions extends Emitter implements IFunctions {
  public selectedRows: number[] = [];
  public cell!: IPageContentCell;
  private helpers: IHelpers;
  private parser: any;
  constructor(public content: IPageContent = [], public vars: IFunctionsVar = {}) {
    super();
    this.helpers = new Helpers();
    this.parser = new Parser();
    this.parser.on('callCellValue', (cellCoord: any, done: any) => {
      if (this.content[cellCoord.row.index] && this.content[cellCoord.row.index][cellCoord.column.index]) {
        done(this.content[cellCoord.row.index][cellCoord.column.index]);
      } else {
        done('#ERROR!');
      }
    });
    this.parser.on('callRangeValue', (startCellCoord: any, endCellCoord: any, done: any) => {
      let fragment: any = [];
      for (let row = startCellCoord.row.index; row <= endCellCoord.row.index; row++) {
        let rowData = this.content[row];
        if (!rowData) continue;
        let colFragment = [];
        for (let col = startCellCoord.column.index; col <= endCellCoord.column.index; col++) {
          if (!rowData[col]) continue;
          colFragment.push(this.getCellValue(rowData[col]));
        }
        if (colFragment.length) fragment.push(colFragment);
      }
      if (fragment.length) {
        done(fragment);
      } else {
        done('#ERROR!');
      }
    });
    this.parser.setFunction('IPPVAR', (params: any) => {
      if (!params[0]) {
        return '#ERROR!';
      }
      let p = `${params[0]}`.toLowerCase();
      if (!this.vars[p]) {
        return '#ERROR!';
      }
      return this.vars[p];
    });
    this.parser.setFunction('MAX', (params: any) => {
      if (params[0] === '#ERROR!' || !params[0].length || !params[0][0].length) {
        return '#ERROR!';
      }
      let max = params[0].flat().filter((n: any) => (`${n}` !== '' && n !== null) ).reduce((x: any, y: any) => y > x ? y : x);
      let nums = !isNaN(params[0][0]);
      let cellValue = this.getCellValue(this.cell, true);
      if (nums) cellValue = this.helpers.convertToNumber(cellValue);
      if (`${cellValue}` === '' || typeof cellValue !== typeof max) return false;
      return (cellValue >= max);
    });
    this.parser.setFunction('MIN', (params: any) => {
      if (params[0] === '#ERROR!' || !params[0].length || !params[0][0].length) {
        return '#ERROR!';
      }
      let max = params[0].flat().filter((n: any) => (`${n}` !== '' && n !== null) ).reduce((x: any, y: any) => y < x ? y : x);
      let nums = !isNaN(params[0][0]);
      let cellValue = this.getCellValue(this.cell, true);
      if (nums) cellValue = this.helpers.convertToNumber(cellValue);
      if (`${cellValue}` === '' || typeof cellValue !== typeof max) return false;
      return (cellValue <= max);
    });
    this.parser.setFunction('NUMBERVALUE', (params: any) => {
      if (params[0] === '#ERROR!') {
        return '#ERROR!';
      }
      // check if multiple values
      if (typeof params[0] === 'object') {
        return params[0].map((row: any) => {
          return row.map((n: any) => this.helpers.convertToNumber(n))
        });
      } else {
        // let cellValue = this.getCellValue(this.cell, true);
        return this.helpers.convertToNumber(params[0]) || '#VALUE!';
      }
    });
    this.parser.setFunction('CHECKED', (params: any) => {
      if (params[0] === '#ERROR!') {
        return '#ERROR!';
      }
      if (!this.cell.index) return false;
      return this.selectedRows.includes(this.cell.index.row);
    });
    this.parser.setFunction('MOD', (params: any) => {
      if (!this.cell || params[0] === undefined || params[1] === undefined || isNaN(params[0]) || isNaN(params[1])) {
        return '#ERROR!';
      }
      let num: number = parseFloat(params[0]);
      let div: number = parseFloat(params[1]);
      return num % div;
    });
    this.parser.setFunction('ROW', (params: any) => {
      if (params[0] === '#ERROR!') {
        return '#ERROR!';
      }
      if (!this.cell.index) return -1;
      return this.cell.index.row;
    });
    this.parser.setFunction('VALUE', (params: any) => {
      if (params[0] === '#ERROR!') {
        return '#ERROR!';
      }
      if (!params[0]) {
        return this.cell ? this.getCellValue(this.cell) : '#ERROR!';
      }
      return this.getCellValue(params[0]);
    });
    this.parser.setFunction('RAWVALUE', (params: any) => {
      if (params[0] === '#ERROR!') {
        return '#ERROR!';
      }
      if (!params[0]) {
        return this.cell ? this.getCellValue(this.cell, true) : '#ERROR!';
      }
      return this.getCellValue(params[0], true);
    });
    this.parser.setFunction('OFFSET', (params: any) => {
      if (!this.cell || params[0] === undefined || params[1] === undefined || isNaN(params[0]) || isNaN(params[1])) {
        return '#ERROR!';
      }
      let row: number = parseFloat(params[0]);
      let col: number = parseFloat(params[1]);
      if (this.content[this.cell.index!.row + row] && this.content[this.cell.index!.row + row][this.cell.index!.col + col]) {
        return this.content[this.cell.index!.row + row][this.cell.index!.col + col];
      }
      return '#ERROR!';
    });
    this.parser.setFunction('DATETIME', (params: any) => {
      const time = moment
        .tz(params[1] || 'UTC')
        .add(params[2] || 0, params[3] || 'minutes')
        .format(params[0]);
      return time;
    });
    this.parser.setFunction('SYTAG', (params: any) => {
      if (params[0] === undefined) {
        return '#ERROR!';
      }
      let message: string = '';
      let arg: string = params[1];
      switch (params[0]) {
        case 'mention':
          // arg = arg.replace(/[^a-z0-9]/gi, '');
          if (this.helpers.isValidEmail(arg)) {
            message = `<mention email="${arg}" strict="false"/>`;
          } else {
            message = '#ERROR!';
          }
          break;
        case 'cash':
          if (!arg) {
            // arg = `ipp.${this.content.data.domain_name}.${this.content.data.name}`;
          }
          message = `<cash tag="${this.helpers.safeTitle(arg)}" />`;
          break;
        case 'hash':
          message = `<hash tag="${this.helpers.safeTitle(arg)}" />`;
          break;
        case 'chime':
          message = `<chime />`;
          break;
        case 'emoji':
          message = `<emoji shortcode="${this.helpers.safeTitle(arg)}" />`;
          break;
        default:
          message = '#ERROR!';
          break;
      }
      return message;
    });
    this.parser.setFunction('TABLE', (params: any) => {
      if (!this.cell || params[0] === undefined || !(params[0] instanceof Array)) {
        return '#ERROR!';
      }
      let headerRow: number = 0;
      if (this.helpers.isNumber(params[1]) && this.content[params[1] - 1]) {
        headerRow = params[1] - 1;
      }
      let columns: number[] = params[0];
      let arr: any = [];
      let row: any = [];
      columns.forEach(col => {
        let cell: IPageContentCell = this.content[headerRow][col];
        row.push(this.getCellValue(cell));
      });
      arr.push(row);
      row = [];
      columns.forEach(col => {
        let cell: IPageContentCell = this.content[this.cell.index!.row][col];
        row.push(this.getCellValue(cell));
      });
      arr.push(row);
      return this.helpers.tableFromArray(arr);
      // TABLE([3,4,5])
      // header row
      // columns
    });
  }
  public updateVars(vars: IFunctionsVar): void {
    this.vars = vars;
  }
  public updateContentDelta(content: IPageContent = []) : void {
    if (!content || !content.length) return;
    for (let rowIndex = 0; rowIndex < content.length; rowIndex++) {
      let row = content[rowIndex];
      if (!row) continue;
      for (let colIndex = 0; colIndex < row.length; colIndex++) {
        let col = row[colIndex];
        if (!col || !this.content[rowIndex] || !this.content[rowIndex][colIndex]) continue;
        this.content[rowIndex][colIndex] = col;
      }
    }
  }
  public parse(str: string, joinWith: string = '\n'): string {
    let results: string[] = [];
    let lines = `${str}`.split(/\n/);
    lines.forEach(line => {
      line = line.trim();
      if (!line || line[0] !== '=') {
        results.push(line);
        return;
      }
      let result = this.parser.parse(this.toUppercaseFunctions(line.replace('=', '')));
      results.push(result.result || result.error);
    });
    return results.join(joinWith);
  }
  public getCellReference(str: string): IPageContentCell {
    let result = this.parser.parse(str.trim());
    return result.result || null;
  }
  public setCellReference(cell: IPageContentCell): void {
    this.cell = cell;
  }
  public isButtonWithinTaskRange(buttonCell: IGridCell, taskGroup: ITask): boolean {
    if (buttonCell.button && buttonCell.button.name !== taskGroup.ref) {
      return false;
    } else if (!buttonCell.button && !taskGroup.range) {
      return false;
    }
    try {
      const range: string = taskGroup.ref ? buttonCell.button!.range : taskGroup.range;
      let cellRange: ICellRange = this.helpers.cellRange(range, this.content.length, this.content[0].length);
      // const [taskFrom, taskTo] = this.helpers.getRefIndex(range); // range.split(':').map(str => this.helpers.getRefIndex(str));
      // let endRow: number = taskTo[0] < 0 ? this.page.Content.current.length : taskTo[0];
      //   const [buttonsFrom, buttonsTo] = buttonRange.split(':').map(str => this.helpers.getRefIndex(str));
      let endRow: number = cellRange.to.row < 0 ? this.content.length : cellRange.to.row;
      if (buttonCell.position.row < cellRange.from.row || buttonCell.position.row > endRow) return false;
      if (buttonCell.position.col < cellRange.from.col || buttonCell.position.col > cellRange.to.col) return false;
      return true;
    } catch (e) {
      return false;
    }
  }
  public isTaskValid(task: ITaskActionSet): boolean {
    if (!task.condition) return true;
    try {
      return this.parse(task.condition) === 'true';
    } catch (e) {
      return false;
    }
  }
  public getCellUpdate(action: ITaskActionSetAction): IFunctionsCellUpdate | null {
    return this.updateCell(action);
  }
  public updateCell(action: ITaskActionSetAction): IFunctionsCellUpdate | null {
    try {
      if (!action.cell) return null;
      const cell: IPageContentCell = this.getCellReference(action.cell.replace('=', '').toUpperCase());
      if (!cell) return null;

      let formatted_value: string | number = this.parse(action.data.formatted_value);
      let value: string | number = this.parse(action.data.value);

      if (this.helpers.isNumber(formatted_value)) {
        formatted_value = parseFloat(formatted_value);
      }

      if (this.helpers.isNumber(value)) {
        value = parseFloat(value);
      }

      const update: IFunctionsCellUpdate = {
        row: cell.index!.row,
        col: cell.index!.col,
        data: {
          formatted_value,
          value,
          style: {},
        },
      };
      // this.content.Content.updateCell(update.row, update.col, update.data);

      return update;
    } catch (e) {
      return null;
    }
  }
  public getCellFormatting(styles: IActionsStyle[], buttons: IActionsButton[] = [], contentDiff?: IPageContent): IActionsStyle[] {
    return this.setCellFormatting(styles, buttons, contentDiff);
  }
  public setCellFormatting(styles: IActionsStyle[], buttons: IActionsButton[] = [], contentDiff?: IPageContent): IActionsStyle[] {
    if (!styles || !styles.length) {
      return [];
    }
    // let dirty: boolean = false;
    let cellStyles: any[] = [];
    styles.forEach(style => {
      // check for a button ref
      let range: string = style.range || '';
      if (!style.range) {
        buttons.forEach(button => {
          if (button.name === style.ref) {
            range = button.range;
          }
        });
      }
      if (!range) {
        return;
      }
      // let content: IPageContentProvider = contentDiff || this.page.Content.current;
      let cellRange: ICellRange = this.helpers.cellRange(range, this.content.length, this.content[0].length);
      // const from = cellRange.from;
      // const to = cellRange.to;
      // const [from, to] = range.split(':').map(str => this.helpers.getRefIndex(str));
      let endRow: number = cellRange.to.row < 0 ? this.content.length : cellRange.to.row;
      for (let colIndex = cellRange.from.col; colIndex <= cellRange.to.col; colIndex++) {
        for (let rowIndex = 0; rowIndex < this.content.length; rowIndex++) {
          if (rowIndex < cellRange.from.row || rowIndex > endRow) continue;
          let cell: IPageContentCell =
            contentDiff && contentDiff[rowIndex] && contentDiff[rowIndex][colIndex] ? contentDiff[rowIndex][colIndex] : this.content[rowIndex][colIndex];
          // cell.formatting = {};
          this.setCellReference(cell);
          style.conditions.forEach((condition: any) => {
            if (this.parse(condition.exp) !== 'true') return;
            // cell.formatting = condition.style;

            if (condition.columns.indexOf(-1) > -1) {
              for (let colConditionIndex = 0; colConditionIndex < this.content[rowIndex].length; colConditionIndex++) {
                if (!cellStyles[rowIndex]) {
                  cellStyles[rowIndex] = [];
                }
                cellStyles[rowIndex][colConditionIndex] = condition.style;
              }
            } else {
              if (!cellStyles[rowIndex]) {
                cellStyles[rowIndex] = [];
              }
              cellStyles[rowIndex][colIndex] = condition.style;
            }
          });
        }
      }
    });
    return cellStyles;
  }
  public getTable(config: IFunctionsTable): string {
    // config.headers
    // config.values
    let arr: any = [];
    arr.push(config.headers);
    config.values.forEach((row: any) => {
      let cells: any = [];
      row.forEach((index: any) => {
        if (typeof index === 'string') {
          cells.push(index);
          return;
        }
        let cell: IPageContentCell = this.content[ this.cell.index!.row + index[0] ][ this.cell.index!.col + index[1] ];
        cells.push(this.getCellValue(cell));
      });
      arr.push(cells);
    });
    return this.helpers.tableFromArray(arr);
  }
  public toUppercaseFunctions(expression: string): string {
    // =value()="three" -> =VALUE()="three"

    const regex = /("(.*?)")/gm;
    let m;
    let replace = [];

    while ((m = regex.exec(expression)) !== null) {
      if (m.index === regex.lastIndex) {
        regex.lastIndex++;
      }
      replace.push(m[1]);
    }

    replace.forEach((str, i) => {
      expression = expression.replace(str, `[${i}]`);
    });

    expression = expression.toUpperCase();

    replace.forEach((str, i) => {
      expression = expression.replace(`[${i}]`, str);
    });

    return expression;
  }
  private getCellValue(cell: IPageContentCell, raw: boolean = false): any {
    let value;
    if (raw) {
      value = cell.value;
    } else {
      value = cell.formatted_value || cell.value;
    }
    return this.helpers.isNumber(value) ? parseFloat(`${value}`) : value;
  }
}
