"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.fromExpression = fromExpression;
exports.safeElementFromExpression = safeElementFromExpression;
exports.toExpression = toExpression;
var _get_type = require("./get_type");
var _grammar = require("./grammar");
/*
 * SPDX-License-Identifier: Apache-2.0
 *
 * The OpenSearch Contributors require contributions made to
 * this file be licensed under the Apache-2.0 license or a
 * compatible open source license.
 *
 * Any modifications Copyright OpenSearch Contributors. See
 * GitHub history for details.
 */

/*
 * Licensed to Elasticsearch B.V. under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch B.V. licenses this file to you under
 * the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

function getArgumentString(arg, argKey) {
  let level = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
  const type = (0, _get_type.getType)(arg);
  function maybeArgKey(argKey, argString) {
    return argKey == null || argKey === '_' ? argString : "".concat(argKey, "=").concat(argString);
  }
  if (type === 'string') {
    // correctly (re)escape double quotes
    const escapedArg = arg.replace(/[\\"]/g, '\\$&'); // $& means the whole matched string
    return maybeArgKey(argKey, "\"".concat(escapedArg, "\""));
  }
  if (type === 'boolean' || type === 'null' || type === 'number') {
    // use values directly
    return maybeArgKey(argKey, "".concat(arg));
  }
  if (type === 'expression') {
    // build subexpressions
    return maybeArgKey(argKey, "{".concat(getExpression(arg.chain, level + 1), "}"));
  }

  // unknown type, throw with type value
  throw new Error("Invalid argument type in AST: ".concat(type));
}
function getExpressionArgs(block) {
  let level = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
  const args = block.arguments;
  const hasValidArgs = typeof args === 'object' && args != null && !Array.isArray(args);
  if (!hasValidArgs) throw new Error('Arguments can only be an object');
  const argKeys = Object.keys(args);
  const MAX_LINE_LENGTH = 80; // length before wrapping arguments
  return argKeys.map(argKey => args[argKey].reduce((acc, arg) => {
    const argString = getArgumentString(arg, argKey, level);
    const lineLength = acc.split('\n').pop().length;

    // if arg values are too long, move it to the next line
    if (level === 0 && lineLength + argString.length > MAX_LINE_LENGTH) {
      return "".concat(acc, "\n  ").concat(argString);
    }

    // append arg values to existing arg values
    if (lineLength > 0) return "".concat(acc, " ").concat(argString);

    // start the accumulator with the first arg value
    return argString;
  }, ''));
}
function fnWithArgs(fnName, args) {
  if (!args || args.length === 0) return fnName;
  return "".concat(fnName, " ").concat(args.join(' '));
}
function getExpression(chain) {
  let level = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
  if (!chain) throw new Error('Expressions must contain a chain');

  // break new functions onto new lines if we're not in a nested/sub-expression
  const separator = level > 0 ? ' | ' : '\n| ';
  return chain.map(chainObj => {
    const type = (0, _get_type.getType)(chainObj);
    if (type === 'function') {
      const fn = chainObj.function;
      if (!fn || fn.length === 0) throw new Error('Functions must have a function name');
      const expArgs = getExpressionArgs(chainObj, level);
      return fnWithArgs(fn, expArgs);
    }
  }, []).join(separator);
}
function fromExpression(expression) {
  let type = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'expression';
  try {
    return (0, _grammar.parse)(String(expression), {
      startRule: type
    });
  } catch (e) {
    throw new Error("Unable to parse expression: ".concat(e.message));
  }
}

// TODO: OMG This is so bad, we need to talk about the right way to handle bad expressions since some are element based and others not
function safeElementFromExpression(expression) {
  try {
    return fromExpression(expression);
  } catch (e) {
    return fromExpression("markdown\n\"## Crud.\nCanvas could not parse this element's expression. I am so sorry this error isn't more useful. I promise it will be soon.\n\nThanks for understanding,\n#### Management\n\"");
  }
}

// TODO: Respect the user's existing formatting
function toExpression(astObj) {
  let type = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'expression';
  if (type === 'argument') return getArgumentString(astObj);
  const validType = ['expression', 'function'].includes((0, _get_type.getType)(astObj));
  if (!validType) throw new Error('Expression must be an expression or argument function');
  if ((0, _get_type.getType)(astObj) === 'expression') {
    if (!Array.isArray(astObj.chain)) throw new Error('Expressions must contain a chain');
    return getExpression(astObj.chain);
  }
  const expArgs = getExpressionArgs(astObj);
  return fnWithArgs(astObj.function, expArgs);
}