"use strict";
/**
 * This file is refactored into TypeScript based on
 * https://github.com/preactjs/wmr/blob/master/src/lib/rollup-plugin-container.js
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (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;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createPluginContainer = void 0;
/**
https://github.com/preactjs/wmr/blob/master/LICENSE

MIT License

Copyright (c) 2020 The Preact Authors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
const fs_1 = __importDefault(require("fs"));
const path_1 = require("path");
const crypto_1 = require("crypto");
const acorn = __importStar(require("acorn"));
const acorn_class_fields_1 = __importDefault(require("acorn-class-fields"));
const merge_source_map_1 = __importDefault(require("merge-source-map"));
const magic_string_1 = __importDefault(require("magic-string"));
const utils_1 = require("../utils");
const chalk_1 = __importDefault(require("chalk"));
async function createPluginContainer({ plugins, logger, root, build: { rollupOptions } }, watcher) {
    const isDebug = process.env.DEBUG;
    const seenResolves = {};
    const debugResolve = utils_1.createDebugger('vite:resolve');
    const debugPluginResolve = utils_1.createDebugger('vite:plugin-resolve', {
        onlyWhenFocused: 'vite:plugin'
    });
    const debugPluginTransform = utils_1.createDebugger('vite:plugin-transform', {
        onlyWhenFocused: 'vite:plugin'
    });
    // ---------------------------------------------------------------------------
    // counter for generating unique emitted asset IDs
    let ids = 0;
    let parser = acorn.Parser;
    const MODULES = new Map();
    const files = new Map();
    const watchFiles = new Set();
    // get rollup version
    const rollupPkgPath = path_1.resolve(require.resolve('rollup'), '../../package.json');
    const minimalContext = {
        meta: {
            rollupVersion: JSON.parse(fs_1.default.readFileSync(rollupPkgPath, 'utf-8'))
                .version,
            watchMode: true
        }
    };
    // we should create a new context for each async hook pipeline so that the
    // active plugin in that pipeline can be tracked in a concurrency-safe manner.
    // using a class to make creating new contexts more efficient
    class Context {
        constructor(initialPlugin) {
            this.meta = minimalContext.meta;
            this._activeId = null;
            this._activeCode = null;
            this._activePlugin = initialPlugin || null;
        }
        parse(code, opts = {}) {
            return parser.parse(code, {
                sourceType: 'module',
                ecmaVersion: 2020,
                locations: true,
                onComment: [],
                ...opts
            });
        }
        async resolve(id, importer, options) {
            const skip = [];
            if ((options === null || options === void 0 ? void 0 : options.skipSelf) && this._activePlugin)
                skip.push(this._activePlugin);
            let out = await container.resolveId(id, importer, skip);
            if (typeof out === 'string')
                out = { id: out };
            return out;
        }
        getModuleInfo(id) {
            let mod = MODULES.get(id);
            if (mod)
                return mod.info;
            mod = {
                /** @type {import('rollup').ModuleInfo} */
                // @ts-ignore-next
                info: {}
            };
            MODULES.set(id, mod);
            return mod.info;
        }
        getModuleIds() {
            return MODULES.keys();
        }
        addWatchFile(id) {
            watchFiles.add(id);
            // only need to add it if file is out of root.
            if (watcher && !id.startsWith(root)) {
                watcher.add(id);
            }
        }
        getWatchFiles() {
            return [...watchFiles];
        }
        emitFile(assetOrFile) {
            const { type, name, fileName } = assetOrFile;
            const source = assetOrFile.type === 'asset' && assetOrFile.source;
            const id = String(++ids);
            const filename = fileName || generateFilename(type, name, source, fileName);
            files.set(id, { type, id, name, fileName: filename });
            if (source) {
                if (type === 'chunk') {
                    throw Error(`emitFile({ type:"chunk" }) cannot include a source`);
                }
                // TODO
                // if (opts.writeFile) opts.writeFile(filename, source)
                // else fs.writeFile(filename, source)
            }
            return id;
        }
        setAssetSource(assetId, source) {
            const asset = files.get(String(assetId));
            if (!asset) {
                throw new Error(`setAssetSource() called on non-existent asset with id ${assetId}`);
            }
            if (asset.type === 'chunk') {
                throw Error(`setAssetSource() called on a chunk`);
            }
            asset.source = source;
            // TODO
            // if (opts.writeFile) opts.writeFile(asset.fileName!, source)
            // else fs.writeFile(asset.fileName!, source)
        }
        getFileName(referenceId) {
            return container.resolveFileUrl(referenceId);
        }
        warn(...args) {
            logger.warn(chalk_1.default.yellow(`[${this._activePlugin.name}]`, ...args));
        }
        error(e, position) {
            const err = (typeof e === 'string' ? new Error(e) : e);
            if (this._activePlugin)
                err.plugin = this._activePlugin.name;
            if (this._activeId && !err.id)
                err.id = this._activeId;
            if (this._activeCode) {
                err.pluginCode = this._activeCode;
                const pos = position || err.pos;
                if (pos) {
                    err.loc = err.loc || {
                        file: err.id,
                        ...utils_1.numberToPos(this._activeCode, pos)
                    };
                    err.frame = err.frame || utils_1.generateCodeFrame(this._activeCode, pos);
                }
                else if (err.line && err.column) {
                    err.loc = {
                        file: err.id,
                        line: err.line,
                        column: err.column
                    };
                    err.frame = err.frame || utils_1.generateCodeFrame(this._activeCode, err.loc);
                }
                else if (err.loc) {
                    err.frame = err.frame || utils_1.generateCodeFrame(this._activeCode, err.loc);
                }
            }
            // error thrown here is caught by the transform middleware and passed on
            // the the error middleware.
            throw err;
        }
    }
    class TransformContext extends Context {
        constructor(filename, code, inMap) {
            super();
            this.originalSourcemap = null;
            this.sourcemapChain = [];
            this.combinedMap = null;
            this.filename = filename;
            this.originalCode = code;
            if (inMap) {
                this.sourcemapChain.push(inMap);
            }
        }
        _getCombinedSourcemap(createIfNull = false) {
            let combinedMap = this.combinedMap;
            for (let m of this.sourcemapChain) {
                if (typeof m === 'string')
                    m = JSON.parse(m);
                if (!combinedMap) {
                    combinedMap = m;
                }
                else {
                    // merge-source-map will overwrite original sources if newMap also has
                    // sourcesContent
                    // @ts-ignore
                    combinedMap = merge_source_map_1.default(combinedMap, {
                        ...m,
                        sourcesContent: combinedMap.sourcesContent
                    });
                }
            }
            if (!combinedMap) {
                return createIfNull
                    ? new magic_string_1.default(this.originalCode).generateMap({
                        includeContent: true,
                        hires: true,
                        source: this.filename
                    })
                    : null;
            }
            if (combinedMap !== this.combinedMap) {
                this.combinedMap = combinedMap;
                this.sourcemapChain.length = 0;
            }
            return this.combinedMap;
        }
        getCombinedSourcemap() {
            return this._getCombinedSourcemap(true);
        }
    }
    let nestedResolveCall = 0;
    const container = {
        options: await (async () => {
            let options = rollupOptions;
            for (const plugin of plugins) {
                if (!plugin.options)
                    continue;
                options =
                    (await plugin.options.call(minimalContext, options)) || options;
            }
            if (options.acornInjectPlugins) {
                parser = acorn.Parser.extend(...[acorn_class_fields_1.default].concat(options.acornInjectPlugins));
            }
            return {
                acorn,
                acornInjectPlugins: [],
                ...options
            };
        })(),
        async buildStart() {
            await Promise.all(plugins.map((plugin) => {
                if (plugin.buildStart) {
                    return plugin.buildStart.call(new Context(plugin), container.options);
                }
            }));
        },
        async resolveId(rawId, importer = path_1.join(root, 'index.html'), _skip) {
            const ctx = new Context();
            const key = `${rawId}\n${importer}` +
                (_skip ? _skip.map((p) => p.name).join('\n') : ``);
            nestedResolveCall++;
            const resolveStart = Date.now();
            let id = null;
            const partial = {};
            for (const plugin of plugins) {
                if (!plugin.resolveId)
                    continue;
                if (_skip) {
                    if (_skip.includes(plugin))
                        continue;
                    if (resolveSkips.has(plugin, key))
                        continue;
                    resolveSkips.add(plugin, key);
                }
                ctx._activePlugin = plugin;
                let result;
                const pluginResolveStart = Date.now();
                try {
                    result = await plugin.resolveId.call(ctx, rawId, importer, {});
                }
                finally {
                    if (_skip)
                        resolveSkips.delete(plugin, key);
                }
                if (!result)
                    continue;
                if (typeof result === 'string') {
                    id = result;
                }
                else {
                    id = result.id;
                    Object.assign(partial, result);
                }
                isDebug &&
                    debugPluginResolve(utils_1.timeFrom(pluginResolveStart), plugin.name, utils_1.prettifyUrl(id, root));
                // resolveId() is hookFirst - first non-null result is returned.
                break;
            }
            if (id) {
                partial.id = id;
            }
            nestedResolveCall--;
            if (isDebug &&
                !nestedResolveCall &&
                rawId !== id &&
                !rawId.startsWith('/@fs/')) {
                const key = rawId + id;
                // avoid spamming
                if (!seenResolves[key]) {
                    seenResolves[key] = true;
                    debugResolve(`${utils_1.timeFrom(resolveStart)} ${chalk_1.default.cyan(rawId)} -> ${chalk_1.default.dim(id)}`);
                }
            }
            return id ? partial : null;
        },
        async load(id) {
            const ctx = new Context();
            for (const plugin of plugins) {
                if (!plugin.load)
                    continue;
                ctx._activePlugin = plugin;
                const result = await plugin.load.call(ctx, id);
                if (result) {
                    return result;
                }
            }
            return null;
        },
        async transform(code, id, inMap) {
            const ctx = new TransformContext(id, code, inMap);
            for (const plugin of plugins) {
                if (!plugin.transform)
                    continue;
                ctx._activePlugin = plugin;
                ctx._activeId = id;
                ctx._activeCode = code;
                const start = Date.now();
                let result;
                try {
                    result = await plugin.transform.call(ctx, code, id);
                }
                catch (e) {
                    ctx.error(e);
                }
                if (!result)
                    continue;
                isDebug &&
                    debugPluginTransform(utils_1.timeFrom(start), plugin.name, utils_1.prettifyUrl(id, root));
                if (typeof result === 'object') {
                    code = result.code || '';
                    if (result.map)
                        ctx.sourcemapChain.push(result.map);
                }
                else {
                    code = result;
                }
            }
            return {
                code,
                map: ctx._getCombinedSourcemap()
            };
        },
        watchChange(id, event = 'update') {
            const ctx = new Context();
            if (watchFiles.has(id)) {
                for (const plugin of plugins) {
                    if (!plugin.watchChange)
                        continue;
                    ctx._activePlugin = plugin;
                    plugin.watchChange.call(ctx, id, { event });
                }
            }
        },
        resolveImportMeta(id, property) {
            const ctx = new Context();
            for (const plugin of plugins) {
                if (!plugin.resolveImportMeta)
                    continue;
                ctx._activePlugin = plugin;
                const result = plugin.resolveImportMeta.call(ctx, property, {
                    chunkId: '',
                    moduleId: id,
                    format: 'es'
                });
                if (result)
                    return result;
            }
            // handle file URLs by default
            const matches = property.match(/^ROLLUP_FILE_URL_(\d+)$/);
            if (matches) {
                const referenceId = matches[1];
                const result = container.resolveFileUrl(referenceId);
                if (result)
                    return result;
            }
        },
        resolveFileUrl(referenceId) {
            referenceId = String(referenceId);
            const file = files.get(referenceId);
            if (file == null)
                return null;
            const out = path_1.resolve(root || '.', outputOptions.dir || '.');
            const fileName = path_1.relative(out, file.fileName);
            const assetInfo = {
                referenceId,
                fileName,
                // @TODO: this should be relative to the module that imported the asset
                relativePath: fileName
            };
            const ctx = new Context();
            for (const plugin of plugins) {
                if (!plugin.resolveFileUrl)
                    continue;
                ctx._activePlugin = plugin;
                // @ts-ignore
                const result = plugin.resolveFileUrl.call(ctx, assetInfo);
                if (result != null) {
                    return result;
                }
            }
            return JSON.stringify('/' + fileName.split(path_1.sep).join(path_1.posix.sep));
        },
        async close() {
            const ctx = new Context();
            await Promise.all(plugins.map((p) => p.buildEnd && p.buildEnd.call(ctx)));
            await Promise.all(plugins.map((p) => p.closeBundle && p.closeBundle.call(ctx)));
        }
    };
    const toPosixPath = (path) => path.split(path_1.sep).join(path_1.posix.sep);
    function popIndex(array, index) {
        const tail = array.pop();
        if (index !== array.length)
            array[index] = tail;
    }
    // Tracks recursive resolveId calls
    const resolveSkips = {
        skip: new Map(),
        has(plugin, key) {
            const skips = this.skip.get(plugin);
            return skips ? skips.includes(key) : false;
        },
        add(plugin, key) {
            const skips = this.skip.get(plugin);
            if (skips)
                skips.push(key);
            else
                this.skip.set(plugin, [key]);
        },
        delete(plugin, key) {
            const skips = this.skip.get(plugin);
            if (!skips)
                return;
            const i = skips.indexOf(key);
            if (i !== -1)
                popIndex(skips, i);
        }
    };
    const outputOptions = Array.isArray(rollupOptions.output)
        ? rollupOptions.output[0]
        : rollupOptions.output || {};
    function generateFilename(type, name, source, fileName) {
        const posixName = toPosixPath(name);
        if (!fileName) {
            fileName = ((type === 'entry' && outputOptions.file) ||
                // @ts-ignore
                outputOptions[`${type}FileNames`] ||
                '[name][extname]');
            fileName = fileName.replace('[hash]', () => crypto_1.createHash('md5').update(String(source)).digest('hex').substring(0, 5));
            fileName = fileName.replace('[extname]', path_1.posix.extname(posixName));
            fileName = fileName.replace('[ext]', path_1.posix.extname(posixName).substring(1));
            fileName = fileName.replace('[name]', path_1.posix.basename(posixName).replace(/\.[a-z0-9]+$/g, ''));
        }
        const result = path_1.resolve(root || '.', outputOptions.dir || '.', fileName);
        return result;
    }
    return container;
}
exports.createPluginContainer = createPluginContainer;
//# sourceMappingURL=pluginContainer.js.map