import { EventEmitter } from "node:events";
import type { ClassifierOptions } from "./change-classifier.js";
/**
 * file-watcher.ts — VextFileWatcher 文件监听器（Phase 2A）
 *
 * 热重载的入口组件，负责：
 *
 *   1. **监听** `src/` 目录、项目根 `preload/` 目录和根目录配置文件的变更
 *   2. **分类** 变更文件为 `cold`（冷重启）、`soft`（热替换）或 `ignore`（忽略）
 *   3. **识别变更类型**（`modify` / `add` / `delete`）— 决定走 Tier 1 还是 Tier 2 编译路径
 *   4. **防抖合并** 100ms 窗口内的多个变更为一次 reload 事件
 *   5. **Docker 兼容** — inotify 不可用时自动降级为 polling
 *
 * 重要设计约束：
 *   - FileWatcher 监听的是 `src/` **源码目录**，不是 `.vext/dev/` 编译产物目录。
 *     这避免了 esbuild 编译输出触发二次变更事件的问题。
 *   - 使用 Node.js 内置 `fs.watch`（零外部依赖），不依赖 chokidar 等第三方库。
 *   - 防抖窗口默认 0ms（不开启），文件变更立即触发重载；可通过 --debounce 选项开启。
 *
 * 事件：
 *   - `change` — 文件变更事件（FileChangeEvent），防抖合并后发射
 *
 * @module lib/dev/file-watcher
 * @see 11c-file-watcher.md（完整设计文档）
 * @see 11-hot-reload.md §3（文件分类规则）
 * @see IMPLEMENTATION-PLAN.md 任务 2.3
 */
/**
 * VextFileWatcher 构造选项
 */
export interface WatcherOptions {
    /** 项目根目录（绝对路径） */
    root: string;
    /** 防抖间隔（ms），默认 0（不开启防抖，文件变更立即触发重载） */
    debounce?: number;
    /** 使用轮询模式（Docker/网络文件系统降级方案） */
    usePolling?: boolean;
    /** 轮询间隔（ms），仅 usePolling 为 true 时有效，默认 1000ms */
    pollInterval?: number;
    /**
     * 用户自定义分类选项（从 config.dev 传入）
     *
     * 允许用户在配置文件中自定义 coldPatterns / ignorePatterns，
     * 覆盖内置的文件分类规则。
     */
    classifierOptions?: ClassifierOptions;
}
/**
 * 单个文件的变更信息
 *
 * 用于分级编译决策：
 *   - `modify` → Tier 1（compileSingle，~1-5ms）
 *   - `add` / `delete` → Tier 2（rebuildWithNewEntryPoints，~50-600ms）
 */
export interface FileChangeInfo {
    /** 相对于项目根目录的文件路径（使用 / 分隔符，如 "src/routes/user.ts"） */
    path: string;
    /** 变更类型：modify=内容修改, add=新增文件, delete=删除文件 */
    type: "modify" | "add" | "delete";
}
/**
 * 文件变更事件（防抖合并后发射）
 *
 * 一个事件可能包含多个文件的变更（防抖窗口内的所有变更合并为一个事件）。
 *
 * action 字段是所有变更的合并结果：
 *   - 只要有一个 `cold` 分类的文件 → action = 'cold'（触发 Cold Restart）
 *   - 全部为 `soft` 分类 → action = 'soft'（触发 Soft Reload）
 */
