//! 项目模块发现与管理
//!
//! 扫描目录，识别入口文件和模块文件，构建项目结构。

use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};

/// C++ Addon DLL 配置
#[derive(Debug, Clone)]
pub struct NativeAddonConfig {
    /// DLL 文件名（如 "sharp.dll" 或 "sharp.node"）
    pub dll: String,
    /// 函数映射：TS 函数名 → DLL 导出函数名
    /// 如 { "resize" => "sharp_resize", "rotate" => "sharp_rotate" }
    pub functions: HashMap<String, String>,
}

/// QuickJS 引擎配置
#[derive(Debug, Clone, Default)]
pub struct JsEngineConfig {
    /// 是否启用 QuickJS fallback
    pub enabled: bool,
    /// 使用 QuickJS 执行的包列表（fallback）
    pub fallback: Vec<String>,
}

/// 项目配置
#[derive(Debug, Clone)]
pub struct ProjectConfig {
    /// 显式指定的入口文件（相对于项目根目录）
    pub entries: Vec<String>,
    /// 源码目录（默认 "src"）
    pub src_dir: String,
    /// 输出目录（默认 "dist"）
    pub out_dir: String,
    /// Shim 配置：包名 -> shim 模块名
    /// 如 { "lodash" => "ts_native_shim_lodash" }
    pub shims: HashMap<String, String>,
    /// 已解析的 npm 包入口路径
    pub npm_packages: HashMap<String, PathBuf>,
    /// C++ Addon DLL 配置
    /// 如 { "sharp" => NativeAddonConfig { dll: "sharp.dll", functions: {...} } }
    pub native_addons: HashMap<String, NativeAddonConfig>,
    /// QuickJS 引擎配置
    pub js_engine: JsEngineConfig,
}

impl Default for ProjectConfig {
    fn default() -> Self {
        Self {
            entries: Vec::new(),
            src_dir: "src".to_string(),
            out_dir: "dist".to_string(),
            shims: HashMap::new(),
            npm_packages: HashMap::new(),
            native_addons: HashMap::new(),
            js_engine: JsEngineConfig::default(),
        }
    }
}

/// 项目中的源文件
#[derive(Debug, Clone)]
pub struct SourceFile {
    /// 文件路径（绝对路径）
    pub path: PathBuf,
    /// 模块名（基于项目根目录的相对路径，不含扩展名）
    /// 如 "src/utils/math" 或 "main"
    pub module_name: String,
    /// 是否为入口文件
    pub is_entry: bool,
    /// 文件内容（延迟加载）
    pub content: Option<String>,
    /// 是否来自 node_modules 的 npm 包文件
    pub is_npm_package: bool,
}

/// 项目结构
#[derive(Debug)]
pub struct Project {
    /// 项目根目录
    pub root: PathBuf,
    /// 配置
    pub config: ProjectConfig,
    /// 所有源文件（路径 -> SourceFile）
    pub files: HashMap<PathBuf, SourceFile>,
    /// 入口文件列表
    pub entries: Vec<PathBuf>,
    /// 模块文件列表（非入口）
    pub modules: Vec<PathBuf>,
}

impl Project {
    /// 从目录创建项目
    ///
    /// 扫描规则：
    /// 1. 如果存在 ts-native.toml，读取 shim 配置
    /// 2. 如果存在 tsn-project.toml，读取配置
    /// 3. 否则，扫描 src/ 目录下的所有 .ts 文件
    /// 4. 入口识别：显式配置 > 根目录下含 main 的文件 > 默认 main.ts
    pub fn from_dir(root: &Path) -> anyhow::Result<Self> {
        let mut config = ProjectConfig::default();

        // 尝试读取 ts-native.toml（shim 配置）
        let ts_native_toml = root.join("ts-native.toml");
        if ts_native_toml.exists() {
            if let Ok(content) = std::fs::read_to_string(&ts_native_toml) {
                Self::parse_ts_native_toml(&content, &mut config);
            }
        }

        // 尝试读取 tsn-project.toml
        let config_path = root.join("tsn-project.toml");
        if config_path.exists() {
            if let Ok(content) = std::fs::read_to_string(&config_path) {
                Self::parse_tsn_project_toml(&content, &mut config);
            }
        }

        let src_dir = root.join(&config.src_dir);
        let mut files = HashMap::new();
        let mut entries = Vec::new();
        let mut modules = Vec::new();

        // 扫描所有 .ts/.js/.mjs 文件
        Self::scan_dir(&src_dir, root, &mut files, false)?;

        // 识别入口文件
        if !config.entries.is_empty() {
            // 显式配置的入口
            for entry in &config.entries {
                let entry_path = root.join(entry);
                if let Some(file) = files.get_mut(&entry_path) {
                    file.is_entry = true;
                    entries.push(entry_path.clone());
                } else {
                    // 尝试加上 src_dir 前缀（如 "main.ts" → "src/main.ts"）
                    let with_src = root.join(&config.src_dir).join(entry);
                    if let Some(file) = files.get_mut(&with_src) {
                        file.is_entry = true;
                        entries.push(with_src.clone());
                    }
                }
            }
        } else {
            // 自动识别入口：
            // 1. src/ 目录下的 .ts 文件（不含子目录）
            // 2. 或者根目录下的 .ts 文件
            // 3. 如果没有找到，默认 src/main.ts 或 main.ts
            let mut entry_candidates: Vec<PathBuf> = Vec::new();
            for (path, _file) in &files {
                let rel = path.strip_prefix(root).unwrap_or(path);
                let parent = rel.parent();
                let parent_count = parent.map(|p| p.components().count()).unwrap_or(0);

                // src/ 目录下的直接子文件视为入口
                if parent_count == 1 {
                    if let Some(first) = parent.and_then(|p| p.components().next()) {
                        if first.as_os_str() == "src" {
                            entry_candidates.push(path.clone());
                        }
                    }
                }
                // 根目录下的 .ts 文件也视为入口
                if parent_count == 0 {
                    entry_candidates.push(path.clone());
                }
            }

            for path in &entry_candidates {
                if let Some(f) = files.get_mut(path) {
                    f.is_entry = true;
                    entries.push(path.clone());
                }
            }

            // 如果没有找到入口，默认 src/main.ts 或 main.ts
            if entries.is_empty() {
                let default_main = root.join("src").join("main.ts");
                let default_main2 = root.join("main.ts");
                let target = if default_main.exists() { default_main } else { default_main2 };
                if target.exists() {
                    if let Some(file) = files.get_mut(&target) {
                        file.is_entry = true;
                        entries.push(target);
                    }
                }
            }
        }

        // 收集模块文件
        for (path, file) in &files {
            if !file.is_entry {
                modules.push(path.clone());
            }
        }

        Ok(Project {
            root: root.to_path_buf(),
            config,
            files,
            entries,
            modules,
        })
    }

