//! 元文件读取与类型转换
//!
//! 本模块定义了元文件（manifest）的 JSON 结构，并提供从元文件到代码生成类型的转换。
//! 元文件由过程宏在 `ROPLAT_PHASE=EXTRACT` 模式下生成。

use std::fs;
use std::io;
use std::path::Path;

use serde::Deserialize;

use crate::codegen::{NodeSpec, OpaqueField};

// ═══════════════════════════════════════════════════════════════════
// 元文件 JSON 结构
// ═══════════════════════════════════════════════════════════════════

/// 元文件条目（按 "kind" 字段区分类型）
#[derive(Debug, Deserialize)]
#[serde(tag = "kind")]
pub enum ManifestEntry {
    /// 透明消息类型
    #[serde(rename = "msg")]
    Msg(MsgManifest),
    /// 不透明数据类型
    #[serde(rename = "opaque")]
    Opaque(OpaqueManifest),
    /// 多语言节点
    #[serde(rename = "node")]
    Node(NodeManifest),
}

/// 透明消息类型元文件
#[derive(Debug, Deserialize)]
pub struct MsgManifest {
    /// 结构体名称（如 `SensorData`）
    pub name: String,
    /// 生成的 C 头文件内容
    pub c_header: String,
    /// 生成的 Python ctypes 存根内容
    pub python_stub: String,
    #[serde(default)]
    pub rust_type: Option<String>,
    #[serde(default)]
    pub package: Option<String>,
    #[serde(default)]
    pub crate_name: Option<String>,
}

/// 不透明数据类型元文件
#[derive(Debug, Deserialize)]
pub struct OpaqueManifest {
    /// 数据类型名称（如 `CppData`，已去除 Puppet 后缀）
    pub name: String,
    /// 目标语言：`"cpp"` 或 `"py"`
    pub lang: String,
    /// 字段列表
    pub fields: Vec<FieldManifest>,
    #[serde(default)]
    pub package: Option<String>,
    #[serde(default)]
    pub crate_name: Option<String>,
    #[serde(default)]
    pub rust_type: Option<String>,
}

/// 多语言节点元文件
#[derive(Debug, Deserialize)]
pub struct NodeManifest {
    /// 节点名称（如 `CppController`，已去除 Puppet 后缀）
    pub name: String,
    /// 目标语言：`"cpp"` 或 `"py"`
    pub lang: String,
    /// 通讯端口列表
    pub ports: Vec<PortManifest>,
    /// 配置字段列表
    pub fields: Vec<FieldManifest>,
    #[serde(default)]
    pub package: Option<String>,
    #[serde(default)]
    pub crate_name: Option<String>,
}

/// 字段元文件
#[derive(Debug, Deserialize)]
pub struct FieldManifest {
    /// 字段名
    pub name: String,
    /// Rust 类型名（如 `f64`, `i32`）
    pub rust_type: String,
}

/// 端口元文件
#[derive(Debug, Deserialize)]
pub struct PortManifest {
    /// 端口名（如 `sensor_input`）
    pub name: String,
    /// 消息类型名（如 `SensorData`）
    pub msg_type: String,
    /// 方向：`"input"` 或 `"output"`
    #[serde(default)]
    pub rust_type: Option<String>,
    pub direction: String,
    #[serde(default)]
    pub is_opaque: bool,
}

// ═══════════════════════════════════════════════════════════════════
// 类型转换
// ═══════════════════════════════════════════════════════════════════

impl FieldManifest {
    /// 将元文件字段转换为 `OpaqueField`
    pub fn to_opaque_field(&self) -> OpaqueField {
        match self.rust_type.as_str() {
            "f64" => OpaqueField::f64(&self.name),
            "f32" => OpaqueField::f32(&self.name),
            "i32" => OpaqueField::i32(&self.name),
            "u32" => OpaqueField::u32(&self.name),
            "u64" => OpaqueField::u64(&self.name),
            "bool" => OpaqueField::bool(&self.name),
            other => panic!("Unsupported Rust type in manifest: {other}"),
        }
    }
}

impl OpaqueManifest {
    /// 将所有字段转换为 `OpaqueField` 列表
    pub fn to_opaque_fields(&self) -> Vec<OpaqueField> {
        self.fields.iter().map(|f| f.to_opaque_field()).collect()
    }
}

