/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports);
        if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports", "../parser/jsonParser", "../utils/strings", "../utils/colors", "@vscode/l10n", "../jsonLanguageTypes"], factory);
    }
})(function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.JSONDocumentSymbols = void 0;
    const Parser = require("../parser/jsonParser");
    const Strings = require("../utils/strings");
    const colors_1 = require("../utils/colors");
    const l10n = require("@vscode/l10n");
    const jsonLanguageTypes_1 = require("../jsonLanguageTypes");
    class JSONDocumentSymbols {
        constructor(schemaService) {
            this.schemaService = schemaService;
        }
        findDocumentSymbols(document, doc, context = { resultLimit: Number.MAX_VALUE }) {
            const root = doc.root;
            if (!root) {
                return [];
            }
            let limit = context.resultLimit || Number.MAX_VALUE;
            // special handling for key bindings
            const resourceString = document.uri;
            if ((resourceString === 'vscode://defaultsettings/keybindings.json') || Strings.endsWith(resourceString.toLowerCase(), '/user/keybindings.json')) {
                if (root.type === 'array') {
                    const result = [];
                    for (const item of root.items) {
                        if (item.type === 'object') {
                            for (const property of item.properties) {
                                if (property.keyNode.value === 'key' && property.valueNode) {
                                    const location = jsonLanguageTypes_1.Location.create(document.uri, getRange(document, item));
                                    result.push({ name: getName(property.valueNode), kind: jsonLanguageTypes_1.SymbolKind.Function, location: location });
                                    limit--;
                                    if (limit <= 0) {
                                        if (context && context.onResultLimitExceeded) {
                                            context.onResultLimitExceeded(resourceString);
                                        }
                                        return result;
                                    }
                                }
                            }
                        }
                    }
                    return result;
                }
            }
            const toVisit = [
                { node: root, containerName: '' }
            ];
            let nextToVisit = 0;
            let limitExceeded = false;
            const result = [];
            const collectOutlineEntries = (node, containerName) => {
                if (node.type === 'array') {
                    node.items.forEach(node => {
                        if (node) {
                            toVisit.push({ node, containerName });
                        }
                    });
                }
                else if (node.type === 'object') {
                    node.properties.forEach((property) => {
                        const valueNode = property.valueNode;
                        if (valueNode) {
                            if (limit > 0) {
                                limit--;
                                const location = jsonLanguageTypes_1.Location.create(document.uri, getRange(document, property));
                                const childContainerName = containerName ? containerName + '.' + property.keyNode.value : property.keyNode.value;
                                result.push({ name: this.getKeyLabel(property), kind: this.getSymbolKind(valueNode.type), location: location, containerName: containerName });
                                toVisit.push({ node: valueNode, containerName: childContainerName });
                            }
                            else {
                                limitExceeded = true;
                            }
                        }
                    });
                }
            };
            // breath first traversal
            while (nextToVisit < toVisit.length) {
                const next = toVisit[nextToVisit++];
                collectOutlineEntries(next.node, next.containerName);
            }
            if (limitExceeded && context && context.onResultLimitExceeded) {
                context.onResultLimitExceeded(resourceString);
            }
            return result;
        }
        findDocumentSymbols2(document, doc, context = { resultLimit: Number.MAX_VALUE }) {
            const root = doc.root;
            if (!root) {
                return [];
            }
            let limit = context.resultLimit || Number.MAX_VALUE;
            // special handling for key bindings
            const resourceString = document.uri;
            if ((resourceString === 'vscode://defaultsettings/keybindings.json') || Strings.endsWith(resourceString.toLowerCase(), '/user/keybindings.json')) {
                if (root.type === 'array') {
                    const result = [];
                    for (const item of root.items) {
                        if (item.type === 'object') {
                            for (const property of item.properties) {
                                if (property.keyNode.value === 'key' && property.valueNode) {
                                    const range = getRange(document, item);
                                    const selectionRange = getRange(document, property.keyNode);
                                    result.push({ name: getName(property.valueNode), kind: jsonLanguageTypes_1.SymbolKind.Function, range, selectionRange });
                                    limit--;
                                    if (limit <= 0) {
                                        if (context && context.onResultLimitExceeded) {
                                            context.onResultLimitExceeded(resourceString);
                                        }
                                        return result;
                                    }
                                }
                            }
                        }
                    }
                    return result;
                }
            }
            const result = [];
            const toVisit = [
                { node: root, result }
            ];
            let nextToVisit = 0;
            let limitExceeded = false;
            const collectOutlineEntries = (node, result) => {
                if (node.type === 'array') {
                    node.items.forEach((node, index) => {
                        if (node) {
                            if (limit > 0) {
                                limit--;
                                const range = getRange(document, node);
                                const selectionRange = range;
                                const name = String(index);
                                const symbol = { name, kind: this.getSymbolKind(node.type), range, selectionRange, children: [] };
                                result.push(symbol);
                                toVisit.push({ result: symbol.children, node });
                            }
                            else {
                                limitExceeded = true;
                            }
                        }
                    });
                }
                else if (node.type === 'object') {
                    node.properties.forEach((property) => {
                        const valueNode = property.valueNode;
                        if (valueNode) {
                            if (limit > 0) {
                                limit--;
                                const range = getRange(document, property);
                                const selectionRange = getRange(document, property.keyNode);
                                const children = [];
                                const symbol = { name: this.getKeyLabel(property), kind: this.getSymbolKind(valueNode.type), range, selectionRange, children, detail: this.getDetail(valueNode) };
                                result.push(symbol);
                                toVisit.push({ result: children, node: valueNode });
                            }
                            else {
                                limitExceeded = true;
                            }
                        }
                    });
                }
            };
            // breath first traversal
            while (nextToVisit < toVisit.length) {
                const next = toVisit[nextToVisit++];
                collectOutlineEntries(next.node, next.result);
            }
            if (limitExceeded && context && context.onResultLimitExceeded) {
                context.onResultLimitExceeded(resourceString);
            }
            return result;
        }
        getSymbolKind(nodeType) {
            switch (nodeType) {
                case 'object':
                    return jsonLanguageTypes_1.SymbolKind.Module;
                case 'string':
                    return jsonLanguageTypes_1.SymbolKind.String;
                case 'number':
                    return jsonLanguageTypes_1.SymbolKind.Number;
                case 'array':
                    return jsonLanguageTypes_1.SymbolKind.Array;
                case 'boolean':
                    return jsonLanguageTypes_1.SymbolKind.Boolean;
                default: // 'null'
                    return jsonLanguageTypes_1.SymbolKind.Variable;
            }
        }
        getKeyLabel(property) {
            let name = property.keyNode.value;
            if (name) {
                name = name.replace(/[\n]/g, '↵');
            }
            if (name && name.trim()) {
                return name;
            }
            return `"${name}"`;
        }
        getDetail(node) {
            if (!node) {
                return undefined;
            }
            if (node.type === 'boolean' || node.type === 'number' || node.type === 'null' || node.type === 'string') {
                return String(node.value);
            }
            else {
                if (node.type === 'array') {
                    return node.children.length ? undefined : '[]';
                }
                else if (node.type === 'object') {
                    return node.children.length ? undefined : '{}';
                }
            }
            return undefined;
        }
        findDocumentColors(document, doc, context) {
            return this.schemaService.getSchemaForResource(document.uri, doc).then(schema => {
                const result = [];
                if (schema) {
                    let limit = context && typeof context.resultLimit === 'number' ? context.resultLimit : Number.MAX_VALUE;
                    const matchingSchemas = doc.getMatchingSchemas(schema.schema);
                    const visitedNode = {};
                    for (const s of matchingSchemas) {
                        if (!s.inverted && s.schema && (s.schema.format === 'color' || s.schema.format === 'color-hex') && s.node && s.node.type === 'string') {
                            const nodeId = String(s.node.offset);
                            if (!visitedNode[nodeId]) {
                                const color = (0, colors_1.colorFromHex)(Parser.getNodeValue(s.node));
                                if (color) {
                                    const range = getRange(document, s.node);
                                    result.push({ color, range });
                                }
                                visitedNode[nodeId] = true;
                                limit--;
                                if (limit <= 0) {
                                    if (context && context.onResultLimitExceeded) {
                                        context.onResultLimitExceeded(document.uri);
                                    }
                                    return result;
                                }
                            }
                        }
                    }
                }
                return result;
            });
        }
        getColorPresentations(document, doc, color, range) {
            const result = [];
            const red256 = Math.round(color.red * 255), green256 = Math.round(color.green * 255), blue256 = Math.round(color.blue * 255);
            function toTwoDigitHex(n) {
                const r = n.toString(16);
                return r.length !== 2 ? '0' + r : r;
            }
            let label;
            if (color.alpha === 1) {
                label = `#${toTwoDigitHex(red256)}${toTwoDigitHex(green256)}${toTwoDigitHex(blue256)}`;
            }
            else {
                label = `#${toTwoDigitHex(red256)}${toTwoDigitHex(green256)}${toTwoDigitHex(blue256)}${toTwoDigitHex(Math.round(color.alpha * 255))}`;
            }
            result.push({ label: label, textEdit: jsonLanguageTypes_1.TextEdit.replace(range, JSON.stringify(label)) });
            return result;
        }
    }
    exports.JSONDocumentSymbols = JSONDocumentSymbols;
    function getRange(document, node) {
        return jsonLanguageTypes_1.Range.create(document.positionAt(node.offset), document.positionAt(node.offset + node.length));
    }
    function getName(node) {
        return Parser.getNodeValue(node) || l10n.t('<empty>');
    }
});