    /// 简单解析 ts-native.toml 中的 [shims]、[native_addons] 和 [js-engine] section
    /// 按行分割，跳过注释和空行，解析各 section 下的 key = "value" 行
    fn parse_ts_native_toml(content: &str, config: &mut ProjectConfig) {
        let mut current_section: Option<String> = None; // 当前 section 名称
        let mut current_addon_name: Option<String> = None; // 当前 native_addon 名称
        let mut in_functions = false; // 是否在 [native_addons.XXX.functions] 中

        for line in content.lines() {
            let trimmed = line.trim();
            // 跳过空行和注释
            if trimmed.is_empty() || trimmed.starts_with('#') {
                continue;
            }
            // 检测 section header
            if trimmed.starts_with('[') && trimmed.ends_with(']') {
                let section = &trimmed[1..trimmed.len()-1];

                // 检测 [native_addons.XXX.functions] 格式
                if let Some(rest) = section.strip_prefix("native_addons.") {
                    if let Some(funcs_pos) = rest.find(".functions") {
                        let addon_name = &rest[..funcs_pos];
                        current_addon_name = Some(addon_name.to_string());
                        in_functions = true;
                        current_section = Some(section.to_string());
                        continue;
                    } else {
                        // [native_addons.XXX] 格式
                        current_addon_name = Some(rest.to_string());
                        in_functions = false;
                        current_section = Some(section.to_string());
                        // 确保该 addon 配置存在
                        if !config.native_addons.contains_key(rest) {
                            config.native_addons.insert(rest.to_string(), NativeAddonConfig {
                                dll: String::new(),
                                functions: HashMap::new(),
                            });
                        }
                        continue;
                    }
                }

                // [js-engine] section
                if section == "js-engine" {
                    current_section = Some(section.to_string());
                    current_addon_name = None;
                    in_functions = false;
                    continue;
                }

                // [shims] section
                current_section = Some(section.to_string());
                current_addon_name = None;
                in_functions = false;
                continue;
            }

            // 解析 key = "value" 行
            if let Some(eq_pos) = trimmed.find('=') {
                let key = trimmed[..eq_pos].trim().to_string();
                let value = trimmed[eq_pos+1..].trim();
                // 去除引号
                let value = value.trim_matches('"').trim_matches('\'').to_string();
                if key.is_empty() || value.is_empty() {
                    continue;
                }

                // 在 [native_addons.XXX.functions] 中
                if in_functions {
                    if let Some(ref addon_name) = current_addon_name {
                        if let Some(addon) = config.native_addons.get_mut(addon_name) {
                            addon.functions.insert(key, value);
                        }
                    }
                    continue;
                }

                // 在 [native_addons.XXX] 中（非 functions 子节）
                if let Some(ref addon_name) = current_addon_name {
                    if current_section.as_deref() == Some(&format!("native_addons.{}", addon_name)) {
                        if let Some(addon) = config.native_addons.get_mut(addon_name) {
                            if key == "dll" {
                                addon.dll = value;
                            }
                        }
                        continue;
                    }
                }

                // 在 [js-engine] section 内
                if current_section.as_deref() == Some("js-engine") {
                    if key == "enabled" {
                        config.js_engine.enabled = value == "true" || value == "1";
                    } else if key == "fallback" {
                        // 解析数组格式: ["pkg1", "pkg2", "pkg3"]
                        let raw = trimmed[eq_pos+1..].trim();
                        if raw.starts_with('[') && raw.ends_with(']') {
                            let inner = &raw[1..raw.len()-1];
                            for item in inner.split(',') {
                                let item = item.trim().trim_matches('"').trim_matches('\'').trim();
                                if !item.is_empty() {
                                    config.js_engine.fallback.push(item.to_string());
                                }
                            }
                        }
                    }
                    continue;
                }

                // 在 [shims] section 内
                if current_section.as_deref() == Some("shims") {
                    config.shims.insert(key, value);
                } else if current_section.as_deref() == Some("project") {
                    match key.as_str() {
                        "entries" => {
                            let raw = trimmed[eq_pos+1..].trim();
                            if raw.starts_with('[') && raw.ends_with(']') {
                                let inner = &raw[1..raw.len() - 1];
                                for item in inner.split(',') {
                                    let item = item.trim().trim_matches('"').trim().to_string();
                                    if !item.is_empty() {
                                        config.entries.push(item);
                                    }
                                }
                            } else if !value.is_empty() {
                                config.entries.push(value);
                            }
                        }
                        "src_dir" => config.src_dir = value,
                        "out_dir" => config.out_dir = value,
                        _ => {}
                    }
                }
            }
        }
    }

    /// 解析 tsn-project.toml（项目配置）
    /// 支持: entries, src_dir, out_dir
    /// 格式类似 .ini: key = "value"
    fn parse_tsn_project_toml(content: &str, config: &mut ProjectConfig) {
        for line in content.lines() {
            let trimmed = line.trim();
            // 跳过空行和注释
            if trimmed.is_empty() || trimmed.starts_with('#') {
                continue;
            }
            if let Some(eq_pos) = trimmed.find('=') {
                let key = trimmed[..eq_pos].trim().to_string();
                let raw_value = trimmed[eq_pos + 1..].trim();
                let value = raw_value.trim_matches('"').to_string();
                match key.as_str() {
                    "src_dir" => config.src_dir = value,
                    "out_dir" => config.out_dir = value,
                    "entries" => {
                        // 解析数组格式: ["entry1.ts", "entry2.ts"]
                        let raw = raw_value.trim();
                        if raw.starts_with('[') && raw.ends_with(']') {
                            let inner = &raw[1..raw.len() - 1];
                            for item in inner.split(',') {
                                let item = item.trim().trim_matches('"').trim().to_string();
                                if !item.is_empty() {
                                    config.entries.push(item);
                                }
                            }
                        } else if !value.is_empty() {
                            config.entries.push(value);
                        }
                    }
                    _ => {} // 忽略未知配置
                }
            }
        }
    }

    /// 递归扫描目录
    fn scan_dir(
        dir: &Path,
        root: &Path,
        files: &mut HashMap<PathBuf, SourceFile>,
        is_npm: bool,
    ) -> anyhow::Result<()> {
        if !dir.exists() {
            return Ok(());
        }

        for entry in std::fs::read_dir(dir)? {
            let entry = entry?;
            let path = entry.path();

            if path.is_dir() {
                Self::scan_dir(&path, root, files, is_npm)?;
            } else {
                let ext = path.extension().map(|e| e.to_string_lossy().to_lowercase());
                let is_source = ext.as_deref() == Some("ts")
                    || ext.as_deref() == Some("js")
                    || ext.as_deref() == Some("mjs");
                if is_source {
                    let module_name = Self::path_to_module_name(&path, root);
                    let rel_path = path.strip_prefix(root).unwrap_or(&path);
                    let store_path = root.join(rel_path);
                    files.insert(store_path.clone(), SourceFile {
                        path: store_path,
                        module_name,
                        is_entry: false,
                        content: None,
                        is_npm_package: is_npm,
                    });
                }
            }
        }

        Ok(())
    }

