//! Client-side routing implementation using trait-based dependency injection
//!
//! This module implements client-side rendering using the pure trait
//! abstractions from rustelo-*-traits crates. This eliminates code generation
//! and provides runtime flexibility while maintaining route agnosticism.

use std::collections::HashMap;
use std::sync::OnceLock;

use leptos::prelude::*;
use rustelo_core_lib::routing::engine::resolver::resolve_unified_route;
#[allow(unused_imports)]
use rustelo_core_types::{
    AppContext, DefaultPageProvider, LanguageDetector, PageProvider, RouteHandler,
    RouteMetadataProvider, RouteParameters, RouteRenderer, RouteResolver, RouteResult,
    RoutesConfig, RoutingResult,
};

/// Load routes configuration from embedded TOML
/// This function loads the routes configuration that was generated at build
/// time
fn load_routes_config() -> &'static RoutesConfig {
    static ROUTES_CACHE: OnceLock<RoutesConfig> = OnceLock::new();

    ROUTES_CACHE.get_or_init(|| {
        // Try to load the embedded routes from build output
        #[cfg(not(debug_assertions))]
        {
            // In release builds, use embedded routes if available
            if let Ok(routes_toml) = load_embedded_routes_if_available() {
                if let Ok(config) = toml::from_str::<RoutesConfig>(&routes_toml) {
                    return config;
                }
            }
        }

        // Fallback to empty routes configuration
        // This is safer than panicking and allows graceful degradation
        RoutesConfig::new()
    })
}

/// Attempt to load embedded routes from the build output
/// Returns empty config if not available (graceful degradation)
#[allow(dead_code)]
fn load_embedded_routes_if_available() -> Result<String, &'static str> {
    // The routes TOML is generated by the website-server build.rs during
    // compilation and placed in the out/generated/ directory. Since we can't
    // use include_str! across crates with dynamic paths, we provide an empty
    // fallback that allows graceful routing through the routing engine. The
    // server-side routing has already resolved the correct component, so the
    // client just needs to pass through the same component selection.
    Err("Routes will be resolved via routing engine with existing AppContext configuration")
}

/// Client-side route renderer using trait-based dependency injection
pub struct ClientRouteRenderer<P: PageProvider = DefaultPageProvider> {
    #[allow(dead_code)]
    app_context: AppContext,
    page_provider: P,
}

impl ClientRouteRenderer<DefaultPageProvider> {
    pub fn new() -> Self {
        Self {
            app_context: AppContext::new(),
            page_provider: DefaultPageProvider::new(),
        }
    }

    pub fn with_context(app_context: AppContext) -> Self {
        Self {
            page_provider: DefaultPageProvider::new(),
            app_context,
        }
    }
}

impl<P: PageProvider> ClientRouteRenderer<P> {
    pub fn with_provider(provider: P) -> Self {
        Self {
            app_context: AppContext::new(),
            page_provider: provider,
        }
    }

    pub fn with_provider_and_context(provider: P, app_context: AppContext) -> Self {
        Self {
            app_context,
            page_provider: provider,
        }
    }
}

impl Default for ClientRouteRenderer<DefaultPageProvider> {
    fn default() -> Self {
        Self::new()
    }
}

// NOTE: No specific DefaultPageProvider implementation needed.
// DefaultPageProvider uses View = String, so it won't match the generic AnyView
// implementations. This avoids trait coherence conflicts while allowing the PAP
// system to work correctly.

// Generic implementation for PageProviders with AnyView (like
// WebsitePageProvider) Note: This excludes DefaultPageProvider which has View =
// String Using explicit type constraint to prevent overlap with
// DefaultPageProvider implementation
impl<P> RouteRenderer for ClientRouteRenderer<P>
where
    P: PageProvider<
        Component = String,
        View = AnyView,
        Props = std::collections::HashMap<String, String>,
    >,
    P: 'static,
    // NOTE: This implementation is for types where P::View = AnyView, which excludes
    // DefaultPageProvider
{
    type View = AnyView;
    type Component = String;
    type Parameters = RouteParameters;

    fn render_component(
        &self,
        component: &Self::Component,
        path: &str,
        language: &str,
        parameters: &Self::Parameters,
    ) -> RoutingResult<Self::View> {
        // Create props from parameters and context
        let mut props = parameters.clone().into_hashmap();
        props.insert("path".to_string(), path.to_string());
        props.insert("language".to_string(), language.to_string());

        // Use the page provider to render the component
        match self
            .page_provider
            .render_component(component, props, language)
        {
            Ok(rendered_view) => {
                // For AnyView, we can return it directly
                Ok(rendered_view)
            }
            Err(_) => {
                // Fallback to not found
                Ok(self.render_not_found(path, language)?)
            }
        }
    }

    fn render_not_found(&self, _path: &str, language: &str) -> RoutingResult<Self::View> {
        use rustelo_pages_leptos::NotFoundPage;
        let lang = language.to_string();
        let view = view! { <NotFoundPage _language=lang /> }.into_any();
        Ok(view)
    }
}

