text: 'use strict';
  
  const ApplyToDefaults = require('@hapi/hoek/lib/applyToDefaults');
  const Assert = require('@hapi/hoek/lib/assert');
  const Clone = require('@hapi/hoek/lib/clone');
  const Topo = require('@hapi/topo');
  
  const Any = require('./any');
  const Common = require('../common');
  const Compile = require('../compile');
  const Errors = require('../errors');
  const Ref = require('../ref');
  const Template = require('../template');
  
  
  const internals = {
      renameDefaults: {
          alias: false,                   // Keep old value in place
          multiple: false,                // Allow renaming multiple keys into the same target
          override: false                 // Overrides an existing key
      }
  };
  
  
  module.exports = Any.extend({
  
      type: '_keys',
  
      properties: {
  
          typeof: 'object'
      },
  
      flags: {
  
          unknown: { default: false }
      },
  
      terms: {
  
          dependencies: { init: null },
          keys: { init: null, manifest: { mapped: { from: 'schema', to: 'key' } } },
          patterns: { init: null },
          renames: { init: null }
      },
  
      args(schema, keys) {
  
          return schema.keys(keys);
      },
  
      validate(value, { schema, error, state, prefs }) {
  
          if (!value ||
              typeof value !== schema.$_property('typeof') ||
              Array.isArray(value)) {
  
              return { value, errors: error('object.base', { type: schema.$_property('typeof') }) };
          }
  
          // Skip if there are no other rules to test
  
          if (!schema.$_terms.renames &&
              !schema.$_terms.dependencies &&
              !schema.$_terms.keys &&                       // null allows any keys
              !schema.$_terms.patterns &&
              !schema.$_terms.externals) {
  
              return;
          }
  
          // Shallow clone value
  
          value = internals.clone(value, prefs);
          const errors = [];
  
          // Rename keys
  
          if (schema.$_terms.renames &&
              !internals.rename(schema, value, state, prefs, errors)) {
  
              return { value, errors };
          }
  
          // Anything allowed
  
          if (!schema.$_terms.keys &&                       // null allows any keys
              !schema.$_terms.patterns &&
              !schema.$_terms.dependencies) {
  
              return { value, errors };
          }
  
          // Defined keys
  
          const unprocessed = new Set(Object.keys(value));
  
          if (schema.$_terms.keys) {
              const ancestors = [value, ...state.ancestors];
  
              for (const child of schema.$_terms.keys) {
                  const key = child.key;
                  const item = value[key];
  
                  unprocessed.delete(key);
  
                  const localState = state.localize([...state.path, key], ancestors, child);
                  const result = child.schema.$_validate(item, localState, prefs);
  
                  if (result.errors) {
                      if (prefs.abortEarly) {
                          return { value, errors: result.errors };
                      }
  
                      errors.push(...result.errors);
                  }
                  else if (child.schema._flags.result === 'strip' ||
                      result.value === undefined && item !== undefined) {
  
                      delete value[key];
                  }
                  else if (result.value !== undefined) {
                      value[key] = result.value;
                  }
              }
          }
  
          // Unknown keys
  
          if (unprocessed.size ||
              schema._flags._hasPatternMatch) {
  
              const early = internals.unknown(schema, value, unprocessed, errors, state, prefs);
              if (early) {
                  return early;
              }
          }
  
          // Validate dependencies
  
          if (schema.$_terms.dependencies) {
              for (const dep of schema.$_terms.dependencies) {
                  if (dep.key &&
                      dep.key.resolve(value, state, prefs, null, { shadow: false }) === undefined) {
  
                      continue;
                  }
  
                  const failed = internals.dependencies[dep.rel](schema, dep, value, state, prefs);
                  if (failed) {
                      const report = schema.$_createError(failed.code, value, failed.context, state, prefs);
                      if (prefs.abortEarly) {
                          return { value, errors: report };
                      }
  
                      errors.push(report);
                  }
              }
          }
  
          return { value, errors };
      },
  
      rules: {
  
          and: {
              method(...peers /*, [options] */) {
  
                  Common.verifyFlat(peers, 'and');
  
                  return internals.dependency(this, 'and', null, peers);
              }
          },
  
          append: {
              method(schema) {
  
                  if (schema === null ||
                      schema === undefined ||
                      Object.keys(schema).length === 0) {
  
                      return this;
                  }
  
                  return this.keys(schema);
              }
          },
  
          assert: {
              method(subject, schema, message) {
  
                  if (!Template.isTemplate(subject)) {
                      subject = Compile.ref(subject);
                  }
  
                  Assert(message === undefined || typeof message === 'string', 'Message must be a string');
  
                  schema = this.$_compile(schema, { appendPath: true });
  
                  const obj = this.$_addRule({ name: 'assert', args: { subject, schema, message } });
                  obj.$_mutateRegister(subject);
                  obj.$_mutateRegister(schema);
                  return obj;
              },
              validate(value, { error, prefs, state }, { subject, schema, message }) {
  
                  const about = subject.resolve(value, state, prefs);
                  const path = Ref.isRef(subject) ? subject.absolute(state) : [];
                  if (schema.$_match(about, state.localize(path, [value, ...state.ancestors], schema), prefs)) {
                      return value;
                  }
  
                  return error('object.assert', { subject, message });
              },
              args: ['subject', 'schema', 'message'],
              multi: true
          },
  
          instance: {
              method(constructor, name) {
  
                  Assert(typeof constructor === 'function', 'constructor must be a function');
  
                  name = name || constructor.name;
  
                  return this.$_addRule({ name: 'instance', args: { constructor, name } });
              },
              validate(value, helpers, { constructor, name }) {
  
                  if (value instanceof constructor) {
                      return value;
                  }
  
                  return helpers.error('object.instance', { type: name, value });
              },
              args: ['constructor', 'name']
          },
  
          keys: {
              method(schema) {
  
                  Assert(schema === undefined || typeof schema === 'object', 'Object schema must be a valid object');
                  Assert(!Common.isSchema(schema), 'Object schema cannot be a joi schema');
  
                  const obj = this.clone();
  
                  if (!schema) {                                      // Allow all
                      obj.$_terms.keys = null;
                  }
                  else if (!Object.keys(schema).length) {             // Allow none
                      obj.$_terms.keys = new internals.Keys();
                  }
                  else {
                      obj.$_terms.keys = obj.$_terms.keys ? obj.$_terms.keys.filter((child) => !schema.hasOwnProperty(child.key)) : new internals.Keys();
                      for (const key in schema) {
                          Common.tryWithPath(() => obj.$_terms.keys.push({ key, schema: this.$_compile(schema[key]) }), key);
                      }
                  }
  
                  return obj.$_mutateRebuild();
              }
          },
  
          length: {
              method(limit) {
  
                  return this.$_addRule({ name: 'length', args: { limit }, operator: '=' });
              },
              validate(value, helpers, { limit }, { name, operator, args }) {
  
                  if (Common.compare(Object.keys(value).length, limit, operator)) {
                      return value;
                  }
  
                  return helpers.error('object.' + name, { limit: args.limit, value });
              },
              args: [
                  {
                      name: 'limit',
                      ref: true,
                      assert: Common.limit,
                      message: 'must be a positive integer'
                  }
              ]
          },
  
          max: {
              method(limit) {
  
                  return this.$_addRule({ name: 'max', method: 'length', args: { limit }, operator: '<=' });
              }
          },
  
          min: {
              method(limit) {
  
                  return this.$_addRule({ name: 'min', method: 'length', args: { limit }, operator: '>=' });
              }
          },
  
          nand: {
              method(...peers /*, [options] */) {
  
                  Common.verifyFlat(peers, 'nand');
  
                  return internals.dependency(this, 'nand', null, peers);
              }
          },
  
          or: {
              method(...peers /*, [options] */) {
  
                  Common.verifyFlat(peers, 'or');
  
                  return internals.dependency(this, 'or', null, peers);
              }
          },
  
          oxor: {
              method(...peers /*, [options] */) {
  
                  return internals.dependency(this, 'oxor', null, peers);
              }
          },
  
          pattern: {
              method(pattern, schema, options = {}) {
  
                  const isRegExp = pattern instanceof RegExp;
                  if (!isRegExp) {
                      pattern = this.$_compile(pattern, { appendPath: true });
                  }
  
                  Assert(schema !== undefined, 'Invalid rule');
                  Common.assertOptions(options, ['fallthrough', 'matches']);
  
                  if (isRegExp) {
                      Assert(!pattern.flags.includes('g') && !pattern.flags.includes('y'), 'pattern should not use global or sticky mode');
                  }
  
                  schema = this.$_compile(schema, { appendPath: true });
  
                  const obj = this.clone();
                  obj.$_terms.patterns = obj.$_terms.patterns || [];
                  const config = { [isRegExp ? 'regex' : 'schema']: pattern, rule: schema };
                  if (options.matches) {
                      config.matches = this.$_compile(options.matches);
                      if (config.matches.type !== 'array') {
                          config.matches = config.matches.$_root.array().items(config.matches);
                      }
  
                      obj.$_mutateRegister(config.matches);
                      obj.$_setFlag('_hasPatternMatch', true, { clone: false });
                  }
  
                  if (options.fallthrough) {
                      config.fallthrough = true;
                  }
  
                  obj.$_terms.patterns.push(config);
                  obj.$_mutateRegister(schema);
                  return obj;
              }
          },
  
          ref: {
              method() {
  
                  return this.$_addRule('ref');
              },
              validate(value, helpers) {
  
                  if (Ref.isRef(value)) {
                      return value;
                  }
  
                  return helpers.error('object.refType', { value });
              }
          },
  
          regex: {
              method() {
  
                  return this.$_addRule('regex');
              },
              validate(value, helpers) {
  
                  if (value instanceof RegExp) {
                      return value;
                  }
  
                  return helpers.error('object.regex', { value });
              }
          },
  
          rename: {
              method(from, to, options = {}) {
  
                  Assert(typeof from === 'string' || from instanceof RegExp, 'Rename missing the from argument');
                  Assert(typeof to === 'string' || to instanceof Template, 'Invalid rename to argument');
                  Assert(to !== from, 'Cannot rename key to same name:', from);
  
                  Common.assertOptions(options, ['alias', 'ignoreUndefined', 'override', 'multiple']);
  
                  const obj = this.clone();
  
                  obj.$_terms.renames = obj.$_terms.renames || [];
                  for (const rename of obj.$_terms.renames) {
                      Assert(rename.from !== from, 'Cannot rename the same key multiple times');
                  }
  
                  if (to instanceof Template) {
                      obj.$_mutateRegister(to);
                  }
  
                  obj.$_terms.renames.push({
                      from,
                      to,
                      options: ApplyToDefaults(internals.renameDefaults, options)
                  });
  
                  return obj;
              }
          },
  
          schema: {
              method(type = 'any') {
  
                  return this.$_addRule({ name: 'schema', args: { type } });
              },
              validate(value, helpers, { type }) {
  
                  if (Common.isSchema(value) &&
                      (type === 'any' || value.type === type)) {
  
                      return value;
                  }
  
                  return helpers.error('object.schema', { type });
              }
          },
  
          unknown: {
              method(allow) {
  
                  return this.$_setFlag('unknown', allow !== false);
              }
          },
  
          with: {
              method(key, peers, options = {}) {
  
                  return internals.dependency(this, 'with', key, peers, options);
              }
          },
  
          without: {
              method(key, peers, options = {}) {
  
                  return internals.dependency(this, 'without', key, peers, options);
              }
          },
  
          xor: {
              method(...peers /*, [options] */) {
  
                  Common.verifyFlat(peers, 'xor');
  
                  return internals.dependency(this, 'xor', null, peers);
              }
          }
      },
  
      overrides: {
  
          default(value, options) {
  
              if (value === undefined) {
                  value = Common.symbols.deepDefault;
              }
  
              return this.$_parent('default', value, options);
          }
      },
  
      rebuild(schema) {
  
          if (schema.$_terms.keys) {
              const topo = new Topo.Sorter();
              for (const child of schema.$_terms.keys) {
                  Common.tryWithPath(() => topo.add(child, { after: child.schema.$_rootReferences(), group: child.key }), child.key);
              }
  
              schema.$_terms.keys = new internals.Keys(...topo.nodes);
          }
      },
  
      manifest: {
  
          build(obj, desc) {
  
              if (desc.keys) {
                  obj = obj.keys(desc.keys);
              }
  
              if (desc.dependencies) {
                  for (const { rel, key = null, peers, options } of desc.dependencies) {
                      obj = internals.dependency(obj, rel, key, peers, options);
                  }
              }
  
              if (desc.patterns) {
                  for (const { regex, schema, rule, fallthrough, matches } of desc.patterns) {
                      obj = obj.pattern(regex || schema, rule, { fallthrough, matches });
                  }
              }
  
              if (desc.renames) {
                  for (const { from, to, options } of desc.renames) {
                      obj = obj.rename(from, to, options);
                  }
              }
  
              return obj;
          }
      },
  
      messages: {
          'object.and': '{{#label}} berisi {{#presentWithLabels}} tanpa rekan yang dibutuhkan {{#missingWithLabels}}',
          'object.assert': '{{#label}} is invalid because {if(#subject.key, `"` + #subject.key + `" failed to ` + (#message || "pass the assertion test"), #message || "the assertion failed")}',
          'object.base': '{{#label}} harus berjenis {{#type}}',
          'object.instance': '{{#label}} harus berupa turunan dari {{:#type}}',
          'object.length': '{{#label}} harus memiliki {{#limit}} kunci {if (#limit == 1, "", "s")}',
          'object.max': '{{#label}} harus memiliki kurang dari atau sama dengan {{#limit}} kunci {if (#limit == 1, "", "s")}',
          'object.min': '{{#label}} harus memiliki setidaknya {{#limit}} kunci {if (#limit == 1, "", "s")}',
          'object.missing': '{{#label}} harus berisi setidaknya satu dari {{#peersWithLabels}}',
          'object.nand': '{{:#mainWithLabel}} tidak boleh ada bersamaan dengan {{#peersWithLabels}}',
          'object.oxor': '{{#label}} berisi konflik antara peer eksklusif opsional {{#peersWithLabels}}',
          'object.pattern.match': '{{#label}} kunci gagal untuk mencocokkan persyaratan pola',
          'object.refType': '{{#label}} harus berupa referensi Joi',
          'object.regex': '{{#label}} harus berupa objek RegExp',
          'object.rename.multiple': '{{#label}} tidak dapat mengganti nama {{:#from}} karena beberapa penggantian nama dinonaktifkan dan kunci lain telah diganti namanya menjadi {{:#to}}',
          'object.rename.override': '{{#label}} tidak dapat mengganti nama {{:#from}} karena override dinonaktifkan dan target {{:#to}} ada',
          'object.schema': '{{#label}} harus berupa skema Joi dari {{#type}} type',
          'object.unknown': '{{#label}} tidak dibolehkan',
          'object.with': '{{:#mainWithLabel}} rekan yang diperlukan tidak ada {{:#peerWithLabel}}',
          'object.without': '{{:#mainWithLabel}} konflik dengan peer terlarang {{:#peerWithLabel}}',
          'object.xor': '{{#label}} berisi konflik antara sesama eksklusif {{#peersWithLabels}}'
      }
  });
  
  
  // Helpers
  
  internals.clone = function (value, prefs) {
  
      // Object
  
      if (typeof value === 'object') {
          if (prefs.nonEnumerables) {
              return Clone(value, { shallow: true });
          }
  
          const clone = Object.create(Object.getPrototypeOf(value));
          Object.assign(clone, value);
          return clone;
      }
  
      // Function
  
      const clone = function (...args) {
  
          return value.apply(this, args);
      };
  
      clone.prototype = Clone(value.prototype);
      Object.defineProperty(clone, 'name', { value: value.name, writable: false });
      Object.defineProperty(clone, 'length', { value: value.length, writable: false });
      Object.assign(clone, value);
      return clone;
  };
  
  
  internals.dependency = function (schema, rel, key, peers, options) {
  
      Assert(key === null || typeof key === 'string', rel, 'key must be a strings');
  
      // Extract options from peers array
  
      if (!options) {
          options = peers.length > 1 && typeof peers[peers.length - 1] === 'object' ? peers.pop() : {};
      }
  
      Common.assertOptions(options, ['separator']);
  
      peers = [].concat(peers);
  
      // Cast peer paths
  
      const separator = Common.default(options.separator, '.');
      const paths = [];
      for (const peer of peers) {
          Assert(typeof peer === 'string', rel, 'peers must be a string or a reference');
          paths.push(Compile.ref(peer, { separator, ancestor: 0, prefix: false }));
      }
  
      // Cast key
  
      if (key !== null) {
          key = Compile.ref(key, { separator, ancestor: 0, prefix: false });
      }
  
      // Add rule
  
      const obj = schema.clone();
      obj.$_terms.dependencies = obj.$_terms.dependencies || [];
      obj.$_terms.dependencies.push(new internals.Dependency(rel, key, paths, peers));
      return obj;
  };
  
  
  internals.dependencies = {
  
      and(schema, dep, value, state, prefs) {
  
          const missing = [];
          const present = [];
          const count = dep.peers.length;
          for (const peer of dep.peers) {
              if (peer.resolve(value, state, prefs, null, { shadow: false }) === undefined) {
                  missing.push(peer.key);
              }
              else {
                  present.push(peer.key);
              }
          }
  
          if (missing.length !== count &&
              present.length !== count) {
  
              return {
                  code: 'object.and',
                  context: {
                      present,
                      presentWithLabels: internals.keysToLabels(schema, present),
                      missing,
                      missingWithLabels: internals.keysToLabels(schema, missing)
                  }
              };
          }
      },
  
      nand(schema, dep, value, state, prefs) {
  
          const present = [];
          for (const peer of dep.peers) {
              if (peer.resolve(value, state, prefs, null, { shadow: false }) !== undefined) {
                  present.push(peer.key);
              }
          }
  
          if (present.length !== dep.peers.length) {
              return;
          }
  
          const main = dep.paths[0];
          const values = dep.paths.slice(1);
          return {
              code: 'object.nand',
              context: {
                  main,
                  mainWithLabel: internals.keysToLabels(schema, main),
                  peers: values,
                  peersWithLabels: internals.keysToLabels(schema, values)
              }
          };
      },
  
      or(schema, dep, value, state, prefs) {
  
          for (const peer of dep.peers) {
              if (peer.resolve(value, state, prefs, null, { shadow: false }) !== undefined) {
                  return;
              }
          }
  
          return {
              code: 'object.missing',
              context: {
                  peers: dep.paths,
                  peersWithLabels: internals.keysToLabels(schema, dep.paths)
              }
          };
      },
  
      oxor(schema, dep, value, state, prefs) {
  
          const present = [];
          for (const peer of dep.peers) {
              if (peer.resolve(value, state, prefs, null, { shadow: false }) !== undefined) {
                  present.push(peer.key);
              }
          }
  
          if (!present.length ||
              present.length === 1) {
  
              return;
          }
  
          const context = { peers: dep.paths, peersWithLabels: internals.keysToLabels(schema, dep.paths) };
          context.present = present;
          context.presentWithLabels = internals.keysToLabels(schema, present);
          return { code: 'object.oxor', context };
      },
  
      with(schema, dep, value, state, prefs) {
  
          for (const peer of dep.peers) {
              if (peer.resolve(value, state, prefs, null, { shadow: false }) === undefined) {
                  return {
                      code: 'object.with',
                      context: {
                          main: dep.key.key,
                          mainWithLabel: internals.keysToLabels(schema, dep.key.key),
                          peer: peer.key,
                          peerWithLabel: internals.keysToLabels(schema, peer.key)
                      }
                  };
              }
          }
      },
  
      without(schema, dep, value, state, prefs) {
  
          for (const peer of dep.peers) {
              if (peer.resolve(value, state, prefs, null, { shadow: false }) !== undefined) {
                  return {
                      code: 'object.without',
                      context: {
                          main: dep.key.key,
                          mainWithLabel: internals.keysToLabels(schema, dep.key.key),
                          peer: peer.key,
                          peerWithLabel: internals.keysToLabels(schema, peer.key)
                      }
                  };
              }
          }
      },
  
      xor(schema, dep, value, state, prefs) {
  
          const present = [];
          for (const peer of dep.peers) {
              if (peer.resolve(value, state, prefs, null, { shadow: false }) !== undefined) {
                  present.push(peer.key);
              }
          }
  
          if (present.length === 1) {
              return;
          }
  
          const context = { peers: dep.paths, peersWithLabels: internals.keysToLabels(schema, dep.paths) };
          if (present.length === 0) {
              return { code: 'object.missing', context };
          }
  
          context.present = present;
          context.presentWithLabels = internals.keysToLabels(schema, present);
          return { code: 'object.xor', context };
      }
  };
  
  
  internals.keysToLabels = function (schema, keys) {
  
      if (Array.isArray(keys)) {
          return keys.map((key) => schema.$_mapLabels(key));
      }
  
      return schema.$_mapLabels(keys);
  };
  
  
  internals.rename = function (schema, value, state, prefs, errors) {
  
      const renamed = {};
      for (const rename of schema.$_terms.renames) {
          const matches = [];
          const pattern = typeof rename.from !== 'string';
  
          if (!pattern) {
              if (Object.prototype.hasOwnProperty.call(value, rename.from) &&
                  (value[rename.from] !== undefined || !rename.options.ignoreUndefined)) {
  
                  matches.push(rename);
              }
          }
          else {
              for (const from in value) {
                  if (value[from] === undefined &&
                      rename.options.ignoreUndefined) {
  
                      continue;
                  }
  
                  if (from === rename.to) {
                      continue;
                  }
  
                  const match = rename.from.exec(from);
                  if (!match) {
                      continue;
                  }
  
                  matches.push({ from, to: rename.to, match });
              }
          }
  
          for (const match of matches) {
              const from = match.from;
              let to = match.to;
              if (to instanceof Template) {
                  to = to.render(value, state, prefs, match.match);
              }
  
              if (from === to) {
                  continue;
              }
  
              if (!rename.options.multiple &&
                  renamed[to]) {
  
                  errors.push(schema.$_createError('object.rename.multiple', value, { from, to, pattern }, state, prefs));
                  if (prefs.abortEarly) {
                      return false;
                  }
              }
  
              if (Object.prototype.hasOwnProperty.call(value, to) &&
                  !rename.options.override &&
                  !renamed[to]) {
  
                  errors.push(schema.$_createError('object.rename.override', value, { from, to, pattern }, state, prefs));
                  if (prefs.abortEarly) {
                      return false;
                  }
              }
  
              if (value[from] === undefined) {
                  delete value[to];
              }
              else {
                  value[to] = value[from];
              }
  
              renamed[to] = true;
  
              if (!rename.options.alias) {
                  delete value[from];
              }
          }
      }
  
      return true;
  };
  
  
  internals.unknown = function (schema, value, unprocessed, errors, state, prefs) {
  
      if (schema.$_terms.patterns) {
          let hasMatches = false;
          const matches = schema.$_terms.patterns.map((pattern) => {
  
              if (pattern.matches) {
                  hasMatches = true;
                  return [];
              }
          });
  
          const ancestors = [value, ...state.ancestors];
  
          for (const key of unprocessed) {
              const item = value[key];
              const path = [...state.path, key];
  
              for (let i = 0; i < schema.$_terms.patterns.length; ++i) {
                  const pattern = schema.$_terms.patterns[i];
                  if (pattern.regex) {
                      const match = pattern.regex.test(key);
                      state.mainstay.tracer.debug(state, 'rule', `pattern.${i}`, match ? 'pass' : 'error');
                      if (!match) {
                          continue;
                      }
                  }
                  else {
                      if (!pattern.schema.$_match(key, state.nest(pattern.schema, `pattern.${i}`), prefs)) {
                          continue;
                      }
                  }
  
                  unprocessed.delete(key);
  
                  const localState = state.localize(path, ancestors, { schema: pattern.rule, key });
                  const result = pattern.rule.$_validate(item, localState, prefs);
                  if (result.errors) {
                      if (prefs.abortEarly) {
                          return { value, errors: result.errors };
                      }
  
                      errors.push(...result.errors);
                  }
  
                  if (pattern.matches) {
                      matches[i].push(key);
                  }
  
                  value[key] = result.value;
                  if (!pattern.fallthrough) {
                      break;
                  }
              }
          }
  
          // Validate pattern matches rules
  
          if (hasMatches) {
              for (let i = 0; i < matches.length; ++i) {
                  const match = matches[i];
                  if (!match) {
                      continue;
                  }
  
                  const stpm = schema.$_terms.patterns[i].matches;
                  const localState = state.localize(state.path, ancestors, stpm);
                  const result = stpm.$_validate(match, localState, prefs);
                  if (result.errors) {
                      const details = Errors.details(result.errors, { override: false });
                      details.matches = match;
                      const report = schema.$_createError('object.pattern.match', value, details, state, prefs);
                      if (prefs.abortEarly) {
                          return { value, errors: report };
                      }
  
                      errors.push(report);
                  }
              }
          }
      }
  
      if (!unprocessed.size ||
          !schema.$_terms.keys && !schema.$_terms.patterns) {     // If no keys or patterns specified, unknown keys allowed
  
          return;
      }
  
      if (prefs.stripUnknown && !schema._flags.unknown ||
          prefs.skipFunctions) {
  
          const stripUnknown = prefs.stripUnknown ? (prefs.stripUnknown === true ? true : !!prefs.stripUnknown.objects) : false;
  
          for (const key of unprocessed) {
              if (stripUnknown) {
                  delete value[key];
                  unprocessed.delete(key);
              }
              else if (typeof value[key] === 'function') {
                  unprocessed.delete(key);
              }
          }
      }
  
      const forbidUnknown = !Common.default(schema._flags.unknown, prefs.allowUnknown);
      if (forbidUnknown) {
          for (const unprocessedKey of unprocessed) {
              const localState = state.localize([...state.path, unprocessedKey], []);
              const report = schema.$_createError('object.unknown', value[unprocessedKey], { child: unprocessedKey }, localState, prefs, { flags: false });
              if (prefs.abortEarly) {
                  return { value, errors: report };
              }
  
              errors.push(report);
          }
      }
  };
  
  
  internals.Dependency = class {
  
      constructor(rel, key, peers, paths) {
  
          this.rel = rel;
          this.key = key;
          this.peers = peers;
          this.paths = paths;
      }
  
      describe() {
  
          const desc = {
              rel: this.rel,
              peers: this.paths
          };
  
          if (this.key !== null) {
              desc.key = this.key.key;
          }
  
          if (this.peers[0].separator !== '.') {
              desc.options = { separator: this.peers[0].separator };
          }
  
          return desc;
      }
  };
  
  
  internals.Keys = class extends Array {
  
      concat(source) {
  
          const result = this.slice();
  
          const keys = new Map();
          for (let i = 0; i < result.length; ++i) {
              keys.set(result[i].key, i);
          }
  
          for (const item of source) {
              const key = item.key;
              const pos = keys.get(key);
              if (pos !== undefined) {
                  result[pos] = { key, schema: result[pos].schema.concat(item.schema) };
              }
              else {
                  result.push(item);
              }
          }
  
          return result;
      }
  };
  