    /// 将文件路径转为模块名
    /// 如 "src/utils/math.ts" -> "utils/math"
    /// 如 "node_modules/lodash/index.js" -> "lodash" (npm 包)
    fn path_to_module_name(path: &Path, root: &Path) -> String {
        let rel = path.strip_prefix(root).unwrap_or(path);
        let mut s = rel.to_string_lossy().replace('\\', "/");

        // 检查是否是 node_modules 下的 npm 包
        if s.starts_with("node_modules/") {
            // 提取包名：node_modules/lodash/index.js -> lodash
            let rest = &s["node_modules/".len()..];
            // 取第一个路径组件作为包名
            if let Some(slash_pos) = rest.find('/') {
                let pkg_name = &rest[..slash_pos];
                // 如果是 scoped package（如 @types/node），取前两个组件
                if pkg_name.starts_with('@') {
                    if let Some(second_slash) = rest[slash_pos+1..].find('/') {
                        let end = slash_pos + 1 + second_slash;
                        return rest[..end].to_string();
                    }
                }
                return pkg_name.to_string();
            } else {
                return rest.to_string();
            }
        }

        // 移除扩展名
        for ext in &[".ts", ".js", ".mjs"] {
            if s.ends_with(ext) {
                s.truncate(s.len() - ext.len());
                break;
            }
        }
        // 移除 src/ 前缀
        if s.starts_with("src/") {
            s = s[4..].to_string();
        }
        s
    }

    /// 根据 import 路径解析源文件
    /// 如 "./utils/math" -> src/utils/math.ts
    pub fn resolve_import(&self, from: &Path, import_path: &str) -> Option<PathBuf> {
        if import_path.starts_with("./") || import_path.starts_with("../") {
            // 相对路径
            let from_rel = from.strip_prefix(&self.root).unwrap_or(from);
            let from_dir = from_rel.parent()?;
            let resolved_rel = from_dir.join(import_path);
            // 规范化 ../ 和 ./
            let resolved_rel = Self::normalize_path(&resolved_rel);
            let resolved = self.root.join(&resolved_rel);

            // 尝试各种扩展名
            for ext in &["ts", "js", "mjs"] {
                let with_ext = resolved.with_extension(ext);
                if self.files.contains_key(&with_ext) {
                    return Some(with_ext);
                }
            }

            // 尝试 index.ts / index.js
            for ext in &["ts", "js", "mjs"] {
                let index = resolved.join(format!("index.{}", ext));
                if self.files.contains_key(&index) {
                    return Some(index);
                }
            }

            None
        } else {
            // 非相对路径（如 "fs", "path", "lodash"）
            // 优先检查项目中是否存在同名用户模块
            let candidate = self.root.join("src").join(import_path).with_extension("ts");
            if self.files.contains_key(&candidate) {
                eprintln!(
                    "[ts-native] 提示: '{}' 解析为用户模块 '{}'，而非内置模块。如需使用内置模块，请使用相对路径导入。",
                    import_path,
                    candidate.display()
                );
                return Some(candidate);
            }

            // 尝试 index.ts
            let candidate_index = self.root.join("src").join(import_path).join("index.ts");
            if self.files.contains_key(&candidate_index) {
                eprintln!(
                    "[ts-native] 提示: '{}' 解析为用户模块 '{}'，而非内置模块。如需使用内置模块，请使用相对路径导入。",
                    import_path,
                    candidate_index.display()
                );
                return Some(candidate_index);
            }

            // Shim 查找
            if self.config.shims.contains_key(import_path) {
                // shim 不需要文件路径，通过 import_bindings 标记
                // 返回一个特殊标记路径
                let shim_path = PathBuf::from(format!("__shim__/{}", import_path));
                return Some(shim_path);
            }

            // node_modules 查找
            if let Some(pkg_entry) = self.resolve_node_modules(from, import_path) {
                return Some(pkg_entry);
            }

            // 内置模块（如 "fs", "path"）-- 不是项目内模块
            None
        }
    }

    /// 从 from 文件向上查找 node_modules/ 目录
    fn resolve_node_modules(&self, from: &Path, import_path: &str) -> Option<PathBuf> {
        // 从 from 文件向上查找 node_modules/
        let mut dir = from.parent()?.to_path_buf();
        loop {
            let nm_dir = dir.join("node_modules").join(import_path);
            if let Some(entry) = self.resolve_package_entry(&nm_dir) {
                return Some(entry);
            }
            if !dir.pop() || dir == self.root {
                break;
            }
        }
        None
    }

    /// 解析 npm 包的入口文件
    /// 优先级: exports > main > index.js > index.ts
    fn resolve_package_entry(&self, pkg_dir: &Path) -> Option<PathBuf> {
        let pkg_json = pkg_dir.join("package.json");
        if !pkg_json.exists() {
            return None;
        }
        let content = std::fs::read_to_string(&pkg_json).ok()?;
        let pkg: serde_json::Value = serde_json::from_str(&content).ok()?;

        // 优先级: exports > main > index.js > index.ts
        if let Some(exports) = pkg.get("exports") {
            if let Some(main) = exports.get(".").and_then(|e| e.get("import").or_else(|| e.get("default"))) {
                if let Some(s) = main.as_str() {
                    let entry = pkg_dir.join(s);
                    if entry.exists() { return Some(entry); }
                }
            }
        }
        if let Some(main) = pkg.get("main").and_then(|m| m.as_str()) {
            let entry = pkg_dir.join(main);
            if entry.exists() { return Some(entry); }
        }
        // 尝试 index.ts 和 index.js
        let index_ts = pkg_dir.join("index.ts");
        if index_ts.exists() { return Some(index_ts); }
        let index_js = pkg_dir.join("index.js");
        if index_js.exists() { return Some(index_js); }
        None
    }

    /// 规范化路径（解析 . 和 ..）
    fn normalize_path(path: &Path) -> PathBuf {
        let mut result = PathBuf::new();
        for component in path.components() {
            match component {
                std::path::Component::ParentDir => {
                    result.pop();
                }
                std::path::Component::CurDir => {}
                _ => {
                    result.push(component);
                }
            }
        }
        result
    }

