import {
  isArray,
} from './validators.js';
import {
  MESSAGES,
} from './constants.js';
import {
  createCompiler,
} from './createCompiler.js';

function createSchema(plugins, compilerOptions) {
  class Schema {
    constructor(schemaDef, schemaOptions = {}) {
      Object.assign(this, {
        schemaDef,
        schemaOptions,
      });
    }

    get compiled() {
      Object.defineProperty(this, 'compiled', {
        value: this.constructor.compiler.compile(this.schemaDef, this.schemaOptions),
      });
      return this.compiled;
    }

    getErrors(value) {
      return this.compiled.validate(value);
    }

    validate(value, {
      label = 'Value',
    } = {}) {
      const error = this.getErrors(value);
      return error && this.constructor.describe(error, {
        label,
      });
    }

    validator(validateOptions, transform) {
      return (value) => {
        const error = this.validate(value, validateOptions);
        if (typeof transform === 'function') {
          return transform(value, error);
        }
        return error;
      };
    }

    static describe(descriptor, {
      label = 'Value',
    } = {}) {
      if (descriptor && typeof descriptor === 'object') {
        if (descriptor.error) {
          const messageTemplate = this.messages[descriptor.error];
          if (!messageTemplate) {
            return descriptor.error;
          }
          return messageTemplate({
            ...descriptor,
            label,
          });
        } else if (descriptor.errors) {
          if (isArray(descriptor.errors)) {
            return descriptor.errors.map((item, index) => this.describe(item, { label: `${label}.${index}` }));
          } else if (typeof descriptor.errors === 'object') {
            const described = {};
            Object.keys(descriptor.errors).forEach((key) => {
              described[key] = this.describe(descriptor.errors[key], { label: `${label}.${key}` });
            });
            return described;
          }
        }
      }
      return undefined;
    }

    static oneOf(array) {
      if (!isArray(array)) {
        throw new Error('Expected an array.');
      }
      if (array.length < 2) {
        throw new Error('Array should contain at least two elements');
      }
      return new this(array, { isOneOf: true });
    }

    static arrayOf(schemaDef) {
      return new this([schemaDef]);
    }
  }

  Schema.compiler = createCompiler(Schema, plugins, compilerOptions);
  Schema.messages = { ...MESSAGES };

  plugins.forEach(plugin => plugin.mixin && plugin.mixin(Schema));

  return Schema;
}

export default createSchema;

@@ -2130,16 +2130,22 @@
 or('
-Expected
+OneOf requires
  an 
@@ -2149,17 +2149,16 @@
 an array
-.
 ');%0A    
@@ -2262,32 +2262,32 @@
 ents');%0A      %7D%0A
-
       return new
@@ -2318,32 +2318,396 @@
 true %7D);%0A    %7D%0A%0A
+    static merge(array) %7B%0A      if (!isArray(array)) %7B%0A        throw new Error('Merge requires an array');%0A      %7D%0A      return new this(array, %7B merge: true %7D);%0A    %7D%0A%0A    static pick(schemaDef, fields) %7B%0A      if (!isArray(fields)) %7B%0A        throw new Error('Pick requires an array of fields');%0A      %7D%0A      return new this(schemaDef, %7B pick: fields %7D);%0A    %7D%0A%0A
     static array

Add a couple of static helper methods