//! 两遍扫描构建编排器 (Two-pass Build Orchestrator)
//!
//! 实现 roplat 的两阶段构建流程：
//!
//! 1. **提取阶段 (Extract)**：通过 `cargo check` 子进程触发过程宏，
//!    将元数据写入 JSON 清单文件。
//! 2. **生成阶段 (Generate)**：读取清单文件，调用 `MsgCodeGen` /
//!    `OpaqueCodeGen` / `NodeCodeGen` 生成多语言文件。
//!
//! ## 用法
//!
//! ```rust,ignore
//! // 在 build.rs 中
//! fn main() {
//!     if std::env::var("ROPLAT_PHASE").as_deref() == Ok("EXTRACT") {
//!         return;
//!     }
//!     roplat_build::BuildOrchestrator::new()
//!         .after_generate(|ctx| {
//!             println!("cargo:warning=generated files under {}", ctx.gen_cpp_dir.display());
//!             Ok(())
//!         })
//!         .build()
//!         .expect("Code generation failed");
//!     println!("cargo:rerun-if-changed=src/");
//!     println!("cargo:rerun-if-changed=build.rs");
//! }
//! ```

use std::path::{Path, PathBuf};
use std::{env, fs};

use crate::codegen::{MsgCodeGen, NodeCodeGen, OpaqueCodeGen};
use crate::manifest::{self, ManifestEntry};
use crate::native_build::NativeBuildConfig;

type BuildHook = Box<dyn Fn(&BuildContext) -> std::io::Result<()>>;

/// Read-only build context passed to custom build hooks.
pub struct BuildContext<'a> {
    pub package_name: &'a str,
    pub crate_dir: &'a Path,
    pub manifest_dir: &'a Path,
    pub gen_cpp_dir: &'a Path,
    pub gen_py_dir: &'a Path,
    pub user_cpp_dir: &'a Path,
    pub user_py_dir: &'a Path,
}

/// 两遍扫描构建编排器
pub struct BuildOrchestrator {
    package_name: String,
    crate_dir: PathBuf,
    manifest_dir: PathBuf,
    gen_cpp_dir: PathBuf,
    gen_py_dir: PathBuf,
    user_cpp_dir: PathBuf,
    user_py_dir: PathBuf,
    native_build: Option<NativeBuildConfig>,
    after_extract_hooks: Vec<BuildHook>,
    after_generate_hooks: Vec<BuildHook>,
    after_native_build_hooks: Vec<BuildHook>,
}

impl BuildOrchestrator {
    /// 创建编排器（自动从环境变量推导包名和路径）
    pub fn new() -> Self {
        let pkg_name = env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME not set");
        let manifest_path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
        let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
        let manifest_dir = out_dir.join("roplat_manifests");

        Self {
            package_name: pkg_name,
            crate_dir: manifest_path.clone(),
            manifest_dir,
            gen_cpp_dir: manifest_path.join("cpp").join("roplat_gen"),
            gen_py_dir: manifest_path.join("py").join("roplat_gen"),
            user_cpp_dir: manifest_path.join("cpp").join("src"),
            user_py_dir: manifest_path.join("py"),
            native_build: None,
            after_extract_hooks: Vec::new(),
            after_generate_hooks: Vec::new(),
            after_native_build_hooks: Vec::new(),
        }
    }

    /// 设置生成的 C++ 基类输出目录
    pub fn gen_cpp_dir(mut self, dir: impl Into<PathBuf>) -> Self {
        self.gen_cpp_dir = dir.into();
        self
    }

    /// 设置生成的 Python 基类输出目录
    pub fn gen_py_dir(mut self, dir: impl Into<PathBuf>) -> Self {
        self.gen_py_dir = dir.into();
        self
    }

    /// 设置 C++ 用户文件输出目录
    pub fn user_cpp_dir(mut self, dir: impl Into<PathBuf>) -> Self {
        self.user_cpp_dir = dir.into();
        self
    }

    /// 设置 Python 用户文件输出目录
    pub fn user_py_dir(mut self, dir: impl Into<PathBuf>) -> Self {
        self.user_py_dir = dir.into();
        self
    }

    /// 设置本地 C++ 构建后端配置（cc 或 cmake）
    pub fn native_build(mut self, config: NativeBuildConfig) -> Self {
        self.native_build = Some(config);
        self
    }

    /// 注册元数据提取后的自定义操作。
    pub fn after_extract<F>(mut self, hook: F) -> Self
    where
        F: Fn(&BuildContext) -> std::io::Result<()> + 'static,
    {
        self.after_extract_hooks.push(Box::new(hook));
        self
    }

    /// 注册多语言文件生成后的自定义操作。
    pub fn after_generate<F>(mut self, hook: F) -> Self
    where
        F: Fn(&BuildContext) -> std::io::Result<()> + 'static,
    {
        self.after_generate_hooks.push(Box::new(hook));
        self
    }

    /// 注册本地 C++ 构建后的自定义操作。
    pub fn after_native_build<F>(mut self, hook: F) -> Self
    where
        F: Fn(&BuildContext) -> std::io::Result<()> + 'static,
    {
        self.after_native_build_hooks.push(Box::new(hook));
        self
    }

    /// 执行完整的两遍扫描构建
    pub fn build(&self) -> std::io::Result<()> {
        println!("cargo:warning=================================================");
        println!(
            "cargo:warning= roplat: two-pass build for '{}'",
            self.package_name
        );
        println!("cargo:warning=================================================");

        // 第一遍：元数据提取
        self.extract_manifests()?;
        self.run_hooks(&self.after_extract_hooks)?;

        // 第二遍：读清单 + 生成文件
        let entries = self.read_manifests()?;
        self.generate_files(&entries)?;
        self.run_hooks(&self.after_generate_hooks)?;

        if let Some(native_build) = &self.native_build {
            native_build.build(&self.crate_dir, &self.gen_cpp_dir)?;
            self.run_hooks(&self.after_native_build_hooks)?;
        }

        println!("cargo:warning=================================================");
        println!("cargo:warning= roplat: code generation complete!");
        println!("cargo:warning=================================================");

        Ok(())
    }

