"use strict";
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
    return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
    if (kind === "m") throw new TypeError("Private method is not writable");
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
    return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var _ContentIndex_data;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AnnotationCategories = void 0;
const Log_1 = require("./Log");
const Utilities_1 = require("./Utilities");
const esprima_next_1 = require("esprima-next");
var AnnotationCategories;
(function (AnnotationCategories) {
    AnnotationCategories["blockTypeDependent"] = "b";
    AnnotationCategories["entityComponentDependent"] = "c";
    AnnotationCategories["blockComponentDependent"] = "d";
    AnnotationCategories["entityTypeDependent"] = "e";
    AnnotationCategories["experiment"] = "E";
    AnnotationCategories["itemTypeDependent"] = "i";
    AnnotationCategories["itemComponentDependent"] = "j";
    AnnotationCategories["entityComponentDependentInGroup"] = "g";
    AnnotationCategories["blockComponentDependentInPermutation"] = "p";
    AnnotationCategories["storagePathDependent"] = "s";
    AnnotationCategories["entityTypeSource"] = "E";
    AnnotationCategories["blockTypeSource"] = "B";
    AnnotationCategories["itemTypeSource"] = "I";
    AnnotationCategories["jsSource"] = "S";
    AnnotationCategories["worldProperty"] = "W";
})(AnnotationCategories = exports.AnnotationCategories || (exports.AnnotationCategories = {}));
const AvoidTermList = ["__proto__", "prototype", "[[Prototype]]"];
class ContentIndex {
    constructor() {
        _ContentIndex_data.set(this, {
            items: [],
            trie: {},
        });
    }
    get data() {
        return __classPrivateFieldGet(this, _ContentIndex_data, "f");
    }
    get startLength() {
        return 4;
    }
    setItems(items) {
        __classPrivateFieldGet(this, _ContentIndex_data, "f").items = items;
    }
    setTrie(trie) {
        __classPrivateFieldGet(this, _ContentIndex_data, "f").trie = trie;
    }
    getAll() {
        const results = {};
        this._appendToResults("", __classPrivateFieldGet(this, _ContentIndex_data, "f").trie, results);
        return results;
    }
    _isTermToAvoid(term) {
        return AvoidTermList.includes(term);
    }
    _appendToResults(prefix, node, results) {
        for (const token in node) {
            const subNode = node[token];
            if (subNode) {
                if (token === "±" || token === "$") {
                    const arr = subNode;
                    if (arr.constructor === Array) {
                        if (!this._isTermToAvoid(prefix)) {
                            results[prefix] = this.getValuesFromIndexArray(arr);
                        }
                    }
                }
                else if (subNode.constructor === Array) {
                    if (!this._isTermToAvoid(prefix + token)) {
                        results[prefix + token] = this.getValuesFromIndexArray(subNode);
                    }
                }
                else {
                    this._appendToResults(prefix + token, subNode, results);
                }
            }
        }
    }
    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);
        }
    }
    getValuesFromIndexArray(indices) {
        const results = [];
        if (!indices) {
            return results;
        }
        for (const index of indices) {
            if (typeof index === "object") {
                const indexN = index.n;
                if (indexN >= 0 && indexN < __classPrivateFieldGet(this, _ContentIndex_data, "f").items.length) {
                    results.push({ value: __classPrivateFieldGet(this, _ContentIndex_data, "f").items[indexN], annotation: index.a });
                }
            }
            else if (index >= 0 && index < __classPrivateFieldGet(this, _ContentIndex_data, "f").items.length) {
                results.push({ value: __classPrivateFieldGet(this, _ContentIndex_data, "f").items[index], annotation: undefined });
            }
        }
        return results;
    }
    loadFromData(data) {
        __classPrivateFieldSet(this, _ContentIndex_data, data, "f");
    }
    async getMatches(searchString) {
        searchString = searchString.trim().toLowerCase();
        const 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 = this.getValuesFromIndexArray(andResults);
        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 = __classPrivateFieldGet(this, _ContentIndex_data, "f").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 = __classPrivateFieldGet(this, _ContentIndex_data, "f").trie;
        let hasAdvanced = true;
        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) {
                        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) {
            return curNode;
        }
        else if (curNode["±"] !== undefined) {
            return curNode["±"];
        }
        else {
            const arr = [];
            this.aggregateIndices(curNode, arr);
            return arr;
        }
    }
    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 > 40) {
            return;
        }
        // since we treat ± as special, ban usage of ± in strings.
        key = key.replace(/±/gi, "").toLowerCase().trim();
        let keyIndex = 0;
        let curNode = __classPrivateFieldGet(this, _ContentIndex_data, "f").trie;
        let parentNode = curNode;
        let curNodeIndex;
        let dataIndex = -1;
        let curIndex = 0;
        for (const itemCand of __classPrivateFieldGet(this, _ContentIndex_data, "f").items) {
            if (itemCand === item) {
                dataIndex = curIndex;
                break;
            }
            curIndex++;
        }
        if (dataIndex < 0) {
            dataIndex = __classPrivateFieldGet(this, _ContentIndex_data, "f").items.length;
            __classPrivateFieldGet(this, _ContentIndex_data, "f").items.push(item);
        }
        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.
                        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 (!this._isTermToAvoid(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 (!this._isTermToAvoid(substr)) {
                    // create a new leaf array
                    curNode[substr] = this.ensureAnnotatedContentInArray([], dataIndex, annotationChar);
                }
            }
        }
        else {
            if (curNode.constructor === Array && curNodeIndex) {
                if (!this._isTermToAvoid(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)) {
                        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)) {
                        dictionaryOfTerms[curWord] = true;
                    }
                    curWord = "";
                }
            }
            else {
                curWord += content[i];
            }
        }
        for (const term in dictionaryOfTerms) {
            this.insert(term, sourcePath);
        }
    }
}
exports.default = ContentIndex;
_ContentIndex_data = new WeakMap();

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