// Generic RouteMetadataProvider for AnyView PageProviders (excludes
// DefaultPageProvider)
impl<P> RouteMetadataProvider for ClientRouteRenderer<P>
where
    P: PageProvider<
        Component = String,
        View = AnyView,
        Props = std::collections::HashMap<String, String>,
    >,
    P: 'static,
{
    type Component = String;

    fn get_title(&self, _component: &Self::Component, _language: &str) -> RoutingResult<String> {
        Ok("Default Title".to_string())
    }

    fn get_description(
        &self,
        _component: &Self::Component,
        _language: &str,
    ) -> RoutingResult<String> {
        Ok("Default Description".to_string())
    }

    fn get_keywords(&self, _component: &Self::Component, _language: &str) -> RoutingResult<String> {
        Ok("Default Keywords".to_string())
    }
}

// Generic RouteHandler for AnyView PageProviders (excludes DefaultPageProvider)
impl<P> RouteHandler for ClientRouteRenderer<P>
where
    P: PageProvider<
        Component = String,
        View = AnyView,
        Props = std::collections::HashMap<String, String>,
    >,
    P: 'static,
{
    type View = AnyView;
    type Component = String;
    type Parameters = RouteParameters;

    fn handle_route(
        &self,
        component: Option<&Self::Component>,
        path: &str,
        language: &str,
        parameters: &Self::Parameters,
    ) -> RoutingResult<RouteResult<Self::View>> {
        let (view, is_not_found) = match component {
            Some(comp) => match self.render_component(comp, path, language, parameters) {
                Ok(v) => (v, false),
                Err(_) => (self.render_not_found(path, language)?, true),
            },
            None => (self.render_not_found(path, language)?, true),
        };

        let title = component
            .map(|c| {
                self.get_title(c, language)
                    .unwrap_or_else(|_| "Rustelo".to_string())
            })
            .unwrap_or_else(|| "Page Not Found".to_string());

        let description = component
            .map(|c| {
                self.get_description(c, language)
                    .unwrap_or_else(|_| "Rustelo Application".to_string())
            })
            .unwrap_or_else(|| "The requested page was not found".to_string());

        let keywords = component
            .map(|c| {
                self.get_keywords(c, language)
                    .unwrap_or_else(|_| "rustelo".to_string())
            })
            .unwrap_or_else(|| "rustelo, not found".to_string());

        Ok(RouteResult {
            view,
            title,
            description,
            keywords,
            language: language.to_string(),
            canonical_path: path.to_string(),
            is_not_found,
            metadata: HashMap::new(),
        })
    }
}

/// Render page content using trait-based routing system with default provider
/// NOTE: This function uses DefaultPageProvider which generates View = String,
/// not AnyView. For PAP-compliant rendering with Leptos components, use
/// render_page_content_with_provider instead.
pub fn render_page_content(_path: &str) -> AnyView {
    // For PAP systems, this fallback just shows a simple message
    // The actual implementation should use render_page_content_with_provider
    view! {
        <div class="ds-bg-page">
            <section class="relative py-ds-4 ds-container ds-rounded-lg ds-shadow-lg">
                <div class="mx-auto max-w-4xl text-center">
                    <h1 class="text-balance text-4xl font-bold tracking-tight ds-text sm:text-6xl mb-ds-4">
                        "Default Provider Not Available"
                    </h1>
                    <p class="text-lg leading-8 ds-text-muted">
                        "Use render_page_content_with_provider with a custom PageProvider instead."
                    </p>
                </div>
            </section>
        </div>
    }.into_any()
}

