// tools/src/main.rs
use serde::Serialize;
use std::env;
use std::fs;
use std::process;
use syn::{
    spanned::Spanned, Attribute, FnArg, Item, ItemEnum, ItemFn, ItemImpl, ItemMod, ItemStruct,
    ItemTrait, Pat, Type, Visibility,
};

#[derive(Serialize)]
struct RustDecl {
    name: String,
    type_: String,
    start_line: usize,
    end_line: usize,
    visibility: String,
    is_async: bool,
    is_unsafe: bool,
    generics: Vec<String>,
    traits: Vec<String>,
    fields: Vec<String>,
    methods: Vec<String>,
    return_type: Option<String>,
    parameters: Vec<String>,
    docstring: Option<String>,
}

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() < 2 {
        eprintln!("Usage: {} <file.rs>", args[0]);
        process::exit(1);
    }

    let filename = &args[1];
    let content = match fs::read_to_string(filename) {
        Ok(content) => content,
        Err(e) => {
            eprintln!("ERR: Failed to read file {}: {}", filename, e);
            process::exit(2);
        }
    };

    let syntax = match syn::parse_file(&content) {
        Ok(syntax) => syntax,
        Err(e) => {
            eprintln!("ERR: Failed to parse Rust file {}: {}", filename, e);
            process::exit(3);
        }
    };

    let mut decls = Vec::new();

    // Process declarations
    for item in &syntax.items {
        match item {
            Item::Fn(item_fn) => {
                if let Some(decl) = parse_function(&item_fn) {
                    decls.push(decl);
                }
            }
            Item::Struct(item_struct) => {
                if let Some(decl) = parse_struct(&item_struct) {
                    decls.push(decl);
                }
            }
            Item::Enum(item_enum) => {
                if let Some(decl) = parse_enum(&item_enum) {
                    decls.push(decl);
                }
            }
            Item::Trait(item_trait) => {
                if let Some(decl) = parse_trait(&item_trait) {
                    decls.push(decl);
                }
            }
            Item::Impl(item_impl) => {
                if let Some(decl) = parse_impl(&item_impl) {
                    decls.push(decl);
                }
            }
            Item::Mod(item_mod) => {
                if let Some(decl) = parse_mod(&item_mod) {
                    decls.push(decl);
                }
            }
            _ => {}
        }
    }

    for item in &syntax.items {
        if let Item::Use(item_use) = item {
            let path = use_tree_to_string(&item_use.tree);

            decls.push(RustDecl {
                name: path.clone(),
                type_: "import".to_string(),
                start_line: 0,
                end_line: 0,
                visibility: "".to_string(),
                is_async: false,
                is_unsafe: false,
                generics: vec![],
                traits: vec![],
                fields: vec![],
                methods: vec![],
                return_type: None,
                parameters: vec![path],
                docstring: none,
            });
        }
    }

    let output = serde_json::to_string(&decls).unwrap_or_else(|_| "[]".to_string());
    println!("{}", output);
}

fn span_start_end<T: Spanned>(node: &T) -> (usize, usize) {
    // Requires proc-macro2 "span-locations" feature.
    let span = node.span();
    let start = span.start().line;
    let end = span.end().line;
    (start, end)
}

fn parse_function(item_fn: &ItemFn) -> Option<RustDecl> {
    let (start_line, end_line) = span_start_end(item_fn);

    let mut parameters = Vec::new();
    for input in &item_fn.sig.inputs {
        if let FnArg::Typed(pat_type) = input {
            let name = if let Pat::Ident(pat_ident) = &*pat_type.pat {
                pat_ident.ident.to_string()
            } else {
                format!("{}", quote::ToTokens::to_token_stream(&pat_type.pat))
            };
            let ty = type_to_string(&*pat_type.ty);
            parameters.push(format!("{}: {}", name, ty));
        } else if let FnArg::Receiver(_) = input {
            parameters.push("self".to_string());
        }
    }

    let return_type = match &item_fn.sig.output {
        syn::ReturnType::Default => None,
        syn::ReturnType::Type(_, ty) => Some(type_to_string(&*ty)),
    };

    Some(RustDecl {
        name: item_fn.sig.ident.to_string(),
        type_: "function".to_string(),
        start_line,
        end_line,
        visibility: visibility_to_string(&item_fn.vis),
        is_async: item_fn.sig.asyncness.is_some(),
        is_unsafe: item_fn.sig.unsafety.is_some(),
        generics: item_fn
            .sig
            .generics
            .params
            .iter()
            .map(generic_param_to_string)
            .collect(),
        traits: Vec::new(),
        fields: Vec::new(),
        methods: Vec::new(),
        return_type,
        parameters,
        docstring: extract_docstring(&item_fn.attrs),
    })
}

