"use strict";
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Red Hat, Inc. 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.yamlDocumentsCache = exports.YamlDocuments = exports.YAMLDocument = exports.SingleYAMLDocument = void 0;
const jsonDocument_1 = require("./jsonDocument");
const yaml_1 = require("yaml");
const yamlParser07_1 = require("./yamlParser07");
const vscode_json_languageservice_1 = require("vscode-json-languageservice");
const ast_converter_1 = require("./ast-converter");
const arrUtils_1 = require("../utils/arrUtils");
const yamlAstUtils_1 = require("../utils/yamlAstUtils");
const strings_1 = require("../utils/strings");
/**
 * These documents are collected into a final YAMLDocument
 * and passed to the `parseYAML` caller.
 */
class SingleYAMLDocument extends jsonDocument_1.JSONDocument {
    constructor(lineCounter) {
        super(null, []);
        this.lineCounter = lineCounter;
    }
    /**
     * Create a deep copy of this document
     */
    clone() {
        const copy = new SingleYAMLDocument(this.lineCounter);
        copy.isKubernetes = this.isKubernetes;
        copy.disableAdditionalProperties = this.disableAdditionalProperties;
        copy.uri = this.uri;
        copy.currentDocIndex = this.currentDocIndex;
        copy._lineComments = this.lineComments.slice();
        // this will re-create root node
        copy.internalDocument = this._internalDocument.clone();
        return copy;
    }
    collectLineComments() {
        this._lineComments = [];
        if (this._internalDocument.commentBefore) {
            const comments = this._internalDocument.commentBefore.split('\n');
            comments.forEach((comment) => this._lineComments.push(`#${comment}`));
        }
        (0, yaml_1.visit)(this.internalDocument, (_key, node) => {
            if (node?.commentBefore) {
                const comments = node?.commentBefore.split('\n');
                comments.forEach((comment) => this._lineComments.push(`#${comment}`));
            }
            if (node?.comment) {
                this._lineComments.push(`#${node.comment}`);
            }
        });
        if (this._internalDocument.comment) {
            this._lineComments.push(`#${this._internalDocument.comment}`);
        }
    }
    /**
     * Updates the internal AST tree of the object
     * from the internal node. This is call whenever the
     * internalDocument is set but also can be called to
     * reflect any changes on the underlying document
     * without setting the internalDocument explicitly.
     */
    updateFromInternalDocument() {
        this.root = (0, ast_converter_1.convertAST)(null, this._internalDocument.contents, this._internalDocument, this.lineCounter);
    }
    set internalDocument(document) {
        this._internalDocument = document;
        this.updateFromInternalDocument();
    }
    get internalDocument() {
        return this._internalDocument;
    }
    get lineComments() {
        if (!this._lineComments) {
            this.collectLineComments();
        }
        return this._lineComments;
    }
    set lineComments(val) {
        this._lineComments = val;
    }
    get errors() {
        return this.internalDocument.errors.map(YAMLErrorToYamlDocDiagnostics);
    }
    get warnings() {
        return this.internalDocument.warnings.map(YAMLErrorToYamlDocDiagnostics);
    }
    getNodeFromPosition(positionOffset, textBuffer, configuredIndentation) {
        const position = textBuffer.getPosition(positionOffset);
        const lineContent = textBuffer.getLineContent(position.line);
        if (lineContent.trim().length === 0) {
            return [this.findClosestNode(positionOffset, textBuffer, configuredIndentation), true];
        }
        const textAfterPosition = lineContent.substring(position.character);
        const spacesAfterPositionMatch = textAfterPosition.match(/^([ ]+)\n?$/);
        const areOnlySpacesAfterPosition = !!spacesAfterPositionMatch;
        const countOfSpacesAfterPosition = spacesAfterPositionMatch?.[1].length;
        let closestNode;
        (0, yaml_1.visit)(this.internalDocument, (key, node) => {
            if (!node) {
                return;
            }
            const range = node.range;
            if (!range) {
                return;
            }
            const isNullNodeOnTheLine = () => areOnlySpacesAfterPosition &&
                positionOffset + countOfSpacesAfterPosition === range[2] &&
                (0, yaml_1.isScalar)(node) &&
                node.value === null;
            if ((range[0] <= positionOffset && range[1] >= positionOffset) || isNullNodeOnTheLine()) {
                closestNode = node;
            }
            else {
                return yaml_1.visit.SKIP;
            }
        });
        return [closestNode, false];
    }
    findClosestNode(offset, textBuffer, configuredIndentation) {
        let offsetDiff = this.internalDocument.range[2];
        let maxOffset = this.internalDocument.range[0];
        let closestNode;
        (0, yaml_1.visit)(this.internalDocument, (key, node) => {
            if (!node) {
                return;
            }
            const range = node.range;
            if (!range) {
                return;
            }
            const diff = range[1] - offset;
            if (maxOffset <= range[0] && diff <= 0 && Math.abs(diff) <= offsetDiff) {
                offsetDiff = Math.abs(diff);
                maxOffset = range[0];
                closestNode = node;
            }
        });
        const position = textBuffer.getPosition(offset);
        const lineContent = textBuffer.getLineContent(position.line);
        const indentation = (0, strings_1.getIndentation)(lineContent, position.character);
        if ((0, yaml_1.isScalar)(closestNode) && closestNode.value === null) {
            return closestNode;
        }
        if (indentation === position.character) {
            closestNode = this.getProperParentByIndentation(indentation, closestNode, textBuffer, '', configuredIndentation);
        }
        return closestNode;
    }
    getProperParentByIndentation(indentation, node, textBuffer, currentLine, configuredIndentation, rootParent) {
        if (!node) {
            return this.internalDocument.contents;
        }
        configuredIndentation = !configuredIndentation ? 2 : configuredIndentation;
        if ((0, yaml_1.isNode)(node) && node.range) {
            const position = textBuffer.getPosition(node.range[0]);
            const lineContent = textBuffer.getLineContent(position.line);
            currentLine = currentLine === '' ? lineContent.trim() : currentLine;
            if (currentLine.startsWith('-') && indentation === configuredIndentation && currentLine === lineContent.trim()) {
                position.character += indentation;
            }
            if (position.character > indentation && position.character > 0) {
                const parent = this.getParent(node);
                if (parent) {
                    return this.getProperParentByIndentation(indentation, parent, textBuffer, currentLine, configuredIndentation, rootParent);
                }
            }
            else if (position.character < indentation) {
                const parent = this.getParent(node);
                if ((0, yaml_1.isPair)(parent) && (0, yaml_1.isNode)(parent.value)) {
                    return parent.value;
                }
                else if ((0, yaml_1.isPair)(rootParent) && (0, yaml_1.isNode)(rootParent.value)) {
                    return rootParent.value;
                }
            }
            else {
                return node;
            }
        }
        else if ((0, yaml_1.isPair)(node)) {
            rootParent = node;
            const parent = this.getParent(node);
            return this.getProperParentByIndentation(indentation, parent, textBuffer, currentLine, configuredIndentation, rootParent);
        }
        return node;
    }
    getParent(node) {
        return (0, yamlAstUtils_1.getParent)(this.internalDocument, node);
    }
}
exports.SingleYAMLDocument = SingleYAMLDocument;
/**
 * Contains the SingleYAMLDocuments, to be passed
 * to the `parseYAML` caller.
 */
