//! Content type resolution system
//!
//! Provides dynamic content type detection from routes and paths
//! Eliminates hardcoded content type logic throughout the application

use std::collections::HashMap;
use std::sync::LazyLock;

use crate::routing::components::RoutesConfig;

/// Cache for content type mappings to avoid repeated computation
static CONTENT_TYPE_CACHE: LazyLock<std::sync::RwLock<HashMap<String, String>>> =
    LazyLock::new(|| std::sync::RwLock::new(HashMap::new()));

/// Resolve content type from route path and language
pub fn resolve_content_type_from_route(path: &str, language: &str) -> Result<String, String> {
    let cache_key = format!("{}:{}", path, language);

    // Check cache first
    if let Ok(cache) = CONTENT_TYPE_CACHE.read() {
        if let Some(content_type) = cache.get(&cache_key) {
            return Ok(content_type.clone());
        }
    }

    let routes_config = crate::routing::utils::load_routes_config();

    // Find matching route for this path and language
    let matching_route = routes_config.routes.iter().find(|route| {
        route.enabled
            && route.language == language
            && (route.path == path || path_matches_route(&route.path, path))
    });

    if let Some(route) = matching_route {
        if let Some(content_type) = extract_content_type_from_route(route, path) {
            // Cache the result
            if let Ok(mut cache) = CONTENT_TYPE_CACHE.write() {
                cache.insert(cache_key, content_type.clone());
            }
            return Ok(content_type);
        }
    }

    // Fallback: extract from path structure
    extract_content_type_from_path(path)
}

/// Extract content type from route configuration
fn extract_content_type_from_route(
    route: &crate::routing::components::RouteConfigToml,
    _path: &str,
) -> Option<String> {
    // First priority: use the get_content_type method which checks both direct
    // field and props
    if let Some(content_type) = route.get_content_type() {
        return Some(content_type);
    }

    // Second priority: try to extract from component name patterns
    // Look for common patterns like "ContentIndexPage" -> extract from route
    // context
    if route.component == "ContentIndexPage" || route.component == "ContentCategoryPage" {
        // These are generic components - content type must be specified in route config
        #[cfg(not(target_arch = "wasm32"))]
        tracing::warn!(
            "Generic component '{}' used without content_type specification for path '{}'. Please \
             add content_type to route props.",
            route.component,
            route.path
        );
        return None;
    }

    // Third priority: try to map component name to content type for legacy
    // compatibility
    let component_lower = route.component.to_lowercase();
    if component_lower.contains("blog") {
        return Some("blog".to_string());
    } else if component_lower.contains("recipe") {
        return Some("recipes".to_string());
    }

    // No content_type and no recognised content pattern. This is NORMAL for page
    // routes (AboutPage, HomePage, ContentGraphPage, …) which are probed during
    // request resolution but legitimately serve no content. Warn only when the
    // component is a content-serving one (PostViewer / ContentIndex / *Category),
    // where a missing content_type is a real misconfiguration; otherwise debug.
    #[cfg(not(target_arch = "wasm32"))]
    {
        let component = route.component.as_str();
        let is_content_component = component.ends_with("Viewer")
            || component == "ContentIndex"
            || component.contains("Category");
        if is_content_component {
            tracing::warn!(
                "No content type mapping found for component '{}' at path '{}'. Please add \
                 content_type field to route configuration.",
                route.component,
                route.path
            );
        } else {
            tracing::debug!(
                "Route component '{}' (path '{}') has no content_type — treated as a page (not \
                 content).",
                route.component,
                route.path
            );
        }
    }

    None
}

/// Check if a path matches a route pattern (supports {param} placeholders)
fn path_matches_route(route_pattern: &str, actual_path: &str) -> bool {
    // Simple pattern matching for {category}, {slug} etc.
    let route_parts: Vec<&str> = route_pattern.split('/').collect();
    let path_parts: Vec<&str> = actual_path.split('/').collect();

    if route_parts.len() != path_parts.len() {
        return false;
    }

    for (route_part, path_part) in route_parts.iter().zip(path_parts.iter()) {
        if route_part.starts_with('{') && route_part.ends_with('}') {
            // This is a parameter placeholder - it matches any value
            continue;
        } else if route_part != path_part {
            return false;
        }
    }

    true
}

