import { fork } from "node:child_process";
// ── ColdRestarter 类 ────────────────────────────────────────
export class ColdRestarter {
    /**
     * 当前活跃的子进程引用
     *
     * 初始为 null，restart() 调用后持有子进程引用。
     * kill() 后重置为 null。
     */
    child = null;
    /**
     * 重启中 guard — 防止并行 restart
     *
     * 当 restart() 正在执行时，后续的 restart() 调用直接返回。
     * 这避免了用户快速连续修改多个配置文件时触发多次 fork。
     */
    isRestarting = false;
    /**
     * 标记是否由 restart 发起的 kill（区分预期退出和异常退出）
     */
    isExpectedKill = false;
    entryScript;
    killTimeout;
    readyTimeout;
    env;
    cwd;
    extraExecArgv;
    events = {};
    constructor(options) {
        this.entryScript = options.entryScript;
        this.killTimeout = options.killTimeout ?? 5000;
        this.readyTimeout = options.readyTimeout ?? 30_000;
        this.env = options.env ?? {};
        this.cwd = options.cwd;
        this.extraExecArgv = options.extraExecArgv ?? [];
    }
    /**
     * 设置事件监听器
     *
     * 允许外部模块（如 cli/dev.ts）监听子进程事件。
     * 每次调用会覆盖之前的监听器。
     *
     * @param events 事件回调集合
     */
    setEvents(events) {
        this.events = events;
    }
    /**
     * 更新冷重启子进程的额外 Node.js 运行参数
     *
     * 主要用于 dev 模式在项目级 preload 发生变化后，
     * 重新计算 `--import file:///...` 列表并在下一次 cold restart 中生效。
     */
    setExtraExecArgv(extraExecArgv) {
        this.extraExecArgv = [...extraExecArgv];
    }
    /**
     * restart — 执行 Cold Restart
     *
     * 完整流程：
     *   1. 检查 isRestarting guard（防并行）
     *   2. safeKill 旧子进程（SIGTERM → 超时 SIGKILL）
     *   3. fork 新子进程（纯 JS 入口，无需 tsx）
     *   4. 注册 IPC 消息监听 + 异常退出监听
     *   5. waitForReady（等待子进程 `{ type: 'ready' }` 消息，超时 30s）
     *
     * @param reason 重启原因（用于日志输出，如 "initial start" 或文件路径）
     * @throws 子进程启动超时或退出非零码时抛出错误
     */
    async restart(_reason) {
        if (this.isRestarting) {
            // 已经在重启中，合并（不重复执行）
            return;
        }
        this.isRestarting = true;
        try {
            // ── 1. 安全终止旧进程 ──────────────────────────────
            if (this.child && !this.child.killed) {
                this.isExpectedKill = true;
                await this.safeKill(this.child);
                this.isExpectedKill = false;
            }
            this.child = null;
            // ── 2. Fork 新进程 ─────────────────────────────────
            //
            // 注意：不需要 tsx loader 或 --import tsx/esm，
            // 因为 esbuild 已将 TS 源码编译为 CJS .js 文件。
            // 子进程入口（dev-entry.js）是纯 JS。
            //
            const childEnv = {
                ...process.env,
                VEXT_DEV_MODE: "1",
                ...this.env,
            };
            // 🆕 合并父进程现有 Node.js 标志（防止覆盖 --inspect、--max-old-space-size 等），
            // 追加 --enable-source-maps 使 Error.stack 自动翻译为 .ts 源码路径（dev 模式调试），
            // 追加 extraExecArgv（预加载模块 --import，如 vextjs-opentelemetry SDK 初始化）
            const devExecArgv = [
                ...process.execArgv.filter((f) => f !== "--enable-source-maps"),
                "--enable-source-maps",
                ...this.extraExecArgv,
            ];
            this.child = fork(this.entryScript, [], {
                env: childEnv,
                stdio: ["inherit", "inherit", "inherit", "ipc"],
                cwd: this.cwd,
                execArgv: devExecArgv,
            });
            this.setupChildListeners(this.child);
            // ── 4. 等待新进程就绪 ──────────────────────────────
            await this.waitForReady(this.child);
        }
        finally {
            this.isRestarting = false;
        }
    }
    /**
     * 向子进程发送 IPC 消息
     *
     * 用于向子进程传递指令，如：
     *   - `{ type: 'reload', files: [...] }` — soft reload 指令
     *   - `{ type: 'shutdown' }` — 优雅关闭指令
     *
     * 如果子进程不存在或 IPC 通道已断开，静默忽略（不抛出错误）。
     *
     * @param msg 要发送的消息（可序列化的对象）
     */
    sendToChild(msg) {
        if (this.child?.connected) {
            this.child.send(msg);
        }
    }
    /**
     * kill — 终止子进程（用于进程退出清理）
     *
     * 在 CLI 退出时调用，确保子进程被正确清理。
     * 使用与 restart 相同的 safeKill 流程（SIGTERM → 超时 SIGKILL）。
     */
    async kill() {
        if (this.child && !this.child.killed) {
            this.isExpectedKill = true;
            await this.safeKill(this.child);
            this.isExpectedKill = false;
            this.child = null;
        }
    }
    /**
     * 获取当前子进程的 PID（调试/日志用）
     *
     * @returns 子进程 PID，如果无活跃子进程返回 null
     */
    getChildPid() {
        return this.child?.pid ?? null;
    }
    /**
     * 检查是否正在重启中
     */
    getIsRestarting() {
        return this.isRestarting;
    }
    /**
     * 检查子进程是否存活
     */
    isChildAlive() {
        return this.child !== null && !this.child.killed;
    }
    // ── 私有方法 ──────────────────────────────────────────────
    /**
     * safeKill — 安全终止子进程
     *
     * 流程：
     *   1. 通知子进程关闭：
     *      - Windows: 优先通过 IPC 发送 { type: 'shutdown' } 消息（触发子进程优雅关闭）
     *        IPC 不可用时降级为 child.kill('SIGTERM')
     *      - Unix: 发送 SIGTERM（触发子进程 process.on('SIGTERM') 处理器）
     *   2. 等待子进程退出（最多 killTimeout ms）
     *   3. 超时后发送 SIGKILL 强制终止
     *
     * 🐛 修复 BUG-014：Windows 上 child.kill('SIGTERM') 不会触发子进程的
     * process.on('SIGTERM') 处理器——Node.js 在 Windows 上直接调用
     * TerminateProcess API 杀死进程，导致 onClose hooks 不执行。
     *
     * 使用 Promise + 双重退出监听确保在任何情况下都能 resolve：
     *   - 正常退出：'exit' 事件触发 → resolve
     *   - 超时强制终止：SIGKILL → 'exit' 事件 → resolve
     *
     * @param child 要终止的子进程
     */
    async safeKill(child) {
        return new Promise((resolve) => {
            let resolved = false;
            const done = () => {
                if (!resolved) {
                    resolved = true;
                    resolve();
                }
            };
            // 监听退出事件
            child.once("exit", () => {
                clearTimeout(timer);
                done();
            });
            // 第一步: 通知子进程优雅关闭
            if (process.platform === "win32" && child.connected) {
                // Windows: 通过 IPC 消息通知子进程执行优雅关闭
                // dev-bootstrap 的 process.on('message') 已监听 { type: 'shutdown' }
                try {
                    child.send({ type: "shutdown" });
                }
                catch {
                    // IPC 发送失败，降级为 kill
                    child.kill("SIGTERM");
                }
            }
            else {
                // Unix: 标准 SIGTERM 信号（触发子进程 process.on('SIGTERM') 处理器）
                child.kill("SIGTERM");
            }
            // 第二步: 超时后 SIGKILL（强制终止）
            const timer = setTimeout(() => {
                if (!child.killed) {
                    child.kill("SIGKILL");
                }
                // SIGKILL 后 exit 事件通常很快触发，
                // 但作为保险也在这里调用 done()
                done();
            }, this.killTimeout);
        });
    }
    /**
     * waitForReady — 等待子进程发送 ready 消息
     *
     * 子进程在 devBootstrap 完成初始化后发送 `{ type: 'ready' }` IPC 消息。
     * 本方法等待该消息，超时则视为启动失败。
     *
     * 异常处理：
     *   - 超时（readyTimeout）→ reject + Error
     *   - 子进程 error 事件 → reject + 原始 Error
     *   - 子进程退出（非零码）→ reject + Error
     *
     * @param child 要等待的子进程
     * @throws 超时或子进程异常退出时抛出错误
     */
    async waitForReady(child) {
        return new Promise((resolve, reject) => {
            let settled = false;
            const cleanup = () => {
                settled = true;
                clearTimeout(timer);
                child.removeListener("message", onMessage);
                child.removeListener("error", onError);
                child.removeListener("exit", onExit);
            };
            const timer = setTimeout(() => {
                if (!settled) {
                    cleanup();
                    reject(new Error(`[vext dev] worker startup timeout (${this.readyTimeout}ms)`));
                }
            }, this.readyTimeout);
            const onMessage = (msg) => {
                if (!settled &&
                    typeof msg === "object" &&
                    msg !== null &&
                    msg.type === "ready") {
                    cleanup();
                    resolve();
                }
            };
            const onError = (err) => {
                if (!settled) {
                    cleanup();
                    reject(err);
                }
            };
            const onExit = (code) => {
                if (!settled) {
                    cleanup();
                    reject(new Error(`[vext dev] worker exited with code ${code ?? "unknown"}`));
                }
            };
            child.on("message", onMessage);
            child.once("error", onError);
            child.once("exit", onExit);
        });
    }
    /**
     * setupChildListeners — 为新 fork 的子进程注册持久监听器
     *
     * 这些监听器在 waitForReady 完成后继续存活，
     * 用于处理运行期间的 IPC 消息和异常退出。
     *
     * @param child 新 fork 的子进程
     */
    setupChildListeners(child) {
        // ── IPC 消息转发 ──────────────────────────────────────
        //
        // 将子进程的 IPC 消息转发给外部监听器。
        // 子进程可能发送的消息类型：
        //   - { type: 'request-cold-restart', reason: '...' } — 级联检测过大
        //
        child.on("message", (msg) => {
            if (this.events.onChildMessage) {
                this.events.onChildMessage(msg);
            }
        });
        // ── 异常退出处理 ──────────────────────────────────────
        //
        // 仅在非预期退出时触发回调（restart 期间的 kill 不算异常）。
        // 外部可据此决定是否自动 restart 或提示用户。
        //
        child.once("exit", (code, signal) => {
            // 清除引用（防止对已退出进程调用 send/kill）
            if (this.child === child) {
                this.child = null;
            }
            // 非预期退出 → 通知外部
            if (!this.isExpectedKill && this.events.onChildExit) {
                this.events.onChildExit(code, signal);
            }
        });
    }
}