    /// 加载文件内容
    pub fn load_content(&mut self, path: &Path) -> anyhow::Result<String> {
        if let Some(file) = self.files.get(path) {
            if let Some(ref content) = file.content {
                return Ok(content.clone());
            }
        }

        let content = std::fs::read_to_string(path)?;
        if let Some(file) = self.files.get_mut(path) {
            file.content = Some(content.clone());
        }
        Ok(content)
    }

    /// 获取模块的导出符号（函数名列表）
    /// 需要调用 swc_transform 解析文件内容
    pub fn get_exports(&self, _path: &Path) -> HashSet<String> {
        // 由 DepGraph 在构建时填充
        HashSet::new()
    }

    /// 检查路径是否为 shim 路径（__shim__/ 前缀）
    pub fn is_shim_path(path: &Path) -> bool {
        path.to_string_lossy().starts_with("__shim__/")
    }

    /// 从 shim 路径提取包名
    /// 如 "__shim__/lodash" -> "lodash"
    pub fn shim_package_name(path: &Path) -> Option<String> {
        let s = path.to_string_lossy();
        if s.starts_with("__shim__/") {
            Some(s["__shim__/".len()..].to_string())
        } else {
            None
        }
    }

    /// 检查包名是否为 native addon
    pub fn is_native_addon(&self, package_name: &str) -> bool {
        self.config.native_addons.contains_key(package_name)
    }

    /// 获取 native addon 的 DLL 路径
    pub fn get_addon_dll(&self, package_name: &str) -> Option<&str> {
        self.config.native_addons.get(package_name).map(|a| a.dll.as_str())
    }

    /// 获取 native addon 的函数映射
    pub fn get_addon_functions(&self, package_name: &str) -> Option<&HashMap<String, String>> {
        self.config.native_addons.get(package_name).map(|a| &a.functions)
    }

    /// 检查包名是否在 QuickJS fallback 列表中
    pub fn is_qjs_fallback(&self, package_name: &str) -> bool {
        self.config.js_engine.enabled && self.config.js_engine.fallback.contains(&package_name.to_string())
    }