/// Extract content type from path structure
pub fn extract_content_type_from_path(path: &str) -> Result<String, String> {
    let path = path.trim_start_matches('/');
    let path = path.trim_end_matches(".html");

    // Handle language-specific paths
    let path_parts: Vec<&str> = path.split('/').filter(|p| !p.is_empty()).collect();

    if path_parts.is_empty() {
        return Err("Cannot determine content type from empty path".to_string());
    }

    // Look for content type indicators in path
    for part in &path_parts {
        if is_known_content_type(part) {
            return Ok(part.to_string());
        }

        // Handle localized content type names - language-agnostic approach
        // Check if this part contains a content type prefix using dynamic discovery
        let available_content_types = crate::get_all_content_types();
        for content_type in &available_content_types {
            if part.starts_with(&format!("{}-", content_type)) {
                return Ok(content_type.to_string());
            }
        }

        // For other localized paths, check if this matches any known route paths
        // This is more dynamic and doesn't hardcode content type mappings
        let routes_config = crate::routing::utils::load_routes_config();
        for route in &routes_config.routes {
            if route.enabled && route.path.contains(part) {
                if let Some(content_type) = &route.content_type {
                    return Ok(content_type.clone());
                }
            }
        }
    }

    // If no clear content type found, use first path segment
    if let Some(first_part) = path_parts.first() {
        // Skip language codes using dynamic language discovery
        let discovered_languages = crate::i18n::discovery::discover_available_languages();

        // Check if the first part is a language code
        if discovered_languages.contains(&first_part.to_string()) {
            if let Some(second_part) = path_parts.get(1) {
                return Ok(second_part.to_string());
            }
        } else {
            return Ok(first_part.to_string());
        }
    }

    Err(format!(
        "Could not determine content type from path: {}",
        path
    ))
}

/// Check if a string is a known content type
fn is_known_content_type(name: &str) -> bool {
    let available_types = get_available_content_types();
    available_types.contains(&name.to_string())
}

/// Get all available content types by scanning content directory
pub fn get_available_content_types() -> Vec<String> {
    #[cfg(not(target_arch = "wasm32"))]
    {
        use std::fs;

        // Use unified path resolution system for content directory
        let content_path = crate::path_resolution::get_content_path();

        match fs::read_dir(content_path) {
            Ok(entries) => {
                let mut content_types = Vec::new();

                for entry in entries.filter_map(|e| e.ok()) {
                    if let Ok(file_type) = entry.file_type() {
                        if file_type.is_dir() {
                            if let Some(name) = entry.file_name().to_str() {
                                // Only include directories that match known content types from
                                // generated registry
                                if crate::get_all_content_types().contains(&name.to_string()) {
                                    content_types.push(name.to_string());
                                }
                            }
                        }
                    }
                }

                content_types.sort();
                content_types
            }
            Err(e) => {
                panic!(
                    "Cannot access content directory: {}. Ensure the content directory exists and \
                     is accessible.",
                    e
                );
            }
        }
    }

    #[cfg(target_arch = "wasm32")]
    {
        // For WASM, return content types from generated registry
        crate::get_all_content_types()
            .into_iter()
            .map(|s| s.to_string())
            .collect()
    }
}

/// Validate if a content type is available
pub fn is_valid_content_type(content_type: &str) -> bool {
    let available_types = get_available_content_types();
    available_types.contains(&content_type.to_string())
}

/// Clear the content type cache (useful for development)
pub fn clear_content_type_cache() {
    if let Ok(mut cache) = CONTENT_TYPE_CACHE.write() {
        cache.clear();
    }
}

