"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.buildExpression = buildExpression;
exports.isExpressionAst = isExpressionAst;
exports.isExpressionAstBuilder = isExpressionAstBuilder;
var _build_function = require("./build_function");
var _format = require("./format");
var _parse = require("./parse");
/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License
 * 2.0 and the Server Side Public License, v 1; you may not use this file except
 * in compliance with, at your election, the Elastic License 2.0 or the Server
 * Side Public License, v 1.
 */

/**
 * Type guard that checks whether a given value is an
 * `ExpressionAstExpressionBuilder`. This is useful when working
 * with subexpressions, where you might be retrieving a function
 * argument, and need to know whether it is an expression builder
 * instance which you can perform operations on.
 *
 * @example
 * const arg = myFunction.getArgument('foo');
 * if (isExpressionAstBuilder(foo)) {
 *   foo.toAst();
 * }
 *
 * @param val Value you want to check.
 * @return boolean
 */
function isExpressionAstBuilder(val) {
  return (val === null || val === void 0 ? void 0 : val.type) === 'expression_builder';
}

/** @internal */
function isExpressionAst(val) {
  return (val === null || val === void 0 ? void 0 : val.type) === 'expression';
}
const generateExpressionAst = fns => ({
  type: 'expression',
  chain: fns.map(fn => fn.toAst())
});

/**
 * Makes it easy to progressively build, update, and traverse an
 * expression AST. You can either start with an empty AST, or
 * provide an expression string, AST, or array of expression
 * function builders to use as initial state.
 *
 * @param initialState Optional. An expression string, AST, or array of `ExpressionAstFunctionBuilder[]`.
 * @return `this`
 */
function buildExpression(initialState) {
  const chainToFunctionBuilder = chain => chain.map(fn => (0, _build_function.buildExpressionFunction)(fn.function, fn.arguments));

  // Takes `initialState` and converts it to an array of `ExpressionAstFunctionBuilder`
  const extractFunctionsFromState = state => {
    if (typeof state === 'string') {
      return chainToFunctionBuilder((0, _parse.parse)(state, 'expression').chain);
    } else if (!Array.isArray(state)) {
      // If it isn't an array, it is an `ExpressionAstExpression`
      return chainToFunctionBuilder(state.chain);
    }
    return state;
  };
  const fns = initialState ? extractFunctionsFromState(initialState) : [];
  return {
    type: 'expression_builder',
    functions: fns,
    findFunction(fnName) {
      const foundFns = [];
      return fns.reduce((found, currFn) => {
        Object.values(currFn.arguments).forEach(values => {
          values.forEach(value => {
            if (isExpressionAstBuilder(value)) {
              // `value` is a subexpression, recurse and continue searching
              found = found.concat(value.findFunction(fnName));
            }
          });
        });
        if (currFn.name === fnName) {
          found.push(currFn);
        }
        return found;
      }, foundFns);
    },
    toAst() {
      if (fns.length < 1) {
        throw new Error('Functions have not been added to the expression builder');
      }
      return generateExpressionAst(fns);
    },
    toString() {
      if (fns.length < 1) {
        throw new Error('Functions have not been added to the expression builder');
      }
      return (0, _format.format)(generateExpressionAst(fns), 'expression');
    }
  };
}