text: import { type, typeMatcher } from './type'
  
  /**
   * @memberof module:@citation-js/core.plugins.input.util
   * @param {module:@citation-js/core.plugins.input~typeParser} data
   */
  class TypeParser {
    /**
     * @access protected
     * @type {Array<module:@citation-js/core.plugins.input~dataType>}
     */
    validDataTypes = ['String', 'Array', 'SimpleObject', 'ComplexObject', 'Primitive']
  
    constructor (data) {
      this.data = data
    }
  
    // ==========================================================================
    // Validation
    // ==========================================================================
  
    /**
     * @access protected
     * @throws {RangeError} if dataType is not valid
     */
    validateDataType () {
      const dataType = this.data.dataType
      if (dataType && !this.validDataTypes.includes(dataType)) {
        throw new RangeError(`dataType was ${dataType}; expected one of ${this.validDataTypes}`)
      }
    }
  
    /**
     * @access protected
     * @throws {TypeError} if predicate is not valid
     */
    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`)
      }
    }
  
    /**
     * @access protected
     * @throws {TypeError} if predicate is not valid
     */
    validateTokenList () {
      const tokenList = this.data.tokenList
      if (tokenList && typeof tokenList !== 'object') {
        throw new TypeError(`tokenList was ${typeof tokenList}; expected object or RegExp`)
      }
    }
  
    /**
     * @access protected
     * @throws {TypeError} if propertyConstraint is not valid
     */
    validatePropertyConstraint () {
      const propertyConstraint = this.data.propertyConstraint
      if (propertyConstraint && typeof propertyConstraint !== 'object') {
        throw new TypeError(`propertyConstraint was ${typeof propertyConstraint}; expected array or object`)
      }
    }
  
    /**
     * @access protected
     * @throws {TypeError} if elementConstraint is not valid
     */
    validateElementConstraint () {
      const elementConstraint = this.data.elementConstraint
      if (elementConstraint && typeof elementConstraint !== 'string') {
        throw new TypeError(`elementConstraint was ${typeof elementConstraint}; expected string`)
      }
    }
  
    /**
     * @access protected
     * @throws {TypeError} if extends is not valid
     */
    validateExtends () {
      const extend = this.data.extends
      if (extend && typeof extend !== 'string') {
        throw new TypeError(`extends was ${typeof extend}; expected string`)
      }
    }
  
    /**
     * @access public
     * @throws {TypeError|RangeError} if typeParser is not valid
     */
    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()
    }
  
    // ==========================================================================
    // Simplification helpers
    // ==========================================================================
  
    /**
     * @access protected
     * @return {Array<module:@citation-js/core.plugins.input~predicate>}
     */
    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]
    }
  
    /**
     * @access protected
     * @return {Array<module:@citation-js/core.plugins.input~predicate>}
     */
    parsePropertyConstraint () {
      const constraints = [].concat(this.data.propertyConstraint || [])
  
      return constraints.map(({ props, match, value }) => {
        props = [].concat(props)
  
        switch (match) {
          case 'any': // fall-through
          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': // fall-through
          default: return input => props.every(prop => prop in input && (!value || value(input[prop])))
        }
      })
    }
  
    /**
     * @access protected
     * @return {Array<module:@citation-js/core.plugins.input~predicate>}
     */
    parseElementConstraint () {
      const constraint = this.data.elementConstraint
      return !constraint ? [] : [input => input.every(entry => type(entry) === constraint)]
    }
  
    /**
     * @access protected
     * @return {Array<module:@citation-js/core.plugins.input~predicate>}
     */
    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 []
      }
    }
  
    /**
     * @access protected
     * @return {module:@citation-js/core.plugins.input~predicate}
     */
    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))
      }
    }
  
    /**
     * @access protected
     * @return {module:@citation-js/core.plugins.input~dataType}
     */
    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'
      }
    }
  
    // ==========================================================================
    // Data simplification
    // ==========================================================================
  
    /**
     * @type {module:@citation-js/core.plugins.input~dataType}
     */
    get dataType () {
      return this.getDataType()
    }
  
    /**
     * @type {module:@citation-js/core.plugins.input~predicate}
     */
    get predicate () {
      return this.getCombinedPredicate()
    }
  
    /**
     * @type {module:@citation-js/core.plugins.input~format}
     */
    get extends () {
      return this.data.extends
    }
  }
  
  /**
   * @memberof module:@citation-js/core.plugins.input.util
   * @param {module:@citation-js/core.plugins.input~dataParser|module:@citation-js/core.plugins.input~asyncDataParser} parser
   * @param {Object} options
   * @param {Boolean} [options.async=false]
   */
  class DataParser {
    constructor (parser, { async } = {}) {
      this.parser = parser
      this.async = async
    }
  
    // ==========================================================================
    // Validation
    // ==========================================================================
  
    /**
     * @throws {TypeError} if dataParser is not valid
     */
    validate () {
      const parser = this.parser
      if (typeof parser !== 'function') {
        throw new TypeError(`parser was ${typeof parser}; expected function`)
      }
    }
  }
  
  /**
   * @memberof module:@citation-js/core.plugins.input.util
   * @param {module:@citation-js/core.plugins.input~format} format
   * @param {module:@citation-js/core.plugins.input~parsers} parsers
   */
  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 })
      }
    }
  
    // ==========================================================================
    // Validation
    // ==========================================================================
  
    /**
     * @access protected
     * @throws {TypeError} if format is not valid
     */
    validateFormat () {
      const format = this.format
      if (!typeMatcher.test(format)) {
        throw new TypeError(`format name was "${format}"; didn't match expected pattern`)
      }
    }
  
    /**
     * @throws {TypeError} if formatParser is not valid
     */
    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 }
  