import jsep from 'jsep';

import { ColumnType } from './Api';

export const jsepCurlyHook = {
  name: 'curly',
  init(jsep) {
    jsep.hooks.add('gobble-token', function gobbleCurlyLiteral(env) {
      const OCURLY_CODE = 123; // {
      const CCURLY_CODE = 125; // }
      const { context } = env;
      if (
        !jsep.isIdentifierStart(context.code) &&
        context.code === OCURLY_CODE
      ) {
        context.index += 1;
        const nodes = context.gobbleExpressions(CCURLY_CODE);
        if (context.code === CCURLY_CODE) {
          context.index += 1;
          env.node = {
            type: jsep.IDENTIFIER,
            name: nodes.map((node) => node.name).join(' '),
          };
          return env.node;
        } else {
          context.throwError('Unclosed }');
        }
      }
    });
  },
} as jsep.IPlugin;

export async function substituteColumnAliasWithIdInFormula(
  formula,
  columns: ColumnType[]
) {
  const substituteId = async (pt: any) => {
    if (pt.type === 'CallExpression') {
      for (const arg of pt.arguments || []) {
        await substituteId(arg);
      }
    } else if (pt.type === 'Literal') {
      return;
    } else if (pt.type === 'Identifier') {
      const colNameOrId = pt.name;
      const column = columns.find(
        (c) =>
          c.id === colNameOrId ||
          c.column_name === colNameOrId ||
          c.title === colNameOrId
      );
      pt.name = '{' + column.id + '}';
    } else if (pt.type === 'BinaryExpression') {
      await substituteId(pt.left);
      await substituteId(pt.right);
    }
  };
  // register jsep curly hook
  jsep.plugins.register(jsepCurlyHook);
  const parsedFormula = jsep(formula);
  await substituteId(parsedFormula);
  return jsepTreeToFormula(parsedFormula);
}

export function substituteColumnIdWithAliasInFormula(
  formula,
  columns: ColumnType[],
  rawFormula?
) {
  const substituteId = (pt: any, ptRaw?: any) => {
    if (pt.type === 'CallExpression') {
      let i = 0;
      for (const arg of pt.arguments || []) {
        substituteId(arg, ptRaw?.arguments?.[i++]);
      }
    } else if (pt.type === 'Literal') {
      return;
    } else if (pt.type === 'Identifier') {
      const colNameOrId = pt?.name;
      const column = columns.find(
        (c) =>
          c.id === colNameOrId ||
          c.column_name === colNameOrId ||
          c.title === colNameOrId
      );
      pt.name = column?.title || ptRaw?.name || pt?.name;
    } else if (pt.type === 'BinaryExpression') {
      substituteId(pt.left, ptRaw?.left);
      substituteId(pt.right, ptRaw?.right);
    }
  };

  // register jsep curly hook
  jsep.plugins.register(jsepCurlyHook);
  const parsedFormula = jsep(formula);
  const parsedRawFormula = rawFormula && jsep(rawFormula);
  substituteId(parsedFormula, parsedRawFormula);
  return jsepTreeToFormula(parsedFormula);
}

export function jsepTreeToFormula(node) {
  if (node.type === 'BinaryExpression' || node.type === 'LogicalExpression') {
    return (
      '(' +
      jsepTreeToFormula(node.left) +
      ' ' +
      node.operator +
      ' ' +
      jsepTreeToFormula(node.right) +
      ')'
    );
  }

  if (node.type === 'UnaryExpression') {
    return node.operator + jsepTreeToFormula(node.argument);
  }

  if (node.type === 'MemberExpression') {
    return (
      jsepTreeToFormula(node.object) +
      '[' +
      jsepTreeToFormula(node.property) +
      ']'
    );
  }

  if (node.type === 'Identifier') {
    const formulas = [
      'AVG',
      'ADD',
      'DATEADD',
      'WEEKDAY',
      'AND',
      'OR',
      'CONCAT',
      'TRIM',
      'UPPER',
      'LOWER',
      'LEN',
      'MIN',
      'MAX',
      'CEILING',
      'FLOOR',
      'ROUND',
      'MOD',
      'REPEAT',
      'LOG',
      'EXP',
      'POWER',
      'SQRT',
      'SQRT',
      'ABS',
      'NOW',
      'REPLACE',
      'SEARCH',
      'INT',
      'RIGHT',
      'LEFT',
      'SUBSTR',
      'MID',
      'IF',
      'SWITCH',
      'URL',
    ];
    if (!formulas.includes(node.name)) return '{' + node.name + '}';
    return node.name;
  }

  if (node.type === 'Literal') {
    if (typeof node.value === 'string') {
      return '"' + node.value + '"';
    }

    return '' + node.value;
  }

  if (node.type === 'CallExpression') {
    return (
      jsepTreeToFormula(node.callee) +
      '(' +
      node.arguments.map(jsepTreeToFormula).join(', ') +
      ')'
    );
  }

  if (node.type === 'ArrayExpression') {
    return '[' + node.elements.map(jsepTreeToFormula).join(', ') + ']';
  }

  if (node.type === 'Compound') {
    return node.body.map((e) => jsepTreeToFormula(e)).join(' ');
  }

  if (node.type === 'ConditionalExpression') {
    return (
      jsepTreeToFormula(node.test) +
      ' ? ' +
      jsepTreeToFormula(node.consequent) +
      ' : ' +
      jsepTreeToFormula(node.alternate)
    );
  }

  return '';
}
