//! Client-side App component with proper routing
//!
//! This module provides the main App component for the client that uses
//! the shared routing system and renders actual page components.

use leptos::prelude::*;
use leptos_meta::provide_meta_context;
use rustelo_components_leptos::theme::ThemeProvider;
use rustelo_web::rustelo_core_lib::{
    i18n::{provide_unified_i18n, UnifiedI18n},
    routing::utils::detect_language_from_path,
    state::{AuthState, LanguageProvider},
    PageProvider,
};
use rustelo_pages_leptos::NavLogProvider;
#[cfg(target_arch = "wasm32")]
use std::time::Duration;

// Import the local page provider implementation
use crate::page_provider::WebsitePageProvider;

/// Main App component for client-side that implements proper routing
#[component]
pub fn AppComponent(#[prop(default = String::new())] initial_path: String) -> impl IntoView {
    // Provide meta context for client-side behavior
    provide_meta_context();

    // Setup navigation signals for client-side routing
    let (path, set_path) = signal(initial_path.clone());

    // Auth state — provided as context so child components can read/update it.
    let auth_state = AuthState::new();
    provide_context(auth_state.clone());

    // Login modal visibility — provided so NavMenu auth controls can open it
    let (show_login, set_show_login) = signal(false);
    provide_context(set_show_login);

    // Provide path signals as context — set_path for navigation, path for reading.
    #[cfg(target_arch = "wasm32")]
    provide_context(set_path);
    #[cfg(target_arch = "wasm32")]
    provide_context(path);

    // Provide the RBAC deny-paths signal so all descendants (nav menu, SPA guard)
    // can reactively filter protected routes.  Seeded from compile-time AUTH_PATTERNS
    // so SSR and the first WASM render produce identical output (hydration safe).
    let deny_paths = crate::policy::init_policy_signal();
    provide_context(deny_paths);

    // Toast signal — created explicitly so it can be passed to the WS loop and
    // provided as context for ToastContainer and any component using push_toast.
    let toast_sig = RwSignal::new(Vec::<rustelo_components_leptos::ToastNotification>::new());
    provide_context(toast_sig);

    // Command palette open signal — shared between CommandPalette (renders the
    // overlay) and FloatingPill (search button toggles it).
    let palette_sig = RwSignal::new(false);
    provide_context(rustelo_components_leptos::CommandPaletteOpen(palette_sig));

    // Inject build-time application pages so the palette can navigate to project
    // pages (Provisioning Systems, Vapora, Kogral, etc.) that have no menu entry.
    provide_context(rustelo_components_leptos::CommandPaletteExtraItems(
        crate::get_palette_app_pages(),
    ));

    // Register signal in the WASM thread-local so push_toast_from_js (the
    // browser-console export `rustelo_push_toast(kind, msg)`) can reach it
    // without requiring a reactive context.
    #[cfg(target_arch = "wasm32")]
    rustelo_components_leptos::toast::register_toast_signal(toast_sig);

    // Restore auth state from existing session cookie on WASM startup.
    // Runs once per tab — if a valid session_token cookie exists the server
    // validates it and returns UserInfo so the tab starts already authenticated.
    #[cfg(target_arch = "wasm32")]
    {
        let auth_for_restore = auth_state.clone();
        leptos::task::spawn_local(async move {
            if let Ok(Some(user)) = rustelo_components_leptos::server_get_session_user().await {
                auth_for_restore.set_user(user);
            }
        });
    }

    // Open WebSocket: handles RBAC hot-reload and JWT lifecycle events.
    // auth_state is resolved inside connect_policy_ws via use_context before
    // the first .await so the reactive owner is still valid.
    #[cfg(target_arch = "wasm32")]
    crate::policy::connect_policy_ws(deny_paths, toast_sig, set_path);

    // Initialize meta config preloader early for WASM
    #[cfg(target_arch = "wasm32")]
    rustelo_core_lib::init_meta_preloader();

    // Provide logo configuration from compile-time constants (site/config.ncl)
    // NOTE: provide_context here matches shell.rs SSR provision — same type, same value
    provide_context(crate::theme::load_logo_config());

    // NOTE: Menu context is already provided by shell.rs during SSR
    // Do NOT create a new signal here as it would override the server-provided context
    // during hydration, causing menu items to be lost

    // Use the LanguageProvider and setup effects inside it
    view! {
        <LanguageProvider>
            <NavLogProvider>
                <AppContent
                    initial_path=initial_path
                    path=path
                    set_path=set_path
                    show_login=show_login
                    set_show_login=set_show_login
                />
            </NavLogProvider>
        </LanguageProvider>
    }
}

