function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : String(i); }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
import { type, typeMatcher } from './type.js';
class TypeParser {
  constructor(data) {
    _defineProperty(this, "validDataTypes", ['String', 'Array', 'SimpleObject', 'ComplexObject', 'Primitive']);
    this.data = data;
  }
  validateDataType() {
    const dataType = this.data.dataType;
    if (dataType && !this.validDataTypes.includes(dataType)) {
      throw new RangeError(`dataType was ${dataType}; expected one of ${this.validDataTypes}`);
    }
  }
  validateParseType() {
    const predicate = this.data.predicate;
    if (predicate && !(predicate instanceof RegExp || typeof predicate === 'function')) {
      throw new TypeError(`predicate was ${typeof predicate}; expected RegExp or function`);
    }
  }
  validateTokenList() {
    const tokenList = this.data.tokenList;
    if (tokenList && typeof tokenList !== 'object') {
      throw new TypeError(`tokenList was ${typeof tokenList}; expected object or RegExp`);
    }
  }
  validatePropertyConstraint() {
    const propertyConstraint = this.data.propertyConstraint;
    if (propertyConstraint && typeof propertyConstraint !== 'object') {
      throw new TypeError(`propertyConstraint was ${typeof propertyConstraint}; expected array or object`);
    }
  }
  validateElementConstraint() {
    const elementConstraint = this.data.elementConstraint;
    if (elementConstraint && typeof elementConstraint !== 'string') {
      throw new TypeError(`elementConstraint was ${typeof elementConstraint}; expected string`);
    }
  }
  validateExtends() {
    const extend = this.data.extends;
    if (extend && typeof extend !== 'string') {
      throw new TypeError(`extends was ${typeof extend}; expected string`);
    }
  }
  validate() {
    if (this.data === null || typeof this.data !== 'object') {
      throw new TypeError(`typeParser was ${typeof this.data}; expected object`);
    }
    this.validateDataType();
    this.validateParseType();
    this.validateTokenList();
    this.validatePropertyConstraint();
    this.validateElementConstraint();
    this.validateExtends();
  }
  parseTokenList() {
    let tokenList = this.data.tokenList;
    if (!tokenList) {
      return [];
    } else if (tokenList instanceof RegExp) {
      tokenList = {
        token: tokenList
      };
    }
    const {
      token,
      split = /\s+/,
      trim = true,
      every = true
    } = tokenList;
    const trimInput = input => trim ? input.trim() : input;
    const testTokens = every ? 'every' : 'some';
    const predicate = input => trimInput(input).split(split)[testTokens](part => token.test(part));
    return [predicate];
  }
  parsePropertyConstraint() {
    const constraints = [].concat(this.data.propertyConstraint || []);
    return constraints.map(({
      props,
      match,
      value
    }) => {
      props = [].concat(props);
      switch (match) {
        case 'any':
        case 'some':
          return input => props.some(prop => prop in input && (!value || value(input[prop])));
        case 'none':
          return input => !props.some(prop => prop in input && (!value || value(input[prop])));
        case 'every':
        default:
          return input => props.every(prop => prop in input && (!value || value(input[prop])));
      }
    });
  }
  parseElementConstraint() {
    const constraint = this.data.elementConstraint;
    return !constraint ? [] : [input => input.every(entry => type(entry) === constraint)];
  }
  parsePredicate() {
    if (this.data.predicate instanceof RegExp) {
      return [this.data.predicate.test.bind(this.data.predicate)];
    } else if (this.data.predicate) {
      return [this.data.predicate];
    } else {
      return [];
    }
  }
  getCombinedPredicate() {
    const predicates = [...this.parsePredicate(), ...this.parseTokenList(), ...this.parsePropertyConstraint(), ...this.parseElementConstraint()];
    if (predicates.length === 0) {
      return () => true;
    } else if (predicates.length === 1) {
      return predicates[0];
    } else {
      return input => predicates.every(predicate => predicate(input));
    }
  }
  getDataType() {
    if (this.data.dataType) {
      return this.data.dataType;
    } else if (this.data.predicate instanceof RegExp) {
      return 'String';
    } else if (this.data.tokenList) {
      return 'String';
    } else if (this.data.elementConstraint) {
      return 'Array';
    } else {
      return 'Primitive';
    }
  }
  get dataType() {
    return this.getDataType();
  }
  get predicate() {
    return this.getCombinedPredicate();
  }
  get extends() {
    return this.data.extends;
  }
}
class DataParser {
  constructor(parser, {
    async
  } = {}) {
    this.parser = parser;
    this.async = async;
  }
  validate() {
    const parser = this.parser;
    if (typeof parser !== 'function') {
      throw new TypeError(`parser was ${typeof parser}; expected function`);
    }
  }
}
class FormatParser {
  constructor(format, parsers = {}) {
    this.format = format;
    if (parsers.parseType) {
      this.typeParser = new TypeParser(parsers.parseType);
    }
    if (parsers.parse) {
      this.dataParser = new DataParser(parsers.parse, {
        async: false
      });
    }
    if (parsers.parseAsync) {
      this.asyncDataParser = new DataParser(parsers.parseAsync, {
        async: true
      });
    }
  }
  validateFormat() {
    const format = this.format;
    if (!typeMatcher.test(format)) {
      throw new TypeError(`format name was "${format}"; didn't match expected pattern`);
    }
  }
  validate() {
    this.validateFormat();
    if (this.typeParser) {
      this.typeParser.validate();
    }
    if (this.dataParser) {
      this.dataParser.validate();
    }
    if (this.asyncDataParser) {
      this.asyncDataParser.validate();
    }
  }
}
export { TypeParser, DataParser, FormatParser };