//! Component Analysis
//!
//! This module analyzes page components and generates comprehensive
//! documentation about their structure, props, dependencies, and usage
//! patterns.

use std::collections::HashSet;
use std::fs;
use std::path::Path;

use regex::Regex;

use crate::route_analysis::{ComponentInfo, ComponentProp, RouteDocumentation};

/// Generate component documentation from the pages crate
pub fn generate_component_documentation(
    pages_crate_path: &str,
    config_path: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    let mut documentation = RouteDocumentation::new();

    // Analyze components in the pages crate
    let src_dir = Path::new(pages_crate_path).join("src");
    if src_dir.exists() {
        let components = analyze_components_directory(&src_dir)?;
        documentation.components.extend(components);
    }

    // Save documentation files
    documentation.save_to_toml(config_path)?;
    documentation.generate_markdown(config_path)?;

    Ok(())
}

/// Analyze all components in the pages directory
fn analyze_components_directory(
    src_dir: &Path,
) -> Result<Vec<ComponentInfo>, Box<dyn std::error::Error>> {
    let mut components = Vec::new();

    // Walk through all Rust files in the pages crate
    for entry in walkdir::WalkDir::new(src_dir) {
        let entry = entry?;
        let path = entry.path();

        if path.is_file() && path.extension().is_some_and(|ext| ext == "rs") {
            let file_components = analyze_component_file(path, src_dir)?;
            components.extend(file_components);
        }
    }

    Ok(components)
}

/// Analyze a single component file
pub fn analyze_component_file(
    file_path: &Path,
    src_base: &Path,
) -> Result<Vec<ComponentInfo>, Box<dyn std::error::Error>> {
    let content = fs::read_to_string(file_path)?;
    let mut components = Vec::new();

    // Split content into lines for line number calculation
    let lines: Vec<&str> = content.lines().collect();

    // Get relative path for documentation
    let relative_path = file_path
        .strip_prefix(src_base)
        .unwrap_or(file_path)
        .to_string_lossy()
        .replace('\\', "/");

    // Find component functions (Leptos components)
    let component_regex =
        Regex::new(r"#\[component\]\s*pub\s+fn\s+(\w+)\s*\([^)]*\)\s*->\s*impl\s+IntoView")?;

    for cap in component_regex.captures_iter(&content) {
        if let Some(component_name) = cap.get(1) {
            // Find line number for this match
            let line_number = content[..cap.get(0).unwrap().start()].matches('\n').count() + 1;

            let component_info = analyze_single_component(
                component_name.as_str(),
                &content,
                &relative_path,
                file_path,
                line_number,
                &lines,
            )?;
            components.push(component_info);
        }
    }

    // Also look for regular functions that return views (non-#[component]
    // components)
    let view_function_regex =
        Regex::new(r"pub\s+fn\s+(\w+(?:Page|Component|View))\s*\([^)]*\)\s*->\s*impl\s+IntoView")?;

    for cap in view_function_regex.captures_iter(&content) {
        if let Some(function_name) = cap.get(1) {
            let name = function_name.as_str();
            // Skip if we already found this as a #[component]
            if !components.iter().any(|c| c.name == name) {
                // Find line number for this match
                let line_number = content[..cap.get(0).unwrap().start()].matches('\n').count() + 1;

                let component_info = analyze_single_component(
                    name,
                    &content,
                    &relative_path,
                    file_path,
                    line_number,
                    &lines,
                )?;
                components.push(component_info);
            }
        }
    }

    Ok(components)
}

/// Analyze a single component within a file
fn analyze_single_component(
    component_name: &str,
    file_content: &str,
    relative_path: &str,
    file_path: &Path,
    line_number: usize,
    _lines: &[&str],
) -> Result<ComponentInfo, Box<dyn std::error::Error>> {
    let module_path = relative_path.trim_end_matches(".rs").replace('/', "::");

    let component_type = crate::route_analysis::determine_component_type(relative_path);

    // Extract props from function signature
    let props = extract_component_props(component_name, file_content)?;

    // Find dependencies (other components used)
    let dependencies = find_component_dependencies(component_name, file_content);

    // Extract documentation comment
    let description = extract_component_description(component_name, file_content);

    // Generate usage example
    let usage_example = generate_usage_example(component_name, &props);

    // Create source reference
    let src_ref = Some(crate::route_analysis::SourceReference {
        path: crate::route_analysis::make_relative_path(file_path),
        line: line_number,
        context: "component function".to_string(),
    });

    Ok(ComponentInfo {
        name: component_name.to_string(),
        file_path: relative_path.to_string(),
        module_path,
        component_type,
        props,
        dependencies,
        usage_example,
        description,
        src_ref,
    })
}