export interface FileChangeEvent {
    /** 变更文件列表（含路径和变更类型） */
    files: FileChangeInfo[];
    /** 合并后的最终动作（有一个 cold 就是 cold） */
    action: "soft" | "cold";
}
export declare class VextFileWatcher extends EventEmitter {
    /**
     * 活跃的 watcher 列表（fs.watch 实例或 polling 定时器包装）
     *
     * stop() 时遍历关闭所有 watcher。
     */
    private watchers;
    /** 当前挂载的项目级 preload 目录 watcher（如存在） */
    private preloadWatcher;
    /**
     * 防抖期间暂存的变更集合
     *
     * key: 相对于项目根目录的文件路径（/ 分隔符）
     * value: 分类动作 + 变更类型
     *
     * 同一文件在防抖窗口内多次变更时，cold 优先级最高。
     */
    private pendingChanges;
    /**
     * 防抖定时器
     *
     * 每次收到新变更时重置定时器。
     * 定时器到期后调用 flush() 合并发射事件。
     */
    private debounceTimer;
    /**
     * 配置选项（已填充默认值）
     */
    private readonly options;
    /**
     * v2.2：已知文件路径集合，用于区分 add 和 modify
     *
     * 在 start() 时扫描 src/ 初始化，后续根据文件变更事件维护：
     *   - add 事件 → 加入集合
     *   - delete 事件 → 从集合移除
     *
     * fs.watch 的 'rename' 事件无法直接区分新建和删除，
     * 需要配合 existsSync + knownFiles 来判断。
     */
    private knownFiles;
    constructor(options: WatcherOptions);
    /**
     * start — 启动文件监听
     *
     * 流程：
     *   1. 扫描 src/ 初始化已知文件集合（knownFiles）
     *   2. 根据 usePolling 选项决定监听模式：
     *      - polling = false → 使用 fs.watch（递归监听 src/ + 单文件监听根配置）
     *      - polling = true → 使用 setInterval 定期扫描
     *
     * 监听目标：
       *   - src/ 目录（递归，所有源码文件变更）
       *   - preload/ 目录（非递归，项目级 preload 文件变更）
     *   - 根目录配置文件（package.json, tsconfig.json）
     *   - .env 文件（.env, .env.local, .env.production 等）
     */
    start(): Promise<void>;
    /**
     * stop — 停止所有 watcher 并清理状态
     *
     * 关闭所有 fs.watch 实例和 polling 定时器，
     * 清空 pending 变更和已知文件集合。
     *
     * 调用后可通过 start() 重新启动。
     */
    stop(): void;
    /**
     * findEnvFiles — 查找根目录下所有 .env 文件
     *
     * 匹配 .env, .env.local, .env.production, .env.development 等。
     *
     * @param root 项目根目录
     * @returns .env 文件名列表
     */
    private findEnvFiles;
    /**
     * detectChangeType — 根据 fs.watch 事件类型和文件系统状态判断变更类型（v2.2）
     *
     * fs.watch 的 eventType 语义在不同平台有差异：
     *
     * | 平台    | eventType='change' | eventType='rename'          |
     * |---------|-------------------|-----------------------------|
     * | macOS   | 内容修改           | 新建/删除/重命名              |
     * | Windows | 内容修改           | rename 可能触发两次            |
     * | Linux   | 内容修改           | vim 写文件时可能报 rename       |
     *
     * 此方法统一处理跨平台差异：
     *   - 'change' → 'modify'
     *   - 'rename' → 检查文件是否存在 + knownFiles 集合来区分 add/modify/delete
     *
     * @param eventType fs.watch 回调的 eventType 参数
     * @param normalizedPath 相对于项目根的规范化路径（/ 分隔符）
     * @param absolutePath 文件的绝对路径（用于 existsSync 检查）
     * @returns 变更类型
     */
    private detectChangeType;
    /**
     * initKnownFiles — 初始化已知文件集合（v2.2）
     *
     * 启动时扫描 src/ 目录，记录所有已存在的文件路径。
     * 后续 detectChangeType() 通过检查文件是否在 knownFiles 中
     * 来区分 add 和 modify。
     *
     * @param root 项目根目录
     */
    private initKnownFiles;
    private attachPreloadWatcher;
    private reconcilePreloadWatcher;
    /**
     * onFileChange — 处理文件变更事件
     *
     * 调用 classifyChange() 分类文件变更，忽略 ignore 类型，
     * 将 cold/soft 类型加入 pending 集合，启动防抖定时器。
     *
     * 合并策略：
     *   - 同一文件在防抖窗口内多次变更 → 保留最高优先级的 action（cold > soft）
     *   - 不同文件独立记录
     *
     * @param relativePath 相对于项目根目录的文件路径（/ 分隔符）
     * @param changeType 变更类型
     */
    private onFileChange;
    /**
     * flush — 将 pending 变更合并为一个 FileChangeEvent 并发射
     *
     * 在防抖定时器到期后调用。
     * 将 pendingChanges Map 转换为 FileChangeEvent 并发射 'change' 事件。
     *
     * action 合并规则：
     *   - 只要有一个文件的 action 为 'cold' → 整体 action = 'cold'
     *   - 全部为 'soft' → 整体 action = 'soft'
     */
    private flush;
    /**
     * restartWithPolling — 当 inotify 限制时降级为 polling 模式
     *
     * 关闭所有现有 watcher，切换到 polling 模式重新开始监听。
     * 这是 Docker 容器中 inotify 报 ENOSPC 时的降级策略。
     */
    private restartWithPolling;
    /**
     * startPolling — Polling 降级方案
     *
     * 适用于 Docker 挂载卷、网络文件系统等 fs.watch 不可靠的环境。
     *
     * 工作原理：
     *   1. 初始扫描建立基线（记录所有文件的 mtime）
     *   2. 每隔 pollInterval 毫秒重新扫描
     *   3. 对比前后两轮的文件列表和 mtime：
     *      - 新出现的文件 → add
     *      - mtime 变化的文件 → modify
     *      - 消失的文件 → delete
     *
     * v2.2 改进：polling 模式也能正确检测 add/delete
     * （通过对比前后两轮文件列表的差集）。
     *
     * 也会监听根目录配置文件（package.json, tsconfig.json, .env*）。
     */
    private startPolling;
    /**
     * walkDirectory — 递归遍历目录，返回所有匹配的文件路径
     *
     * 遍历规则：
     *   - 跳过以 `.` 开头的隐藏目录（如 .git, .vext）
     *   - 跳过 node_modules 目录
     *   - 只收集代码文件（.ts / .js / .mjs / .cjs / .json）
     *
     * @param dir 要遍历的目录绝对路径
     * @returns 匹配的文件绝对路径列表
     */
    private walkDirectory;
    /**
     * listPreloadFiles — 列出项目根 preload/ 目录中的一级文件
     *
     * 仅收集项目级 preload 支持的候选文件类型，且不递归子目录。
     */
    private listPreloadFiles;
}