/// Inner app content that has access to the language context
#[component]
fn AppContent(
    initial_path: String,
    path: ReadSignal<String>,
    set_path: WriteSignal<String>,
    show_login: ReadSignal<bool>,
    set_show_login: WriteSignal<bool>,
) -> impl IntoView {
    // Now we can use the shared language context
    let language_context = rustelo_core_lib::state::use_language();

    // Set initial language from initial path once, before creating effects.
    // Prefer the generated route table (config-driven) over path-prefix heuristics.
    let initial_language = crate::routes::get_language_for_path(&initial_path)
        .map(|s| s.to_string())
        .unwrap_or_else(|| detect_language_from_path(&initial_path));
    language_context.current.set(initial_language.clone());

    // Reactively detect language from current path and update context.
    // Only update when the route table returns an UNAMBIGUOUS language for the path.
    // Paths shared across languages (e.g. "/blog" for both EN and ES) return None —
    // in that case the current language is preserved, preventing spurious language resets.
    Effect::new(move |_| {
        let current_path = path.get();

        if let Some(lang) = crate::routes::get_language_for_path(&current_path) {
            let detected = lang.to_string();
            if language_context.current.get_untracked() != detected {
                #[cfg(target_arch = "wasm32")]
                web_sys::console::log_1(
                    &format!(
                        "🔄 App Path Change: path='{}' → language='{}'",
                        current_path, detected
                    )
                    .into(),
                );
                language_context.current.set(detected);
            }
        }
        // If get_language_for_path returns None → language-neutral path → preserve current language
    });

    // Create and provide unified i18n context that updates with language changes
    let unified_i18n = Memo::new(move |_| {
        let current_lang = language_context.current.get();
        let current_path = path.get();

        #[cfg(target_arch = "wasm32")]
        web_sys::console::log_1(
            &format!(
                "🔄 App i18n Memo: language='{}', path='{}'",
                current_lang, current_path
            )
            .into(),
        );

        UnifiedI18n::new(&current_lang, &current_path)
    });

    // Provide the reactive i18n context
    Effect::new(move |_| {
        let i18n = unified_i18n.get();
        provide_unified_i18n(i18n);
    });

    // Setup page transition effects - signal created outside CFG block to avoid hydration mismatch
    let (is_first_render, set_is_first_render) = signal(true);

    Effect::new(move |_| {
        let current_path = path.get();

        #[cfg(target_arch = "wasm32")]
        web_sys::console::log_1(
            &format!(
                "🎬 App transition Effect: path='{}', first_render={}",
                current_path,
                is_first_render.get()
            )
            .into(),
        );

        // Only apply transitions and dispatch events on client-side
        #[cfg(target_arch = "wasm32")]
        {
            if is_first_render.get() {
                set_is_first_render.set(false);
            } else {
                apply_page_transition();
            }

            // Dispatch custom event to reinitialize components
            set_timeout(
                move || {
                    web_sys::console::log_1(
                        &format!(
                            "⏰ Dispatching reinitializeComponents event after 200ms for path: {}",
                            current_path
                        )
                        .into(),
                    );

                    if let Some(window) = web_sys::window() {
                        if let Ok(event) = web_sys::CustomEvent::new("reinitializeComponents") {
                            let _ = window.dispatch_event(&event);
                        }
                    }
                },
                Duration::from_millis(200),
            );
        }
    });

    // Extract the language signal (RwSignal is Copy)
    let lang_signal = language_context.current;

    // Capture auth state for the client-side route guard below.
    // Provided by AppComponent via provide_context(auth_state).
    let auth_state = use_context::<AuthState>();

    // Register the 401 unauthorized handler once at startup (WASM only).
    // The framework fetch layer calls on_unauthorized() on any HTTP 401 response.
    // This handler owns the reactive context — the framework layer knows nothing about AuthState.
    // When rbac.ncl is hot-reloaded on the server, any subsequent request that returns 401
    // will immediately clear auth state and redirect, making the server the single authority.
    #[cfg(target_arch = "wasm32")]
    {
        let auth_for_401 = auth_state.clone();
        // WriteSignal<String> is Copy — captured directly without Arc.
        rustelo_core_lib::utils::fetch::register_unauthorized_handler(move || {
            // Clear local auth state — server has rejected the session.
            if let Some(ref auth) = auth_for_401 {
                auth.clear_auth();
            }
            // Redirect to login, preserving the current path for post-login return.
            let current_path = rustelo_core_lib::utils::fetch::current_location_path();
            rustelo_core_lib::utils::nav::anchor_navigate(
                set_path,
                &format!("/login?return={}", current_path),
            );
        });
    }

    // Extract the user signal (RwSignal<T> is Copy) so the DynChild closure below
    // captures a Copy type.  Option<AuthState> is Clone-only; capturing it by move
    // inside the {move || …} DynChild makes the outer <div> wrapper FnOnce, which
    // Leptos rejects (it requires Fn).
    let auth_user_signal = auth_state.as_ref().map(|a| a.user);

    // Deny-paths signal provided by AppComponent — updated via WebSocket on rbac.ncl reload.
    // RwSignal<Vec<String>> is Copy so it can be captured by the move closure below.
    let deny_paths_signal = use_context::<RwSignal<Vec<String>>>();

    // Derive privacy policy page path from config-driven route lookup.
    // get_route("en", "privacy") → "/privacy", ("es", "privacy") → "/privacidad"
    // &'static str is Copy — no move into the view! closure → closure stays Fn.
    let cookie_policy_path: &'static str = crate::routes::get_route(
        lang_signal.get_untracked().as_str(),
        "privacy",
    )
    .unwrap_or("/privacy");

    // Use the client routing with actual page components.
    // NavMenu and Footer are rendered statically (no reactive wrapper) so that
    // Leptos emits no DynChild comment-marker nodes, matching the server's shell.rs
    // which also renders them directly.  Language reactivity for labels is handled
    // internally by UnifiedNavMenu/Footer via use_current_language().
    view! {
        <ThemeProvider>
            <div class="min-h-screen ds-bg-page flex flex-col">
                <rustelo_components_leptos::navigation::NavMenu
                    language=lang_signal.get_untracked()
                    _set_path=set_path
                    _path=path
                    set_show_login=Some(set_show_login)
                />
                <main class="max-w-7xl mx-auto py-2 sm:ds-container flex-grow page-content fade-out">
                    {
                        move || {
                            let current_path = path.get();
                            let current_lang = lang_signal.get();

                            #[cfg(target_arch = "wasm32")]
                            web_sys::console::log_1(&format!("🎬 App main content render: path='{}', language='{}' (content re-rendering)", current_path, current_lang).into());

                            // Client-side auth guard — uses the runtime deny-paths signal
                            // so it reacts to rbac.ncl hot-reloads without a WASM rebuild.
                            // SSR is protected by the RBAC middleware; this guard covers
                            // SPA navigation where the server is never consulted.
                            #[cfg(target_arch = "wasm32")]
                            {
                                let deny_paths = deny_paths_signal
                                    .map(|s| s.get())
                                    .unwrap_or_default();
                                if crate::policy::requires_auth_runtime(&current_path, &deny_paths) {
                                    let is_authed = auth_user_signal
                                        .map(|sig| sig.get_untracked().is_some())
                                        .unwrap_or(false);
                                    if !is_authed {
                                        set_path.set(format!("/login?return={}", current_path));
                                        return view! { <div></div> }.into_any();
                                    }
                                }
                            }

                            render_website_page(&current_path, &current_lang)
                        }
                    }
                </main>
                <rustelo_components_leptos::navigation::Footer
                    language=lang_signal.get_untracked()
                    _set_path=set_path
                    _path=path
                />
                // Auth login modal — WASM only, renders nothing in SSR
                <rustelo_components_leptos::LoginModal show=show_login set_show=set_show_login />
                // Cookie consent banner — WASM only, renders nothing in SSR
                <rustelo_components_leptos::CookieBanner policy_page=cookie_policy_path.to_string() detail_mode="page".to_string() />
                <rustelo_components_leptos::ToastContainer />
                // Keyboard command palette — Cmd+K / Ctrl+K, WASM only
                <rustelo_components_leptos::CommandPalette />
                // Scroll-aware floating pill — appears after 80px scroll, WASM only
                <rustelo_components_leptos::FloatingPill />
            </div>
        </ThemeProvider>
    }
}

