"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ModuleGraph = exports.ModuleNode = void 0;
const path_1 = require("path");
const css_1 = require("../plugins/css");
const utils_1 = require("../utils");
const url_1 = require("url");
class ModuleNode {
    constructor(url) {
        /**
         * Resolved file system path + query
         */
        this.id = null;
        this.file = null;
        this.importers = new Set();
        this.importedModules = new Set();
        this.acceptedHmrDeps = new Set();
        this.isSelfAccepting = false;
        this.transformResult = null;
        this.lastHMRTimestamp = 0;
        this.url = url;
        this.type = css_1.isDirectCSSRequest(url) ? 'css' : 'js';
    }
}
exports.ModuleNode = ModuleNode;
class ModuleGraph {
    constructor(container) {
        this.urlToModuleMap = new Map();
        this.idToModuleMap = new Map();
        // a single file may corresponds to multiple modules with different queries
        this.fileToModulesMap = new Map();
        this.container = container;
    }
    async getModuleByUrl(rawUrl) {
        const [url] = await this.resolveUrl(rawUrl);
        return this.urlToModuleMap.get(url);
    }
    getModuleById(id) {
        return this.idToModuleMap.get(utils_1.removeTimestampQuery(id));
    }
    getModulesByFile(file) {
        return this.fileToModulesMap.get(file);
    }
    onFileChange(file) {
        const mods = this.getModulesByFile(file);
        if (mods) {
            mods.forEach((mod) => {
                mod.transformResult = null;
            });
        }
    }
    /**
     * Update the module graph based on a module's updated imports information
     * If there are dependencies that no longer have any importers, they are
     * returned as a Set.
     */
    async updateModuleInfo(mod, importedModules, acceptedModules, isSelfAccepting) {
        mod.isSelfAccepting = isSelfAccepting;
        const prevImports = mod.importedModules;
        const nextImports = (mod.importedModules = new Set());
        let noLongerImported;
        // update import graph
        for (const imported of importedModules) {
            const dep = typeof imported === 'string'
                ? await this.ensureEntryFromUrl(imported)
                : imported;
            dep.importers.add(mod);
            nextImports.add(dep);
        }
        // remove the importer from deps that were imported but no longer are.
        prevImports.forEach((dep) => {
            if (!nextImports.has(dep)) {
                dep.importers.delete(mod);
                if (!dep.importers.size) {
                    // dependency no longer imported
                    ;
                    (noLongerImported || (noLongerImported = new Set())).add(dep);
                }
            }
        });
        // update accepted hmr deps
        const deps = (mod.acceptedHmrDeps = new Set());
        for (const accepted of acceptedModules) {
            const dep = typeof accepted === 'string'
                ? await this.ensureEntryFromUrl(accepted)
                : accepted;
            deps.add(dep);
        }
        return noLongerImported;
    }
    async ensureEntryFromUrl(rawUrl) {
        const [url, resolvedId] = await this.resolveUrl(rawUrl);
        let mod = this.urlToModuleMap.get(url);
        if (!mod) {
            mod = new ModuleNode(url);
            this.urlToModuleMap.set(url, mod);
            mod.id = resolvedId;
            this.idToModuleMap.set(resolvedId, mod);
            const file = (mod.file = utils_1.cleanUrl(resolvedId));
            let fileMappedMdoules = this.fileToModulesMap.get(file);
            if (!fileMappedMdoules) {
                fileMappedMdoules = new Set();
                this.fileToModulesMap.set(file, fileMappedMdoules);
            }
            fileMappedMdoules.add(mod);
        }
        return mod;
    }
    // some deps, like a css file referenced via @import, don't have its own
    // url because they are inlined into the main css import. But they still
    // need to be represented in the module graph so that they can trigger
    // hmr in the importing css file.
    createFileOnlyEntry(file) {
        file = utils_1.normalizePath(file);
        const url = `/@fs/${file}`;
        let fileMappedMdoules = this.fileToModulesMap.get(file);
        if (!fileMappedMdoules) {
            fileMappedMdoules = new Set();
            this.fileToModulesMap.set(file, fileMappedMdoules);
        }
        for (const m of fileMappedMdoules) {
            if (m.url === url) {
                return m;
            }
        }
        const mod = new ModuleNode(url);
        mod.file = file;
        fileMappedMdoules.add(mod);
        return mod;
    }
    // for incoming urls, it is important to:
    // 1. remove the HMR timestamp query (?t=xxxx)
    // 2. resolve its extension so that urls with or without extension all map to
    // the same module
    async resolveUrl(url) {
        var _a;
        url = utils_1.removeImportQuery(utils_1.removeTimestampQuery(url));
        const resolvedId = ((_a = (await this.container.resolveId(url))) === null || _a === void 0 ? void 0 : _a.id) || url;
        const ext = path_1.extname(utils_1.cleanUrl(resolvedId));
        const { pathname, search, hash } = url_1.parse(url);
        if (ext && !pathname.endsWith(ext)) {
            url = pathname + ext + (search || '') + (hash || '');
        }
        return [url, resolvedId];
    }
}
exports.ModuleGraph = ModuleGraph;
//# sourceMappingURL=moduleGraph.js.map