"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DocsLibrary = void 0;
const yaml_1 = require("../utils/yaml");
const docsFinder_1 = require("../utils/docsFinder");
const docsParser_1 = require("../utils/docsParser");
const path = __importStar(require("path"));
const fs_1 = require("fs");
const vscode_uri_1 = require("vscode-uri");
const docsLibraryUtilsForPAC_1 = require("./docsLibraryUtilsForPAC");
const pathUtils_1 = require("../utils/pathUtils");
class DocsLibrary {
    connection;
    modules = new Map();
    _moduleFqcns = new Set();
    docFragments = new Map();
    context;
    pluginRouting = new Map();
    constructor(connection, context) {
        this.connection = connection;
        this.context = context;
    }
    async initialize() {
        try {
            const settings = await this.context.documentSettings.get(this.context.workspaceFolder.uri);
            const ansibleConfig = await this.context.ansibleConfig;
            if (settings.executionEnvironment.enabled) {
                // ensure plugin/module cache is established
                const executionEnvironment = await this.context.executionEnvironment;
                await executionEnvironment.fetchPluginDocs(ansibleConfig);
            }
            for (const modulesPath of ansibleConfig.module_locations) {
                await this.findDocumentationInModulesPath(modulesPath);
            }
            (await (0, docsFinder_1.findPluginRouting)(ansibleConfig.ansible_location, "builtin")).forEach((r, collection) => this.pluginRouting.set(collection, r));
            for (const collectionsPath of ansibleConfig.collections_paths) {
                await this.findDocumentationInCollectionsPath(collectionsPath);
            }
        }
        catch (error) {
            if (error instanceof Error) {
                this.connection.window.showErrorMessage(error.message);
            }
            else {
                this.connection.console.error(`Exception in DocsLibrary service: ${JSON.stringify(error)}`);
            }
        }
    }
    /**
     * Tries to find an Ansible module for a given name or FQCN.
     *
     * Parameters `contextPath` and `documentUri` are used to obtain contextual
     * information on declared collections. Hence these are not needed when
     * searching with FQCN.
     *
     * Returns the module if found and an FQCN for which either a module or a
     * route has been found.
     */
    async findModule(searchText, contextPath, documentUri) {
        // support playbook adjacent collections
        const playbookDirectory = vscode_uri_1.URI.parse(String(documentUri)).path.split(path.sep);
        playbookDirectory.pop();
        playbookDirectory.push("collections");
        const playbookAdjacentCollectionsPath = playbookDirectory.join(path.sep);
        const isAdjacentCollectionAvailable = (0, fs_1.existsSync)(playbookAdjacentCollectionsPath);
        // check if a module code is actually present or not
        const moduleFiles = (0, pathUtils_1.globArray)([
            `${playbookAdjacentCollectionsPath}/ansible_collections/*/*/plugins/modules/*.py`,
            `${playbookAdjacentCollectionsPath}/ansible_collections/*/*/plugins/modules/**/*.py`,
            `!${playbookAdjacentCollectionsPath}/ansible_collections/*/*/plugins/modules/_*.py`,
            `!${playbookAdjacentCollectionsPath}/ansible_collections/*/*/plugins/modules/**/_*.py`,
        ]).filter((item) => !(0, fs_1.lstatSync)(item).isSymbolicLink());
        if (isAdjacentCollectionAvailable && moduleFiles.length !== 0) {
            const [PAModule, PAHitFqcn] = await (0, docsLibraryUtilsForPAC_1.findModulesUtils)(playbookAdjacentCollectionsPath, searchText, this.context, contextPath, documentUri);
            if (PAModule) {
                // return early if module found in playbook adjacent collection
                return [PAModule, PAHitFqcn];
            }
        }
        // Now, start finding the module
        let hitFqcn;
        const candidateFqcns = await this.getCandidateFqcns(searchText, documentUri, contextPath);
        // check routing
        let moduleRoute;
        for (const fqcn of candidateFqcns) {
            moduleRoute = this.getModuleRoute(fqcn);
            if (moduleRoute) {
                hitFqcn = fqcn;
                break; // find first
            }
        }
        // find module
        let module;
        if (moduleRoute && moduleRoute.redirect) {
            module = this.modules.get(moduleRoute.redirect);
        }
        else {
            for (const fqcn of candidateFqcns) {
                module = this.modules.get(fqcn);
                if (module) {
                    if (!hitFqcn) {
                        hitFqcn = fqcn;
                    }
                    break; // find first
                }
            }
        }
        if (module) {
            if (!module.fragments) {
                // collect information from documentation fragments
                (0, docsParser_1.processDocumentationFragments)(module, this.docFragments);
            }
            if (!module.documentation) {
                // translate raw documentation into a typed structure
                module.documentation = (0, docsParser_1.processRawDocumentation)(module.rawDocumentationFragments);
            }
        }
        return [module, hitFqcn];
    }
    async findDocumentationInModulesPath(modulesPath) {
        (await (0, docsFinder_1.findDocumentation)(modulesPath, "builtin")).forEach((doc) => {
            this.modules.set(doc.fqcn, doc);
            this._moduleFqcns.add(doc.fqcn);
        });
        (await (0, docsFinder_1.findDocumentation)(modulesPath, "builtin_doc_fragment")).forEach((doc) => {
            this.docFragments.set(doc.fqcn, doc);
        });
    }
    async findDocumentationInCollectionsPath(collectionsPath) {
        (await (0, docsFinder_1.findDocumentation)(collectionsPath, "collection")).forEach((doc) => {
            this.modules.set(doc.fqcn, doc);
            this._moduleFqcns.add(doc.fqcn);
        });
        (await (0, docsFinder_1.findDocumentation)(collectionsPath, "collection_doc_fragment")).forEach((doc) => {
            this.docFragments.set(doc.fqcn, doc);
        });
        (await (0, docsFinder_1.findPluginRouting)(collectionsPath, "collection")).forEach((r, collection) => this.pluginRouting.set(collection, r));
        // add all valid redirect routes as possible FQCNs
        for (const [collection, routesByType] of this.pluginRouting) {
            for (const [name, route] of routesByType.get("modules") || []) {
                if (route.redirect && !route.tombstone) {
                    this._moduleFqcns.add(`${collection}.${name}`);
                }
            }
        }
    }
    async getCandidateFqcns(searchText, documentUri, contextPath) {
        const candidateFqcns = [];
        if (searchText.split(".").length >= 3) {
            candidateFqcns.push(searchText); // try searching as-is (FQCN match)
        }
        else {
            candidateFqcns.push(`ansible.builtin.${searchText}`); // try searching built-in
            if (documentUri) {
                const metadata = await this.context.documentMetadata.get(documentUri);
                if (metadata) {
                    // try searching declared collections
                    candidateFqcns.push(...metadata.collections.map((c) => `${c}.${searchText}`));
                }
            }
            if (contextPath) {
                candidateFqcns.push(...(0, yaml_1.getDeclaredCollections)(contextPath).map((c) => `${c}.${searchText}`));
            }
        }
        return candidateFqcns;
    }
    getModuleRoute(fqcn) {
        const fqcn_array = fqcn.split(".");
        if (fqcn_array.length >= 3) {
            const [namespace, collection] = fqcn_array;
            const name = fqcn_array.slice(2).join(".");
            return this.pluginRouting
                .get(`${namespace}.${collection}`)
                ?.get("modules")
                ?.get(name);
        }
    }
    async getModuleFqcns(documentUri) {
        // support playbook adjacent collections
        const playbookDirectory = vscode_uri_1.URI.parse(documentUri).path.split(path.sep);
        playbookDirectory.pop();
        playbookDirectory.push("collections");
        const playbookAdjacentCollectionsPath = playbookDirectory.join(path.sep);
        const isAdjacentCollectionAvailable = (0, fs_1.existsSync)(playbookAdjacentCollectionsPath);
        if (isAdjacentCollectionAvailable) {
            const paModuleFqcns = await (0, docsLibraryUtilsForPAC_1.getModuleFqcnsUtils)(playbookAdjacentCollectionsPath);
            // return early if appended list
            return new Set([...this._moduleFqcns, ...paModuleFqcns]);
        }
        return this._moduleFqcns;
    }
}
exports.DocsLibrary = DocsLibrary;
