/*---------------------------------------------------------------------------------------------
 *  Copyright (c) IBM Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
import { ErrorCode, SchemaDraft } from '../../jsonLanguageTypes';
import { isNumber } from '../../utils/objects';
import * as l10n from '@vscode/l10n';
import { DiagnosticSeverity } from 'vscode-languageserver-types';
import { Draft07Validator } from './draft07Validator';
import { ValidationResult, asSchema } from './baseValidator';
export class Draft2019Validator extends Draft07Validator {
    getCurrentSchemaDraft() {
        return SchemaDraft.v2019_09;
    }
    /**
     * Keyword: contains + minContains/maxContains
     *
     * Draft-07 behavior: contains must match at least 1 item.
     * Draft-2019-09 behavior: minContains/maxContains constrain how many matches are required/allowed.
     */
    applyContains(node, schema, originalSchema, validationResult, _matchingSchemas, options) {
        const containsSchema = asSchema(schema.contains);
        if (!containsSchema)
            return;
        const minContainsRaw = schema.minContains;
        const maxContainsRaw = schema.maxContains;
        const minContains = isNumber(minContainsRaw) ? minContainsRaw : 1;
        const maxContains = isNumber(maxContainsRaw) ? maxContainsRaw : undefined;
        let matchCount = 0;
        const items = (node.items ?? []);
        for (const item of items) {
            const itemValidationResult = new ValidationResult(options.isKubernetes);
            this.validateNode(item, containsSchema, schema, itemValidationResult, this.getNoOpCollector(), options);
            if (!itemValidationResult.hasProblems()) {
                matchCount++;
                if (maxContains !== undefined && matchCount > maxContains) {
                    break;
                }
            }
        }
        if (matchCount < minContains) {
            validationResult.problems.push({
                location: { offset: node.offset, length: node.length },
                severity: DiagnosticSeverity.Warning,
                message: schema.errorMessage || l10n.t('Array has too few items matching "contains". Expected {0} or more.', minContains),
                source: this.getSchemaSource(schema, originalSchema),
                schemaUri: this.getSchemaUri(schema, originalSchema),
            });
        }
        if (maxContains !== undefined && matchCount > maxContains) {
            validationResult.problems.push({
                location: { offset: node.offset, length: node.length },
                severity: DiagnosticSeverity.Warning,
                message: schema.errorMessage || l10n.t('Array has too many items matching "contains". Expected {0} or fewer.', maxContains),
                source: this.getSchemaSource(schema, originalSchema),
                schemaUri: this.getSchemaUri(schema, originalSchema),
            });
        }
    }
    /**
     * Keyword: dependentRequired + dependentSchemas.
     */
    applyDependencies(node, schema, originalSchema, validationResult, matchingSchemas, options, seenKeys) {
        // keep draft-07 dependencies support
        super.applyDependencies(node, schema, originalSchema, validationResult, matchingSchemas, options, seenKeys);
        const dependentRequired = schema.dependentRequired;
        if (dependentRequired && typeof dependentRequired === 'object') {
            for (const prop of Object.keys(dependentRequired)) {
                if (!seenKeys[prop])
                    continue;
                const requiredProps = dependentRequired[prop];
                if (!Array.isArray(requiredProps))
                    continue;
                for (const requiredProp of requiredProps) {
                    if (!seenKeys[requiredProp]) {
                        validationResult.problems.push({
                            location: { offset: node.offset, length: node.length },
                            severity: DiagnosticSeverity.Warning,
                            message: l10n.t('Object is missing property {0} required by property {1}.', requiredProp, prop),
                            source: this.getSchemaSource(schema, originalSchema),
                            schemaUri: this.getSchemaUri(schema, originalSchema),
                        });
                    }
                    else {
                        validationResult.propertiesValueMatches++;
                    }
                }
            }
        }
        const dependentSchemas = schema.dependentSchemas;
        if (dependentSchemas && typeof dependentSchemas === 'object') {
            for (const prop of Object.keys(dependentSchemas)) {
                if (!seenKeys[prop])
                    continue;
                const depSchema = asSchema(dependentSchemas[prop]);
                if (!depSchema)
                    continue;
                const depValidationResult = new ValidationResult(options.isKubernetes);
                this.validateNode(node, depSchema, schema, depValidationResult, matchingSchemas, options);
                validationResult.mergePropertyMatch(depValidationResult);
                validationResult.mergeEnumValues(depValidationResult);
            }
        }
    }
    /**
     * Keyword: unevaluatedProperties
     */
    applyUnevaluatedProperties(node, schema, originalSchema, validationResult, matchingSchemas, options, seenKeys) {
        const unevaluated = schema.unevaluatedProperties;
        if (unevaluated === undefined)
            return;
        if (!seenKeys)
            return;
        // ensure evaluatedProperties exists
        validationResult.evaluatedProperties ?? (validationResult.evaluatedProperties = new Set());
        // remaining = properties not evaluated by properties/patternProperties/additionalProperties
        const remaining = Object.keys(seenKeys).filter((name) => !validationResult.evaluatedProperties?.has(name));
        if (remaining.length === 0)
            return;
        // unevaluatedProperties: false => forbid remaining properties
        if (unevaluated === false) {
            for (const propName of remaining) {
                const child = seenKeys[propName];
                if (!child)
                    continue;
                const propertyNode = child.type === 'property' ? child : child.parent;
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                const keyNode = propertyNode.keyNode;
                if (!keyNode)
                    continue;
                validationResult.problems.push({
                    location: { offset: keyNode.offset, length: keyNode.length },
                    severity: DiagnosticSeverity.Warning,
                    code: ErrorCode.PropertyExpected,
                    message: schema.errorMessage || l10n.t('Property {0} is not allowed.', propName),
                    source: this.getSchemaSource(schema, originalSchema),
                    schemaUri: this.getSchemaUri(schema, originalSchema),
                });
                validationResult.evaluatedProperties?.add(propName);
            }
            return;
        }
        // unevaluatedProperties: true => allow anything remaining, but mark evaluated
        if (unevaluated === true) {
            for (const propName of remaining) {
                validationResult.evaluatedProperties?.add(propName);
            }
            return;
        }
        // unevaluatedProperties: <schema> => validate value of each remaining property
        const unevaluatedSchema = asSchema(unevaluated);
        if (!unevaluatedSchema)
            return;
        for (const propName of remaining) {
            const child = seenKeys[propName];
            if (!child)
                continue;
            const valueNode = child.type === 'property' ? child.valueNode : child;
            if (!valueNode)
                continue;
            const subResult = new ValidationResult(options.isKubernetes);
            this.validateNode(valueNode, unevaluatedSchema, schema, subResult, matchingSchemas, options);
            validationResult.mergePropertyMatch(subResult);
            validationResult.mergeEnumValues(subResult);
            validationResult.evaluatedProperties?.add(propName);
        }
    }
    /**
     * Keyword: unevaluatedItems
     */
    applyUnevaluatedItems(node, schema, originalSchema, validationResult, matchingSchemas, options) {
        const unevaluated = schema.unevaluatedItems;
        if (unevaluated === undefined)
            return;
        const items = (node.items ?? []);
        if (items.length === 0)
            return;
        const evaluated = validationResult.getEvaluatedItems(node);
        const remaining = [];
        for (let i = 0; i < items.length; i++) {
            if (!evaluated.has(i))
                remaining.push(i);
        }
        if (remaining.length === 0)
            return;
        // unevaluatedItems: false => forbid remaining indices
        if (unevaluated === false) {
            for (const idx of remaining) {
                const item = items[idx];
                validationResult.problems.push({
                    location: { offset: item.offset, length: item.length || 1 },
                    severity: DiagnosticSeverity.Warning,
                    code: ErrorCode.PropertyExpected,
                    message: schema.errorMessage || l10n.t('Array has too many items according to schema. Expected {0} or fewer.', idx),
                    source: this.getSchemaSource(schema, originalSchema),
                    schemaUri: this.getSchemaUri(schema, originalSchema),
                });
                evaluated.add(idx);
            }
            return;
        }
        // unevaluatedItems: true => allow everything remaining, but mark evaluated
        if (unevaluated === true) {
            for (const idx of remaining)
                evaluated.add(idx);
            return;
        }
        // unevaluatedItems: <schema> => validate remaining items against that schema
        const unevaluatedSchema = asSchema(unevaluated);
        if (!unevaluatedSchema)
            return;
        for (const idx of remaining) {
            const item = items[idx];
            const subResult = new ValidationResult(options.isKubernetes);
            // validate the item node with the unevaluatedItems subschema
            this.validateNode(item, unevaluatedSchema, schema, subResult, matchingSchemas, options);
            validationResult.merge(subResult);
            validationResult.mergeEnumValues(subResult);
            evaluated.add(idx);
        }
    }
}
//# sourceMappingURL=draft2019Validator.js.map