"use strict";
/*
 * Licensed to Elasticsearch B.V. under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch B.V. licenses this file to you under
 * the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.ObjectType = void 0;
const tslib_1 = require("tslib");
const type_detect_1 = tslib_1.__importDefault(require("type-detect"));
const internals_1 = require("../internals");
const type_1 = require("./type");
const errors_1 = require("../errors");
class ObjectType extends type_1.Type {
    constructor(props, options = {}) {
        const schemaKeys = {};
        const { unknowns = 'forbid', ...typeOptions } = options;
        for (const [key, value] of Object.entries(props)) {
            schemaKeys[key] = value.getSchema();
        }
        const schema = internals_1.internals
            .object()
            .keys(schemaKeys)
            .default()
            .optional()
            .unknown(unknowns === 'allow')
            .options({ stripUnknown: { objects: unknowns === 'ignore' } });
        super(schema, typeOptions);
        this.props = props;
        this.propSchemas = schemaKeys;
        this.options = options;
    }
    /**
     * Return a new `ObjectType` instance extended with given `newProps` properties.
     * Original properties can be deleted from the copy by passing a `null` or `undefined` value for the key.
     *
     * @example
     * How to add a new key to an object schema
     * ```ts
     * const origin = schema.object({
     *   initial: schema.string(),
     * });
     *
     * const extended = origin.extends({
     *   added: schema.number(),
     * });
     * ```
     *
     * How to remove an existing key from an object schema
     * ```ts
     * const origin = schema.object({
     *   initial: schema.string(),
     *   toRemove: schema.number(),
     * });
     *
     * const extended = origin.extends({
     *   toRemove: undefined,
     * });
     * ```
     *
     * How to override the schema's options
     * ```ts
     * const origin = schema.object({
     *   initial: schema.string(),
     * }, { defaultValue: { initial: 'foo' }});
     *
     * const extended = origin.extends({
     *   added: schema.number(),
     * }, { defaultValue: { initial: 'foo', added: 'bar' }});
     *
     * @remarks
     * `extends` only support extending first-level properties. It's currently not possible to perform deep/nested extensions.
     *
     * ```ts
     * const origin = schema.object({
     *   foo: schema.string(),
     *   nested: schema.object({
     *     a: schema.string(),
     *     b: schema.string(),
     *   }),
     * });
     *
     * const extended = origin.extends({
     *   nested: schema.object({
     *     c: schema.string(),
     *   }),
     * });
     *
     * // TypeOf<typeof extended> is `{ foo: string; nested: { c: string } }`
     * ```
     */
    extends(newProps, newOptions) {
        const extendedProps = Object.entries({
            ...this.props,
            ...newProps,
        }).reduce((memo, [key, value]) => {
            if (value !== null && value !== undefined) {
                return {
                    ...memo,
                    [key]: value,
                };
            }
            return memo;
        }, {});
        const extendedOptions = {
            ...this.options,
            ...newOptions,
        };
        return new ObjectType(extendedProps, extendedOptions);
    }
    handleError(type, { reason, value }) {
        switch (type) {
            case 'any.required':
            case 'object.base':
                return `expected a plain object value, but found [${type_detect_1.default(value)}] instead.`;
            case 'object.parse':
                return `could not parse object value from json input`;
            case 'object.allowUnknown':
                return `definition for this key is missing`;
            case 'object.child':
                return reason[0];
        }
    }
    validateKey(key, value) {
        if (!this.propSchemas[key]) {
            throw new Error(`${key} is not a valid part of this schema`);
        }
        const { value: validatedValue, error } = this.propSchemas[key].validate(value);
        if (error) {
            throw new errors_1.ValidationError(error, key);
        }
        return validatedValue;
    }
}
exports.ObjectType = ObjectType;