fn parse_struct(item_struct: &ItemStruct) -> Option<RustDecl> {
    let (start_line, end_line) = span_start_end(item_struct);

    let fields: Vec<String> = item_struct
        .fields
        .iter()
        .filter_map(|field| field.ident.as_ref().map(|ident| ident.to_string()))
        .collect();

    Some(RustDecl {
        name: item_struct.ident.to_string(),
        type_: "struct".to_string(),
        start_line,
        end_line,
        visibility: visibility_to_string(&item_struct.vis),
        is_async: false,
        is_unsafe: false,
        generics: item_struct
            .generics
            .params
            .iter()
            .map(generic_param_to_string)
            .collect(),
        traits: Vec::new(),
        fields,
        methods: Vec::new(),
        return_type: None,
        parameters: Vec::new(),
        docstring: extract_docstring(&item_struct.attrs),
    })
}

fn parse_enum(item_enum: &ItemEnum) -> Option<RustDecl> {
    let (start_line, end_line) = span_start_end(item_enum);

    let variants: Vec<String> = item_enum
        .variants
        .iter()
        .map(|variant| variant.ident.to_string())
        .collect();

    Some(RustDecl {
        name: item_enum.ident.to_string(),
        type_: "enum".to_string(),
        start_line,
        end_line,
        visibility: visibility_to_string(&item_enum.vis),
        is_async: false,
        is_unsafe: false,
        generics: item_enum
            .generics
            .params
            .iter()
            .map(generic_param_to_string)
            .collect(),
        traits: Vec::new(),
        fields: variants,
        methods: Vec::new(),
        return_type: None,
        parameters: Vec::new(),
        docstring: extract_docstring(&item_enum.attrs),
    })
}

fn parse_trait(item_trait: &ItemTrait) -> Option<RustDecl> {
    let (start_line, end_line) = span_start_end(item_trait);

    let methods: Vec<String> = item_trait
        .items
        .iter()
        .filter_map(|item| {
            if let syn::TraitItem::Fn(method) = item {
                Some(method.sig.ident.to_string())
            } else {
                None
            }
        })
        .collect();

    Some(RustDecl {
        name: item_trait.ident.to_string(),
        type_: "trait".to_string(),
        start_line,
        end_line,
        visibility: visibility_to_string(&item_trait.vis),
        is_async: false,
        is_unsafe: false,
        generics: item_trait
            .generics
            .params
            .iter()
            .map(generic_param_to_string)
            .collect(),
        traits: Vec::new(),
        fields: Vec::new(),
        methods,
        return_type: None,
        parameters: Vec::new(),
        docstring: extract_docstring(&item_trait.attrs),
    })
}

fn parse_impl(item_impl: &ItemImpl) -> Option<RustDecl> {
    let (start_line, end_line) = span_start_end(item_impl);

    let target = type_to_string(&*item_impl.self_ty);

    let methods: Vec<String> = item_impl
        .items
        .iter()
        .filter_map(|item| {
            if let syn::ImplItem::Fn(method) = item {
                Some(method.sig.ident.to_string())
            } else {
                None
            }
        })
        .collect();

    // If this impl implements a trait, extract its path
    let implements = item_impl
        .trait_
        .as_ref()
        .map(|(_, path, _)| path_to_string(path))
        .into_iter()
        .collect::<Vec<_>>();

    // Prefer the first trait name (if any) as the identifier; otherwise fall back to the target type
    let name = if !implements.is_empty() {
        implements[0].clone()
    } else {
        target
    };

    // Extract any docstring attached to the impl block
    let docstring = extract_docstring(&item_impl.attrs);

    Some(RustDecl {
        name,
        type_: "impl".to_string(),
        start_line,
        end_line,
        visibility: "".to_string(),
        is_async: false,
        is_unsafe: item_impl.unsafety.is_some(),
        generics: item_impl
            .generics
            .params
            .iter()
            .map(generic_param_to_string)
            .collect(),
        traits: implements.clone(),
        fields: Vec::new(),
        methods,
        return_type: None,
        parameters: Vec::new(),
        docstring,
    })
}