/// Extract component props from function signature
fn extract_component_props(
    component_name: &str,
    content: &str,
) -> Result<Vec<ComponentProp>, Box<dyn std::error::Error>> {
    let mut props = Vec::new();

    // Find the component function definition
    let function_regex = Regex::new(&format!(
        r"(?:#\[component\]\s*)?pub\s+fn\s+{}\s*\(([^)]*)\)",
        regex::escape(component_name)
    ))?;

    if let Some(cap) = function_regex.find_iter(content).next() {
        let function_match = cap.as_str();

        // Extract parameters from the function signature
        if let Some(params_start) = function_match.find('(') {
            if let Some(params_end) = function_match.rfind(')') {
                let params_str = &function_match[params_start + 1..params_end];

                // Parse individual parameters
                let param_regex = Regex::new(r"(\w+):\s*([^,]+)")?;

                for param_cap in param_regex.captures_iter(params_str) {
                    if let (Some(name), Some(type_match)) = (param_cap.get(1), param_cap.get(2)) {
                        let param_name = name.as_str().to_string();
                        let mut param_type = type_match.as_str().trim().to_string();

                        // Determine if optional based on type
                        let optional = param_type.starts_with("Option<")
                            || param_type.contains("= ")
                            || param_type.starts_with("impl Fn")
                            || param_type.starts_with("impl Signal");

                        // Clean up type for display
                        if let Some(default_pos) = param_type.find(" = ") {
                            param_type = param_type[..default_pos].to_string();
                        }

                        props.push(ComponentProp {
                            name: param_name,
                            prop_type: param_type,
                            optional,
                            default_value: None,
                            description: None,
                        });
                    }
                }
            }
        }
    }

    Ok(props)
}

/// Find dependencies (other components used within this component)
fn find_component_dependencies(component_name: &str, content: &str) -> Vec<String> {
    let mut dependencies = HashSet::new();

    // Find the component function body
    if let Some(function_start) = content.find(&format!("fn {}", component_name)) {
        if let Some(body_start) = content[function_start..].find('{') {
            let actual_start = function_start + body_start;

            // Find the matching closing brace (simplified approach)
            if let Some(function_body) = extract_function_body(&content[actual_start..]) {
                // Look for view! macro calls and component usage
                find_leptos_components_in_body(&function_body, &mut dependencies);
            }
        }
    }

    dependencies.into_iter().collect()
}

/// Extract function body between matching braces
fn extract_function_body(content_from_brace: &str) -> Option<String> {
    let mut brace_count = 0;
    let mut end_pos = 0;

    for (i, ch) in content_from_brace.char_indices() {
        match ch {
            '{' => brace_count += 1,
            '}' => {
                brace_count -= 1;
                if brace_count == 0 {
                    end_pos = i;
                    break;
                }
            }
            _ => {}
        }
    }

    if end_pos > 0 {
        Some(content_from_brace[..=end_pos].to_string())
    } else {
        None
    }
}

/// Find Leptos components used in function body
fn find_leptos_components_in_body(body: &str, dependencies: &mut HashSet<String>) {
    // Look for component calls in view! macro
    let component_call_regex = Regex::new(r"<(\w+)").unwrap();

    for cap in component_call_regex.captures_iter(body) {
        if let Some(component) = cap.get(1) {
            let comp_name = component.as_str();

            // Filter out HTML elements (lowercase) and common Leptos constructs
            if comp_name.chars().next().is_some_and(|c| c.is_uppercase())
                && !["Show", "For", "Suspense", "ErrorBoundary", "Transition"].contains(&comp_name)
            {
                dependencies.insert(comp_name.to_string());
            }
        }
    }

    // Also look for direct component calls (outside view! macro)
    let direct_call_regex = Regex::new(r"(\w+)\s*\(").unwrap();

    for cap in direct_call_regex.captures_iter(body) {
        if let Some(function_call) = cap.get(1) {
            let func_name = function_call.as_str();

            // If it looks like a component (UpperCase and ends with common suffixes)
            if func_name.chars().next().is_some_and(|c| c.is_uppercase())
                && (func_name.ends_with("Page")
                    || func_name.ends_with("Component")
                    || func_name.ends_with("View"))
            {
                dependencies.insert(func_name.to_string());
            }
        }
    }
}

/// Extract documentation comment for component
fn extract_component_description(component_name: &str, content: &str) -> Option<String> {
    let lines: Vec<&str> = content.lines().collect();

    for (i, line) in lines.iter().enumerate() {
        if line.contains(&format!("fn {}", component_name)) {
            // Look backwards for doc comments
            let mut doc_lines = Vec::new();
            let mut j = i;

            while j > 0 {
                j -= 1;
                let prev_line = lines[j].trim();

                if prev_line.starts_with("///") {
                    doc_lines.insert(0, prev_line.trim_start_matches("///").trim());
                } else if prev_line.starts_with("#[component]") || prev_line.is_empty() {
                    continue;
                } else {
                    break;
                }
            }

            if !doc_lines.is_empty() {
                return Some(doc_lines.join(" "));
            }
        }
    }

    None
}

/// Generate usage example for component
fn generate_usage_example(component_name: &str, props: &[ComponentProp]) -> Option<String> {
    if props.is_empty() {
        Some(format!("<{} />", component_name))
    } else {
        let mut example = format!("<{}\n", component_name);

        for prop in props {
            let value = match prop.prop_type.as_str() {
                t if t.contains("String") => "\"example\"".to_string(),
                t if t.contains("bool") => "true".to_string(),
                t if t.contains("i32") | t.contains("u32") | t.contains("usize") => {
                    "42".to_string()
                }
                t if t.contains("f64") | t.contains("f32") => "3.14".to_string(),
                t if t.contains("Signal") => "signal".to_string(),
                t if t.contains("Fn") => "|| {}".to_string(),
                _ => "value".to_string(),
            };

            example.push_str(&format!("    {}={}\n", prop.name, value));
        }

        example.push_str("/>");
        Some(example)
    }
}