impl NodeManifest {
    /// 将节点元文件转换为 `NodeSpec`
    pub fn to_node_spec(&self) -> NodeSpec {
        let mut spec = NodeSpec::new(&self.name);
        for port in &self.ports {
            match port.direction.as_str() {
                "input" if port.is_opaque => spec = spec.opaque_input(&port.name, &port.msg_type),
                "input" => spec = spec.input(&port.name, &port.msg_type),
                "output" if port.is_opaque => spec = spec.opaque_output(&port.name, &port.msg_type),
                "output" => spec = spec.output(&port.name, &port.msg_type),
                d => panic!("Unknown port direction in manifest: {d}"),
            }
        }
        for field in &self.fields {
            spec = spec.field(field.to_opaque_field());
        }
        spec
    }
}

// ═══════════════════════════════════════════════════════════════════
// 元文件读取
// ═══════════════════════════════════════════════════════════════════

/// 从目录中读取所有元文件
pub fn read_all_manifests(dir: &Path) -> std::io::Result<Vec<ManifestEntry>> {
    if !dir.exists() {
        return Ok(Vec::new());
    }
    let mut entries = Vec::new();
    for entry in fs::read_dir(dir)? {
        let entry = entry?;
        let path = entry.path();
        if path.extension().is_some_and(|e| e == "json") {
            let content = fs::read_to_string(&path)?;
            let manifest: ManifestEntry = serde_json::from_str(&content)
                .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
            entries.push(manifest);
        }
    }
    let opaque_types = OpaqueTypeRegistry::from_entries(&entries);

    for entry in &mut entries {
        if let ManifestEntry::Node(node) = entry {
            let node_name = node.name.clone();
            let node_lang = node.lang.clone();
            for port in &mut node.ports {
                port.is_opaque |= opaque_types.resolve_port(&node_name, &node_lang, port)?;
            }
        }
    }

    Ok(entries)
}

#[derive(Debug, Clone)]
struct OpaqueTypeInfo {
    name: String,
    lang: String,
    crate_name: Option<String>,
    rust_type: Option<String>,
}

struct OpaqueTypeRegistry {
    types: Vec<OpaqueTypeInfo>,
}

impl OpaqueTypeRegistry {
    fn from_entries(entries: &[ManifestEntry]) -> Self {
        let types = entries
            .iter()
            .filter_map(|entry| match entry {
                ManifestEntry::Opaque(opaque) => Some(OpaqueTypeInfo {
                    name: opaque.name.clone(),
                    lang: opaque.lang.clone(),
                    crate_name: opaque.crate_name.clone(),
                    rust_type: opaque.rust_type.clone(),
                }),
                _ => None,
            })
            .collect();
        Self { types }
    }

    fn resolve_port(
        &self,
        node_name: &str,
        node_lang: &str,
        port: &PortManifest,
    ) -> io::Result<bool> {
        let candidates: Vec<&OpaqueTypeInfo> = self
            .types
            .iter()
            .filter(|info| info.lang == node_lang)
            .filter(|info| port_matches_opaque(info, port))
            .collect();

        match candidates.as_slice() {
            [] => Ok(false),
            [_] => Ok(true),
            many => Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!(
                    "ambiguous roplat opaque type `{}` for node `{}`: {}. Use a qualified type path in #[roplat::node].",
                    port.rust_type.as_deref().unwrap_or(&port.msg_type),
                    node_name,
                    many.iter()
                        .map(|info| info.display_name())
                        .collect::<Vec<_>>()
                        .join(", ")
                ),
            )),
        }
    }
}

impl OpaqueTypeInfo {
    fn display_name(&self) -> String {
        self.rust_type
            .clone()
            .or_else(|| {
                self.crate_name
                    .as_ref()
                    .map(|c| format!("{c}::{}", self.name))
            })
            .unwrap_or_else(|| self.name.clone())
    }
}

fn port_matches_opaque(info: &OpaqueTypeInfo, port: &PortManifest) -> bool {
    let port_rust_type = port.rust_type.as_deref().map(normalize_type);
    let info_rust_type = info.rust_type.as_deref().map(normalize_type);

    if let (Some(port_ty), Some(info_ty)) = (&port_rust_type, &info_rust_type)
        && port_ty == info_ty
    {
        return true;
    }

    if let Some(crate_name) = &info.crate_name {
        let qualified = normalize_type(&format!("{crate_name}::{}", info.name));
        if port_rust_type.as_deref() == Some(qualified.as_str()) {
            return true;
        }
    }

    if port_rust_type
        .as_deref()
        .is_some_and(|ty| ty.contains("::"))
    {
        return false;
    }

    port.msg_type == info.name
}

fn normalize_type(ty: &str) -> String {
    ty.split_whitespace().collect::<String>()
}