class YAMLDocument {
    constructor(documents, tokens) {
        this.documents = documents;
        this.tokens = tokens;
        this.errors = [];
        this.warnings = [];
    }
}
exports.YAMLDocument = YAMLDocument;
class YamlDocuments {
    constructor() {
        // a mapping of URIs to cached documents
        this.cache = new Map();
    }
    /**
     * Get cached YAMLDocument
     * @param document TextDocument to parse
     * @param parserOptions YAML parserOptions
     * @param addRootObject if true and document is empty add empty object {} to force schema usage
     * @returns the YAMLDocument
     */
    getYamlDocument(document, parserOptions, addRootObject = false) {
        this.ensureCache(document, parserOptions ?? yamlParser07_1.defaultOptions, addRootObject);
        return this.cache.get(document.uri).document;
    }
    /**
     * For test purpose only!
     */
    clear() {
        this.cache.clear();
    }
    ensureCache(document, parserOptions, addRootObject) {
        const key = document.uri;
        if (!this.cache.has(key)) {
            this.cache.set(key, { version: -1, document: new YAMLDocument([], []), parserOptions: yamlParser07_1.defaultOptions });
        }
        const cacheEntry = this.cache.get(key);
        if (cacheEntry.version !== document.version ||
            (parserOptions.customTags && !(0, arrUtils_1.isArrayEqual)(cacheEntry.parserOptions.customTags, parserOptions.customTags))) {
            let text = document.getText();
            // if text is contains only whitespace wrap all text in object to force schema selection
            if (addRootObject && !/\S/.test(text)) {
                text = `{${text}}`;
            }
            const doc = (0, yamlParser07_1.parse)(text, parserOptions, document);
            cacheEntry.document = doc;
            cacheEntry.version = document.version;
            cacheEntry.parserOptions = parserOptions;
        }
    }
}
exports.YamlDocuments = YamlDocuments;
exports.yamlDocumentsCache = new YamlDocuments();
function YAMLErrorToYamlDocDiagnostics(error) {
    return {
        message: error.message,
        location: {
            start: error.pos[0],
            end: error.pos[1],
            toLineEnd: true,
        },
        severity: 1,
        code: vscode_json_languageservice_1.ErrorCode.Undefined,
    };
}
//# sourceMappingURL=yaml-documents.js.map