/// Get content index data for filtering categories/tags
/// This function loads the index.json data for a content type and language
pub fn get_content_index(content_type: &str, language: &str) -> Result<String, String> {
    #[cfg(not(target_arch = "wasm32"))]
    {
        use std::fs;

        // Use unified path resolution system for content directory
        let content_path = crate::path_resolution::get_content_path();

        let index_file = format!("{}/{}/{}/index.json", content_path, content_type, language);

        tracing::debug!("Loading content index from: {}", index_file);

        match fs::read_to_string(&index_file) {
            Ok(content) => Ok(content),
            Err(e) => Err(format!("Failed to read index file {}: {}", index_file, e)),
        }
    }

    #[cfg(target_arch = "wasm32")]
    {
        // For WASM, try to get content from browser window if available
        // This will use data that was passed from the server during hydration
        use wasm_bindgen::prelude::*;
        use web_sys::window;

        // Try to get the content index from window.__CONTENT_INDEX
        if let Some(win) = window() {
            let global_key = format!(
                "__CONTENT_INDEX_{}__{}",
                content_type.to_uppercase(),
                language.to_uppercase()
            );

            // Use js_sys to safely access window properties
            let js_value = js_sys::Reflect::get(&win, &JsValue::from_str(&global_key))
                .unwrap_or(JsValue::undefined());

            if !js_value.is_undefined() && !js_value.is_null() {
                if let Some(content_str) = js_value.as_string() {
                    tracing::debug!("WASM: Found content index in window.{}", global_key);
                    return Ok(content_str);
                }
            }
        }

        // If no content index is available, return empty JSON to prevent showing all
        // categories
        tracing::debug!(
            "WASM: No content index found, returning empty content to prevent showing empty \
             categories"
        );
        Ok(format!(r#"{{"{}": []}}"#, content_type))
    }
}

/// Get content type display name for UI
pub fn get_content_type_display_name(content_type: &str) -> String {
    // Generic transformation: capitalize first letter, keep rest as-is
    // This works for any content type without hardcoded assumptions
    if content_type.is_empty() {
        return "Content".to_string();
    }

    let mut chars = content_type.chars();
    if let Some(first_char) = chars.next() {
        first_char.to_uppercase().collect::<String>() + &chars.collect::<String>()
    } else {
        "Content".to_string()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_extract_content_type_from_path() {
        // Skip test if content directory is not accessible (e.g., in minimal test
        // environments)
        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
            let available_types = crate::get_all_content_types();

            if available_types.is_empty() {
                // Skip test if no content types are configured
                return;
            }

            // Test basic paths with available content types
            for content_type in &available_types {
                let path = format!("/{}", content_type);
                assert_eq!(
                    extract_content_type_from_path(&path),
                    Ok(content_type.to_string())
                );
            }

            // Test localized content type names using first available type
            let first_content_type = &available_types[0];
            let discovered_languages = crate::i18n::discovery::discover_available_languages();

            if let Some(lang) = discovered_languages.first() {
                let path_with_suffix = format!("/{}-{}/category", first_content_type, lang);
                assert_eq!(
                    extract_content_type_from_path(&path_with_suffix),
                    Ok(first_content_type.to_string())
                );

                // Test language-prefixed paths
                let path_with_lang = format!("/{}/{}", lang, first_content_type);
                assert_eq!(
                    extract_content_type_from_path(&path_with_lang),
                    Ok(first_content_type.to_string())
                );
            }
        }));

        if result.is_err() {
            println!("Skipping test - content directory not accessible");
        }
    }

    #[test]
    fn test_path_matches_route() {
        // Skip test if content directory is not accessible (e.g., in minimal test
        // environments)
        let available_types = match std::panic::catch_unwind(get_available_content_types) {
            Ok(types) => types,
            Err(_) => {
                println!("Skipping test - content directory not accessible");
                return;
            }
        };

        if available_types.is_empty() {
            return;
        }

        let first_type = &available_types[0];
        let second_type = available_types.get(1).unwrap_or(first_type);

        // Test parametric route matching with dynamic content types
        assert!(path_matches_route(
            &format!("/{}/*{{category}}", first_type),
            &format!("/{}/rust", first_type)
        ));
        assert!(!path_matches_route(
            &format!("/{}/*{{category}}", first_type),
            &format!("/{}/docker", second_type)
        ));
        assert!(path_matches_route(
            "/post/{type}/{slug}",
            &format!("/post/{}/my-post", first_type)
        ));
    }

    #[test]
    fn test_is_known_content_type() {
        // Skip test if content directory is not accessible (e.g., in minimal test
        // environments)
        let available_types = match std::panic::catch_unwind(get_available_content_types) {
            Ok(types) => types,
            Err(_) => {
                println!("Skipping test - content directory not accessible");
                return;
            }
        };

        // Test with dynamically discovered content types
        for content_type in &available_types {
            assert!(is_known_content_type(content_type));
        }

        // Test unknown content type
        assert!(!is_known_content_type("definitely-unknown-content-type"));
    }
}
