/*---------------------------------------------------------------------------------------------
 *  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.
 *--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
const objects = require("../utils/objects");
const nls = require("vscode-nls");
const localize = nls.loadMessageBundle();
var ErrorCode;
(function (ErrorCode) {
    ErrorCode[ErrorCode["Undefined"] = 0] = "Undefined";
    ErrorCode[ErrorCode["EnumValueMismatch"] = 1] = "EnumValueMismatch";
    ErrorCode[ErrorCode["CommentsNotAllowed"] = 2] = "CommentsNotAllowed";
})(ErrorCode = exports.ErrorCode || (exports.ErrorCode = {}));
var ProblemSeverity;
(function (ProblemSeverity) {
    ProblemSeverity[ProblemSeverity["Error"] = 0] = "Error";
    ProblemSeverity[ProblemSeverity["Warning"] = 1] = "Warning";
})(ProblemSeverity = exports.ProblemSeverity || (exports.ProblemSeverity = {}));
class ASTNode {
    constructor(parent, type, location, start, end) {
        this.type = type;
        this.location = location;
        this.start = start;
        this.end = end;
        this.parent = parent;
        this.parserSettings = {
            isKubernetes: false
        };
    }
    setParserSettings(parserSettings) {
        this.parserSettings = parserSettings;
    }
    getPath() {
        const path = this.parent ? this.parent.getPath() : [];
        if (this.location !== null) {
            path.push(this.location);
        }
        return path;
    }
    getChildNodes() {
        return [];
    }
    getLastChild() {
        return null;
    }
    // tslint:disable-next-line: no-any
    getValue() {
        // override in children
        return;
    }
    contains(offset, includeRightBound = false) {
        return offset >= this.start && offset < this.end || includeRightBound && offset === this.end;
    }
    toString() {
        return 'type: ' + this.type + ' (' + this.start + '/' + this.end + ')' + (this.parent ? ' parent: {' + this.parent.toString() + '}' : '');
    }
    visit(visitor) {
        return visitor(this);
    }
    getNodeFromOffset(offset) {
        const findNode = (node) => {
            if (offset >= node.start && offset < node.end) {
                const children = node.getChildNodes();
                for (let i = 0; i < children.length && children[i].start <= offset; i++) {
                    const item = findNode(children[i]);
                    if (item) {
                        return item;
                    }
                }
                return node;
            }
            return null;
        };
        return findNode(this);
    }
    getNodeCollectorCount(offset) {
        const collector = [];
        const findNode = (node) => {
            const children = node.getChildNodes();
            for (let i = 0; i < children.length; i++) {
                const item = findNode(children[i]);
                if (item && item.type === 'property') {
                    collector.push(item);
                }
            }
            return node;
        };
        const foundNode = findNode(this);
        return collector.length;
    }
    getNodeFromOffsetEndInclusive(offset) {
        const collector = [];
        const findNode = (node) => {
            if (offset >= node.start && offset <= node.end) {
                const children = node.getChildNodes();
                for (let i = 0; i < children.length && children[i].start <= 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 possibleNode in collector) {
            const currNode = collector[possibleNode];
            const minDist = (currNode.end - offset) + (offset - currNode.start);
            if (minDist < currMinDist) {
                currMinNode = currNode;
                currMinDist = minDist;
            }
        }
        return currMinNode || foundNode;
    }
    validate(schema, validationResult, matchingSchemas) {
        if (!matchingSchemas.include(this)) {
            return;
        }
        if (Array.isArray(schema.type)) {
            if (schema.type.indexOf(this.type) === -1) {
                validationResult.problems.push({
                    location: { start: this.start, end: this.end },
                    severity: ProblemSeverity.Warning,
                    message: schema.errorMessage || localize('typeArrayMismatchWarning', 'Incorrect type. Expected one of {0}.', schema.type.join(', '))
                });
            }
        }
        else if (schema.type) {
            if (this.type !== schema.type) {
                validationResult.problems.push({
                    location: { start: this.start, end: this.end },
                    severity: ProblemSeverity.Warning,
                    message: schema.errorMessage || localize('typeMismatchWarning', 'Incorrect type. Expected "{0}".', schema.type)
                });
            }
        }
        if (Array.isArray(schema.allOf)) {
            schema.allOf.forEach(subSchema => {
                this.validate(subSchema, validationResult, matchingSchemas);
            });
        }
        if (schema.not) {
            const subValidationResult = new ValidationResult();
            const subMatchingSchemas = matchingSchemas.newSub();
            this.validate(schema.not, subValidationResult, subMatchingSchemas);
            if (!subValidationResult.hasProblems()) {
                validationResult.problems.push({
                    location: { start: this.start, end: this.end },
                    severity: ProblemSeverity.Warning,
                    message: localize('notSchemaWarning', 'Matches a schema that is not allowed.')
                });
            }
            subMatchingSchemas.schemas.forEach(ms => {
                ms.inverted = !ms.inverted;
                matchingSchemas.add(ms);
            });
        }
        const testAlternatives = (alternatives, maxOneMatch) => {
            const matches = [];
            // remember the best match that is used for error messages
            let bestMatch = null;
            alternatives.forEach(subSchema => {
                const subValidationResult = new ValidationResult();
                const subMatchingSchemas = matchingSchemas.newSub();
                this.validate(subSchema, subValidationResult, subMatchingSchemas);
                if (!subValidationResult.hasProblems()) {
                    matches.push(subSchema);
                }
                if (!bestMatch) {
                    bestMatch = { schema: subSchema, validationResult: subValidationResult, matchingSchemas: subMatchingSchemas };
                }
                else if (this.parserSettings.isKubernetes) {
                    bestMatch = alternativeComparison(subValidationResult, bestMatch, subSchema, subMatchingSchemas);
                }
                else {
                    bestMatch = genericComparison(maxOneMatch, subValidationResult, bestMatch, subSchema, subMatchingSchemas);
                }
            });
            if (matches.length > 1 && maxOneMatch && !this.parserSettings.isKubernetes) {
                validationResult.problems.push({
                    location: { start: this.start, end: this.start + 1 },
                    severity: ProblemSeverity.Warning,
                    message: localize('oneOfWarning', 'Matches multiple schemas when only one must validate.')
                });
            }
            if (bestMatch !== null) {
                validationResult.merge(bestMatch.validationResult);
                validationResult.propertiesMatches += bestMatch.validationResult.propertiesMatches;
                validationResult.propertiesValueMatches += bestMatch.validationResult.propertiesValueMatches;
                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);
        }
        if (Array.isArray(schema.enum)) {
            const val = this.getValue();
            let enumValueMatch = false;
            for (const e of schema.enum) {
                if (objects.equals(val, e)) {
                    enumValueMatch = true;
                    break;
                }
            }
            validationResult.enumValues = schema.enum;
            validationResult.enumValueMatch = enumValueMatch;
            if (!enumValueMatch) {
                validationResult.problems.push({
                    location: { start: this.start, end: this.end },
                    severity: ProblemSeverity.Warning,
                    code: ErrorCode.EnumValueMismatch,
                    message: schema.errorMessage || localize('enumWarning', 'Value is not accepted. Valid values: {0}.', schema.enum.map(v => JSON.stringify(v)).join(', '))
                });
            }
        }
        if (schema.deprecationMessage && this.parent) {
            validationResult.problems.push({
                location: { start: this.parent.start, end: this.parent.end },
                severity: ProblemSeverity.Warning,
                message: schema.deprecationMessage
            });
        }
        matchingSchemas.add({ node: this, schema: schema });
    }
}
exports.ASTNode = ASTNode;
class NullASTNode extends ASTNode {
    constructor(parent, name, start, end) {
        super(parent, 'null', name, start, end);
    }
    // tslint:disable-next-line: no-any
    getValue() {
        return null;
    }
}
exports.NullASTNode = NullASTNode;
class BooleanASTNode extends ASTNode {
    constructor(parent, name, value, start, end) {
        super(parent, 'boolean', name, start, end);
        this.value = value;
    }
    // tslint:disable-next-line: no-any
    getValue() {
        return this.value;
    }
}
exports.BooleanASTNode = BooleanASTNode;
class ArrayASTNode extends ASTNode {
    constructor(parent, name, start, end) {
        super(parent, 'array', name, start, end);
        this.items = [];
    }
    getChildNodes() {
        return this.items;
    }
    getLastChild() {
        return this.items[this.items.length - 1];
    }
    // tslint:disable-next-line: no-any
    getValue() {
        return this.items.map(v => v.getValue());
    }
    addItem(item) {
        if (item) {
            this.items.push(item);
            return true;
        }
        return false;
    }
    visit(visitor) {
        let ctn = visitor(this);
        for (let i = 0; i < this.items.length && ctn; i++) {
            ctn = this.items[i].visit(visitor);
        }
        return ctn;
    }
    validate(schema, validationResult, matchingSchemas) {
        if (!matchingSchemas.include(this)) {
            return;
        }
        super.validate(schema, validationResult, matchingSchemas);
        if (Array.isArray(schema.items)) {
            const subSchemas = schema.items;
            subSchemas.forEach((subSchema, index) => {
                const itemValidationResult = new ValidationResult();
                const item = this.items[index];
                if (item) {
                    item.validate(subSchema, itemValidationResult, matchingSchemas);
                    validationResult.mergePropertyMatch(itemValidationResult);
                }
                else if (this.items.length >= subSchemas.length) {
                    validationResult.propertiesValueMatches++;
                }
            });
            if (this.items.length > subSchemas.length) {
                if (typeof schema.additionalItems === 'object') {
                    for (let i = subSchemas.length; i < this.items.length; i++) {
                        const itemValidationResult = new ValidationResult();
                        // tslint:disable-next-line: no-any
                        this.items[i].validate(schema.additionalItems, itemValidationResult, matchingSchemas);
                        validationResult.mergePropertyMatch(itemValidationResult);
                    }
                }
                else if (schema.additionalItems === false) {
                    validationResult.problems.push({
                        location: { start: this.start, end: this.end },
                        severity: ProblemSeverity.Warning,
                        message: localize('additionalItemsWarning', 'Array has too many items according to schema. Expected {0} or fewer.', subSchemas.length)
                    });
                }
            }
        }
        else if (schema.items) {
            this.items.forEach(item => {
                const itemValidationResult = new ValidationResult();
                item.validate(schema.items, itemValidationResult, matchingSchemas);
                validationResult.mergePropertyMatch(itemValidationResult);
            });
        }
        if (schema.minItems && this.items.length < schema.minItems) {
            validationResult.problems.push({
                location: { start: this.start, end: this.end },
                severity: ProblemSeverity.Warning,
                message: localize('minItemsWarning', 'Array has too few items. Expected {0} or more.', schema.minItems)
            });
        }
        if (schema.maxItems && this.items.length > schema.maxItems) {
            validationResult.problems.push({
                location: { start: this.start, end: this.end },
                severity: ProblemSeverity.Warning,
                message: localize('maxItemsWarning', 'Array has too many items. Expected {0} or fewer.', schema.minItems)
            });
        }
        if (schema.uniqueItems === true) {
            const values = this.items.map(node => node.getValue());
            const duplicates = values.some((value, index) => index !== values.lastIndexOf(value));
            if (duplicates) {
                validationResult.problems.push({
                    location: { start: this.start, end: this.end },
                    severity: ProblemSeverity.Warning,
                    message: localize('uniqueItemsWarning', 'Array has duplicate items.')
                });
            }
        }
    }
}
exports.ArrayASTNode = ArrayASTNode;
class NumberASTNode extends ASTNode {
    constructor(parent, name, start, end) {
        super(parent, 'number', name, start, end);
        this.isInteger = true;
        this.value = Number.NaN;
    }
    // tslint:disable-next-line: no-any
    getValue() {
        return this.value;
    }
    validate(schema, validationResult, matchingSchemas) {
        if (!matchingSchemas.include(this)) {
            return;
        }
        // work around type validation in the base class
        let typeIsInteger = false;
        if (schema.type === 'integer' || (Array.isArray(schema.type) && schema.type.indexOf('integer') !== -1)) {
            typeIsInteger = true;
        }
        if (typeIsInteger && this.isInteger === true) {
            this.type = 'integer';
        }
        super.validate(schema, validationResult, matchingSchemas);
        this.type = 'number';
        const val = this.getValue();
        if (typeof schema.multipleOf === 'number') {
            if (val % schema.multipleOf !== 0) {
                validationResult.problems.push({
                    location: { start: this.start, end: this.end },
                    severity: ProblemSeverity.Warning,
                    message: localize('multipleOfWarning', 'Value is not divisible by {0}.', schema.multipleOf)
                });
            }
        }
        if (typeof schema.minimum === 'number') {
            if (schema.exclusiveMinimum && val <= schema.minimum) {
                validationResult.problems.push({
                    location: { start: this.start, end: this.end },
                    severity: ProblemSeverity.Warning,
                    message: localize('exclusiveMinimumWarning', 'Value is below the exclusive minimum of {0}.', schema.minimum)
                });
            }
            if (!schema.exclusiveMinimum && val < schema.minimum) {
                validationResult.problems.push({
                    location: { start: this.start, end: this.end },
                    severity: ProblemSeverity.Warning,
                    message: localize('minimumWarning', 'Value is below the minimum of {0}.', schema.minimum)
                });
            }
        }
        if (typeof schema.maximum === 'number') {
            if (schema.exclusiveMaximum && val >= schema.maximum) {
                validationResult.problems.push({
                    location: { start: this.start, end: this.end },
                    severity: ProblemSeverity.Warning,
                    message: localize('exclusiveMaximumWarning', 'Value is above the exclusive maximum of {0}.', schema.maximum)
                });
            }
            if (!schema.exclusiveMaximum && val > schema.maximum) {
                validationResult.problems.push({
                    location: { start: this.start, end: this.end },
                    severity: ProblemSeverity.Warning,
                    message: localize('maximumWarning', 'Value is above the maximum of {0}.', schema.maximum)
                });
            }
        }
    }
}
exports.NumberASTNode = NumberASTNode;
class StringASTNode extends ASTNode {
    constructor(parent, name, isKey, start, end) {
        super(parent, 'string', name, start, end);
        this.isKey = isKey;
        this.value = '';
    }
    // tslint:disable-next-line: no-any
    getValue() {
        return this.value;
    }
    validate(schema, validationResult, matchingSchemas) {
        if (!matchingSchemas.include(this)) {
            return;
        }
        super.validate(schema, validationResult, matchingSchemas);
        if (schema.minLength && this.value.length < schema.minLength) {
            validationResult.problems.push({
                location: { start: this.start, end: this.end },
                severity: ProblemSeverity.Warning,
                message: localize('minLengthWarning', 'String is shorter than the minimum length of {0}.', schema.minLength)
            });
        }
        if (schema.maxLength && this.value.length > schema.maxLength) {
            validationResult.problems.push({
                location: { start: this.start, end: this.end },
                severity: ProblemSeverity.Warning,
                message: localize('maxLengthWarning', 'String is longer than the maximum length of {0}.', schema.maxLength)
            });
        }
        if (schema.pattern) {
            const regex = new RegExp(schema.pattern);
            if (!regex.test(this.value)) {
                validationResult.problems.push({
                    location: { start: this.start, end: this.end },
                    severity: ProblemSeverity.Warning,
                    message: schema.patternErrorMessage || schema.errorMessage || localize('patternWarning', 'String does not match the pattern of "{0}".', schema.pattern)
                });
            }
        }
    }
}
exports.StringASTNode = StringASTNode;
class PropertyASTNode extends ASTNode {
    constructor(parent, key) {
        super(parent, 'property', null, key.start);
        this.key = key;
        key.parent = this;
        key.location = key.value;
        this.colonOffset = -1;
    }
    getChildNodes() {
        return this.value ? [this.key, this.value] : [this.key];
    }
    getLastChild() {
        return this.value;
    }
    setValue(value) {
        this.value = value;
        return value !== null;
    }
    visit(visitor) {
        return visitor(this) && this.key.visit(visitor) && this.value && this.value.visit(visitor);
    }
    validate(schema, validationResult, matchingSchemas) {
        if (!matchingSchemas.include(this)) {
            return;
        }
        if (this.value) {
            this.value.validate(schema, validationResult, matchingSchemas);
        }
    }
}
exports.PropertyASTNode = PropertyASTNode;
class ObjectASTNode extends ASTNode {
    constructor(parent, name, start, end) {
        super(parent, 'object', name, start, end);
        this.properties = [];
    }
    getChildNodes() {
        return this.properties;
    }
    getLastChild() {
        return this.properties[this.properties.length - 1];
    }
    addProperty(node) {
        if (!node) {
            return false;
        }
        this.properties.push(node);
        return true;
    }
    getFirstProperty(key) {
        for (let i = 0; i < this.properties.length; i++) {
            if (this.properties[i].key.value === key) {
                return this.properties[i];
            }
        }
        return null;
    }
    getKeyList() {
        return this.properties.map(p => p.key.getValue());
    }
    // tslint:disable-next-line: no-any
    getValue() {
        // tslint:disable-next-line: no-any
        const value = Object.create(null);
        this.properties.forEach(p => {
            const v = p.value && p.value.getValue();
            if (typeof v !== 'undefined') {
                value[p.key.getValue()] = v;
            }
        });
        return value;
    }
    visit(visitor) {
        let ctn = visitor(this);
        for (let i = 0; i < this.properties.length && ctn; i++) {
            ctn = this.properties[i].visit(visitor);
        }
        return ctn;
    }
    validate(schema, validationResult, matchingSchemas) {
        if (!matchingSchemas.include(this)) {
            return;
        }
        super.validate(schema, validationResult, matchingSchemas);
        const seenKeys = Object.create(null);
        const unprocessedProperties = [];
        this.properties.forEach(node => {
            const key = node.key.value;
            //Replace the merge key with the actual values of what the node value points to in seen keys
            if (key === '<<' && node.value) {
                switch (node.value.type) {
                    case 'object': {
                        node.value['properties'].forEach(propASTNode => {
                            const propKey = propASTNode.key.value;
                            seenKeys[propKey] = propASTNode.value;
                            unprocessedProperties.push(propKey);
                        });
                        break;
                    }
                    case 'array': {
                        node.value['items'].forEach(sequenceNode => {
                            sequenceNode['properties'].forEach(propASTNode => {
                                const seqKey = propASTNode.key.value;
                                seenKeys[seqKey] = propASTNode.value;
                                unprocessedProperties.push(seqKey);
                            });
                        });
                        break;
                    }
                    default: {
                        break;
                    }
                }
            }
            else {
                seenKeys[key] = node.value;
                unprocessedProperties.push(key);
            }
        });
        if (Array.isArray(schema.required)) {
            schema.required.forEach((propertyName) => {
                if (!seenKeys[propertyName]) {
                    const key = this.parent && this.parent && this.parent.key;
                    const location = key ? { start: key.start, end: key.end } : { start: this.start, end: this.start + 1 };
                    validationResult.problems.push({
                        location: location,
                        severity: ProblemSeverity.Warning,
                        message: localize('MissingRequiredPropWarning', 'Missing property "{0}".', propertyName)
                    });
                }
            });
        }
        const propertyProcessed = (prop) => {
            let index = unprocessedProperties.indexOf(prop);
            while (index >= 0) {
                unprocessedProperties.splice(index, 1);
                index = unprocessedProperties.indexOf(prop);
            }
        };
        if (schema.properties) {
            Object.keys(schema.properties).forEach((propertyName) => {
                propertyProcessed(propertyName);
                const prop = schema.properties[propertyName];
                const child = seenKeys[propertyName];
                if (child) {
                    const propertyValidationResult = new ValidationResult();
                    child.validate(prop, propertyValidationResult, matchingSchemas);
                    validationResult.mergePropertyMatch(propertyValidationResult);
                }
            });
        }
        if (schema.patternProperties) {
            Object.keys(schema.patternProperties).forEach((propertyPattern) => {
                const regex = new RegExp(propertyPattern);
                unprocessedProperties.slice(0).forEach((propertyName) => {
                    if (regex.test(propertyName)) {
                        propertyProcessed(propertyName);
                        const child = seenKeys[propertyName];
                        if (child) {
                            const propertyValidationResult = new ValidationResult();
                            child.validate(schema.patternProperties[propertyPattern], propertyValidationResult, matchingSchemas);
                            validationResult.mergePropertyMatch(propertyValidationResult);
                        }
                    }
                });
            });
        }
        if (typeof schema.additionalProperties === 'object') {
            unprocessedProperties.forEach((propertyName) => {
                const child = seenKeys[propertyName];
                if (child) {
                    const propertyValidationResult = new ValidationResult();
                    // tslint:disable-next-line: no-any
                    child.validate(schema.additionalProperties, propertyValidationResult, matchingSchemas);
                    validationResult.mergePropertyMatch(propertyValidationResult);
                }
            });
        }
        else if (schema.additionalProperties === false) {
            if (unprocessedProperties.length > 0) {
                unprocessedProperties.forEach((propertyName) => {
                    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;
                        }
                        validationResult.problems.push({
                            location: { start: propertyNode.key.start, end: propertyNode.key.end },
                            severity: ProblemSeverity.Warning,
                            message: schema.errorMessage || localize('DisallowedExtraPropWarning', 'Unexpected property {0}', propertyName)
                        });
                    }
                });
            }
        }
        if (schema.maxProperties) {
            if (this.properties.length > schema.maxProperties) {
                validationResult.problems.push({
                    location: { start: this.start, end: this.end },
                    severity: ProblemSeverity.Warning,
                    message: localize('MaxPropWarning', 'Object has more properties than limit of {0}.', schema.maxProperties)
                });
            }
        }
        if (schema.minProperties) {
            if (this.properties.length < schema.minProperties) {
                validationResult.problems.push({
                    location: { start: this.start, end: this.end },
                    severity: ProblemSeverity.Warning,
                    message: localize('MinPropWarning', 'Object has fewer properties than the required number of {0}', schema.minProperties)
                });
            }
        }
        if (schema.dependencies) {
            Object.keys(schema.dependencies).forEach((key) => {
                const prop = seenKeys[key];
                if (prop) {
                    const propertyDep = schema.dependencies[key];
                    if (Array.isArray(propertyDep)) {
                        propertyDep.forEach((requiredProp) => {
                            if (!seenKeys[requiredProp]) {
                                validationResult.problems.push({
                                    location: { start: this.start, end: this.end },
                                    severity: ProblemSeverity.Warning,
                                    message: localize('RequiredDependentPropWarning', 'Object is missing property {0} required by property {1}.', requiredProp, key)
                                });
                            }
                            else {
                                validationResult.propertiesValueMatches++;
                            }
                        });
                    }
                    else if (propertyDep) {
                        const propertyvalidationResult = new ValidationResult();
                        this.validate(propertyDep, propertyvalidationResult, matchingSchemas);
                        validationResult.mergePropertyMatch(propertyvalidationResult);
                    }
                }
            });
        }
    }
}
exports.ObjectASTNode = ObjectASTNode;
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 || node.contains(this.focusOffset)) && (node !== this.exclude);
    }
    newSub() {
        return new SchemaCollector(-1, this.exclude);
    }
}
class NoOpSchemaCollector {
    get schemas() { return []; }
    add(schema) { }
    merge(other) { }
    include(node) { return true; }
    newSub() { return this; }
}
class ValidationResult {
    constructor() {
        this.problems = [];
        this.propertiesMatches = 0;
        this.propertiesValueMatches = 0;
        this.primaryValueMatches = 0;
        this.enumValueMatch = false;
        this.enumValues = null;
        this.warnings = [];
        this.errors = [];
    }
    hasProblems() {
        return !!this.problems.length;
    }
    mergeAll(validationResults) {
        validationResults.forEach(validationResult => {
            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 === ErrorCode.EnumValueMismatch) {
                    error.message = localize('enumWarning', 'Value is not accepted. Valid values: {0}.', this.enumValues.map(v => JSON.stringify(v)).join(', '));
                }
            }
        }
    }
    mergePropertyMatch(propertyValidationResult) {
        this.merge(propertyValidationResult);
        this.propertiesMatches++;
        if (propertyValidationResult.enumValueMatch || !this.hasProblems() && propertyValidationResult.propertiesMatches) {
            this.propertiesValueMatches++;
        }
        if (propertyValidationResult.enumValueMatch && propertyValidationResult.enumValues && propertyValidationResult.enumValues.length === 1) {
            this.primaryValueMatches++;
        }
    }
    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;
class JSONDocument {
    constructor(root, syntaxErrors) {
        this.root = root;
        this.syntaxErrors = syntaxErrors;
    }
    getNodeFromOffset(offset) {
        return this.root && this.root.getNodeFromOffset(offset);
    }
    getNodeFromOffsetEndInclusive(offset) {
        return this.root && this.root.getNodeFromOffsetEndInclusive(offset);
    }
    visit(visitor) {
        if (this.root) {
            this.root.visit(visitor);
        }
    }
    configureSettings(parserSettings) {
        if (this.root) {
            this.root.setParserSettings(parserSettings);
        }
    }
    validate(schema) {
        if (this.root && schema) {
            const validationResult = new ValidationResult();
            this.root.validate(schema, validationResult, new NoOpSchemaCollector());
            return validationResult.problems;
        }
        return null;
    }
    getMatchingSchemas(schema, focusOffset = -1, exclude = null) {
        const matchingSchemas = new SchemaCollector(focusOffset, exclude);
        const validationResult = new ValidationResult();
        if (this.root && schema) {
            this.root.validate(schema, validationResult, matchingSchemas);
        }
        return matchingSchemas.schemas;
    }
    getValidationProblems(schema, focusOffset = -1, exclude = null) {
        const matchingSchemas = new SchemaCollector(focusOffset, exclude);
        const validationResult = new ValidationResult();
        if (this.root && schema) {
            this.root.validate(schema, validationResult, matchingSchemas);
        }
        return validationResult.problems;
    }
}
exports.JSONDocument = JSONDocument;
//Alternative comparison is specifically used by the kubernetes/openshift schema but may lead to better results then genericComparison depending on the schema
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(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) {
            // 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;
}
//# sourceMappingURL=jsonParser04.js.map