/// Render page content with custom provider using trait-based routing
pub fn render_page_content_with_provider<P>(path: &str, language: &str, provider: P) -> AnyView
where
    P: PageProvider<
        Component = String,
        View = AnyView,
        Props = std::collections::HashMap<String, String>,
    >,
    P: 'static,
    ClientRouteRenderer<P>:
        RouteHandler<View = AnyView, Component = String, Parameters = RouteParameters>,
    ClientRouteRenderer<P>:
        RouteRenderer<View = AnyView, Component = String, Parameters = RouteParameters>,
{
    let renderer = ClientRouteRenderer::with_provider(provider);
    let app_context = AppContext::new();

    #[cfg(target_arch = "wasm32")]
    web_sys::console::log_1(
        &format!(
            "🔍 CLIENT ROUTING (Custom Provider): Attempting to render path='{}' with \
             language='{}'",
            path, language
        )
        .into(),
    );

    // Use trait-based language detection
    #[allow(unused_variables)] // detected_lang only used in wasm32 target
    let (detected_lang, clean_path) = app_context.language_detector.extract_language(path);

    #[cfg(target_arch = "wasm32")]
    {
        if detected_lang != language {
            web_sys::console::log_1(
                &format!(
                    "⚠️ ROUTING: Language mismatch - path '{}' detected as '{}' but requested \
                     '{}'. Using requested language.",
                    path, detected_lang, language
                )
                .into(),
            );
        }
    }

    // Load generated routes configuration and use the proper routing engine
    let routes_config = load_routes_config();
    let resolution = resolve_unified_route(routes_config, path);

    // Convert RouteComponent to String for the renderer
    let component_name = resolution
        .component
        .as_ref()
        .map(|c| c.as_str().to_string())
        .or_else(|| {
            if resolution.exists {
                Some("NotFound".to_string())
            } else {
                None
            }
        });

    #[cfg(target_arch = "wasm32")]
    web_sys::console::log_1(
        &format!(
            "🔍 CLIENT ROUTING: Unified route resolution - component: {:?}, path: {}, exists: {}",
            component_name, resolution.path, resolution.exists
        )
        .into(),
    );

    // Convert HashMap parameters to RouteParameters (RouteParameters wraps HashMap)
    let mut route_params = RouteParameters::new();
    for (key, value) in resolution.parameters.into_iter() {
        route_params.insert(key, value);
    }

    // Handle route with requested language override
    match renderer.handle_route(
        component_name.as_ref(),
        &resolution.path,
        language, // Use requested language
        &route_params,
    ) {
        Ok(route_result) => {
            #[cfg(target_arch = "wasm32")]
            if route_result.is_not_found {
                web_sys::console::log_1(
                    &format!("⚠️ CLIENT ROUTING: Rendered not found page").into(),
                );
            } else {
                web_sys::console::log_1(
                    &format!(
                        "✅ CLIENT ROUTING: Successfully rendered component for path '{}'",
                        resolution.path
                    )
                    .into(),
                );
            }

            route_result.view
        }
        Err(_) => {
            // Fallback to not found - create directly since render_not_found may not be
            // available
            use rustelo_pages_leptos::NotFoundPage;
            let lang = language.to_string();
            view! { <NotFoundPage _language=lang /> }.into_any()
        }
    }
}

/// Render page content with explicit language using trait-based routing
/// (default provider) NOTE: This function uses DefaultPageProvider which
/// generates View = String, not AnyView. For PAP-compliant rendering with
/// Leptos components, use render_page_content_with_provider instead.
pub fn render_page_content_with_language(_path: &str, language: &str) -> AnyView {
    // For PAP systems, this fallback just shows a simple message with the requested
    // language The actual implementation should use
    // render_page_content_with_provider
    view! {
        <div class="ds-bg-page">
            <section class="relative py-ds-4 ds-container ds-rounded-lg ds-shadow-lg">
                <div class="mx-auto max-w-4xl text-center">
                    <h1 class="text-balance text-4xl font-bold tracking-tight ds-text sm:text-6xl mb-ds-4">
                        {format!("Default Provider Not Available ({})", language)}
                    </h1>
                    <p class="text-lg leading-8 ds-text-muted">
                        "Use render_page_content_with_provider with a custom PageProvider instead."
                    </p>
                </div>
            </section>
        </div>
    }.into_any()
}

/// Check if a path looks like it should be a valid route using trait-based
/// configuration This helps prevent showing NotFound during hydration for
/// potentially valid routes
#[cfg(target_arch = "wasm32")]
#[allow(dead_code)]
fn looks_like_valid_route(path: &str) -> bool {
    let app_context = AppContext::new();

    // Try to get available routes for default language
    let default_lang = app_context.language_detector.default_language();

    match app_context
        .route_resolver
        .get_routes_for_language(default_lang)
    {
        Ok(routes) => {
            // Check against actual configured routes
            for route_path in &routes {
                // Check if path matches the route pattern (simple prefix match for now)
                if path.starts_with(route_path) {
                    return true;
                }

                // Check for parametric route patterns (e.g., "/content/{slug}" matches
                // "/content/my-post")
                if route_path.contains('{') {
                    let pattern_parts: Vec<&str> = route_path.split('/').collect();
                    let path_parts: Vec<&str> = path.split('/').collect();

                    if pattern_parts.len() == path_parts.len() {
                        let matches = pattern_parts.iter().zip(path_parts.iter()).all(
                            |(pattern, path_part)| {
                                pattern.starts_with('{') && pattern.ends_with('}')
                                    || pattern == path_part
                            },
                        );
                        if matches {
                            return true;
                        }
                    }
                }
            }

            // If no routes matched but we have routes configured, do fallback check
            !routes.is_empty() && path.split('/').filter(|s| !s.is_empty()).count() >= 2
        }
        Err(_) => {
            // If we can't get routes, do a basic heuristic
            path.split('/').filter(|s| !s.is_empty()).count() >= 2
        }
    }
}