    /// 获取 QuickJS fallback 包列表
    pub fn get_qjs_fallback_packages(&self) -> &[String] {
        &self.config.js_engine.fallback
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::path::PathBuf;

    /// 创建一个用于测试的 Project 实例
    fn make_test_project(root: &Path, files: &[(&str, &str)]) -> Project {
        let mut file_map = HashMap::new();
        for (path_str, module_name) in files {
            let path = root.join(path_str);
            file_map.insert(path.clone(), SourceFile {
                path: path.clone(),
                module_name: module_name.to_string(),
                is_entry: false,
                content: None,
                is_npm_package: false,
            });
        }
        Project {
            root: root.to_path_buf(),
            config: ProjectConfig::default(),
            files: file_map,
            entries: Vec::new(),
            modules: Vec::new(),
        }
    }

    // ========== 用户模块优先级测试 ==========

    #[test]
    fn test_user_module_overrides_builtin() {
        // 项目中存在 src/fs.ts，import from "fs" 应解析为用户模块
        let root = PathBuf::from("/project");
        let project = make_test_project(&root, &[
            ("src/fs.ts", "fs"),
            ("src/main.ts", "main"),
        ]);

        let from = root.join("src/main.ts");
        let resolved = project.resolve_import(&from, "fs");

        assert!(resolved.is_some(), "应解析为用户模块 fs.ts");
        let resolved_path = resolved.unwrap();
        assert_eq!(resolved_path, root.join("src/fs.ts"));
    }

    #[test]
    fn test_user_index_ts_overrides_builtin() {
        // 项目中存在 src/os/index.ts，import from "os" 应解析为用户模块
        let root = PathBuf::from("/project");
        let project = make_test_project(&root, &[
            ("src/os/index.ts", "os"),
            ("src/main.ts", "main"),
        ]);

        let from = root.join("src/main.ts");
        let resolved = project.resolve_import(&from, "os");

        assert!(resolved.is_some(), "应解析为用户模块 os/index.ts");
        let resolved_path = resolved.unwrap();
        assert_eq!(resolved_path, root.join("src/os/index.ts"));
    }

    #[test]
    fn test_no_user_module_falls_through_to_builtin() {
        // 项目中不存在 src/fs.ts，import from "fs" 应返回 None（走内置模块）
        let root = PathBuf::from("/project");
        let project = make_test_project(&root, &[
            ("src/main.ts", "main"),
            ("src/utils.ts", "utils"),
        ]);

        let from = root.join("src/main.ts");
        let resolved = project.resolve_import(&from, "fs");

        assert!(resolved.is_none(), "无用户模块时应返回 None（走内置模块）");
    }

    #[test]
    fn test_relative_path_unaffected_by_user_module_priority() {
        // 相对路径导入不受用户模块优先级影响
        let root = PathBuf::from("/project");
        let project = make_test_project(&root, &[
            ("src/fs.ts", "fs"),
            ("src/main.ts", "main"),
        ]);

        let from = root.join("src/main.ts");
        let resolved = project.resolve_import(&from, "./fs");

        assert!(resolved.is_some(), "相对路径 ./fs 应正常解析");
        let resolved_path = resolved.unwrap();
        assert_eq!(resolved_path, root.join("src/fs.ts"));
    }

    #[test]
    fn test_user_module_priority_prefers_direct_ts_over_index_ts() {
        // 同时存在 src/utils.ts 和 src/utils/index.ts 时，优先匹配 .ts
        let root = PathBuf::from("/project");
        let project = make_test_project(&root, &[
            ("src/utils.ts", "utils"),
            ("src/utils/index.ts", "utils/index"),
            ("src/main.ts", "main"),
        ]);

        let from = root.join("src/main.ts");
        let resolved = project.resolve_import(&from, "utils");

        assert!(resolved.is_some());
        let resolved_path = resolved.unwrap();
        assert_eq!(resolved_path, root.join("src/utils.ts"),
            "应优先匹配 .ts 而非 index.ts");
    }

    #[test]
    fn test_user_module_in_subdirectory() {
        // 项目中存在 src/crypto/hasher.ts，import from "crypto" 不会匹配
        // （只匹配 src/crypto.ts 或 src/crypto/index.ts）
        let root = PathBuf::from("/project");
        let project = make_test_project(&root, &[
            ("src/crypto/hasher.ts", "crypto/hasher"),
            ("src/main.ts", "main"),
        ]);

        let from = root.join("src/main.ts");
        let resolved = project.resolve_import(&from, "crypto");

        assert!(resolved.is_none(),
            "子目录中的文件不应被非相对路径匹配");
    }

    #[test]
    fn test_relative_path_with_parent_dir() {
        // 相对路径 ../ 不受影响
        let root = PathBuf::from("/project");
        let project = make_test_project(&root, &[
            ("src/fs.ts", "fs"),
            ("src/utils/helper.ts", "utils/helper"),
            ("src/main.ts", "main"),
        ]);

        let from = root.join("src/utils/helper.ts");
        let resolved = project.resolve_import(&from, "../fs");

        assert!(resolved.is_some(), "相对路径 ../fs 应正常解析");
        let resolved_path = resolved.unwrap();
        assert_eq!(resolved_path, root.join("src/fs.ts"));
    }

    #[test]
    fn test_non_relative_path_unknown_module_returns_none() {
        // 完全未知的非相对路径返回 None
        let root = PathBuf::from("/project");
        let project = make_test_project(&root, &[
            ("src/main.ts", "main"),
        ]);

        let from = root.join("src/main.ts");
        let resolved = project.resolve_import(&from, "some-unknown-package");

        assert!(resolved.is_none(), "未知模块应返回 None");
    }

    // ========== Shim 测试 ==========

    #[test]
    fn test_shim_resolve() {
        // 配置了 shim 的包应返回 __shim__/ 前缀路径
        let root = PathBuf::from("/project");
        let mut config = ProjectConfig::default();
        config.shims.insert("lodash".to_string(), "ts_native_shim_lodash".to_string());
        let mut project = make_test_project(&root, &[
            ("src/main.ts", "main"),
        ]);
        project.config = config;

        let from = root.join("src/main.ts");
        let resolved = project.resolve_import(&from, "lodash");

        assert!(resolved.is_some(), "shim 包应返回 __shim__/ 路径");
        let resolved_path = resolved.unwrap();
        assert_eq!(resolved_path.to_string_lossy(), "__shim__/lodash");
    }

    #[test]
    fn test_shim_not_conflict_with_user_module() {
        // 用户模块优先于 shim
        let root = PathBuf::from("/project");
        let mut config = ProjectConfig::default();
        config.shims.insert("lodash".to_string(), "ts_native_shim_lodash".to_string());
        let mut project = make_test_project(&root, &[
            ("src/main.ts", "main"),
            ("src/lodash.ts", "lodash"),
        ]);
        project.config = config;

        let from = root.join("src/main.ts");
        let resolved = project.resolve_import(&from, "lodash");

        assert!(resolved.is_some(), "用户模块应优先于 shim");
        let resolved_path = resolved.unwrap();
        assert_eq!(resolved_path, root.join("src/lodash.ts"));
    }

    #[test]
    fn test_is_shim_path() {
        assert!(Project::is_shim_path(&PathBuf::from("__shim__/lodash")));
        assert!(!Project::is_shim_path(&PathBuf::from("src/lodash.ts")));
    }

    #[test]
    fn test_shim_package_name() {
        assert_eq!(Project::shim_package_name(&PathBuf::from("__shim__/lodash")), Some("lodash".to_string()));
        assert_eq!(Project::shim_package_name(&PathBuf::from("__shim__/dayjs")), Some("dayjs".to_string()));
        assert_eq!(Project::shim_package_name(&PathBuf::from("src/lodash.ts")), None);
    }

    // ========== ts-native.toml 解析测试 ==========

    #[test]
    fn test_parse_ts_native_toml() {
        let content = r#"
# 这是注释
[shims]
lodash = "ts_native_shim_lodash"
dayjs = "ts_native_shim_dayjs"
"#;
        let mut config = ProjectConfig::default();
        Project::parse_ts_native_toml(content, &mut config);

        assert_eq!(config.shims.get("lodash"), Some(&"ts_native_shim_lodash".to_string()));
        assert_eq!(config.shims.get("dayjs"), Some(&"ts_native_shim_dayjs".to_string()));
        assert_eq!(config.shims.len(), 2);
    }

    #[test]
    fn test_parse_ts_native_toml_empty() {
        let content = "";
        let mut config = ProjectConfig::default();
        Project::parse_ts_native_toml(content, &mut config);
        assert!(config.shims.is_empty());
    }

    #[test]
    fn test_parse_ts_native_toml_project_entries() {
        let content = r#"
[project]
entries = ["main.ts"]
src_dir = "src"
"#;
        let mut config = ProjectConfig::default();
        Project::parse_ts_native_toml(content, &mut config);
        assert_eq!(config.entries.len(), 1, "应解析出 1 个入口, 实际: {:?}", config.entries);
        assert_eq!(config.entries[0], "main.ts");
        assert_eq!(config.src_dir, "src");
    }

    #[test]
    fn test_parse_ts_native_toml_other_sections_ignored() {
        let content = r#"
[other]
foo = "bar"

[shims]
lodash = "ts_native_shim_lodash"
"#;
        let mut config = ProjectConfig::default();
        Project::parse_ts_native_toml(content, &mut config);

        assert_eq!(config.shims.len(), 1);
        assert_eq!(config.shims.get("lodash"), Some(&"ts_native_shim_lodash".to_string()));
    }

    // ========== npm 包模块名测试 ==========

    #[test]
    fn test_npm_module_name() {
        let root = PathBuf::from("/project");
        let path = root.join("node_modules/lodash/index.js");
        let module_name = Project::path_to_module_name(&path, &root);
        assert_eq!(module_name, "lodash");
    }

    #[test]
    fn test_npm_scoped_module_name() {
        let root = PathBuf::from("/project");
        let path = root.join("node_modules/@types/node/index.d.ts");
        let module_name = Project::path_to_module_name(&path, &root);
        assert_eq!(module_name, "@types/node");
    }

    #[test]
    fn test_npm_module_with_subpath() {
        let root = PathBuf::from("/project");
        let path = root.join("node_modules/lodash/deep/index.js");
        let module_name = Project::path_to_module_name(&path, &root);
        assert_eq!(module_name, "lodash");
    }

    // ========== native_addons 解析测试 ==========

    #[test]
    fn test_parse_native_addons_config() {
        let toml_content = r#"
[native_addons.sharp]
dll = "sharp.dll"

[native_addons.sharp.functions]
resize = "sharp_resize"
rotate = "sharp_rotate"
"#;
        let mut config = ProjectConfig::default();
        Project::parse_ts_native_toml(toml_content, &mut config);
        assert!(config.native_addons.contains_key("sharp"));
        let sharp = &config.native_addons["sharp"];
        assert_eq!(sharp.dll, "sharp.dll");
        assert_eq!(sharp.functions.get("resize").unwrap(), "sharp_resize");
        assert_eq!(sharp.functions.get("rotate").unwrap(), "sharp_rotate");
    }

    #[test]
    fn test_parse_native_addons_multiple() {
        let toml_content = r#"
[native_addons.sharp]
dll = "sharp.dll"

[native_addons.sharp.functions]
resize = "sharp_resize"

[native_addons.better_sqlite3]
dll = "better_sqlite3.dll"

[native_addons.better_sqlite3.functions]
open = "sqlite3_open"
exec = "sqlite3_exec"
close = "sqlite3_close"
"#;
        let mut config = ProjectConfig::default();
        Project::parse_ts_native_toml(toml_content, &mut config);

        assert_eq!(config.native_addons.len(), 2);

        let sharp = &config.native_addons["sharp"];
        assert_eq!(sharp.dll, "sharp.dll");
        assert_eq!(sharp.functions.len(), 1);

        let sqlite = &config.native_addons["better_sqlite3"];
        assert_eq!(sqlite.dll, "better_sqlite3.dll");
        assert_eq!(sqlite.functions.len(), 3);
        assert_eq!(sqlite.functions.get("open").unwrap(), "sqlite3_open");
    }

    #[test]
    fn test_parse_native_addons_with_shims() {
        // 验证 native_addons 和 shims 可以共存
        let toml_content = r#"
[shims]
lodash = "ts_native_shim_lodash"

[native_addons.sharp]
dll = "sharp.dll"

[native_addons.sharp.functions]
resize = "sharp_resize"
"#;
        let mut config = ProjectConfig::default();
        Project::parse_ts_native_toml(toml_content, &mut config);

        assert_eq!(config.shims.len(), 1);
        assert_eq!(config.shims.get("lodash"), Some(&"ts_native_shim_lodash".to_string()));
        assert_eq!(config.native_addons.len(), 1);
        assert_eq!(config.native_addons["sharp"].dll, "sharp.dll");
    }

    #[test]
    fn test_parse_native_addons_empty() {
        let toml_content = "";
        let mut config = ProjectConfig::default();
        Project::parse_ts_native_toml(toml_content, &mut config);
        assert!(config.native_addons.is_empty());
    }

    #[test]
    fn test_is_native_addon() {
        let root = PathBuf::from("/project");
        let mut project = make_test_project(&root, &[("src/main.ts", "main")]);
        project.config.native_addons.insert("sharp".to_string(), NativeAddonConfig {
            dll: "sharp.dll".to_string(),
            functions: HashMap::new(),
        });
        assert!(project.is_native_addon("sharp"));
        assert!(!project.is_native_addon("lodash"));
    }

    #[test]
    fn test_get_addon_dll() {
        let root = PathBuf::from("/project");
        let mut project = make_test_project(&root, &[("src/main.ts", "main")]);
        project.config.native_addons.insert("sharp".to_string(), NativeAddonConfig {
            dll: "sharp.dll".to_string(),
            functions: HashMap::new(),
        });
        assert_eq!(project.get_addon_dll("sharp"), Some("sharp.dll"));
        assert_eq!(project.get_addon_dll("lodash"), None);
    }

    #[test]
    fn test_get_addon_functions() {
        let root = PathBuf::from("/project");
        let mut project = make_test_project(&root, &[("src/main.ts", "main")]);
        let mut functions = HashMap::new();
        functions.insert("resize".to_string(), "sharp_resize".to_string());
        functions.insert("rotate".to_string(), "sharp_rotate".to_string());
        project.config.native_addons.insert("sharp".to_string(), NativeAddonConfig {
            dll: "sharp.dll".to_string(),
            functions,
        });
        let funcs = project.get_addon_functions("sharp").unwrap();
        assert_eq!(funcs.len(), 2);
        assert_eq!(funcs.get("resize").unwrap(), "sharp_resize");
        assert_eq!(project.get_addon_functions("unknown"), None);
    }

    // ========== Shim 集成测试 ==========

    #[test]
    fn test_shim_priority_over_builtin() {
        // shim 优先于内置模块：lodash 配置为 shim 时，应返回 __shim__ 路径
        let root = PathBuf::from("/project");
        let mut project = make_test_project(&root, &[("src/main.ts", "main")]);
        project.config.shims.insert("lodash".to_string(), "ts_native_shim_lodash".to_string());

        let from = root.join("src/main.ts");
        let resolved = project.resolve_import(&from, "lodash");

        assert!(resolved.is_some(), "shim 包应解析为 __shim__ 路径");
        let path = resolved.unwrap();
        assert!(Project::is_shim_path(&path), "路径应为 shim 类型");
        assert_eq!(Project::shim_package_name(&path), Some("lodash".to_string()));
    }

    #[test]
    fn test_user_module_higher_priority_than_shim() {
        // 用户模块优先级 > shim：src/lodash.ts 存在时，不使用 shim
        let root = PathBuf::from("/project");
        let mut project = make_test_project(&root, &[
            ("src/lodash.ts", "lodash"),
            ("src/main.ts", "main"),
        ]);
        project.config.shims.insert("lodash".to_string(), "ts_native_shim_lodash".to_string());

        let from = root.join("src/main.ts");
        let resolved = project.resolve_import(&from, "lodash");

        assert!(resolved.is_some());
        let path = resolved.unwrap();
        assert_eq!(path, root.join("src/lodash.ts"), "应解析为用户模块而非 shim");
        assert!(!Project::is_shim_path(&path), "用户模块路径不是 shim");
    }

    #[test]
    fn test_shim_not_resolve_without_config() {
        // 未配置 shim 时，非相对路径走内置模块（返回 None）
        let root = PathBuf::from("/project");
        let project = make_test_project(&root, &[("src/main.ts", "main")]);

        let from = root.join("src/main.ts");
        let resolved = project.resolve_import(&from, "lodash");

        assert!(resolved.is_none(), "未配置 shim 时应返回 None");
    }

    #[test]
    fn test_multiple_shims_coexist() {
        // 多个 shim 共存
        let root = PathBuf::from("/project");
        let mut project = make_test_project(&root, &[("src/main.ts", "main")]);
        project.config.shims.insert("lodash".to_string(), "ts_native_shim_lodash".to_string());
        project.config.shims.insert("dayjs".to_string(), "ts_native_shim_dayjs".to_string());
        project.config.shims.insert("uuid".to_string(), "ts_native_shim_uuid".to_string());

        let from = root.join("src/main.ts");
        assert!(project.resolve_import(&from, "lodash").unwrap().to_str().unwrap().contains("__shim__"));
        assert!(project.resolve_import(&from, "dayjs").unwrap().to_str().unwrap().contains("__shim__"));
        assert!(project.resolve_import(&from, "uuid").unwrap().to_str().unwrap().contains("__shim__"));
        assert!(project.resolve_import(&from, "unknown_pkg").is_none());
    }

    // ========== node_modules 解析集成测试 ==========

    #[test]
    fn test_npm_package_file_is_npm_flag() {
        // npm 包文件应标记 is_npm_package = true
        let root = PathBuf::from("/project");
        let mut project = make_test_project(&root, &[("src/main.ts", "main")]);

        // 手动添加一个 npm 包文件
        let npm_path = root.join("node_modules/lodash/index.js");
        project.files.insert(npm_path.clone(), SourceFile {
            path: npm_path,
            module_name: "lodash".to_string(),
            is_entry: false,
            content: None,
            is_npm_package: true,
        });

        let npm_file = project.files.get(&root.join("node_modules/lodash/index.js")).unwrap();
        assert!(npm_file.is_npm_package, "npm 包文件应标记 is_npm_package");
    }

    #[test]
    fn test_npm_package_entry_resolve() {
        // npm 包入口文件：在内存 files 中存在时，resolve_import 应能找到
        // 注意：resolve_node_modules 使用文件系统，但 resolve_import 也会检查 files HashMap
        let root = PathBuf::from("/project");
        let mut project = make_test_project(&root, &[("src/main.ts", "main")]);

        // 模拟 node_modules/lodash/index.js 存在于 files 中
        let npm_path = root.join("node_modules/lodash/index.js");
        project.files.insert(npm_path.clone(), SourceFile {
            path: npm_path,
            module_name: "lodash".to_string(),
            is_entry: false,
            content: None,
            is_npm_package: true,
        });

        let from = root.join("src/main.ts");
        let resolved = project.resolve_import(&from, "lodash");

        // resolve_import 会先检查用户模块（src/lodash.ts），再检查 shim，再检查 node_modules
        // node_modules 检查依赖文件系统（package.json），内存中无真实文件时返回 None
        // 这里验证的是：没有 shim 配置时，非相对路径返回 None（走内置模块）
        assert!(resolved.is_none(), "无真实 node_modules 目录时应返回 None");
    }

    #[test]
    fn test_npm_scoped_package_resolve() {
        // scoped 包 @types/node：同上，依赖真实文件系统
        let root = PathBuf::from("/project");
        let mut project = make_test_project(&root, &[("src/main.ts", "main")]);

        let npm_path = root.join("node_modules/@types/node/index.d.ts");
        project.files.insert(npm_path.clone(), SourceFile {
            path: npm_path,
            module_name: "@types/node".to_string(),
            is_entry: false,
            content: None,
            is_npm_package: true,
        });

        let from = root.join("src/main.ts");
        let resolved = project.resolve_import(&from, "@types/node");

        assert!(resolved.is_none(), "无真实 node_modules 目录时应返回 None");
    }

    #[test]
    fn test_npm_package_not_conflict_with_shim() {
        // npm 包和 shim 可以共存，shim 优先
        let root = PathBuf::from("/project");
        let mut project = make_test_project(&root, &[("src/main.ts", "main")]);

        // 同时存在 shim 配置和 npm 包文件
        project.config.shims.insert("lodash".to_string(), "ts_native_shim_lodash".to_string());
        let npm_path = root.join("node_modules/lodash/index.js");
        project.files.insert(npm_path.clone(), SourceFile {
            path: npm_path,
            module_name: "lodash".to_string(),
            is_entry: false,
            content: None,
            is_npm_package: true,
        });

        let from = root.join("src/main.ts");
        let resolved = project.resolve_import(&from, "lodash");

        // shim 优先于 npm 包（因为用户模块优先级 > shim > npm 包 > 内置）
        // 但当前实现中 shim 在用户模块检查之后
        // 如果没有 src/lodash.ts，shim 应优先于 npm 包
        assert!(resolved.is_some());
        let path = resolved.unwrap();
        assert!(Project::is_shim_path(&path), "shim 应优先于 npm 包");
    }

    // ========== FFI 桥接集成测试 ==========

    #[test]
    fn test_native_addon_resolve() {
        // native addon 应被正确识别和解析
        let root = PathBuf::from("/project");
        let mut project = make_test_project(&root, &[("src/main.ts", "main")]);
        let mut functions = HashMap::new();
        functions.insert("open".to_string(), "sqlite3_open".to_string());
        project.config.native_addons.insert("better_sqlite3".to_string(), NativeAddonConfig {
            dll: "better_sqlite3.dll".to_string(),
            functions,
        });

        assert!(project.is_native_addon("better_sqlite3"));
        assert!(!project.is_native_addon("lodash"));
        assert_eq!(project.get_addon_dll("better_sqlite3"), Some("better_sqlite3.dll"));
    }

    #[test]
    fn test_native_addon_not_resolve_as_import() {
        // native addon 不应被 resolve_import 解析为文件路径
        // （addon 通过 codegen 的 external_functions 处理，不经过文件解析）
        let root = PathBuf::from("/project");
        let mut project = make_test_project(&root, &[("src/main.ts", "main")]);
        let mut functions = HashMap::new();
        functions.insert("resize".to_string(), "sharp_resize".to_string());
        project.config.native_addons.insert("sharp".to_string(), NativeAddonConfig {
            dll: "sharp.dll".to_string(),
            functions,
        });

        let from = root.join("src/main.ts");
        let resolved = project.resolve_import(&from, "sharp");

        // addon 不在 shims/npm 包/用户模块中，应返回 None（走内置模块路径）
        // codegen 层会通过 native_addon_config 检查 addon
        assert!(resolved.is_none(), "addon 应返回 None，由 codegen 层处理");
    }

    #[test]
    fn test_addon_shim_npm_priority_order() {
        // 验证优先级：用户模块 > shim > npm 包 > addon > 内置
        let root = PathBuf::from("/project");
        let mut project = make_test_project(&root, &[
            ("src/main.ts", "main"),
        ]);

        // 配置 shim
        project.config.shims.insert("foo".to_string(), "ts_native_shim_foo".to_string());
        // 配置 addon
        let mut functions = HashMap::new();
        functions.insert("bar".to_string(), "foo_bar".to_string());
        project.config.native_addons.insert("foo".to_string(), NativeAddonConfig {
            dll: "foo.dll".to_string(),
            functions,
        });
        // 添加 npm 包文件
        let npm_path = root.join("node_modules/foo/index.js");
        project.files.insert(npm_path.clone(), SourceFile {
            path: npm_path,
            module_name: "foo".to_string(),
            is_entry: false,
            content: None,
            is_npm_package: true,
        });

        let from = root.join("src/main.ts");
        let resolved = project.resolve_import(&from, "foo");

        // shim 优先于 npm 包（用户模块不存在时）
        assert!(resolved.is_some());
        let path = resolved.unwrap();
        assert!(Project::is_shim_path(&path), "shim 应优先于 npm 包");
        // addon 不影响 resolve_import（由 codegen 处理）
    }

    #[test]
    fn test_full_priority_chain() {
        // 完整优先级链：用户模块 > shim > npm 包 > 内置
        let root = PathBuf::from("/project");
        let mut project = make_test_project(&root, &[
            ("src/fs.ts", "fs"),       // 用户模块
            ("src/main.ts", "main"),
        ]);
        project.config.shims.insert("path".to_string(), "ts_native_shim_path".to_string());
        let npm_path = root.join("node_modules/os/index.js");
        project.files.insert(npm_path.clone(), SourceFile {
            path: npm_path,
            module_name: "os".to_string(),
            is_entry: false,
            content: None,
            is_npm_package: true,
        });

        let from = root.join("src/main.ts");

        // fs: 用户模块存在 → 解析为用户模块
        let fs_resolved = project.resolve_import(&from, "fs");
        assert_eq!(fs_resolved.unwrap(), root.join("src/fs.ts"));

        // path: shim 配置 → 解析为 shim
        let path_resolved = project.resolve_import(&from, "path");
        assert!(Project::is_shim_path(&path_resolved.unwrap()));

        // os: npm 包存在但无真实文件系统 → 返回 None（走内置）
        let os_resolved = project.resolve_import(&from, "os");
        assert!(os_resolved.is_none(), "无真实 node_modules 时 npm 包返回 None");

        // crypto: 无用户模块/shim/npm → 返回 None（内置）
        let crypto_resolved = project.resolve_import(&from, "crypto");
        assert!(crypto_resolved.is_none());
    }

    // ========== ts-native.toml 综合配置测试 ==========

    #[test]
    fn test_full_toml_config() {
        // 完整的 ts-native.toml 配置解析
        let toml_content = r#"
[shims]
lodash = "ts_native_shim_lodash"
dayjs = "ts_native_shim_dayjs"

[native_addons.sharp]
dll = "sharp.dll"

[native_addons.sharp.functions]
resize = "sharp_resize"
rotate = "sharp_rotate"

[native_addons.better_sqlite3]
dll = "better_sqlite3.dll"

[native_addons.better_sqlite3.functions]
open = "sqlite3_open"
close = "sqlite3_close"
"#;
        let mut config = ProjectConfig::default();
        Project::parse_ts_native_toml(toml_content, &mut config);

        // shims
        assert_eq!(config.shims.len(), 2);
        assert_eq!(config.shims.get("lodash").unwrap(), "ts_native_shim_lodash");
        assert_eq!(config.shims.get("dayjs").unwrap(), "ts_native_shim_dayjs");

        // native_addons
        assert_eq!(config.native_addons.len(), 2);
        let sharp = &config.native_addons["sharp"];
        assert_eq!(sharp.dll, "sharp.dll");
        assert_eq!(sharp.functions.len(), 2);
        assert_eq!(sharp.functions.get("resize").unwrap(), "sharp_resize");

        let sqlite = &config.native_addons["better_sqlite3"];
        assert_eq!(sqlite.dll, "better_sqlite3.dll");
        assert_eq!(sqlite.functions.len(), 2);
        assert_eq!(sqlite.functions.get("open").unwrap(), "sqlite3_open");
    }

    #[test]
    fn test_toml_config_backward_compatible() {
        // 只有 shims 的旧配置仍能正常解析
        let toml_content = r#"
[shims]
lodash = "ts_native_shim_lodash"
"#;
        let mut config = ProjectConfig::default();
        Project::parse_ts_native_toml(toml_content, &mut config);
        assert_eq!(config.shims.len(), 1);
        assert_eq!(config.native_addons.len(), 0);
    }

    #[test]
    fn test_empty_toml_config() {
        // 空 toml 不应 panic
        let mut config = ProjectConfig::default();
        Project::parse_ts_native_toml("", &mut config);
        assert_eq!(config.shims.len(), 0);
        assert_eq!(config.native_addons.len(), 0);
        assert!(!config.js_engine.enabled);
        assert!(config.js_engine.fallback.is_empty());
    }

    // ========== js-engine 配置解析测试 ==========

    #[test]
    fn test_parse_js_engine_config() {
        let toml_content = r#"
[js-engine]
enabled = true
fallback = ["express", "axios", "koa"]
"#;
        let mut config = ProjectConfig::default();
        Project::parse_ts_native_toml(toml_content, &mut config);

        assert!(config.js_engine.enabled);
        assert_eq!(config.js_engine.fallback, vec!["express", "axios", "koa"]);
    }

    #[test]
    fn test_parse_js_engine_disabled() {
        let toml_content = r#"
[js-engine]
enabled = false
fallback = ["express"]
"#;
        let mut config = ProjectConfig::default();
        Project::parse_ts_native_toml(toml_content, &mut config);

        assert!(!config.js_engine.enabled);
        // fallback 仍然被解析
        assert_eq!(config.js_engine.fallback, vec!["express"]);
    }

    #[test]
    fn test_parse_js_engine_with_other_sections() {
        // js-engine 与 shims/native_addons 共存
        let toml_content = r#"
[shims]
lodash = "ts_native_shim_lodash"

[js-engine]
enabled = true
fallback = ["express", "axios"]

[native_addons.sharp]
dll = "sharp.dll"
"#;
        let mut config = ProjectConfig::default();
        Project::parse_ts_native_toml(toml_content, &mut config);

        assert_eq!(config.shims.len(), 1);
        assert!(config.js_engine.enabled);
        assert_eq!(config.js_engine.fallback, vec!["express", "axios"]);
        assert_eq!(config.native_addons.len(), 1);
    }

    #[test]
    fn test_is_qjs_fallback() {
        let root = PathBuf::from("/project");
        let mut project = make_test_project(&root, &[("src/main.ts", "main")]);
        project.config.js_engine.enabled = true;
        project.config.js_engine.fallback = vec!["express".to_string(), "axios".to_string()];

        assert!(project.is_qjs_fallback("express"));
        assert!(project.is_qjs_fallback("axios"));
        assert!(!project.is_qjs_fallback("lodash"));
    }

    #[test]
    fn test_is_qjs_fallback_disabled() {
        let root = PathBuf::from("/project");
        let mut project = make_test_project(&root, &[("src/main.ts", "main")]);
        project.config.js_engine.enabled = false;
        project.config.js_engine.fallback = vec!["express".to_string()];

        // 即使在 fallback 列表中，disabled 时也不应匹配
        assert!(!project.is_qjs_fallback("express"));
    }

    #[test]
    fn test_get_qjs_fallback_packages() {
        let root = PathBuf::from("/project");
        let mut project = make_test_project(&root, &[("src/main.ts", "main")]);
        project.config.js_engine.enabled = true;
        project.config.js_engine.fallback = vec!["express".to_string(), "koa".to_string()];

        let packages = project.get_qjs_fallback_packages();
        assert_eq!(packages.len(), 2);
        assert!(packages.contains(&"express".to_string()));
        assert!(packages.contains(&"koa".to_string()));
    }
}
