//! Type and signature rendering utilities for Rust types from rustdoc JSON.

use rustdoc_types::{
    Constant as ConstVal, Function, FunctionSignature, GenericArg, GenericArgs as RGenericArgs,
    Path as RustdocPath, Type, Visibility,
};

/// Extract the first paragraph from documentation text.
///
/// Returns text up to the first empty line, or the first line if no empty line exists.
pub fn extract_summary(doc: &str) -> String {
    let mut first = String::new();
    for line in doc.lines() {
        if line.trim().is_empty() {
            break;
        }
        if !first.is_empty() {
            first.push(' ');
        }
        first.push_str(line.trim());
    }
    if first.is_empty() {
        doc.lines()
            .next()
            .map(|l| l.trim().to_string())
            .unwrap_or_default()
    } else {
        first
    }
}

/// Render a complete function signature as a Rust code string.
///
/// Includes visibility, modifiers (`const`, `async`, `unsafe`), ABI, parameters, and return type.
pub fn render_function_signature(name: &str, func: &Function, visibility: &Visibility) -> String {
    let mut sig = String::new();

    if let Some(vis) = visibility_prefix(visibility) {
        sig.push_str(&vis);
        sig.push(' ');
    }

    if func.header.is_const {
        sig.push_str("const ");
    }
    if func.header.is_async {
        sig.push_str("async ");
    }
    if func.header.is_unsafe {
        sig.push_str("unsafe ");
    }

    if let Some(abi) = render_abi(&func.header.abi) {
        sig.push_str(&abi);
        sig.push(' ');
    }

    sig.push_str("fn ");
    sig.push_str(name);
    // Generics omitted for now
    sig.push('(');
    sig.push_str(&render_fn_sig_inputs(&func.sig));
    sig.push(')');
    if let Some(ret) = render_fn_sig_output(&func.sig) {
        sig.push_str(" -> ");
        sig.push_str(&ret);
    }
    sig
}

/// Convert ABI enum to string representation.
///
/// Returns `None` for Rust ABI, `Some("extern \"C\"")` for C ABI, etc.
fn render_abi(abi: &rustdoc_types::Abi) -> Option<String> {
    use rustdoc_types::Abi;
    match abi {
        Abi::Rust => None,
        Abi::C { unwind: false } => Some("extern \"C\"".to_string()),
        Abi::C { unwind: true } => Some("extern \"C-unwind\"".to_string()),
        Abi::Cdecl { unwind: false } => Some("extern \"cdecl\"".to_string()),
        Abi::Cdecl { unwind: true } => Some("extern \"cdecl-unwind\"".to_string()),
        Abi::Stdcall { unwind: false } => Some("extern \"stdcall\"".to_string()),
        Abi::Stdcall { unwind: true } => Some("extern \"stdcall-unwind\"".to_string()),
        Abi::Fastcall { unwind: false } => Some("extern \"fastcall\"".to_string()),
        Abi::Fastcall { unwind: true } => Some("extern \"fastcall-unwind\"".to_string()),
        Abi::Aapcs { unwind: false } => Some("extern \"aapcs\"".to_string()),
        Abi::Aapcs { unwind: true } => Some("extern \"aapcs-unwind\"".to_string()),
        Abi::Win64 { unwind: false } => Some("extern \"win64\"".to_string()),
        Abi::Win64 { unwind: true } => Some("extern \"win64-unwind\"".to_string()),
        Abi::SysV64 { unwind: false } => Some("extern \"sysv64\"".to_string()),
        Abi::SysV64 { unwind: true } => Some("extern \"sysv64-unwind\"".to_string()),
        Abi::System { unwind: false } => Some("extern \"system\"".to_string()),
        Abi::System { unwind: true } => Some("extern \"system-unwind\"".to_string()),
        Abi::Other(name) => Some(format!("extern \"{}\"", name)),
    }
}

/// Render function parameters as comma-separated string.
fn render_fn_sig_inputs(sig: &FunctionSignature) -> String {
    sig.inputs
        .iter()
        .map(|(name, ty)| format!("{}: {}", name, render_type(ty)))
        .collect::<Vec<_>>()
        .join(", ")
}

/// Render function return type, if present.
fn render_fn_sig_output(sig: &FunctionSignature) -> Option<String> {
    sig.output.as_ref().map(render_type)
}

