use quote::ToTokens;
use regex::Regex;
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, UseTree, 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>,
    imports: Vec<String>,
    calls: Vec<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();
    let mut imports = Vec::new();

    // Process imports first
    for item in &syntax.items {
        if let Item::Use(item_use) = item {
            let import_paths = extract_use_paths(&item_use.tree);
            imports.extend(import_paths.clone());

            for path in import_paths {
                decls.push(RustDecl {
                    name: path.clone(),
                    type_: "import".to_string(),
                    start_line: get_span_start(&item_use.span()),
                    end_line: get_span_end(&item_use.span()),
                    visibility: visibility_to_string(&item_use.vis),
                    is_async: false,
                    is_unsafe: false,
                    generics: vec![],
                    traits: vec![],
                    fields: vec![],
                    methods: vec![],
                    return_type: None,
                    parameters: vec![path.clone()],
                    docstring: extract_docstring(&item_use.attrs),
                    imports: vec![],
                    calls: vec![],
                });
            }
        }
    }

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

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

// Fixed span handling
fn get_span_start(span: &proc_macro2::Span) -> usize {
    span.start().line
}

fn get_span_end(span: &proc_macro2::Span) -> usize {
    span.end().line
}

// Fixed import extraction
fn extract_use_paths(tree: &UseTree) -> Vec<String> {
    let mut paths = Vec::new();
    extract_use_paths_recursive(tree, "", &mut paths);
    paths
}

fn extract_use_paths_recursive(tree: &UseTree, current_path: &str, paths: &mut Vec<String>) {
    match tree {
        UseTree::Path(path) => {
            let new_path = if current_path.is_empty() {
                path.ident.to_string()
            } else {
                format!("{}::{}", current_path, path.ident)
            };
            extract_use_paths_recursive(&path.tree, &new_path, paths);
        }
        UseTree::Name(name) => {
            let full_path = if current_path.is_empty() {
                name.ident.to_string()
            } else {
                format!("{}::{}", current_path, name.ident)
            };
            paths.push(full_path);
        }
        UseTree::Rename(rename) => {
            let full_path = if current_path.is_empty() {
                rename.ident.to_string()
            } else {
                format!("{}::{}", current_path, rename.ident)
            };
            paths.push(full_path);
        }
        UseTree::Glob(_) => {
            if !current_path.is_empty() {
                paths.push(format!("{}::*", current_path));
            }
        }
        UseTree::Group(group) => {
            for item in &group.items {
                extract_use_paths_recursive(item, current_path, paths);
            }
        }
    }
}

// Fixed function parsing
fn parse_function(item_fn: &ItemFn, file_content: &str) -> Option<RustDecl> {
    let start_line = get_span_start(&item_fn.span());
    let end_line = get_span_end(&item_fn.span());

    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 {
                quote::ToTokens::to_token_stream(&pat_type.pat).to_string()
            };
            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)),
    };

    let calls = extract_function_calls(&item_fn.block, file_content);

    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),
        imports: Vec::new(),
        calls,
    })
}

// Fixed function call extraction
fn extract_function_calls(block: &syn::Block, _file_content: &str) -> Vec<String> {
    let mut calls = Vec::new();
    let content = quote::ToTokens::to_token_stream(block).to_string();

    let re = Regex::new(r"\b([a-zA-Z_][a-zA-Z0-9_]*)\s*\(").unwrap();
    for cap in re.captures_iter(&content) {
        if let Some(name) = cap.get(1) {
            let call = name.as_str().to_string();
            if !is_rust_keyword(&call) && !call.chars().next().map_or(false, |c| c.is_uppercase()) {
                calls.push(call);
            }
        }
    }

    calls.dedup();
    calls
}

fn is_rust_keyword(word: &str) -> bool {
    matches!(
        word,
        "if" | "else"
            | "for"
            | "while"
            | "loop"
            | "match"
            | "return"
            | "break"
            | "continue"
            | "let"
            | "mut"
            | "const"
            | "static"
            | "fn"
            | "struct"
            | "enum"
            | "trait"
            | "impl"
            | "mod"
            | "use"
            | "pub"
            | "crate"
            | "self"
            | "super"
            | "async"
            | "await"
            | "unsafe"
            | "dyn"
            | "move"
            | "where"
            | "in"
            | "type"
            | "as"
            | "ref"
            | "box"
            | "true"
            | "false"
    )
}

// Fixed struct parsing
fn parse_struct(item_struct: &ItemStruct) -> Option<RustDecl> {
    let start_line = get_span_start(&item_struct.span());
    let end_line = get_span_end(&item_struct.span());

    let fields: Vec<String> = item_struct
        .fields
        .iter()
        .filter_map(|field| {
            field.ident.as_ref().map(|ident| {
                let field_name = ident.to_string();
                let field_type = type_to_string(&field.ty);
                format!("{}: {}", field_name, field_type)
            })
        })
        .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),
        imports: Vec::new(),
        calls: Vec::new(),
    })
}

// Fixed enum parsing
fn parse_enum(item_enum: &ItemEnum) -> Option<RustDecl> {
    let start_line = get_span_start(&item_enum.span());
    let end_line = get_span_end(&item_enum.span());

    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),
        imports: Vec::new(),
        calls: Vec::new(),
    })
}

// Fixed trait parsing
fn parse_trait(item_trait: &ItemTrait) -> Option<RustDecl> {
    let start_line = get_span_start(&item_trait.span());
    let end_line = get_span_end(&item_trait.span());

    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),
        imports: Vec::new(),
        calls: Vec::new(),
    })
}

// Fixed impl parsing
fn parse_impl(item_impl: &ItemImpl) -> Option<RustDecl> {
    let start_line = get_span_start(&item_impl.span());
    let end_line = get_span_end(&item_impl.span());

    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();

    let implements = item_impl
        .trait_
        .as_ref()
        .map(|(_, path, _)| path_to_string(path))
        .into_iter()
        .collect::<Vec<_>>();

    let name = if !implements.is_empty() {
        implements[0].clone()
    } else {
        target
    };

    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,
        imports: Vec::new(),
        calls: Vec::new(),
    })
}

// Fixed mod parsing
fn parse_mod(item_mod: &ItemMod) -> Option<RustDecl> {
    let start_line = get_span_start(&item_mod.span());
    let end_line = get_span_end(&item_mod.span());

    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),
        imports: Vec::new(),
        calls: Vec::new(),
    })
}

// Fixed docstring extraction
fn extract_docstring(attrs: &[Attribute]) -> Option<String> {
    let text = attrs
        .iter()
        .map(|a| a.to_token_stream().to_string())
        .collect::<Vec<_>>()
        .join("\n");

    let re = Regex::new(r#"(?m)^\s*(?:///|#\[\s*doc\s*=\s*")(.+?)(?:")?$"#).unwrap();

    let mut doc_lines = Vec::new();
    for line in text.lines() {
        if let Some(caps) = re.captures(line) {
            if let Some(m) = caps.get(1) {
                doc_lines.push(m.as_str().to_string());
            }
        }
    }

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

fn visibility_to_string(vis: &Visibility) -> String {
    match vis {
        Visibility::Public(_) => "pub".to_string(),
        Visibility::Restricted(_) => "pub(restricted)".to_string(),
        _ => "private".to_string(),
    }
}

fn type_to_string(ty: &Type) -> String {
    match ty {
        Type::Path(type_path) => type_path
            .path
            .segments
            .iter()
            .map(|seg| seg.ident.to_string())
            .collect::<Vec<_>>()
            .join("::"),
        Type::Reference(type_ref) => {
            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(),
    }
}
