"use strict";
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
Object.defineProperty(exports, "__esModule", { value: true });
exports.AnnotationCategory = void 0;
const Log_1 = require("./Log");
const Utilities_1 = require("./Utilities");
const esprima_next_1 = require("esprima-next");
var AnnotationCategory;
(function (AnnotationCategory) {
    AnnotationCategory["blockTextureReferenceSource"] = "a";
    AnnotationCategory["blockTypeDependent"] = "b";
    AnnotationCategory["entityComponentDependent"] = "c";
    AnnotationCategory["blockComponentDependent"] = "d";
    AnnotationCategory["entityTypeDependent"] = "e";
    AnnotationCategory["entityFilter"] = "f";
    AnnotationCategory["entityComponentDependentInGroup"] = "g";
    AnnotationCategory["blockTextureReferenceDependent"] = "h";
    AnnotationCategory["itemTypeDependent"] = "i";
    AnnotationCategory["itemComponentDependent"] = "j";
    AnnotationCategory["itemTextureReferenceSource"] = "k";
    AnnotationCategory["featureSource"] = "l";
    AnnotationCategory["featureDependent"] = "m";
    AnnotationCategory["featureRuleSource"] = "n";
    AnnotationCategory["blockComponentDependentInPermutation"] = "p";
    AnnotationCategory["storagePathDependent"] = "s";
    AnnotationCategory["textureFile"] = "t";
    AnnotationCategory["entityEvent"] = "v";
    AnnotationCategory["blockTypeSource"] = "B";
    AnnotationCategory["entityTypeSource"] = "E";
    AnnotationCategory["itemTypeSource"] = "I";
    AnnotationCategory["itemTextureSource"] = "J";
    AnnotationCategory["blockSounds"] = "L";
    AnnotationCategory["musicDefinitionSource"] = "M";
    AnnotationCategory["entitySounds"] = "N";
    AnnotationCategory["interactiveSounds"] = "R";
    AnnotationCategory["jsSource"] = "S";
    AnnotationCategory["terrainTextureSource"] = "T";
    AnnotationCategory["soundDefinitionSource"] = "U";
    AnnotationCategory["individualEventSoundsSource"] = "V";
    AnnotationCategory["worldProperty"] = "W";
    AnnotationCategory["experiment"] = "X";
})(AnnotationCategory = exports.AnnotationCategory || (exports.AnnotationCategory = {}));
class ContentIndex {
    _processedPathsCache;
    #data = {
        items: [],
        trie: {},
    };
    #iteration = Math.floor(Math.random() * 1000000);
    get iteration() {
        return this.#iteration;
    }
    set iteration(newIteration) {
        this.#iteration = newIteration;
    }
    static getAnnotationCategoryKeys() {
        const keys = [];
        for (const key in AnnotationCategory) {
            keys.push(key.toLowerCase());
        }
        return keys;
    }
    static getAnnotationCategoryFromLongString(longStr) {
        longStr = longStr.toLowerCase();
        for (const key in AnnotationCategory) {
            if (key.toLowerCase() === longStr) {
                return AnnotationCategory[key];
            }
        }
        return undefined;
    }
    get data() {
        return this.#data;
    }
    get startLength() {
        return 4;
    }
    setItems(items) {
        this.#data.items = items;
        this._processedPathsCache = undefined; // reset processed paths cache
    }
    setTrie(trie) {
        this.#data.trie = trie;
    }
    getAll(withAnnotation) {
        const results = {};
        this._appendToResults("", this.#data.trie, results, withAnnotation);
        return results;
    }
    _appendToResults(prefix, node, results, withAnnotation) {
        for (const token in node) {
            const subNode = node[token];
            if (subNode) {
                if (token === "±" || token === "$") {
                    const arr = subNode;
                    if (arr.constructor === Array) {
                        if (Utilities_1.default.isUsableAsObjectKey(prefix)) {
                            let res = this.getValuesFromIndexArray(arr, withAnnotation);
                            if (res) {
                                results[prefix] = res;
                            }
                        }
                    }
                }
                else if (subNode.constructor === Array) {
                    if (Utilities_1.default.isUsableAsObjectKey(prefix + token)) {
                        let res = this.getValuesFromIndexArray(subNode, withAnnotation);
                        if (res) {
                            results[prefix + token] = res;
                        }
                    }
                }
                else {
                    this._appendToResults(prefix + token, subNode, results, withAnnotation);
                }
            }
        }
    }
    mergeFrom(index, newItem) {
        const all = index.getAll();
        for (const fullKey in all) {
            const annVals = all[fullKey];
            let annVal;
            for (const subVal of annVals) {
                if (subVal.annotation) {
                    if (!annVal) {
                        annVal = subVal.annotation;
                    }
                    else if (annVal.indexOf(subVal.annotation) < 0) {
                        annVal += subVal.annotation;
                    }
                }
            }
            this.insert(fullKey, newItem, annVal);
        }
    }
    static processResultValues(annotatedValues, withAnyAnnotation) {
        if (!annotatedValues) {
            return undefined;
        }
        if (withAnyAnnotation) {
            let newAnnotatedValues = [];
            for (const annV of annotatedValues) {
                if (annV.annotation && withAnyAnnotation.includes(annV.annotation)) {
                    newAnnotatedValues.push(annV);
                }
            }
            annotatedValues = newAnnotatedValues;
        }
        return annotatedValues;
    }
    getValuesFromIndexArray(indices, withAnnotation) {
        let results = [];
        if (!indices) {
            return undefined;
        }
        if (Utilities_1.default.arrayHasNegativeAndIsNumeric(indices)) {
            indices = Utilities_1.default.decodeSequentialRunLengthUsingNegative(indices);
        }
        for (const index of indices) {
            if (typeof index === "object") {
                const indexN = index.n;
                if (indexN >= 0 && indexN < this.#data.items.length) {
                    const annotate = index.a;
                    if (!withAnnotation || withAnnotation.includes(annotate)) {
                        results.push({ value: this.#data.items[indexN], annotation: index.a });
                    }
                }
            }
            else if (index >= 0 && index < this.#data.items.length && !withAnnotation) {
                results.push({ value: this.#data.items[index], annotation: undefined });
            }
        }
        if (results.length === 0) {
            return undefined;
        }
        return results;
    }
    loadFromData(data) {
        this.#data = data;
        this._processedPathsCache = undefined; // reset processed paths cache
    }
    _getProcessedPaths() {
        if (this._processedPathsCache)
            return this._processedPathsCache;
        this._processedPathsCache = this.data.items
            .filter((item) => item.startsWith("/"))
            .map((item) => {
            const lastPeriod = item.lastIndexOf(".");
            return lastPeriod >= 0 ? item.substring(0, lastPeriod) : item;
        });
        return this._processedPathsCache;
    }
    hasPathMatches(pathEnd) {
        pathEnd = pathEnd.toLowerCase();
        const lastPeriodEnd = pathEnd.lastIndexOf(".");
        if (lastPeriodEnd >= 0) {
            pathEnd = pathEnd.substring(0, lastPeriodEnd);
        }
        return this._getProcessedPaths().some((path) => path.endsWith(pathEnd));
    }
    async getMatches(searchString, wholeTermSearch, withAnyAnnotation) {
        searchString = searchString.trim().toLowerCase();
        let terms = [searchString];
        if (!wholeTermSearch) {
            terms = searchString.split(" ");
        }
        let termWasSearched = false;
        let andResults;
        for (const term of terms) {
            if (term.length > 1) {
                const results = this.getTermMatch(term);
                termWasSearched = true;
                if (results && results.length) {
                    if (andResults === undefined) {
                        andResults = results;
                    }
                    else {
                        const newArr = [];
                        for (let num of results) {
                            if (andResults.includes(num)) {
                                newArr.push(num);
                            }
                        }
                    }
                }
            }
        }
        if (andResults === undefined || andResults.length === 0) {
            if (termWasSearched) {
                return [];
            }
            return undefined;
        }
        let annotatedValues = ContentIndex.processResultValues(this.getValuesFromIndexArray(andResults), withAnyAnnotation);
        if (!annotatedValues) {
            return undefined;
        }
        return annotatedValues.sort((a, b) => {
            let aTermMatches = 0;
            let bTermMatches = 0;
            const aVal = a.value.toLowerCase();
            const bVal = b.value.toLowerCase();
            for (const term of terms) {
                if (aVal.startsWith(term)) {
                    aTermMatches += 5;
                }
                else if (aVal.includes(term)) {
                    aTermMatches++;
                }
                if (bVal.startsWith(term)) {
                    bTermMatches += 5;
                }
                else if (bVal.includes(term)) {
                    bTermMatches++;
                }
            }
            if (aTermMatches === bTermMatches) {
                return a.value.localeCompare(b.value);
            }
            return bTermMatches - aTermMatches;
        });
    }
    getTermMatchStrings(term) {
        const results = this.getTermMatch(term);
        if (results === undefined) {
            return results;
        }
        return this.getValuesFromIndexArray(results);
    }
    async getDescendentStrings(term) {
        let termIndex = 0;
        let curNode = this.#data.trie;
        const results = {};
        let hasAdvanced = true;
        let termSubstr = "";
        while (termIndex < term.length && hasAdvanced) {
            hasAdvanced = false;
            if (curNode.constructor === Array) {
                return undefined;
            }
            let nextNode = curNode[term[termIndex]];
            if (nextNode) {
                curNode = nextNode;
                termIndex++;
                termSubstr = term.substring(0, termIndex);
                hasAdvanced = true;
            }
            else {
                let nextStart = term[termIndex];
                for (const item in curNode) {
                    // we've found part of our string in this node
                    if (item.startsWith(nextStart) && curNode[item] !== undefined) {
                        let itemIndex = 0;
                        hasAdvanced = true;
                        curNode = curNode[item];
                        termSubstr = term.substring(0, termIndex) + item;
                        while (termIndex < term.length && itemIndex < item.length && item[itemIndex] === term[termIndex]) {
                            itemIndex++;
                            termIndex++;
                        }
                        break;
                    }
                }
            }
        }
        if (termIndex < term.length) {
            const termStub = term.substring(termIndex);
            for (const childNodeName in curNode) {
                if (childNodeName.startsWith(termStub) && curNode[childNodeName]) {
                    this._appendToResults(term.substring(0, termIndex) + childNodeName, curNode[childNodeName], results);
                }
            }
        }
        else {
            if (curNode.constructor === Array) {
                results[termSubstr] = this.getValuesFromIndexArray(curNode);
            }
            else if (curNode["±"] !== undefined) {
                this._appendToResults(termSubstr, curNode, results);
            }
        }
        return results;
    }
    getTermMatch(term) {
        let termIndex = 0;
        let curNode = this.#data.trie;
        let hasAdvanced = true;
        let pathMatches = undefined;
        let i = 0;
        for (const item of this.#data.items) {
            if (item.indexOf(term) >= 0) {
                if (!pathMatches) {
                    pathMatches = [];
                }
                pathMatches.push(i);
            }
            i++;
        }
        while (termIndex < term.length && hasAdvanced) {
            hasAdvanced = false;
            if (curNode.constructor === Array) {
                return undefined;
            }
            let nextNode = curNode[term[termIndex]];
            if (nextNode) {
                curNode = nextNode;
                termIndex++;
                hasAdvanced = true;
            }
            else {
                let nextStart = term[termIndex];
                if (termIndex < term.length - 1) {
                    nextStart += term[termIndex + 1];
                }
                for (const item in curNode) {
                    // we've found part of our string in this node
                    if (item.startsWith(nextStart) && curNode[item] !== undefined && !hasAdvanced) {
                        let itemIndex = 0;
                        hasAdvanced = true;
                        curNode = curNode[item];
                        while (termIndex < term.length && itemIndex < item.length && item[itemIndex] === term[termIndex]) {
                            itemIndex++;
                            termIndex++;
                        }
                    }
                }
            }
        }
        if (termIndex < term.length) {
            return undefined;
        }
        if (curNode.constructor === Array) {
            if (pathMatches) {
                return ContentIndex.mergeResults(curNode, pathMatches);
            }
            return curNode;
        }
        else if (curNode["±"] !== undefined) {
            if (pathMatches) {
                return ContentIndex.mergeResults(curNode["±"], pathMatches);
            }
            return curNode["±"];
        }
        else {
            const arr = [];
            this.aggregateIndices(curNode, arr);
            if (pathMatches) {
                return ContentIndex.mergeResults(arr, pathMatches);
            }
            return arr;
        }
    }
    static mergeResults(resultsArrA, resultsArrB) {
        const results = [];
        for (const item of resultsArrA) {
            if (typeof item === "object") {
                if (!results.some((res) => typeof res === "object" && res.value === item.value && res.annotation === item.annotation)) {
                    results.push(item);
                }
            }
            else if (!results.includes(item)) {
                results.push(item);
            }
        }
        for (const item of resultsArrB) {
            if (typeof item === "object") {
                if (!results.some((res) => typeof res === "object" && res.value === item.value && res.annotation === item.annotation)) {
                    results.push(item);
                }
            }
            else if (!results.includes(item)) {
                results.push(item);
            }
        }
        return results;
    }
    aggregateIndices(curNode, arr) {
        for (const childNodeName in curNode) {
            const childNode = curNode[childNodeName];
            if (childNode) {
                if (childNode.constructor === Array) {
                    for (const num of childNode) {
                        if (!arr.includes(num)) {
                            arr.push(num);
                        }
                    }
                }
                else if (childNode["±"] !== undefined) {
                    for (const num of childNode["±"]) {
                        if (!arr.includes(num)) {
                            arr.push(num);
                        }
                    }
                }
                else {
                    this.aggregateIndices(childNode, arr);
                }
            }
        }
    }
    insertArray(key, items) {
        for (const item of items) {
            this.insert(key, item.value, item.annotation);
        }
    }
    insert(key, item, annotationChar) {
        if (Utilities_1.default.isNumericIsh(key) || key.length > 70) {
            return;
        }
        // since we treat ± as special, ban usage of ± in strings.
        key = key.replace(/±/gi, "").toLowerCase().trim();
        let keyIndex = 0;
        let curNode = this.#data.trie;
        let parentNode = curNode;
        let curNodeIndex;
        let dataIndex = -1;
        let curIndex = 0;
        for (const itemCand of this.#data.items) {
            if (itemCand === item) {
                dataIndex = curIndex;
                break;
            }
            curIndex++;
        }
        if (dataIndex < 0) {
            dataIndex = this.#data.items.length;
            this.#data.items.push(item);
            this._processedPathsCache = undefined;
        }
        let hasAdvanced = true;
        while (keyIndex < key.length && hasAdvanced) {
            hasAdvanced = false;
            if (curNode.constructor !== Array) {
                for (const item in curNode) {
                    // we've found part of our string in this node
                    if (item.startsWith(key[keyIndex]) && curNode[item] !== undefined) {
                        // && curNode[item].constructor !== Array) {
                        let itemIndex = 0;
                        hasAdvanced = true;
                        curNodeIndex = item;
                        parentNode = curNode;
                        curNode = curNode[item];
                        while (keyIndex < key.length && itemIndex < item.length && item[itemIndex] === key[keyIndex]) {
                            itemIndex++;
                            keyIndex++;
                        }
                        // if we're in the middle of a string like "subset", and we're trying add the word "subpar",
                        // create a new node called "sub" and place "set" underneath it.
                        // also support the case where we're adding "sub" but "subset" already exists (keyIndex === key.length)
                        if (item[itemIndex] !== key[keyIndex] && itemIndex < item.length && keyIndex <= key.length) {
                            parentNode[curNodeIndex] = undefined;
                            curNodeIndex = item.substring(0, itemIndex);
                            let newNode = {};
                            parentNode[curNodeIndex] = newNode;
                            const term = item.substring(itemIndex);
                            if (Utilities_1.default.isUsableAsObjectKey(term)) {
                                newNode[term] = curNode;
                            }
                            curNode = newNode;
                        }
                        break;
                    }
                }
            }
        }
        // we've reached the end of the trie; we need to add a new node
        if (keyIndex < key.length) {
            // if parent node was a leaf array, switch to an object
            if (curNode.constructor === Array && curNodeIndex) {
                parentNode[curNodeIndex] = {};
                parentNode[curNodeIndex]["±"] = curNode;
                curNode = parentNode[curNodeIndex];
            }
            const substr = key.substring(keyIndex);
            if (substr !== "±") {
                if (Utilities_1.default.isUsableAsObjectKey(substr)) {
                    // create a new leaf array
                    curNode[substr] = this.ensureAnnotatedContentInArray([], dataIndex, annotationChar);
                }
            }
        }
        else {
            if (curNode.constructor === Array && curNodeIndex) {
                if (Utilities_1.default.isUsableAsObjectKey(curNodeIndex)) {
                    parentNode[curNodeIndex] = this.ensureAnnotatedContentInArray(curNode, dataIndex, annotationChar);
                }
            }
            else {
                if (curNode["±"] === undefined) {
                    curNode["±"] = [];
                }
                curNode["±"] = this.ensureAnnotatedContentInArray(curNode["±"], dataIndex, annotationChar);
            }
        }
    }
    ensureAnnotatedContentInArray(arr, dataIndex, annotationChar) {
        try {
            for (const item of arr) {
                if (typeof item === "object") {
                    if (item.n === dataIndex) {
                        if (annotationChar) {
                            if (!item.a) {
                                item.a = annotationChar;
                            }
                            else {
                                if (item.a.indexOf(annotationChar) < 0) {
                                    item.a += annotationChar;
                                }
                            }
                        }
                        return arr;
                    }
                }
                else if (item === dataIndex) {
                    if (!annotationChar) {
                        return arr;
                    }
                    // convert simple number to annotated object
                    const newArr = [];
                    for (const existItem of arr) {
                        if (existItem !== dataIndex) {
                            newArr.push(existItem);
                        }
                    }
                    newArr.push({ n: dataIndex, a: annotationChar });
                    return newArr;
                }
            }
            if (annotationChar) {
                arr.push({ n: dataIndex, a: annotationChar });
            }
            else {
                arr.push(dataIndex);
            }
        }
        catch (e) {
            console.warn("Error ensuring annotated content: " + e + "|" + arr + "|" + JSON.stringify(arr));
        }
        return arr;
    }
    parseJsContent(sourcePath, content) {
        try {
            const results = esprima_next_1.default.tokenize(content);
            if (results) {
                for (const token of results) {
                    if (token.type === "Identifier" && token.value && token.value.length > 3) {
                        if (token.value !== "from") {
                            this.insert(token.value.toLowerCase(), sourcePath, "S");
                        }
                    }
                }
            }
        }
        catch (e) {
            Log_1.default.debugAlert("JS parsing error:" + e);
        }
    }
    parseTextContent(sourcePath, content) {
        const dictionaryOfTerms = {};
        let curWord = "";
        content = content.toLowerCase();
        for (let i = 0; i < content.length; i++) {
            const curChar = content[i];
            if (curChar === "{" ||
                curChar === "}" ||
                curChar === " " ||
                curChar === "\r" ||
                curChar === "\n" ||
                curChar === "\t" ||
                curChar === "(" ||
                curChar === ")" ||
                curChar === "[" ||
                curChar === "]" ||
                curChar === ":" ||
                curChar === '"' ||
                curChar === "'") {
                if (curWord.length > 0) {
                    if (curWord.length > 3 && !Utilities_1.default.isNumericIsh(curWord) && Utilities_1.default.isUsableAsObjectKey(curWord)) {
                        dictionaryOfTerms[curWord] = true;
                    }
                    curWord = "";
                }
            }
            else {
                curWord += content[i];
            }
        }
        for (const term in dictionaryOfTerms) {
            this.insert(term, sourcePath);
        }
    }
    parseJsonContent(sourcePath, content) {
        const dictionaryOfTerms = {};
        let curWord = "";
        content = content.toLowerCase();
        for (let i = 0; i < content.length; i++) {
            const curChar = content[i];
            if (curChar === "{" ||
                curChar === "}" ||
                curChar === " " ||
                curChar === "\r" ||
                curChar === "\n" ||
                curChar === "\t" ||
                curChar === "(" ||
                curChar === ")" ||
                curChar === "[" ||
                curChar === "]" ||
                curChar === ":" ||
                curChar === '"' ||
                curChar === "'") {
                if (curWord.length > 0) {
                    if (curWord.length > 3 && !Utilities_1.default.isNumericIsh(curWord) && Utilities_1.default.isUsableAsObjectKey(curWord)) {
                        dictionaryOfTerms[curWord] = true;
                    }
                    curWord = "";
                }
            }
            else {
                curWord += content[i];
            }
        }
        for (const term in dictionaryOfTerms) {
            this.insert(term, sourcePath);
        }
    }
}
exports.default = ContentIndex;

//# sourceMappingURL=../maps/core/ContentIndex.js.map