    fn context(&self) -> BuildContext<'_> {
        BuildContext {
            package_name: &self.package_name,
            crate_dir: &self.crate_dir,
            manifest_dir: &self.manifest_dir,
            gen_cpp_dir: &self.gen_cpp_dir,
            gen_py_dir: &self.gen_py_dir,
            user_cpp_dir: &self.user_cpp_dir,
            user_py_dir: &self.user_py_dir,
        }
    }

    fn run_hooks(&self, hooks: &[BuildHook]) -> std::io::Result<()> {
        let context = self.context();
        for hook in hooks {
            hook(&context)?;
        }
        Ok(())
    }

    /// 第一阶段：通过 cargo check 子进程提取元数据
    fn extract_manifests(&self) -> std::io::Result<()> {
        println!("cargo:warning=[Phase 1] Extracting manifests via cargo check...");

        // 清除旧元文件
        if self.manifest_dir.exists() {
            fs::remove_dir_all(&self.manifest_dir)?;
        }
        fs::create_dir_all(&self.manifest_dir)?;

        // 使用独立的 target 目录避免与父 cargo 进程的文件锁冲突
        let extract_target = find_target_dir().join("roplat-extract");

        let status = std::process::Command::new("cargo")
            .arg("check")
            .arg("-p")
            .arg(&self.package_name)
            .env("ROPLAT_PHASE", "EXTRACT")
            .env("ROPLAT_MANIFEST_DIR", &self.manifest_dir)
            .env("CARGO_TARGET_DIR", &extract_target)
            .env_remove("RUSTC_WRAPPER")
            .env_remove("RUSTC_WORKSPACE_WRAPPER")
            .env_remove("CLIPPY_ARGS")
            .status()?;

        if !status.success() {
            return Err(std::io::Error::other(format!(
                "Manifest extraction failed: cargo check -p {} exited with {}",
                self.package_name, status
            )));
        }

        // 汇报提取结果
        let count = fs::read_dir(&self.manifest_dir)?
            .filter_map(|e| e.ok())
            .filter(|e| e.path().extension().is_some_and(|ext| ext == "json"))
            .count();

        println!("cargo:warning=[Phase 1] Extracted {count} manifest(s)");
        Ok(())
    }

    /// 读取所有元文件
    fn read_manifests(&self) -> std::io::Result<Vec<ManifestEntry>> {
        manifest::read_all_manifests(&self.manifest_dir)
    }

    /// 第二阶段：根据元文件生成多语言文件
    fn generate_files(&self, entries: &[ManifestEntry]) -> std::io::Result<()> {
        println!("cargo:warning=[Phase 2] Generating multi-language files...");

        let msg_gen = MsgCodeGen::new(&self.gen_cpp_dir, &self.gen_py_dir);
        let opaque_gen = OpaqueCodeGen::with_user_dirs(
            &self.gen_cpp_dir,
            &self.gen_py_dir,
            &self.user_cpp_dir,
            &self.user_py_dir,
        );
        let node_gen = NodeCodeGen::new(
            &self.gen_cpp_dir,
            &self.gen_py_dir,
            &self.user_cpp_dir,
            &self.user_py_dir,
        );

        for entry in entries {
            match entry {
                ManifestEntry::Msg(m) => {
                    println!("cargo:warning=  Generating msg: {}", m.name);
                    // 透明结构数据：生成系统管理文件（C 头文件 + Python ctypes 存根）
                    msg_gen.write_all(&m.name, &m.c_header, &m.python_stub)?;
                }
                ManifestEntry::Opaque(o) => {
                    println!(
                        "cargo:warning=  Generating opaque: {} (lang={})",
                        o.name, o.lang
                    );
                    let fields = o.to_opaque_fields();
                    match o.lang.as_str() {
                        "cpp" => opaque_gen.write_cpp_user_only(&o.name, &fields)?,
                        "py" => opaque_gen.write_python_user_only(&o.name, &fields)?,
                        _ => {
                            return Err(std::io::Error::new(
                                std::io::ErrorKind::InvalidInput,
                                format!("unsupported opaque lang: {}", o.lang),
                            ));
                        }
                    }
                }
                ManifestEntry::Node(n) => {
                    if n.package
                        .as_deref()
                        .is_some_and(|package| package != self.package_name)
                    {
                        continue;
                    }
                    println!(
                        "cargo:warning=  Generating node: {} (lang={})",
                        n.name, n.lang
                    );
                    let spec = n.to_node_spec();
                    match n.lang.as_str() {
                        "cpp" => node_gen.generate_cpp(&spec)?,
                        "py" => node_gen.generate_py(&spec)?,
                        _ => node_gen.generate_all(&spec)?,
                    }
                }
            }
        }

        Ok(())
    }
}

impl Default for BuildOrchestrator {
    fn default() -> Self {
        Self::new()
    }
}

/// 从 `OUT_DIR` 向上查找 `target` 目录
fn find_target_dir() -> PathBuf {
    let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
    let mut current = out_dir.as_path();
    loop {
        if current.file_name().is_some_and(|n| n == "target") {
            return current.to_path_buf();
        }
        current = match current.parent() {
            Some(p) => p,
            None => panic!(
                "Could not find 'target' directory from OUT_DIR: {}",
                out_dir.display()
            ),
        };
    }
}
