"use strict";
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Red Hat, Inc. All rights reserved.
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.JSONDocument = exports.findNodeAtOffset = exports.contains = exports.getNodeValue = exports.newJSONDocument = exports.ValidationResult = exports.EnumMatch = exports.asSchema = exports.ObjectASTNodeImpl = exports.PropertyASTNodeImpl = exports.StringASTNodeImpl = exports.NumberASTNodeImpl = exports.ArrayASTNodeImpl = exports.BooleanASTNodeImpl = exports.NullASTNodeImpl = exports.ASTNodeImpl = exports.ProblemTypeMessages = exports.ProblemType = exports.YAML_SOURCE = exports.formats = void 0;
const objects_1 = require("../utils/objects");
const schemaUtils_1 = require("../utils/schemaUtils");
const vscode_json_languageservice_1 = require("vscode-json-languageservice");
const nls = require("vscode-nls");
const vscode_uri_1 = require("vscode-uri");
const vscode_languageserver_types_1 = require("vscode-languageserver-types");
const arrUtils_1 = require("../utils/arrUtils");
const strings_1 = require("../utils/strings");
const yamlSchemaService_1 = require("../services/yamlSchemaService");
const math_1 = require("../utils/math");
const localize = nls.loadMessageBundle();
const MSG_PROPERTY_NOT_ALLOWED = 'Property {0} is not allowed.';
exports.formats = {
    'color-hex': {
        errorMessage: localize('colorHexFormatWarning', 'Invalid color format. Use #RGB, #RGBA, #RRGGBB or #RRGGBBAA.'),
        pattern: /^#([0-9A-Fa-f]{3,4}|([0-9A-Fa-f]{2}){3,4})$/,
    },
    'date-time': {
        errorMessage: localize('dateTimeFormatWarning', 'String is not a RFC3339 date-time.'),
        pattern: /^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)([01][0-9]|2[0-3]):([0-5][0-9]))$/i,
    },
    date: {
        errorMessage: localize('dateFormatWarning', 'String is not a RFC3339 date.'),
        pattern: /^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/i,
    },
    time: {
        errorMessage: localize('timeFormatWarning', 'String is not a RFC3339 time.'),
        pattern: /^([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)([01][0-9]|2[0-3]):([0-5][0-9]))$/i,
    },
    email: {
        errorMessage: localize('emailFormatWarning', 'String is not an e-mail address.'),
        pattern: /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
    },
    ipv4: {
        errorMessage: localize('ipv4FormatWarning', 'String does not match IPv4 format.'),
        pattern: /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/,
    },
    ipv6: {
        errorMessage: localize('ipv6FormatWarning', 'String does not match IPv6 format.'),
        pattern: /^([0-9a-f]|:){1,4}(:([0-9a-f]{0,4})*){1,7}$/i,
    },
};
exports.YAML_SOURCE = 'YAML';
const YAML_SCHEMA_PREFIX = 'yaml-schema: ';
var ProblemType;
(function (ProblemType) {
    ProblemType["missingRequiredPropWarning"] = "missingRequiredPropWarning";
    ProblemType["typeMismatchWarning"] = "typeMismatchWarning";
    ProblemType["constWarning"] = "constWarning";
})(ProblemType = exports.ProblemType || (exports.ProblemType = {}));
exports.ProblemTypeMessages = {
    [ProblemType.missingRequiredPropWarning]: 'Missing property "{0}".',
    [ProblemType.typeMismatchWarning]: 'Incorrect type. Expected "{0}".',
    [ProblemType.constWarning]: 'Value must be {0}.',
};
class ASTNodeImpl {
    constructor(parent, internalNode, offset, length) {
        this.offset = offset;
        this.length = length;
        this.parent = parent;
        this.internalNode = internalNode;
    }
    getNodeFromOffsetEndInclusive(offset) {
        const collector = [];
        const findNode = (node) => {
            if (offset >= node.offset && offset <= node.offset + node.length) {
                const children = node.children;
                for (let i = 0; i < children.length && children[i].offset <= offset; i++) {
                    const item = findNode(children[i]);
                    if (item) {
                        collector.push(item);
                    }
                }
                return node;
            }
            return null;
        };
        const foundNode = findNode(this);
        let currMinDist = Number.MAX_VALUE;
        let currMinNode = null;
        for (const currNode of collector) {
            const minDist = currNode.length + currNode.offset - offset + (offset - currNode.offset);
            if (minDist < currMinDist) {
                currMinNode = currNode;
                currMinDist = minDist;
            }
        }
        return currMinNode || foundNode;
    }
    get children() {
        return [];
    }
    toString() {
        return ('type: ' +
            this.type +
            ' (' +
            this.offset +
            '/' +
            this.length +
            ')' +
            (this.parent ? ' parent: {' + this.parent.toString() + '}' : ''));
    }
}
exports.ASTNodeImpl = ASTNodeImpl;
class NullASTNodeImpl extends ASTNodeImpl {
    constructor(parent, internalNode, offset, length) {
        super(parent, internalNode, offset, length);
        this.type = 'null';
        this.value = null;
    }
}
exports.NullASTNodeImpl = NullASTNodeImpl;
class BooleanASTNodeImpl extends ASTNodeImpl {
    constructor(parent, internalNode, boolValue, boolSource, offset, length) {
        super(parent, internalNode, offset, length);
        this.type = 'boolean';
        this.value = boolValue;
        this.source = boolSource;
    }
}
exports.BooleanASTNodeImpl = BooleanASTNodeImpl;
class ArrayASTNodeImpl extends ASTNodeImpl {
    constructor(parent, internalNode, offset, length) {
        super(parent, internalNode, offset, length);
        this.type = 'array';
        this.items = [];
    }
    get children() {
        return this.items;
    }
}
exports.ArrayASTNodeImpl = ArrayASTNodeImpl;
class NumberASTNodeImpl extends ASTNodeImpl {
    constructor(parent, internalNode, offset, length) {
        super(parent, internalNode, offset, length);
        this.type = 'number';
        this.isInteger = true;
        this.value = Number.NaN;
    }
}
exports.NumberASTNodeImpl = NumberASTNodeImpl;
class StringASTNodeImpl extends ASTNodeImpl {
    constructor(parent, internalNode, offset, length) {
        super(parent, internalNode, offset, length);
        this.type = 'string';
        this.value = '';
    }
}
exports.StringASTNodeImpl = StringASTNodeImpl;
class PropertyASTNodeImpl extends ASTNodeImpl {
    constructor(parent, internalNode, offset, length) {
        super(parent, internalNode, offset, length);
        this.type = 'property';
        this.colonOffset = -1;
    }
    get children() {
        return this.valueNode ? [this.keyNode, this.valueNode] : [this.keyNode];
    }
}
exports.PropertyASTNodeImpl = PropertyASTNodeImpl;
class ObjectASTNodeImpl extends ASTNodeImpl {
    constructor(parent, internalNode, offset, length) {
        super(parent, internalNode, offset, length);
        this.type = 'object';
        this.properties = [];
    }
    get children() {
        return this.properties;
    }
}
exports.ObjectASTNodeImpl = ObjectASTNodeImpl;
function asSchema(schema) {
    if (schema === undefined) {
        return undefined;
    }
    if ((0, objects_1.isBoolean)(schema)) {
        return schema ? {} : { not: {} };
    }
    if (typeof schema !== 'object') {
        // we need to report this case as JSONSchemaRef MUST be an Object or Boolean
        console.warn(`Wrong schema: ${JSON.stringify(schema)}, it MUST be an Object or Boolean`);
        schema = {
            type: schema,
        };
    }
    return schema;
}
exports.asSchema = asSchema;
var EnumMatch;
(function (EnumMatch) {
    EnumMatch[EnumMatch["Key"] = 0] = "Key";
    EnumMatch[EnumMatch["Enum"] = 1] = "Enum";
})(EnumMatch = exports.EnumMatch || (exports.EnumMatch = {}));
class SchemaCollector {
    constructor(focusOffset = -1, exclude = null) {
        this.focusOffset = focusOffset;
        this.exclude = exclude;
        this.schemas = [];
    }
    add(schema) {
        this.schemas.push(schema);
    }
    merge(other) {
        this.schemas.push(...other.schemas);
    }
    include(node) {
        return (this.focusOffset === -1 || contains(node, this.focusOffset)) && node !== this.exclude;
    }
    newSub() {
        return new SchemaCollector(-1, this.exclude);
    }
}
class NoOpSchemaCollector {
    constructor() {
        // ignore
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    get schemas() {
        return [];
    }
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    add(schema) {
        // ignore
    }
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    merge(other) {
        // ignore
    }
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    include(node) {
        return true;
    }
    newSub() {
        return this;
    }
}
NoOpSchemaCollector.instance = new NoOpSchemaCollector();
class ValidationResult {
    constructor(isKubernetes) {
        this.problems = [];
        this.propertiesMatches = 0;
        this.propertiesValueMatches = 0;
        this.primaryValueMatches = 0;
        this.enumValueMatch = false;
        if (isKubernetes) {
            this.enumValues = [];
        }
        else {
            this.enumValues = null;
        }
    }
    hasProblems() {
        return !!this.problems.length;
    }
    mergeAll(validationResults) {
        for (const validationResult of validationResults) {
            this.merge(validationResult);
        }
    }
    merge(validationResult) {
        this.problems = this.problems.concat(validationResult.problems);
    }
    mergeEnumValues(validationResult) {
        if (!this.enumValueMatch && !validationResult.enumValueMatch && this.enumValues && validationResult.enumValues) {
            this.enumValues = this.enumValues.concat(validationResult.enumValues);
            for (const error of this.problems) {
                if (error.code === vscode_json_languageservice_1.ErrorCode.EnumValueMismatch) {
                    error.message = localize('enumWarning', 'Value is not accepted. Valid values: {0}.', [...new Set(this.enumValues)]
                        .map((v) => {
                        return JSON.stringify(v);
                    })
                        .join(', '));
                }
            }
        }
    }
    /**
     * Merge multiple warnings with same problemType together
     * @param subValidationResult another possible result
     */
    mergeWarningGeneric(subValidationResult, problemTypesToMerge) {
        if (this.problems?.length) {
            for (const problemType of problemTypesToMerge) {
                const bestResults = this.problems.filter((p) => p.problemType === problemType);
                for (const bestResult of bestResults) {
                    const mergingResult = subValidationResult.problems?.find((p) => p.problemType === problemType &&
                        bestResult.location.offset === p.location.offset &&
                        (problemType !== ProblemType.missingRequiredPropWarning || (0, arrUtils_1.isArrayEqual)(p.problemArgs, bestResult.problemArgs)) // missingProp is merged only with same problemArg
                    );
                    if (mergingResult) {
                        if (mergingResult.problemArgs?.length) {
                            mergingResult.problemArgs
                                .filter((p) => !bestResult.problemArgs.includes(p))
                                .forEach((p) => bestResult.problemArgs.push(p));
                            bestResult.message = getWarningMessage(bestResult.problemType, bestResult.problemArgs);
                        }
                        this.mergeSources(mergingResult, bestResult);
                    }
                }
            }
        }
    }
    mergePropertyMatch(propertyValidationResult) {
        this.merge(propertyValidationResult);
        this.propertiesMatches++;
        if (propertyValidationResult.enumValueMatch ||
            (!propertyValidationResult.hasProblems() && propertyValidationResult.propertiesMatches)) {
            this.propertiesValueMatches++;
        }
        if (propertyValidationResult.enumValueMatch && propertyValidationResult.enumValues) {
            this.primaryValueMatches++;
        }
    }
    mergeSources(mergingResult, bestResult) {
        const mergingSource = mergingResult.source.replace(YAML_SCHEMA_PREFIX, '');
        if (!bestResult.source.includes(mergingSource)) {
            bestResult.source = bestResult.source + ' | ' + mergingSource;
        }
        if (!bestResult.schemaUri.includes(mergingResult.schemaUri[0])) {
            bestResult.schemaUri = bestResult.schemaUri.concat(mergingResult.schemaUri);
        }
    }
    compareGeneric(other) {
        const hasProblems = this.hasProblems();
        if (hasProblems !== other.hasProblems()) {
            return hasProblems ? -1 : 1;
        }
        if (this.enumValueMatch !== other.enumValueMatch) {
            return other.enumValueMatch ? -1 : 1;
        }
        if (this.propertiesValueMatches !== other.propertiesValueMatches) {
            return this.propertiesValueMatches - other.propertiesValueMatches;
        }
        if (this.primaryValueMatches !== other.primaryValueMatches) {
            return this.primaryValueMatches - other.primaryValueMatches;
        }
        return this.propertiesMatches - other.propertiesMatches;
    }
    compareKubernetes(other) {
        const hasProblems = this.hasProblems();
        if (this.propertiesMatches !== other.propertiesMatches) {
            return this.propertiesMatches - other.propertiesMatches;
        }
        if (this.enumValueMatch !== other.enumValueMatch) {
            return other.enumValueMatch ? -1 : 1;
        }
        if (this.primaryValueMatches !== other.primaryValueMatches) {
            return this.primaryValueMatches - other.primaryValueMatches;
        }
        if (this.propertiesValueMatches !== other.propertiesValueMatches) {
            return this.propertiesValueMatches - other.propertiesValueMatches;
        }
        if (hasProblems !== other.hasProblems()) {
            return hasProblems ? -1 : 1;
        }
        return this.propertiesMatches - other.propertiesMatches;
    }
}
exports.ValidationResult = ValidationResult;
function newJSONDocument(root, diagnostics = []) {
    return new JSONDocument(root, diagnostics, []);
}
exports.newJSONDocument = newJSONDocument;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function getNodeValue(node) {
    switch (node.type) {
        case 'array':
            return node.children.map(getNodeValue);
        case 'object': {
            const obj = Object.create(null);
            for (let _i = 0, _a = node.children; _i < _a.length; _i++) {
                const prop = _a[_i];
                const valueNode = prop.children[1];
                if (valueNode) {
                    obj[prop.children[0].value] = getNodeValue(valueNode);
                }
            }
            return obj;
        }
        case 'null':
        case 'string':
        case 'number':
            return node.value;
        case 'boolean':
            return node.source;
        default:
            return undefined;
    }
}
exports.getNodeValue = getNodeValue;
function contains(node, offset, includeRightBound = false) {
    return ((offset >= node.offset && offset <= node.offset + node.length) || (includeRightBound && offset === node.offset + node.length));
}
exports.contains = contains;
function findNodeAtOffset(node, offset, includeRightBound) {
    if (includeRightBound === void 0) {
        includeRightBound = false;
    }
    if (contains(node, offset, includeRightBound)) {
        const children = node.children;
        if (Array.isArray(children)) {
            for (let i = 0; i < children.length && children[i].offset <= offset; i++) {
                const item = findNodeAtOffset(children[i], offset, includeRightBound);
                if (item) {
                    return item;
                }
            }
        }
        return node;
    }
    return undefined;
}
exports.findNodeAtOffset = findNodeAtOffset;
class JSONDocument {
    constructor(root, syntaxErrors = [], comments = []) {
        this.root = root;
        this.syntaxErrors = syntaxErrors;
        this.comments = comments;
    }
    getNodeFromOffset(offset, includeRightBound = false) {
        if (this.root) {
            return findNodeAtOffset(this.root, offset, includeRightBound);
        }
        return undefined;
    }
    getNodeFromOffsetEndInclusive(offset) {
        return this.root && this.root.getNodeFromOffsetEndInclusive(offset);
    }
    visit(visitor) {
        if (this.root) {
            const doVisit = (node) => {
                let ctn = visitor(node);
                const children = node.children;
                if (Array.isArray(children)) {
                    for (let i = 0; i < children.length && ctn; i++) {
                        ctn = doVisit(children[i]);
                    }
                }
                return ctn;
            };
            doVisit(this.root);
        }
    }
    validate(textDocument, schema) {
        if (this.root && schema) {
            const validationResult = new ValidationResult(this.isKubernetes);
            validate(this.root, schema, schema, validationResult, NoOpSchemaCollector.instance, {
                isKubernetes: this.isKubernetes,
                disableAdditionalProperties: this.disableAdditionalProperties,
                uri: this.uri,
            });
            return validationResult.problems.map((p) => {
                const range = vscode_languageserver_types_1.Range.create(textDocument.positionAt(p.location.offset), textDocument.positionAt(p.location.offset + p.location.length));
                const diagnostic = vscode_languageserver_types_1.Diagnostic.create(range, p.message, p.severity, p.code ? p.code : vscode_json_languageservice_1.ErrorCode.Undefined, p.source);
                diagnostic.data = { schemaUri: p.schemaUri, ...p.data };
                return diagnostic;
            });
        }
        return null;
    }
    /**
     * This method returns the list of applicable schemas
     *
     * currently used @param didCallFromAutoComplete flag to differentiate the method call, when it is from auto complete
     * then user still types something and skip the validation for timebeing untill completed.
     * On https://github.com/redhat-developer/yaml-language-server/pull/719 the auto completes need to populate the list of enum string which matches to the enum
     * and on https://github.com/redhat-developer/vscode-yaml/issues/803 the validation should throw the error based on the enum string.
     *
     * @param schema schema
     * @param focusOffset  offsetValue
     * @param exclude excluded Node
     * @param didCallFromAutoComplete true if method called from AutoComplete
     * @returns array of applicable schemas
     */
    getMatchingSchemas(schema, focusOffset = -1, exclude = null, didCallFromAutoComplete) {
        const matchingSchemas = new SchemaCollector(focusOffset, exclude);
        if (this.root && schema) {
            validate(this.root, schema, schema, new ValidationResult(this.isKubernetes), matchingSchemas, {
                isKubernetes: this.isKubernetes,
                disableAdditionalProperties: this.disableAdditionalProperties,
                uri: this.uri,
                callFromAutoComplete: didCallFromAutoComplete,
            });
        }
        return matchingSchemas.schemas;
    }
}
exports.JSONDocument = JSONDocument;
function validate(node, schema, originalSchema, validationResult, matchingSchemas, options
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) {
    const { isKubernetes, callFromAutoComplete } = options;
    if (!node) {
        return;
    }
    // schema should be an Object
    if (typeof schema !== 'object') {
        return;
    }
    if (!schema.url) {
        schema.url = originalSchema.url;
    }
    schema.closestTitle = schema.title || originalSchema.closestTitle;
    switch (node.type) {
        case 'object':
            _validateObjectNode(node, schema, validationResult, matchingSchemas);
            break;
        case 'array':
            _validateArrayNode(node, schema, validationResult, matchingSchemas);
            break;
        case 'string':
            _validateStringNode(node, schema, validationResult);
            break;
        case 'number':
            _validateNumberNode(node, schema, validationResult);
            break;
        case 'property':
            return validate(node.valueNode, schema, schema, validationResult, matchingSchemas, options);
    }
    _validateNode();
    matchingSchemas.add({ node: node, schema: schema });
    function _validateNode() {
        function matchesType(type) {
            return node.type === type || (type === 'integer' && node.type === 'number' && node.isInteger);
        }
        if (Array.isArray(schema.type)) {
            if (!schema.type.some(matchesType)) {
                validationResult.problems.push({
                    location: { offset: node.offset, length: node.length },
                    severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                    message: schema.errorMessage ||
                        localize('typeArrayMismatchWarning', 'Incorrect type. Expected one of {0}.', schema.type.join(', ')),
                    source: getSchemaSource(schema, originalSchema),
                    schemaUri: getSchemaUri(schema, originalSchema),
                });
            }
        }
        else if (schema.type) {
            if (!matchesType(schema.type)) {
                //get more specific name than just object
                const schemaType = schema.type === 'object' ? (0, schemaUtils_1.getSchemaTypeName)(schema) : schema.type;
                validationResult.problems.push({
                    location: { offset: node.offset, length: node.length },
                    severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                    message: schema.errorMessage || getWarningMessage(ProblemType.typeMismatchWarning, [schemaType]),
                    source: getSchemaSource(schema, originalSchema),
                    schemaUri: getSchemaUri(schema, originalSchema),
                    problemType: ProblemType.typeMismatchWarning,
                    problemArgs: [schemaType],
                });
            }
        }
        if (Array.isArray(schema.allOf)) {
            for (const subSchemaRef of schema.allOf) {
                validate(node, asSchema(subSchemaRef), schema, validationResult, matchingSchemas, options);
            }
        }
        const notSchema = asSchema(schema.not);
        if (notSchema) {
            const subValidationResult = new ValidationResult(isKubernetes);
            const subMatchingSchemas = matchingSchemas.newSub();
            validate(node, notSchema, schema, subValidationResult, subMatchingSchemas, options);
            if (!subValidationResult.hasProblems()) {
                validationResult.problems.push({
                    location: { offset: node.offset, length: node.length },
                    severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                    message: localize('notSchemaWarning', 'Matches a schema that is not allowed.'),
                    source: getSchemaSource(schema, originalSchema),
                    schemaUri: getSchemaUri(schema, originalSchema),
                });
            }
            for (const ms of subMatchingSchemas.schemas) {
                ms.inverted = !ms.inverted;
                matchingSchemas.add(ms);
            }
        }
        const testAlternatives = (alternatives, maxOneMatch) => {
            const matches = [];
            const subMatches = [];
            const noPropertyMatches = [];
            // remember the best match that is used for error messages
            let bestMatch = null;
            for (const subSchemaRef of alternatives) {
                const subSchema = { ...asSchema(subSchemaRef) };
                const subValidationResult = new ValidationResult(isKubernetes);
                const subMatchingSchemas = matchingSchemas.newSub();
                validate(node, subSchema, schema, subValidationResult, subMatchingSchemas, options);
                if (!subValidationResult.hasProblems() || callFromAutoComplete) {
                    matches.push(subSchema);
                    subMatches.push(subSchema);
                    if (subValidationResult.propertiesMatches === 0) {
                        noPropertyMatches.push(subSchema);
                    }
                    if (subSchema.format) {
                        subMatches.pop();
                    }
                }
                if (!bestMatch) {
                    bestMatch = {
                        schema: subSchema,
                        validationResult: subValidationResult,
                        matchingSchemas: subMatchingSchemas,
                    };
                }
                else if (isKubernetes) {
                    bestMatch = alternativeComparison(subValidationResult, bestMatch, subSchema, subMatchingSchemas);
                }
                else {
                    bestMatch = genericComparison(node, maxOneMatch, subValidationResult, bestMatch, subSchema, subMatchingSchemas);
                }
            }
            if (subMatches.length > 1 && (subMatches.length > 1 || noPropertyMatches.length === 0) && maxOneMatch) {
                validationResult.problems.push({
                    location: { offset: node.offset, length: 1 },
                    severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                    message: localize('oneOfWarning', 'Matches multiple schemas when only one must validate.'),
                    source: getSchemaSource(schema, originalSchema),
                    schemaUri: getSchemaUri(schema, originalSchema),
                });
            }
            if (bestMatch !== null) {
                validationResult.merge(bestMatch.validationResult);
                validationResult.propertiesMatches += bestMatch.validationResult.propertiesMatches;
                validationResult.propertiesValueMatches += bestMatch.validationResult.propertiesValueMatches;
                validationResult.enumValueMatch = validationResult.enumValueMatch || bestMatch.validationResult.enumValueMatch;
                if (bestMatch.validationResult.enumValues?.length) {
                    validationResult.enumValues = (validationResult.enumValues || []).concat(bestMatch.validationResult.enumValues);
                }
                matchingSchemas.merge(bestMatch.matchingSchemas);
            }
            return matches.length;
        };
        if (Array.isArray(schema.anyOf)) {
            testAlternatives(schema.anyOf, false);
        }
        if (Array.isArray(schema.oneOf)) {
            testAlternatives(schema.oneOf, true);
        }
        const testBranch = (schema, originalSchema) => {
            const subValidationResult = new ValidationResult(isKubernetes);
            const subMatchingSchemas = matchingSchemas.newSub();
            validate(node, asSchema(schema), originalSchema, subValidationResult, subMatchingSchemas, options);
            validationResult.merge(subValidationResult);
            validationResult.propertiesMatches += subValidationResult.propertiesMatches;
            validationResult.propertiesValueMatches += subValidationResult.propertiesValueMatches;
            matchingSchemas.merge(subMatchingSchemas);
        };
        const testCondition = (ifSchema, originalSchema, thenSchema, elseSchema) => {
            const subSchema = asSchema(ifSchema);
            const subValidationResult = new ValidationResult(isKubernetes);
            const subMatchingSchemas = matchingSchemas.newSub();
            validate(node, subSchema, originalSchema, subValidationResult, subMatchingSchemas, options);
            matchingSchemas.merge(subMatchingSchemas);
            const { filePatternAssociation } = subSchema;
            if (filePatternAssociation) {
                const association = new yamlSchemaService_1.FilePatternAssociation(filePatternAssociation);
                if (!association.matchesPattern(options.uri)) {
                    subValidationResult.problems.push({
                        location: { offset: node.offset, length: node.length },
                        severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                        message: localize('ifFilePatternAssociation', `filePatternAssociation '${filePatternAssociation}' does not match with doc uri '${options.uri}'.`),
                        source: getSchemaSource(schema, originalSchema),
                        schemaUri: getSchemaUri(schema, originalSchema),
                    });
                    // don't want to expose the error up to code-completion results
                    // validationResult.merge(subValidationResult);
                }
            }
            if (!subValidationResult.hasProblems()) {
                if (thenSchema) {
                    testBranch(thenSchema, originalSchema);
                }
            }
            else if (elseSchema) {
                testBranch(elseSchema, originalSchema);
            }
        };
        const ifSchema = asSchema(schema.if);
        if (ifSchema) {
            testCondition(ifSchema, schema, asSchema(schema.then), asSchema(schema.else));
        }
        if (Array.isArray(schema.enum)) {
            const val = getNodeValue(node);
            let enumValueMatch = false;
            for (const e of schema.enum) {
                if (val === e || isAutoCompleteEqualMaybe(callFromAutoComplete, node, val, e)) {
                    enumValueMatch = true;
                    break;
                }
            }
            validationResult.enumValues = schema.enum;
            validationResult.enumValueMatch = enumValueMatch;
            if (!enumValueMatch) {
                validationResult.problems.push({
                    location: { offset: node.offset, length: node.length },
                    severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                    code: vscode_json_languageservice_1.ErrorCode.EnumValueMismatch,
                    message: schema.errorMessage ||
                        localize('enumWarning', 'Value is not accepted. Valid values: {0}.', schema.enum
                            .map((v) => {
                            return JSON.stringify(v);
                        })
                            .join(', ')),
                    source: getSchemaSource(schema, originalSchema),
                    schemaUri: getSchemaUri(schema, originalSchema),
                    data: { values: schema.enum },
                });
            }
        }
        if ((0, objects_1.isDefined)(schema.const)) {
            const val = getNodeValue(node);
            if (!(0, objects_1.equals)(val, schema.const) && !isAutoCompleteEqualMaybe(callFromAutoComplete, node, val, schema.const)) {
                validationResult.problems.push({
                    location: { offset: node.offset, length: node.length },
                    severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                    code: vscode_json_languageservice_1.ErrorCode.EnumValueMismatch,
                    problemType: ProblemType.constWarning,
                    message: schema.errorMessage || getWarningMessage(ProblemType.constWarning, [JSON.stringify(schema.const)]),
                    source: getSchemaSource(schema, originalSchema),
                    schemaUri: getSchemaUri(schema, originalSchema),
                    problemArgs: [JSON.stringify(schema.const)],
                    data: { values: [schema.const] },
                });
                validationResult.enumValueMatch = false;
            }
            else {
                validationResult.enumValueMatch = true;
            }
            validationResult.enumValues = [schema.const];
        }
        if (schema.deprecationMessage && node.parent) {
            validationResult.problems.push({
                location: { offset: node.parent.offset, length: node.parent.length },
                severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                message: schema.deprecationMessage,
                source: getSchemaSource(schema, originalSchema),
                schemaUri: getSchemaUri(schema, originalSchema),
            });
        }
    }
    function _validateNumberNode(node, schema, validationResult) {
        const val = node.value;
        if ((0, objects_1.isNumber)(schema.multipleOf)) {
            if ((0, math_1.floatSafeRemainder)(val, schema.multipleOf) !== 0) {
                validationResult.problems.push({
                    location: { offset: node.offset, length: node.length },
                    severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                    message: localize('multipleOfWarning', 'Value is not divisible by {0}.', schema.multipleOf),
                    source: getSchemaSource(schema, originalSchema),
                    schemaUri: getSchemaUri(schema, originalSchema),
                });
            }
        }
        function getExclusiveLimit(limit, exclusive) {
            if ((0, objects_1.isNumber)(exclusive)) {
                return exclusive;
            }
            if ((0, objects_1.isBoolean)(exclusive) && exclusive) {
                return limit;
            }
            return undefined;
        }
        function getLimit(limit, exclusive) {
            if (!(0, objects_1.isBoolean)(exclusive) || !exclusive) {
                return limit;
            }
            return undefined;
        }
        const exclusiveMinimum = getExclusiveLimit(schema.minimum, schema.exclusiveMinimum);
        if ((0, objects_1.isNumber)(exclusiveMinimum) && val <= exclusiveMinimum) {
            validationResult.problems.push({
                location: { offset: node.offset, length: node.length },
                severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                message: localize('exclusiveMinimumWarning', 'Value is below the exclusive minimum of {0}.', exclusiveMinimum),
                source: getSchemaSource(schema, originalSchema),
                schemaUri: getSchemaUri(schema, originalSchema),
            });
        }
        const exclusiveMaximum = getExclusiveLimit(schema.maximum, schema.exclusiveMaximum);
        if ((0, objects_1.isNumber)(exclusiveMaximum) && val >= exclusiveMaximum) {
            validationResult.problems.push({
                location: { offset: node.offset, length: node.length },
                severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                message: localize('exclusiveMaximumWarning', 'Value is above the exclusive maximum of {0}.', exclusiveMaximum),
                source: getSchemaSource(schema, originalSchema),
                schemaUri: getSchemaUri(schema, originalSchema),
            });
        }
        const minimum = getLimit(schema.minimum, schema.exclusiveMinimum);
        if ((0, objects_1.isNumber)(minimum) && val < minimum) {
            validationResult.problems.push({
                location: { offset: node.offset, length: node.length },
                severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                message: localize('minimumWarning', 'Value is below the minimum of {0}.', minimum),
                source: getSchemaSource(schema, originalSchema),
                schemaUri: getSchemaUri(schema, originalSchema),
            });
        }
        const maximum = getLimit(schema.maximum, schema.exclusiveMaximum);
        if ((0, objects_1.isNumber)(maximum) && val > maximum) {
            validationResult.problems.push({
                location: { offset: node.offset, length: node.length },
                severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                message: localize('maximumWarning', 'Value is above the maximum of {0}.', maximum),
                source: getSchemaSource(schema, originalSchema),
                schemaUri: getSchemaUri(schema, originalSchema),
            });
        }
    }
    function _validateStringNode(node, schema, validationResult) {
        if ((0, objects_1.isNumber)(schema.minLength) && node.value.length < schema.minLength) {
            validationResult.problems.push({
                location: { offset: node.offset, length: node.length },
                severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                message: localize('minLengthWarning', 'String is shorter than the minimum length of {0}.', schema.minLength),
                source: getSchemaSource(schema, originalSchema),
                schemaUri: getSchemaUri(schema, originalSchema),
            });
        }
        if ((0, objects_1.isNumber)(schema.maxLength) && node.value.length > schema.maxLength) {
            validationResult.problems.push({
                location: { offset: node.offset, length: node.length },
                severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                message: localize('maxLengthWarning', 'String is longer than the maximum length of {0}.', schema.maxLength),
                source: getSchemaSource(schema, originalSchema),
                schemaUri: getSchemaUri(schema, originalSchema),
            });
        }
        if ((0, objects_1.isString)(schema.pattern)) {
            const regex = (0, strings_1.safeCreateUnicodeRegExp)(schema.pattern);
            if (!regex.test(node.value)) {
                validationResult.problems.push({
                    location: { offset: node.offset, length: node.length },
                    severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                    message: schema.patternErrorMessage ||
                        schema.errorMessage ||
                        localize('patternWarning', 'String does not match the pattern of "{0}".', schema.pattern),
                    source: getSchemaSource(schema, originalSchema),
                    schemaUri: getSchemaUri(schema, originalSchema),
                });
            }
        }
        if (schema.format) {
            switch (schema.format) {
                case 'uri':
                case 'uri-reference':
                    {
                        let errorMessage;
                        if (!node.value) {
                            errorMessage = localize('uriEmpty', 'URI expected.');
                        }
                        else {
                            try {
                                const uri = vscode_uri_1.URI.parse(node.value);
                                if (!uri.scheme && schema.format === 'uri') {
                                    errorMessage = localize('uriSchemeMissing', 'URI with a scheme is expected.');
                                }
                            }
                            catch (e) {
                                errorMessage = e.message;
                            }
                        }
                        if (errorMessage) {
                            validationResult.problems.push({
                                location: { offset: node.offset, length: node.length },
                                severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                                message: schema.patternErrorMessage ||
                                    schema.errorMessage ||
                                    localize('uriFormatWarning', 'String is not a URI: {0}', errorMessage),
                                source: getSchemaSource(schema, originalSchema),
                                schemaUri: getSchemaUri(schema, originalSchema),
                            });
                        }
                    }
                    break;
                case 'color-hex':
                case 'date-time':
                case 'date':
                case 'time':
                case 'email':
                case 'ipv4':
                case 'ipv6':
                    {
                        const format = exports.formats[schema.format];
                        if (!node.value || !format.pattern.test(node.value)) {
                            validationResult.problems.push({
                                location: { offset: node.offset, length: node.length },
                                severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                                message: schema.patternErrorMessage || schema.errorMessage || format.errorMessage,
                                source: getSchemaSource(schema, originalSchema),
                                schemaUri: getSchemaUri(schema, originalSchema),
                            });
                        }
                    }
                    break;
                default:
            }
        }
    }
    function _validateArrayNode(node, schema, validationResult, matchingSchemas) {
        if (Array.isArray(schema.items)) {
            const subSchemas = schema.items;
            for (let index = 0; index < subSchemas.length; index++) {
                const subSchemaRef = subSchemas[index];
                const subSchema = asSchema(subSchemaRef);
                const itemValidationResult = new ValidationResult(isKubernetes);
                const item = node.items[index];
                if (item) {
                    validate(item, subSchema, schema, itemValidationResult, matchingSchemas, options);
                    validationResult.mergePropertyMatch(itemValidationResult);
                    validationResult.mergeEnumValues(itemValidationResult);
                }
                else if (node.items.length >= subSchemas.length) {
                    validationResult.propertiesValueMatches++;
                }
            }
            if (node.items.length > subSchemas.length) {
                if (typeof schema.additionalItems === 'object') {
                    for (let i = subSchemas.length; i < node.items.length; i++) {
                        const itemValidationResult = new ValidationResult(isKubernetes);
                        // eslint-disable-next-line @typescript-eslint/no-explicit-any
                        validate(node.items[i], schema.additionalItems, schema, itemValidationResult, matchingSchemas, options);
                        validationResult.mergePropertyMatch(itemValidationResult);
                        validationResult.mergeEnumValues(itemValidationResult);
                    }
                }
                else if (schema.additionalItems === false) {
                    validationResult.problems.push({
                        location: { offset: node.offset, length: node.length },
                        severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                        message: localize('additionalItemsWarning', 'Array has too many items according to schema. Expected {0} or fewer.', subSchemas.length),
                        source: getSchemaSource(schema, originalSchema),
                        schemaUri: getSchemaUri(schema, originalSchema),
                    });
                }
            }
        }
        else {
            const itemSchema = asSchema(schema.items);
            if (itemSchema) {
                const itemValidationResult = new ValidationResult(isKubernetes);
                node.items.forEach((item) => {
                    if (itemSchema.oneOf && itemSchema.oneOf.length === 1) {
                        const subSchemaRef = itemSchema.oneOf[0];
                        const subSchema = { ...asSchema(subSchemaRef) };
                        subSchema.title = schema.title;
                        subSchema.closestTitle = schema.closestTitle;
                        validate(item, subSchema, schema, itemValidationResult, matchingSchemas, options);
                        validationResult.mergePropertyMatch(itemValidationResult);
                        validationResult.mergeEnumValues(itemValidationResult);
                    }
                    else {
                        validate(item, itemSchema, schema, itemValidationResult, matchingSchemas, options);
                        validationResult.mergePropertyMatch(itemValidationResult);
                        validationResult.mergeEnumValues(itemValidationResult);
                    }
                });
            }
        }
        const containsSchema = asSchema(schema.contains);
        if (containsSchema) {
            const doesContain = node.items.some((item) => {
                const itemValidationResult = new ValidationResult(isKubernetes);
                validate(item, containsSchema, schema, itemValidationResult, NoOpSchemaCollector.instance, options);
                return !itemValidationResult.hasProblems();
            });
            if (!doesContain) {
                validationResult.problems.push({
                    location: { offset: node.offset, length: node.length },
                    severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                    message: schema.errorMessage || localize('requiredItemMissingWarning', 'Array does not contain required item.'),
                    source: getSchemaSource(schema, originalSchema),
                    schemaUri: getSchemaUri(schema, originalSchema),
                });
            }
        }
        if ((0, objects_1.isNumber)(schema.minItems) && node.items.length < schema.minItems) {
            validationResult.problems.push({
                location: { offset: node.offset, length: node.length },
                severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                message: localize('minItemsWarning', 'Array has too few items. Expected {0} or more.', schema.minItems),
                source: getSchemaSource(schema, originalSchema),
                schemaUri: getSchemaUri(schema, originalSchema),
            });
        }
        if ((0, objects_1.isNumber)(schema.maxItems) && node.items.length > schema.maxItems) {
            validationResult.problems.push({
                location: { offset: node.offset, length: node.length },
                severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                message: localize('maxItemsWarning', 'Array has too many items. Expected {0} or fewer.', schema.maxItems),
                source: getSchemaSource(schema, originalSchema),
                schemaUri: getSchemaUri(schema, originalSchema),
            });
        }
        if (schema.uniqueItems === true) {
            const values = getNodeValue(node);
            const duplicates = values.some((value, index) => {
                return index !== values.lastIndexOf(value);
            });
            if (duplicates) {
                validationResult.problems.push({
                    location: { offset: node.offset, length: node.length },
                    severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                    message: localize('uniqueItemsWarning', 'Array has duplicate items.'),
                    source: getSchemaSource(schema, originalSchema),
                    schemaUri: getSchemaUri(schema, originalSchema),
                });
            }
        }
    }
    function _validateObjectNode(node, schema, validationResult, matchingSchemas) {
        const seenKeys = Object.create(null);
        const unprocessedProperties = [];
        const unprocessedNodes = [...node.properties];
        while (unprocessedNodes.length > 0) {
            const propertyNode = unprocessedNodes.pop();
            const key = propertyNode.keyNode.value;
            //Replace the merge key with the actual values of what the node value points to in seen keys
            if (key === '<<' && propertyNode.valueNode) {
                switch (propertyNode.valueNode.type) {
                    case 'object': {
                        unprocessedNodes.push(...propertyNode.valueNode['properties']);
                        break;
                    }
                    case 'array': {
                        propertyNode.valueNode['items'].forEach((sequenceNode) => {
                            if (sequenceNode && (0, objects_1.isIterable)(sequenceNode['properties'])) {
                                unprocessedNodes.push(...sequenceNode['properties']);
                            }
                        });
                        break;
                    }
                    default: {
                        break;
                    }
                }
            }
            else {
                seenKeys[key] = propertyNode.valueNode;
                unprocessedProperties.push(key);
            }
        }
        if (Array.isArray(schema.required)) {
            for (const propertyName of schema.required) {
                if (seenKeys[propertyName] === undefined) {
                    const keyNode = node.parent && node.parent.type === 'property' && node.parent.keyNode;
                    const location = keyNode ? { offset: keyNode.offset, length: keyNode.length } : { offset: node.offset, length: 1 };
                    validationResult.problems.push({
                        location: location,
                        severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                        message: getWarningMessage(ProblemType.missingRequiredPropWarning, [propertyName]),
                        source: getSchemaSource(schema, originalSchema),
                        schemaUri: getSchemaUri(schema, originalSchema),
                        problemArgs: [propertyName],
                        problemType: ProblemType.missingRequiredPropWarning,
                    });
                }
            }
        }
        const propertyProcessed = (prop) => {
            let index = unprocessedProperties.indexOf(prop);
            while (index >= 0) {
                unprocessedProperties.splice(index, 1);
                index = unprocessedProperties.indexOf(prop);
            }
        };
        if (schema.properties) {
            for (const propertyName of Object.keys(schema.properties)) {
                propertyProcessed(propertyName);
                const propertySchema = schema.properties[propertyName];
                const child = seenKeys[propertyName];
                if (child) {
                    if ((0, objects_1.isBoolean)(propertySchema)) {
                        if (!propertySchema) {
                            const propertyNode = child.parent;
                            validationResult.problems.push({
                                location: {
                                    offset: propertyNode.keyNode.offset,
                                    length: propertyNode.keyNode.length,
                                },
                                severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                                message: schema.errorMessage || localize('DisallowedExtraPropWarning', MSG_PROPERTY_NOT_ALLOWED, propertyName),
                                source: getSchemaSource(schema, originalSchema),
                                schemaUri: getSchemaUri(schema, originalSchema),
                            });
                        }
                        else {
                            validationResult.propertiesMatches++;
                            validationResult.propertiesValueMatches++;
                        }
                    }
                    else {
                        propertySchema.url = schema.url ?? originalSchema.url;
                        const propertyValidationResult = new ValidationResult(isKubernetes);
                        validate(child, propertySchema, schema, propertyValidationResult, matchingSchemas, options);
                        validationResult.mergePropertyMatch(propertyValidationResult);
                        validationResult.mergeEnumValues(propertyValidationResult);
                    }
                }
            }
        }
        if (schema.patternProperties) {
            for (const propertyPattern of Object.keys(schema.patternProperties)) {
                const regex = (0, strings_1.safeCreateUnicodeRegExp)(propertyPattern);
                for (const propertyName of unprocessedProperties.slice(0)) {
                    if (regex.test(propertyName)) {
                        propertyProcessed(propertyName);
                        const child = seenKeys[propertyName];
                        if (child) {
                            const propertySchema = schema.patternProperties[propertyPattern];
                            if ((0, objects_1.isBoolean)(propertySchema)) {
                                if (!propertySchema) {
                                    const propertyNode = child.parent;
                                    validationResult.problems.push({
                                        location: {
                                            offset: propertyNode.keyNode.offset,
                                            length: propertyNode.keyNode.length,
                                        },
                                        severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                                        message: schema.errorMessage || localize('DisallowedExtraPropWarning', MSG_PROPERTY_NOT_ALLOWED, propertyName),
                                        source: getSchemaSource(schema, originalSchema),
                                        schemaUri: getSchemaUri(schema, originalSchema),
                                    });
                                }
                                else {
                                    validationResult.propertiesMatches++;
                                    validationResult.propertiesValueMatches++;
                                }
                            }
                            else {
                                const propertyValidationResult = new ValidationResult(isKubernetes);
                                validate(child, propertySchema, schema, propertyValidationResult, matchingSchemas, options);
                                validationResult.mergePropertyMatch(propertyValidationResult);
                                validationResult.mergeEnumValues(propertyValidationResult);
                            }
                        }
                    }
                }
            }
        }
        if (typeof schema.additionalProperties === 'object') {
            for (const propertyName of unprocessedProperties) {
                const child = seenKeys[propertyName];
                if (child) {
                    const propertyValidationResult = new ValidationResult(isKubernetes);
                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                    validate(child, schema.additionalProperties, schema, propertyValidationResult, matchingSchemas, options);
                    validationResult.mergePropertyMatch(propertyValidationResult);
                    validationResult.mergeEnumValues(propertyValidationResult);
                }
            }
        }
        else if (schema.additionalProperties === false ||
            (schema.type === 'object' && schema.additionalProperties === undefined && options.disableAdditionalProperties === true)) {
            if (unprocessedProperties.length > 0) {
                const possibleProperties = schema.properties &&
                    Object.entries(schema.properties)
                        .filter(([key, property]) => {
                        // don't include existing properties
                        if (seenKeys[key]) {
                            return false;
                        }
                        // don't include properties that are not suggested in completion
                        if (property && typeof property === 'object' && (property.doNotSuggest || property.deprecationMessage)) {
                            return false;
                        }
                        return true;
                    })
                        .map(([key]) => key);
                for (const propertyName of unprocessedProperties) {
                    const child = seenKeys[propertyName];
                    if (child) {
                        let propertyNode = null;
                        if (child.type !== 'property') {
                            propertyNode = child.parent;
                            if (propertyNode.type === 'object') {
                                propertyNode = propertyNode.properties[0];
                            }
                        }
                        else {
                            propertyNode = child;
                        }
                        const problem = {
                            location: {
                                offset: propertyNode.keyNode.offset,
                                length: propertyNode.keyNode.length,
                            },
                            severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                            code: vscode_json_languageservice_1.ErrorCode.PropertyExpected,
                            message: schema.errorMessage || localize('DisallowedExtraPropWarning', MSG_PROPERTY_NOT_ALLOWED, propertyName),
                            source: getSchemaSource(schema, originalSchema),
                            schemaUri: getSchemaUri(schema, originalSchema),
                        };
                        if (possibleProperties?.length) {
                            problem.data = { properties: possibleProperties };
                        }
                        validationResult.problems.push(problem);
                    }
                }
            }
        }
        if ((0, objects_1.isNumber)(schema.maxProperties)) {
            if (node.properties.length > schema.maxProperties) {
                validationResult.problems.push({
                    location: { offset: node.offset, length: node.length },
                    severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                    message: localize('MaxPropWarning', 'Object has more properties than limit of {0}.', schema.maxProperties),
                    source: getSchemaSource(schema, originalSchema),
                    schemaUri: getSchemaUri(schema, originalSchema),
                });
            }
        }
        if ((0, objects_1.isNumber)(schema.minProperties)) {
            if (node.properties.length < schema.minProperties) {
                validationResult.problems.push({
                    location: { offset: node.offset, length: node.length },
                    severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                    message: localize('MinPropWarning', 'Object has fewer properties than the required number of {0}', schema.minProperties),
                    source: getSchemaSource(schema, originalSchema),
                    schemaUri: getSchemaUri(schema, originalSchema),
                });
            }
        }
        if (schema.dependencies) {
            for (const key of Object.keys(schema.dependencies)) {
                const prop = seenKeys[key];
                if (prop) {
                    const propertyDep = schema.dependencies[key];
                    if (Array.isArray(propertyDep)) {
                        for (const requiredProp of propertyDep) {
                            if (!seenKeys[requiredProp]) {
                                validationResult.problems.push({
                                    location: { offset: node.offset, length: node.length },
                                    severity: vscode_languageserver_types_1.DiagnosticSeverity.Warning,
                                    message: localize('RequiredDependentPropWarning', 'Object is missing property {0} required by property {1}.', requiredProp, key),
                                    source: getSchemaSource(schema, originalSchema),
                                    schemaUri: getSchemaUri(schema, originalSchema),
                                });
                            }
                            else {
                                validationResult.propertiesValueMatches++;
                            }
                        }
                    }
                    else {
                        const propertySchema = asSchema(propertyDep);
                        if (propertySchema) {
                            const propertyValidationResult = new ValidationResult(isKubernetes);
                            validate(node, propertySchema, schema, propertyValidationResult, matchingSchemas, options);
                            validationResult.mergePropertyMatch(propertyValidationResult);
                            validationResult.mergeEnumValues(propertyValidationResult);
                        }
                    }
                }
            }
        }
        const propertyNames = asSchema(schema.propertyNames);
        if (propertyNames) {
            for (const f of node.properties) {
                const key = f.keyNode;
                if (key) {
                    validate(key, propertyNames, schema, validationResult, NoOpSchemaCollector.instance, options);
                }
            }
        }
    }
    //Alternative comparison is specifically used by the kubernetes/openshift schema but may lead to better results then genericComparison depending on the schema
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    function alternativeComparison(subValidationResult, bestMatch, subSchema, subMatchingSchemas) {
        const compareResult = subValidationResult.compareKubernetes(bestMatch.validationResult);
        if (compareResult > 0) {
            // our node is the best matching so far
            bestMatch = {
                schema: subSchema,
                validationResult: subValidationResult,
                matchingSchemas: subMatchingSchemas,
            };
        }
        else if (compareResult === 0) {
            // there's already a best matching but we are as good
            bestMatch.matchingSchemas.merge(subMatchingSchemas);
            bestMatch.validationResult.mergeEnumValues(subValidationResult);
        }
        return bestMatch;
    }
    //genericComparison tries to find the best matching schema using a generic comparison
    function genericComparison(node, maxOneMatch, subValidationResult, bestMatch, subSchema, subMatchingSchemas) {
        if (!maxOneMatch && !subValidationResult.hasProblems() && !bestMatch.validationResult.hasProblems()) {
            // no errors, both are equally good matches
            bestMatch.matchingSchemas.merge(subMatchingSchemas);
            bestMatch.validationResult.propertiesMatches += subValidationResult.propertiesMatches;
            bestMatch.validationResult.propertiesValueMatches += subValidationResult.propertiesValueMatches;
        }
        else {
            const compareResult = subValidationResult.compareGeneric(bestMatch.validationResult);
            if (compareResult > 0 ||
                (compareResult === 0 &&
                    maxOneMatch &&
                    bestMatch.schema.type === 'object' &&
                    node.type !== 'null' &&
                    node.type !== bestMatch.schema.type)) {
                // our node is the best matching so far
                bestMatch = {
                    schema: subSchema,
                    validationResult: subValidationResult,
                    matchingSchemas: subMatchingSchemas,
                };
            }
            else if (compareResult === 0 ||
                ((node.value === null || node.type === 'null') && node.length === 0) // node with no value can match any schema potentially
            ) {
                // there's already a best matching but we are as good
                mergeValidationMatches(bestMatch, subMatchingSchemas, subValidationResult);
            }
        }
        return bestMatch;
    }
    function mergeValidationMatches(bestMatch, subMatchingSchemas, subValidationResult) {
        bestMatch.matchingSchemas.merge(subMatchingSchemas);
        bestMatch.validationResult.mergeEnumValues(subValidationResult);
        bestMatch.validationResult.mergeWarningGeneric(subValidationResult, [
            ProblemType.missingRequiredPropWarning,
            ProblemType.typeMismatchWarning,
            ProblemType.constWarning,
        ]);
    }
}
function getSchemaSource(schema, originalSchema) {
    if (schema) {
        let label;
        if (schema.title) {
            label = schema.title;
        }
        else if (schema.closestTitle) {
            label = schema.closestTitle;
        }
        else if (originalSchema.closestTitle) {
            label = originalSchema.closestTitle;
        }
        else {
            const uriString = schema.url ?? originalSchema.url;
            if (uriString) {
                const url = vscode_uri_1.URI.parse(uriString);
                if (url.scheme === 'file') {
                    label = url.fsPath;
                }
                label = url.toString();
            }
        }
        if (label) {
            return `${YAML_SCHEMA_PREFIX}${label}`;
        }
    }
    return exports.YAML_SOURCE;
}
function getSchemaUri(schema, originalSchema) {
    const uriString = schema.url ?? originalSchema.url;
    return uriString ? [uriString] : [];
}
function getWarningMessage(problemType, args) {
    return localize(problemType, exports.ProblemTypeMessages[problemType], args.join(' | '));
}
/**
 * if callFromAutoComplete than compare value from yaml and value from schema (s.const | s.enum[i])
 * allows partial match for autocompletion
 */
function isAutoCompleteEqualMaybe(callFromAutoComplete, node, nodeValue, schemaValue) {
    if (!callFromAutoComplete) {
        return false;
    }
    // if autocompletion property doesn't have value, then it could be a match
    const isWithoutValue = nodeValue === null && node.length === 0; // allows `prop: ` but ignore `prop: null`
    if (isWithoutValue) {
        return true;
    }
    return (0, objects_1.isString)(nodeValue) && (0, objects_1.isString)(schemaValue) && schemaValue.startsWith(nodeValue);
}
//# sourceMappingURL=jsonParser07.js.map