/// Pull a concatenated doc‑string from a list of attributes.
/// Handles both `///` comments (converted to `#[doc = "..."]` by syn)
/// and explicit `#[doc = "..."]` attributes.
fn extract_docstring(attrs: &[Attribute]) -> Option<String> {
    let mut docs = Vec::new();

    for attr in attrs {
        // `path()` is the correct method; `path` is a field
        if attr.path().is_ident("doc") {
            // #[doc = "…"]
            if let Ok(meta) = attr.parse_meta() {
                if let syn::Meta::NameValue(nv) = meta {
                    // The field is `value`, not `lit`
                    if let syn::Lit::Str(lit) = nv.value {
                        docs.push(lit.value());
                    }
                }
            }
        }
    }

    if docs.is_empty() {
        None
    } else {
        Some(docs.join("\n"))
    }
}

fn parse_mod(item_mod: &ItemMod) -> Option<RustDecl> {
    let (start_line, end_line) = span_start_end(item_mod);

    Some(RustDecl {
        name: item_mod.ident.to_string(),
        type_: "module".to_string(),
        start_line,
        end_line,
        visibility: visibility_to_string(&item_mod.vis),
        is_async: false,
        is_unsafe: false,
        generics: Vec::new(),
        traits: Vec::new(),
        fields: Vec::new(),
        methods: Vec::new(),
        return_type: None,
        parameters: Vec::new(),
        docstring: extract_docstring(&item_mod.attrs),
    })
}

fn visibility_to_string(vis: &Visibility) -> String {
    match vis {
        Visibility::Public(_) => "pub".to_string(),
        Visibility::Restricted(r) => {
            // pub(crate) or pub(in ...)
            format!("pub({})", quote::ToTokens::to_token_stream(r).to_string())
        }
        _ => "private".to_string(),
    }
}

fn type_to_string(ty: &Type) -> String {
    // Simple string representation for common types
    match ty {
        Type::Path(type_path) => type_path
            .path
            .segments
            .last()
            .map(|seg| seg.ident.to_string())
            .unwrap_or_else(|| "unknown".to_string()),
        Type::Reference(type_ref) => {
            // type_ref.elem is Box<Type>
            format!("&{}", type_to_string(&*type_ref.elem))
        }
        _ => quote::ToTokens::to_token_stream(ty).to_string(),
    }
}

fn path_to_string(path: &syn::Path) -> String {
    path.segments
        .iter()
        .map(|seg| seg.ident.to_string())
        .collect::<Vec<_>>()
        .join("::")
}

fn generic_param_to_string(param: &syn::GenericParam) -> String {
    match param {
        syn::GenericParam::Type(ty) => ty.ident.to_string(),
        syn::GenericParam::Lifetime(lf) => lf.lifetime.to_string(),
        syn::GenericParam::Const(cnst) => cnst.ident.to_string(),
    }
}

fn use_tree_to_string(tree: &syn::UseTree) -> String {
    match tree {
        syn::UseTree::Path(p) => {
            let base = p.ident.to_string();
            let nested = use_tree_to_string(&*p.tree);
            format!("{}::{}", base, nested)
        }
        syn::UseTree::Name(n) => n.ident.to_string(),
        syn::UseTree::Rename(r) => r.ident.to_string(),
        syn::UseTree::Glob(_) => "*".to_string(),
        syn::UseTree::Group(g) => {
            let items: Vec<String> = g.items.iter().map(use_tree_to_string).collect();
            items.join("::")
        }
    }
}