/// Render a rustdoc JSON type into a Rust type string.
///
/// Handles primitives, paths, tuples, generics, references, slices, arrays, and qualified paths.
pub fn render_type(ty: &Type) -> String {
    match ty {
        Type::ResolvedPath(path) => render_resolved_path(path),
        Type::Primitive(p) => p.clone(),
        Type::Tuple(elems) => {
            let inner = elems.iter().map(render_type).collect::<Vec<_>>().join(", ");
            format!("({})", inner)
        }
        Type::Generic(name) => name.clone(),
        Type::BorrowedRef {
            lifetime,
            is_mutable,
            type_,
        } => {
            let mut s = String::from("&");
            if let Some(lt) = lifetime {
                s.push_str(lt);
                s.push(' ');
            }
            if *is_mutable {
                s.push_str("mut ");
            }
            s.push_str(&render_type(type_));
            s
        }
        Type::Slice(inner) => format!("[{}]", render_type(inner)),
        Type::Array { type_, len } => format!("[{}; {}]", render_type(type_), len),
        Type::QualifiedPath {
            name,
            args,
            self_type,
            trait_,
        } => {
            let ty = render_type(self_type);
            let tr = trait_.as_ref().map(render_resolved_path);
            let args = args
                .as_ref()
                .map(|a| render_generic_args(a))
                .unwrap_or_default();
            match tr {
                Some(trait_path) => format!("<{} as {}>::{}{}", ty, trait_path, name, args),
                None => format!("{}::{}{}", ty, name, args),
            }
        }
        _ => "_".to_string(),
    }
}

/// Render a resolved path with generic arguments.
fn render_resolved_path(path: &RustdocPath) -> String {
    let mut s = String::new();
    s.push_str(&path.path);
    if let Some(args) = &path.args {
        s.push_str(&render_generic_args(args));
    }
    s
}

/// Render generic arguments (angle-bracketed or parenthesized).
fn render_generic_args(args: &RGenericArgs) -> String {
    match args {
        RGenericArgs::AngleBracketed { args, constraints } => {
            let mut parts = Vec::new();
            for a in args {
                match a {
                    GenericArg::Lifetime(l) => parts.push(l.clone()),
                    GenericArg::Type(t) => parts.push(render_type(t)),
                    GenericArg::Const(ConstVal { expr, .. }) => parts.push(expr.clone()),
                    GenericArg::Infer => parts.push("_".into()),
                }
            }
            for b in constraints {
                match &b.binding {
                    rustdoc_types::AssocItemConstraintKind::Equality(term) => {
                        parts.push(format!("{} = {}", b.name, render_term(term)));
                    }
                    rustdoc_types::AssocItemConstraintKind::Constraint(bounds) => {
                        let joined = bounds
                            .iter()
                            .map(render_generic_bound)
                            .collect::<Vec<_>>()
                            .join(" + ");
                        parts.push(format!("{}: {}", b.name, joined));
                    }
                }
            }
            format!("::<{}>", parts.join(", "))
        }
        RGenericArgs::Parenthesized { inputs, output } => {
            let ins = inputs
                .iter()
                .map(render_type)
                .collect::<Vec<_>>()
                .join(", ");
            if let Some(out) = output {
                format!("({}) -> {}", ins, render_type(out))
            } else {
                format!("({})", ins)
            }
        }
        RGenericArgs::ReturnTypeNotation => String::new(),
    }
}

/// Render a generic bound (trait bound, lifetime, or use clause).
fn render_generic_bound(b: &rustdoc_types::GenericBound) -> String {
    use rustdoc_types::GenericBound as GB;
    match b {
        GB::TraitBound {
            trait_,
            generic_params,
            ..
        } => {
            let path = render_resolved_path(trait_);
            if generic_params.is_empty() {
                path
            } else {
                let params = generic_params
                    .iter()
                    .map(|p| p.name.clone())
                    .collect::<Vec<_>>()
                    .join(", ");
                format!("{}<{}>", path, params)
            }
        }
        GB::Outlives(l) => format!("'{}", l),
        GB::Use(args) => args
            .iter()
            .map(|a| match a {
                rustdoc_types::PreciseCapturingArg::Lifetime(l) => format!("'{}", l),
                rustdoc_types::PreciseCapturingArg::Param(p) => p.clone(),
            })
            .collect::<Vec<_>>()
            .join(", "),
    }
}

/// Render a term (type or constant expression).
fn render_term(term: &rustdoc_types::Term) -> String {
    match term {
        rustdoc_types::Term::Type(t) => render_type(t),
        rustdoc_types::Term::Constant(c) => c.expr.clone(),
    }
}

/// Convert a visibility enum into a Rust source prefix.
///
/// Returns `Some("pub")`, `Some("pub(crate)")`, `Some("pub(path)")`, or `None` for private items.
pub fn visibility_prefix(visibility: &Visibility) -> Option<String> {
    match visibility {
        Visibility::Public => Some("pub".to_string()),
        Visibility::Crate => Some("pub(crate)".to_string()),
        Visibility::Restricted { path, .. } => Some(format!("pub({})", path)),
        Visibility::Default => None,
    }
}
