(function WildcardExtension() {
    // Regex
    const WC_REGEX = new RegExp(/__([^,]+)__([^, ]*)/g);

    // Trigger conditions
    const WC_TRIGGER = () =>
        TAC.CFG.useWildcards &&
        [
            ...TAC.Globals.tagword.matchAll(
                new RegExp(
                    WC_REGEX.source.replaceAll("__", TAC.Utils.escapeRegExp(TAC.CFG.wcWrap)),
                    "g"
                )
            ),
        ].length > 0;
    const WC_FILE_TRIGGER = () =>
        TAC.CFG.useWildcards &&
        ((TAC.Globals.tagword.startsWith(TAC.CFG.wcWrap) &&
            !TAC.Globals.tagword.endsWith(TAC.CFG.wcWrap)) ||
            TAC.Globals.tagword === TAC.CFG.wcWrap);

    class WildcardParser extends TAC.BaseTagParser {
        async parse() {
            // Show wildcards from a file with that name
            let wcMatch = [
                ...TAC.Globals.tagword.matchAll(
                    new RegExp(
                        WC_REGEX.source.replaceAll("__", TAC.Utils.escapeRegExp(TAC.CFG.wcWrap)),
                        "g"
                    )
                ),
            ];
            let wcFile = wcMatch[0][1];
            let wcWord = wcMatch[0][2];

            // Look in normal wildcard files
            let wcFound = TAC.Globals.wildcardFiles.filter((x) => x[1].toLowerCase() === wcFile);
            if (wcFound.length === 0) wcFound = null;
            // Use found wildcard file or look in external wildcard files
            let wcPairs =
                wcFound ||
                TAC.Globals.wildcardExtFiles.filter((x) => x[1].toLowerCase() === wcFile);

            if (!wcPairs) return [];

            let wildcards = [];
            for (let i = 0; i < wcPairs.length; i++) {
                const basePath = wcPairs[i][0];
                const fileName = wcPairs[i][1];
                if (!basePath || !fileName) return;

                // YAML wildcards are already loaded as json, so we can get the values directly.
                // basePath is the name of the file in this case, and fileName the key
                if (basePath.endsWith(".yaml")) {
                    const getDescendantProp = (obj, desc) => {
                        const arr = desc.split("/");
                        while (arr.length) {
                            obj = obj[arr.shift()];
                        }
                        return obj;
                    };
                    wildcards = wildcards.concat(
                        getDescendantProp(TAC.Globals.yamlWildcards[basePath], fileName)
                    );
                } else {
                    const fileContent = (
                        await TAC.Utils.fetchAPI(
                            `tacapi/v1/wildcard-contents?basepath=${basePath}&filename=${fileName}.txt`,
                            false
                        )
                    )
                        .split("\n")
                        .filter((x) => x.trim().length > 0 && !x.startsWith("#")); // Remove empty lines and comments
                    wildcards = wildcards.concat(fileContent);
                }
            }

            if (TAC.CFG.sortWildcardResults) wildcards.sort((a, b) => a.localeCompare(b));

            let finalResults = [];
            let tempResults = wildcards.filter((x) =>
                wcWord !== null && wcWord.length > 0 ? x.toLowerCase().includes(wcWord) : x
            ); // Filter by tagword
            tempResults.forEach((t) => {
                let result = new TAC.AutocompleteResult(t.trim(), TAC.ResultType.wildcardTag);
                result.meta = wcFile;
                finalResults.push(result);
            });

            return finalResults;
        }
    }

    class WildcardFileParser extends TAC.BaseTagParser {
        parse() {
            // Show available wildcard files
            let tempResults = [];
            if (TAC.Globals.tagword !== TAC.CFG.wcWrap) {
                let lmb = (x) =>
                    x[1].toLowerCase().includes(TAC.Globals.tagword.replace(TAC.CFG.wcWrap, ""));
                tempResults = TAC.Globals.wildcardFiles
                    .filter(lmb)
                    .concat(TAC.Globals.wildcardExtFiles.filter(lmb)); // Filter by tagword
            } else {
                tempResults = TAC.Globals.wildcardFiles.concat(TAC.Globals.wildcardExtFiles);
            }

            let finalResults = [];
            const alreadyAdded = new Map();
            // Get final results
            tempResults.forEach((wcFile) => {
                // Skip duplicate entries incase multiple files have the same name or yaml category
                if (alreadyAdded.has(wcFile[1])) return;

                let result = null;
                if (wcFile[0].endsWith(".yaml")) {
                    result = new TAC.AutocompleteResult(
                        wcFile[1].trim(),
                        TAC.ResultType.yamlWildcard
                    );
                    result.meta = "YAML wildcard collection";
                } else {
                    result = new TAC.AutocompleteResult(
                        wcFile[1].trim(),
                        TAC.ResultType.wildcardFile
                    );
                    result.meta = "Wildcard file";
                    result.sortKey = wcFile[2].trim();
                }

                finalResults.push(result);
                alreadyAdded.set(wcFile[1], true);
            });

            finalResults.sort(TAC.Utils.getSortFunction());

            return finalResults;
        }
    }

    async function load() {
        if (TAC.Globals.wildcardFiles.length === 0 && TAC.Globals.wildcardExtFiles.length === 0) {
            try {
                let wcFileArr = await TAC.Utils.loadCSV(`${TAC.Globals.tagBasePath}/temp/wc.txt`);
                if (wcFileArr && wcFileArr.length > 0) {
                    let wcBasePath = wcFileArr[0][0].trim(); // First line should be the base path
                    TAC.Globals.wildcardFiles = wcFileArr
                        .slice(1)
                        .filter((x) => x[0]?.trim().length > 0) //Remove empty lines
                        .map((x) => [wcBasePath, x[0]?.trim().replace(".txt", ""), x[1]]); // Remove file extension & newlines
                }

                // To support multiple sources, we need to separate them using the provided "-----" strings
                let wcExtFileArr = await TAC.Utils.loadCSV(
                    `${TAC.Globals.tagBasePath}/temp/wce.txt`
                );
                let splitIndices = [];
                for (let index = 0; index < wcExtFileArr.length; index++) {
                    if (wcExtFileArr[index][0].trim() === "-----") {
                        splitIndices.push(index);
                    }
                }
                // For each group, add them to the wildcardFiles array with the base path as the first element
                for (let i = 0; i < splitIndices.length; i++) {
                    let start = splitIndices[i - 1] || 0;
                    if (i > 0) start++; // Skip the "-----" line
                    let end = splitIndices[i];

                    let wcExtFile = wcExtFileArr.slice(start, end);
                    if (wcExtFile && wcExtFile.length > 0) {
                        let base = wcExtFile[0][0].trim() + "/";
                        wcExtFile = wcExtFile
                            .slice(1)
                            .filter((x) => x[0]?.trim().length > 0) //Remove empty lines
                            .map((x) => [
                                base,
                                x[0]?.trim().replace(base, "").replace(".txt", ""),
                                x[1],
                            ]);
                        TAC.Globals.wildcardExtFiles.push(...wcExtFile);
                    }
                }

                // Load the yaml wildcard json file and append it as a wildcard file, appending each key as a path component until we reach the end
                TAC.Globals.yamlWildcards = await TAC.Utils.readFile(
                    `${TAC.Globals.tagBasePath}/temp/wc_yaml.json`,
                    true
                );

                // Append each key as a path component until we reach a leaf
                Object.keys(TAC.Globals.yamlWildcards).forEach((file) => {
                    const flattened = TAC.Utils.flatten(TAC.Globals.yamlWildcards[file], [], "/");
                    Object.keys(flattened).forEach((key) => {
                        TAC.Globals.wildcardExtFiles.push([file, key]);
                    });
                });
            } catch (e) {
                console.error("Error loading wildcards: " + e);
            }
        }
    }

    function sanitize(tagType, text) {
        if (tagType === TAC.ResultType.wildcardFile || tagType === TAC.ResultType.yamlWildcard) {
            return `${TAC.CFG.wcWrap}${text}${TAC.CFG.wcWrap}`;
        } else if (tagType === TAC.ResultType.wildcardTag) {
            return text;
        }
        return null;
    }

    function keepOpenIfWildcard(tagType, sanitizedText, newPrompt, textArea) {
        // If it's a wildcard, we want to keep the results open so the user can select another wildcard
        if (tagType === TAC.ResultType.wildcardFile || tagType === TAC.ResultType.yamlWildcard) {
            TAC.Globals.hideBlocked = true;
            setTimeout(() => {
                TAC.Globals.hideBlocked = false;
            }, 450);
            return true;
        }
        return false;
    }

    // Register the parsers
    TAC.Ext.PARSERS.push(new WildcardParser(WC_TRIGGER));
    TAC.Ext.PARSERS.push(new WildcardFileParser(WC_FILE_TRIGGER));

    // Add our utility functions to their respective queues
    TAC.Ext.QUEUE_FILE_LOAD.push(load);
    TAC.Ext.QUEUE_SANITIZE.push(sanitize);
    TAC.Ext.QUEUE_AFTER_INSERT.push(keepOpenIfWildcard);
})();
