"use strict";
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
    for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isObject = exports.Checker = void 0;
const chalk_1 = __importDefault(require("chalk"));
const child_process_1 = require("child_process");
const lodash_pick_1 = __importDefault(require("lodash.pick"));
const npm_run_path_1 = __importDefault(require("npm-run-path"));
const path_1 = __importDefault(require("path"));
const Checker_1 = require("./Checker");
const index_1 = require("./client/index");
const types_1 = require("./types");
__exportStar(require("./types"), exports);
__exportStar(require("./codeFrame"), exports);
__exportStar(require("./worker"), exports);
var Checker_2 = require("./Checker");
Object.defineProperty(exports, "Checker", { enumerable: true, get: function () { return Checker_2.Checker; } });
const sharedConfigKeys = ['enableBuild', 'overlay'];
const buildInCheckerKeys = ['typescript', 'vueTsc', 'vls', 'eslint'];
function createCheckers(userConfig, env) {
    const serveAndBuildCheckers = [];
    const sharedConfig = (0, lodash_pick_1.default)(userConfig, sharedConfigKeys);
    buildInCheckerKeys.forEach((name) => {
        if (!userConfig[name])
            return;
        const { createServeAndBuild } = require(`./checkers/${name}/main`);
        serveAndBuildCheckers.push(createServeAndBuild(Object.assign({ [name]: userConfig[name] }, sharedConfig), env));
    });
    return serveAndBuildCheckers;
}
function Plugin(userConfig) {
    var _a;
    const enableBuild = (_a = userConfig === null || userConfig === void 0 ? void 0 : userConfig.enableBuild) !== null && _a !== void 0 ? _a : true;
    const enableOverlay = (userConfig === null || userConfig === void 0 ? void 0 : userConfig.overlay) !== false;
    const enableTerminal = (userConfig === null || userConfig === void 0 ? void 0 : userConfig.terminal) !== false;
    const overlayConfig = typeof (userConfig === null || userConfig === void 0 ? void 0 : userConfig.overlay) === 'object' ? userConfig === null || userConfig === void 0 ? void 0 : userConfig.overlay : null;
    let checkers = [];
    let viteMode;
    let resolvedConfig;
    return {
        name: 'vite-plugin-checker',
        config: (config, env) => {
            // for dev mode (1/2)
            // Initialize checker with config
            viteMode = env.command;
            checkers = createCheckers(userConfig || {}, env);
            if (viteMode !== 'serve')
                return;
            checkers.forEach((checker) => {
                const workerConfig = checker.serve.config;
                workerConfig({
                    enableOverlay,
                    enableTerminal,
                    env,
                });
            });
        },
        configResolved(config) {
            resolvedConfig = config;
        },
        buildEnd() {
            if (viteMode === 'serve') {
                checkers.forEach((checker) => {
                    const { worker } = checker.serve;
                    worker.terminate();
                });
            }
        },
        resolveId(id) {
            if (viteMode === 'serve') {
                if (id === index_1.RUNTIME_PUBLIC_PATH) {
                    return id;
                }
            }
        },
        load(id) {
            if (viteMode === 'serve') {
                if (id === index_1.RUNTIME_PUBLIC_PATH) {
                    return index_1.runtimeCode;
                }
            }
        },
        transform(code, id) {
            if (id === index_1.RUNTIME_PUBLIC_PATH) {
                if (!resolvedConfig)
                    return;
                // #region
                // copied from https://github.dev/vitejs/vite/blob/76bbcd09985f85f7786b7e2e2d5ce177ee7d1916/packages/vite/src/client/client.ts#L25
                let options = resolvedConfig.server.hmr;
                options = options && typeof options !== 'boolean' ? options : {};
                const host = options.host || null;
                const protocol = options.protocol || null;
                let port;
                if (isObject(resolvedConfig.server.hmr)) {
                    port = resolvedConfig.server.hmr.clientPort || resolvedConfig.server.hmr.port;
                }
                if (resolvedConfig.server.middlewareMode) {
                    port = String(port || 24678);
                }
                else {
                    port = String(port || options.port || resolvedConfig.server.port);
                }
                let hmrBase = resolvedConfig.base;
                if (options.path) {
                    hmrBase = path_1.default.posix.join(hmrBase, options.path);
                }
                if (hmrBase !== '/') {
                    port = path_1.default.posix.normalize(`${port}${hmrBase}`);
                }
                return code
                    .replace(/__HMR_PROTOCOL__/g, JSON.stringify(protocol))
                    .replace(/__HMR_HOSTNAME__/g, JSON.stringify(host))
                    .replace(/__HMR_PORT__/g, JSON.stringify(port));
                // #endregion
            }
            return null;
        },
        transformIndexHtml() {
            if (viteMode === 'serve') {
                return [
                    {
                        tag: 'script',
                        attrs: { type: 'module' },
                        children: `import { inject } from "${index_1.RUNTIME_PUBLIC_PATH}"; inject();`,
                    },
                ];
            }
        },
        buildStart: () => {
            // for build mode
            // run a bin command in a separated process
            if (viteMode !== 'build')
                return;
            // do not do anything when disable build mode
            if (!enableBuild)
                return;
            const localEnv = npm_run_path_1.default.env({
                env: process.env,
                cwd: process.cwd(),
                execPath: process.execPath,
            });
            (() => __awaiter(this, void 0, void 0, function* () {
                var _a;
                const exitCodes = yield Promise.all(checkers.map((checker) => spawnChecker(checker, userConfig, localEnv)));
                const exitCode = (_a = exitCodes.find((code) => code !== 0)) !== null && _a !== void 0 ? _a : 0;
                if (exitCode !== 0)
                    process.exit(exitCode);
            }))();
        },
        configureServer(server) {
            let connectedTimes = 0;
            let latestOverlayErrors = new Array(checkers.length);
            // for dev mode (2/2)
            // Get the server instance and keep reference in a closure
            if (overlayConfig) {
                server.ws.send({
                    type: 'custom',
                    event: index_1.WS_CHECKER_CONFIG_RUNTIME_EVENT,
                    data: overlayConfig,
                });
            }
            checkers.forEach((checker, index) => {
                const { worker, configureServer: workerConfigureServer } = checker.serve;
                workerConfigureServer({ root: server.config.root });
                worker.on('message', (action) => {
                    if (action.type === types_1.ACTION_TYPES.overlayError) {
                        latestOverlayErrors[index] = action.payload;
                        if (action.payload) {
                            server.ws.send(action.payload);
                        }
                    }
                    else if (action.type === types_1.ACTION_TYPES.console) {
                        Checker_1.Checker.log(action);
                    }
                });
            });
            return () => {
                if (server.ws.on) {
                    // sometimes Vite will trigger a full-reload instead of HMR, but the checker
                    // may update the overlay before full-reload fired. So we make sure the overlay
                    // will be displayed again after full-reload.
                    server.ws.on('connection', () => {
                        connectedTimes++;
                        // if connectedCount !== 1, means Vite is doing a full-reload, so we don't need to send overlay again
                        if (connectedTimes > 1) {
                            if (overlayConfig) {
                                server.ws.send({
                                    type: 'custom',
                                    event: index_1.WS_CHECKER_CONFIG_RUNTIME_EVENT,
                                    data: overlayConfig,
                                });
                            }
                            server.ws.send({
                                type: 'custom',
                                event: index_1.WS_CHECKER_RECONNECT_EVENT,
                                data: latestOverlayErrors.filter(Boolean),
                            });
                        }
                    });
                }
                else {
                    setTimeout(() => {
                        console.warn(chalk_1.default.yellow("[vite-plugin-checker]: `server.ws.on` is introduced to Vite in 2.6.8, see [PR](https://github.com/vitejs/vite/pull/5273) and [changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md#268-2021-10-18). \nvite-plugin-checker relies on `server.ws.on` to bring diagnostics back after a full reload and it' not available for you now due to the old version of Vite. You can upgrade Vite to latest version to eliminate this warning."));
                        // make a delay to avoid flush by Vite's console
                    }, 5000);
                }
                server.middlewares.use((req, res, next) => {
                    next();
                });
            };
        },
    };
}
exports.default = Plugin;
function spawnChecker(checker, userConfig, localEnv) {
    return new Promise((resolve) => {
        const buildBin = checker.build.buildBin;
        const finalBin = typeof buildBin === 'function' ? buildBin(userConfig) : buildBin;
        const proc = (0, child_process_1.spawn)(...finalBin, {
            cwd: process.cwd(),
            stdio: 'inherit',
            env: localEnv,
            // shell is necessary on windows to get the process to even start.
            // Command line args constructed by checkers therefore need to escape double quotes
            // to have them not striped out by cmd.exe. Using shell on all platforms lets us avoid
            // having to perform platform-specific logic around escaping quotes since all platform
            // shells will strip out unescaped double quotes. E.g. shell=false on linux only would
            // result in escaped quotes not being unescaped.
            shell: true,
        });
        proc.on('exit', (code) => {
            if (code !== null && code !== 0) {
                resolve(code);
            }
            else {
                resolve(0);
            }
        });
    });
}
function isObject(value) {
    return Object.prototype.toString.call(value) === '[object Object]';
}
exports.isObject = isObject;
