"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.fromExpression = fromExpression;
exports.safeElementFromExpression = safeElementFromExpression;
exports.toExpression = toExpression;

var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));

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) {
  var level = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
  var 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
    var 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) {
  var level = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
  var args = block.arguments;
  var hasValidArgs = (0, _typeof2.default)(args) === 'object' && args != null && !Array.isArray(args);
  if (!hasValidArgs) throw new Error('Arguments can only be an object');
  var argKeys = Object.keys(args);
  var MAX_LINE_LENGTH = 80; // length before wrapping arguments

  return argKeys.map(function (argKey) {
    return args[argKey].reduce(function (acc, arg) {
      var argString = getArgumentString(arg, argKey, level);
      var 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) {
  var 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

  var separator = level > 0 ? ' | ' : '\n| ';
  return chain.map(function (chainObj) {
    var type = (0, _get_type.getType)(chainObj);

    if (type === 'function') {
      var fn = chainObj.function;
      if (!fn || fn.length === 0) throw new Error('Functions must have a function name');
      var expArgs = getExpressionArgs(chainObj, level);
      return fnWithArgs(fn, expArgs);
    }
  }, []).join(separator);
}

function fromExpression(expression) {
  var 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) {
  var type = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'expression';
  if (type === 'argument') return getArgumentString(astObj);
  var 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);
  }

  var expArgs = getExpressionArgs(astObj);
  return fnWithArgs(astObj.function, expArgs);
}