/// Render website page using the local PageProvider
fn render_website_page(_path: &str, language: &str) -> AnyView {
    let provider = WebsitePageProvider;
    let lang = language.to_string();

    // Resolve component name from generated route table (config-driven, PAP-compliant).
    // Strip "Page" suffix to match the page_provider_impl.rs match keys (e.g. "Home" not "HomePage"),
    // mirroring the server's server_routing.rs which also strips the suffix.
    let component_name = crate::routes::get_component_for_path(_path)
        .unwrap_or("NotFound")
        .trim_end_matches("Page")
        .to_string();

    // Populate props from the route config — content_type drives ContentIndex/PostViewer/etc.
    let mut props = std::collections::HashMap::new();
    if let Some(ct) = crate::routes::get_content_type_for_path(_path) {
        props.insert("content_type".to_string(), ct.to_string());
    }
    // Extract parametric path segments for PostViewer (slug = last, category = second-to-last).
    let segs: Vec<&str> = _path.split('/').filter(|s| !s.is_empty()).collect();
    if segs.len() >= 2 {
        props.insert(
            "slug".to_string(),
            segs.last().copied().unwrap_or("").to_string(),
        );
        props.insert(
            "category".to_string(),
            segs.get(segs.len().saturating_sub(2))
                .copied()
                .unwrap_or("")
                .to_string(),
        );
    }

    match provider.render_component(&component_name, props, &lang) {
        Ok(view) => view,
        Err(_) => {
            // Fallback to NotFound if component rendering fails
            let not_found = "NotFound".to_string();
            match provider.render_component(&not_found, std::collections::HashMap::new(), &lang) {
                Ok(view) => view,
                Err(_) => view! { <div>"Error rendering page"</div> }.into_any(),
            }
        }
    }
}

/// Apply fade transition to page content
#[cfg(target_arch = "wasm32")]
fn apply_page_transition() {
    if let Some(document) = web_sys::window().and_then(|w| w.document()) {
        if let Ok(main_element) = document.query_selector("main.page-content") {
            if let Some(element) = main_element {
                let _ = element.class_list().add_1("fade-out");

                let element_clone = element.clone();
                set_timeout(
                    move || {
                        let _ = element_clone.class_list().remove_1("fade-out");
                        let _ = element_clone.class_list().add_1("fade-in");
                    },
                    std::time::Duration::from_millis(150),
                );

                let element_clone2 = element.clone();
                set_timeout(
                    move || {
                        let _ = element_clone2.class_list().remove_1("fade-in");
                    },
                    std::time::Duration::from_millis(300),
                );
            }
        }
    }
}
