import SimpleSchema from 'simpl-schema';
import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js';
import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
import {
  parse,
  prettifyParseError,
} from '/imports/parser/parser.js';
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
import resolve, { Context, traverse } from '/imports/parser/resolve.js';

/*
 * Constants are primitive values that can be used elsewhere in computations
 */
let ConstantSchema = new SimpleSchema({
  name: {
    type: String,
    optional: true,
    max: STORAGE_LIMITS.name,
  },
  // The technical, lowercase, single-word name used in formulae
  variableName: {
    type: String,
    regEx: VARIABLE_NAME_REGEX,
    min: 2,
    defaultValue: 'newConstant',
    max: STORAGE_LIMITS.variableName,
  },
  // The input value to be parsed, must return a constant node or an array
  // of constant nodes to be valid
  calculation: {
    type: String,
    optional: true,
    max: STORAGE_LIMITS.calculation,
  },
  errors: {
    type: Array,
    maxCount: STORAGE_LIMITS.errorCount,
    autoValue() {
      let calc = this.field('calculation');
      if (!calc.isSet && this.isModifier) {
        this.unset()
        return;
      }
      let string = calc.value;
      if (!string) return [];
      // Evaluate the calculation with no scope
      let { result, context } = parseString(string);
      // Any existing errors will result in an early failure
      if (context && context.errors.length) return context.errors;
      // Ban variables in constants if necessary
      result && traverse(result, node => {
        if (node.parseType === 'symbol' || node.parseType === 'accessor') {
          context.error('Variables can\'t be used to define a constant');
        }
      });
      return context && context.errors || [];
    }
  },
  'errors.$': {
    type: ErrorSchema,
  },
});

function parseString(string, fn = 'compile') {
  let context = new Context();
  if (!string) {
    return { result: string, context };
  }

  // Parse the string using mathjs
  let node;
  try {
    node = parse(string);
  } catch (e) {
    let message = prettifyParseError(e);
    context.error(message);
    return { context };
  }
  if (!node) return { context };
  let { result } = resolve(fn, node, {/*empty scope*/ }, context);
  return { result, context }
}

const ComputedOnlyConstantSchema = new SimpleSchema({});

export { ConstantSchema, ComputedOnlyConstantSchema };
