//! Realm landing page after login.

use std::collections::HashMap;

use leptos::prelude::*;
use leptos::reactive::owner::LocalStorage;
#[cfg(feature = "hydrate")]
use leptos::task::spawn_local;
#[cfg(feature = "hydrate")]
use leptos_router::hooks::use_navigate;
use leptos_router::hooks::use_params_map;
use uuid::Uuid;

use crate::components::{
    ActiveBubble, Card, ChatInput, ChatMessage, InventoryPopup, MessageLog, RealmHeader,
    RealmSceneViewer, DEFAULT_BUBBLE_TIMEOUT_MS,
};
#[cfg(feature = "hydrate")]
use crate::components::use_channel_websocket;
use chattyness_db::models::{
    AvatarWithPaths, ChannelMemberWithAvatar, EmotionAvailability, EmotionState, LooseProp,
    RealmRole, RealmWithUserRole, Scene,
};
#[cfg(feature = "hydrate")]
use chattyness_db::ws_messages::ClientMessage;

use crate::components::ws_client::WsSender;

/// Realm landing page component.
#[component]
pub fn RealmPage() -> impl IntoView {
    let params = use_params_map();
    #[cfg(feature = "hydrate")]
    let navigate = use_navigate();

    let slug = Signal::derive(move || params.read().get("slug").unwrap_or_default());

    // Channel member state
    let (members, set_members) = signal(Vec::<ChannelMemberWithAvatar>::new());
    let (channel_id, set_channel_id) = signal(Option::<uuid::Uuid>::None);

    // Chat focus coordination
    let (chat_focused, set_chat_focused) = signal(false);
    let (focus_chat_trigger, set_focus_chat_trigger) = signal(false);

    // Emotion availability for emote picker
    let (emotion_availability, set_emotion_availability) =
        signal(Option::<EmotionAvailability>::None);
    // Skin preview path for emote picker (position 4 of skin layer)
    let (skin_preview_path, set_skin_preview_path) = signal(Option::<String>::None);

    // Chat message state - use StoredValue for WASM compatibility (single-threaded)
    let message_log: StoredValue<MessageLog, LocalStorage> =
        StoredValue::new_local(MessageLog::new());
    let (active_bubbles, set_active_bubbles) =
        signal(HashMap::<(Option<Uuid>, Option<Uuid>), ActiveBubble>::new());

    // Inventory popup state
    let (inventory_open, set_inventory_open) = signal(false);

    // Loose props state
    let (loose_props, set_loose_props) = signal(Vec::<LooseProp>::new());

    let realm_data = LocalResource::new(move || {
        let slug = slug.get();
        async move {
            if slug.is_empty() {
                return None;
            }

            #[cfg(feature = "hydrate")]
            {
                use gloo_net::http::Request;
                let response = Request::get(&format!("/api/realms/{}", slug)).send().await;
                match response {
                    Ok(resp) if resp.ok() => resp.json::<RealmWithUserRole>().await.ok(),
                    _ => None,
                }
            }
            #[cfg(not(feature = "hydrate"))]
            {
                let _ = slug;
                None::<RealmWithUserRole>
            }
        }
    });

    // Fetch entry scene for the realm
    let entry_scene = LocalResource::new(move || {
        let slug = slug.get();
        async move {
            if slug.is_empty() {
                return None;
            }

            #[cfg(feature = "hydrate")]
            {
                use gloo_net::http::Request;
                let response = Request::get(&format!("/api/realms/{}/entry-scene", slug))
                    .send()
                    .await;
                match response {
                    Ok(resp) if resp.ok() => resp.json::<Scene>().await.ok(),
                    _ => None,
                }
            }
            #[cfg(not(feature = "hydrate"))]
            {
                let _ = slug;
                None::<Scene>
            }
        }
    });

    // Fetch full avatar with paths for client-side emotion computation
    #[cfg(feature = "hydrate")]
    {
        let slug_for_avatar = slug.clone();
        Effect::new(move |_| {
            use gloo_net::http::Request;

            let current_slug = slug_for_avatar.get();
            if current_slug.is_empty() {
                return;
            }

            // Fetch full avatar with all paths resolved
            spawn_local(async move {
                let response = Request::get(&format!("/api/realms/{}/avatar", current_slug))
                    .send()
                    .await;
                if let Ok(resp) = response {
                    if resp.ok() {
                        if let Ok(avatar) = resp.json::<AvatarWithPaths>().await {
                            // Compute emotion availability client-side
                            let avail = avatar.compute_emotion_availability();
                            set_emotion_availability.set(Some(avail));
                            // Get skin layer position 4 (center) for preview
                            set_skin_preview_path.set(avatar.skin_layer[4].clone());
                        }
                    }
                }
            });
        });
    }

    // WebSocket connection for real-time updates
    #[cfg(feature = "hydrate")]
    let on_members_update = Callback::new(move |new_members: Vec<ChannelMemberWithAvatar>| {
        set_members.set(new_members);
    });

    // Chat message callback
    #[cfg(feature = "hydrate")]
    let on_chat_message = Callback::new(move |msg: ChatMessage| {
        // Add to message log
        message_log.update_value(|log| log.push(msg.clone()));

        // Update active bubbles
        let key = (msg.user_id, msg.guest_session_id);
        let expires_at = msg.timestamp + DEFAULT_BUBBLE_TIMEOUT_MS;

        set_active_bubbles.update(|bubbles| {
            bubbles.insert(
                key,
                ActiveBubble {
                    message: msg,
                    expires_at,
                },
            );
        });
    });

    // Loose props callbacks
    #[cfg(feature = "hydrate")]
    let on_loose_props_sync = Callback::new(move |props: Vec<LooseProp>| {
        set_loose_props.set(props);
    });

    #[cfg(feature = "hydrate")]
    let on_prop_dropped = Callback::new(move |prop: LooseProp| {
        set_loose_props.update(|props| {
            props.push(prop);
        });
    });

    #[cfg(feature = "hydrate")]
    let on_prop_picked_up = Callback::new(move |prop_id: Uuid| {
        set_loose_props.update(|props| {
            props.retain(|p| p.id != prop_id);
        });
    });

    #[cfg(feature = "hydrate")]
    let (_ws_state, ws_sender) = use_channel_websocket(
        slug,
        Signal::derive(move || channel_id.get()),
        on_members_update,
        on_chat_message,
        on_loose_props_sync,
        on_prop_dropped,
        on_prop_picked_up,
    );

    // Set channel ID when scene loads (triggers WebSocket connection)
    // Note: Currently using scene.id as the channel_id since channel_members
    // uses scenes directly. Proper channel infrastructure can be added later.
    #[cfg(feature = "hydrate")]
    {
        Effect::new(move |_| {
            let Some(scene) = entry_scene.get().flatten() else {
                return;
            };
            set_channel_id.set(Some(scene.id));
        });
    }

    // Cleanup expired speech bubbles every 5 seconds
    #[cfg(feature = "hydrate")]
    {
        use gloo_timers::callback::Interval;

        let cleanup_interval = Interval::new(5000, move || {
            let now = js_sys::Date::now() as i64;
            set_active_bubbles.update(|bubbles| {
                bubbles.retain(|_, bubble| bubble.expires_at > now);
            });
        });
        // Keep interval alive
        std::mem::forget(cleanup_interval);
    }

    // Handle position update via WebSocket
    #[cfg(feature = "hydrate")]
    let on_move = Callback::new(move |(x, y): (f64, f64)| {
        ws_sender.with_value(|sender| {
            if let Some(send_fn) = sender {
                send_fn(ClientMessage::UpdatePosition { x, y });
            }
        });
    });

    #[cfg(not(feature = "hydrate"))]
    let on_move = Callback::new(move |(_x, _y): (f64, f64)| {});

    // Handle prop click (pickup) via WebSocket
    #[cfg(feature = "hydrate")]
    let on_prop_click = Callback::new(move |prop_id: Uuid| {
        ws_sender.with_value(|sender| {
            if let Some(send_fn) = sender {
                send_fn(ClientMessage::PickUpProp {
                    loose_prop_id: prop_id,
                });
            }
        });
    });

    #[cfg(not(feature = "hydrate"))]
    let on_prop_click = Callback::new(move |_prop_id: Uuid| {});

    // Handle global keyboard shortcuts (e+0-9 for emotions, : for chat focus)
    #[cfg(feature = "hydrate")]
    {
        use std::cell::RefCell;
        use std::rc::Rc;
        use wasm_bindgen::{closure::Closure, JsCast};

        let closure_holder: Rc<RefCell<Option<Closure<dyn Fn(web_sys::KeyboardEvent)>>>> =
            Rc::new(RefCell::new(None));
        let closure_holder_clone = closure_holder.clone();

        Effect::new(move |_| {
            // Cleanup previous closure if any
            if let Some(old_closure) = closure_holder_clone.borrow_mut().take() {
                if let Some(window) = web_sys::window() {
                    let _ = window.remove_event_listener_with_callback(
                        "keydown",
                        old_closure.as_ref().unchecked_ref(),
                    );
                }
            }

            let current_slug = slug.get();
            if current_slug.is_empty() {
                return;
            }

            // Track if 'e' was pressed (for e+0-9 emotion sequence)
            let e_pressed: Rc<RefCell<bool>> = Rc::new(RefCell::new(false));
            let e_pressed_clone = e_pressed.clone();

            let closure = Closure::new(move |ev: web_sys::KeyboardEvent| {
                let key = ev.key();

                // If chat is focused, let it handle all keys
                // Use get_untracked() since we're in a JS event handler, not a reactive context
                if chat_focused.get_untracked() {
                    *e_pressed_clone.borrow_mut() = false;
                    return;
                }

                // Handle ':' to focus chat input
                if key == ":" {
                    set_focus_chat_trigger.set(true);
                    // Reset trigger after a short delay so it can be triggered again
                    use gloo_timers::callback::Timeout;
                    Timeout::new(100, move || {
                        set_focus_chat_trigger.set(false);
                    })
                    .forget();
                    ev.prevent_default();
                    return;
                }

                // Handle 'i' to open inventory
                if key == "i" || key == "I" {
                    set_inventory_open.set(true);
                    ev.prevent_default();
                    return;
                }

                // Check if 'e' key was pressed
                if key == "e" || key == "E" {
                    *e_pressed_clone.borrow_mut() = true;
                    return;
                }

                // Check for 0-9 after 'e' was pressed
                if *e_pressed_clone.borrow() {
                    *e_pressed_clone.borrow_mut() = false; // Reset regardless of outcome
                    if key.len() == 1 {
                        if let Ok(digit) = key.parse::<u8>() {
                            // Convert digit to emotion name using EmotionState
                            if let Some(emotion_state) = EmotionState::from_index(digit) {
                                let emotion = emotion_state.to_string();
                                #[cfg(debug_assertions)]
                                web_sys::console::log_1(
                                    &format!("[Emotion] Sending emotion {}", emotion).into(),
                                );
                                ws_sender.with_value(|sender| {
                                    if let Some(send_fn) = sender {
                                        send_fn(ClientMessage::UpdateEmotion { emotion });
                                    }
                                });
                            }
                        }
                    }
                } else {
                    // Any other key resets the 'e' state
                    *e_pressed_clone.borrow_mut() = false;
                }
            });

            if let Some(window) = web_sys::window() {
                let _ = window
                    .add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref());
            }

            // Store the closure for cleanup
            *closure_holder_clone.borrow_mut() = Some(closure);
        });
    }

    // Callback for chat focus changes
    let on_chat_focus_change = Callback::new(move |focused: bool| {
        set_chat_focused.set(focused);
    });

    // Create logout callback (WebSocket disconnects automatically)
    let on_logout = Callback::new(move |_: ()| {
        #[cfg(feature = "hydrate")]
        {
            use gloo_net::http::Request;
            let navigate = navigate.clone();

            spawn_local(async move {
                // WebSocket close handles channel leave automatically
                let _: Result<gloo_net::http::Response, gloo_net::Error> =
                    Request::post("/api/auth/logout").send().await;
                navigate("/", Default::default());
            });
        }
    });

    view! {
        <div class="h-screen bg-gray-900 text-white flex flex-col overflow-hidden">
            <Suspense fallback=move || {
                view! {
                    <div class="flex items-center justify-center min-h-screen">
                        <p class="text-gray-400">"Loading realm..."</p>
                    </div>
                }
            }>
                {move || {
                    let on_logout = on_logout.clone();
                    let on_move = on_move.clone();
                    realm_data
                        .get()
                        .map(|maybe_data| {
                            match maybe_data {
                                Some(data) => {
                                    let realm = data.realm;
                                    let user_role = data.user_role;

                                    // Determine if user can access admin
                                    // Admin visible for: Owner, Moderator, or staff
                                    let can_admin = matches!(
                                        user_role,
                                        Some(RealmRole::Owner) | Some(RealmRole::Moderator)
                                    );

                                    // Get scene name and description for header
                                    let scene_info = entry_scene
                                        .get()
                                        .flatten()
                                        .map(|s| (s.name.clone(), s.description.clone()))
                                        .unwrap_or_else(|| ("Loading...".to_string(), None));

                                    let realm_name = realm.name.clone();
                                    let realm_slug_val = realm.slug.clone();
                                    let realm_description = realm.tagline.clone();
                                    let online_count = realm.current_user_count;
                                    let total_members = realm.member_count;
                                    let max_capacity = realm.max_users;
                                    let scene_name = scene_info.0;
                                    let scene_description = scene_info.1;

                                    view! {
                                        <RealmHeader
                                            realm_name=realm_name
                                            realm_slug=realm_slug_val.clone()
                                            realm_description=realm_description
                                            scene_name=scene_name
                                            scene_description=scene_description
                                            online_count=online_count
                                            total_members=total_members
                                            max_capacity=max_capacity
                                            can_admin=can_admin
                                            on_logout=on_logout.clone()
                                        />
                                        <main class="flex-1 w-full">
                                            // Scene viewer - full width
                                            <Suspense fallback=move || {
                                                view! {
                                                    <div class="flex items-center justify-center py-12">
                                                        <p class="text-gray-400">"Loading scene..."</p>
                                                    </div>
                                                }
                                            }>
                                                {move || {
                                                    let on_move = on_move.clone();
                                                    let on_prop_click = on_prop_click.clone();
                                                    let on_chat_focus_change = on_chat_focus_change.clone();
                                                    let realm_slug_for_viewer = realm_slug_val.clone();
                                                    #[cfg(feature = "hydrate")]
                                                    let ws_sender_clone = ws_sender.clone();
                                                    entry_scene
                                                        .get()
                                                        .map(|maybe_scene| {
                                                            match maybe_scene {
                                                                Some(scene) => {
                                                                    let members_signal = Signal::derive(move || members.get());
                                                                    let emotion_avail_signal = Signal::derive(move || emotion_availability.get());
                                                                    let skin_path_signal = Signal::derive(move || skin_preview_path.get());
                                                                    let focus_trigger_signal = Signal::derive(move || focus_chat_trigger.get());
                                                                    #[cfg(feature = "hydrate")]
                                                                    let ws_for_chat = ws_sender_clone.clone();
                                                                    #[cfg(not(feature = "hydrate"))]
                                                                    let ws_for_chat: StoredValue<Option<WsSender>, LocalStorage> = StoredValue::new_local(None);
                                                                    let active_bubbles_signal = Signal::derive(move || active_bubbles.get());
                                                                    let loose_props_signal = Signal::derive(move || loose_props.get());
                                                                    view! {
                                                                        <div class="relative w-full">
                                                                            <RealmSceneViewer
                                                                                scene=scene
                                                                                realm_slug=realm_slug_for_viewer.clone()
                                                                                members=members_signal
                                                                                active_bubbles=active_bubbles_signal
                                                                                loose_props=loose_props_signal
                                                                                on_move=on_move.clone()
                                                                                on_prop_click=on_prop_click.clone()
                                                                            />
                                                                            <div class="absolute bottom-0 left-0 right-0 z-10 pb-4 px-4 pointer-events-none">
                                                                                <ChatInput
                                                                                    ws_sender=ws_for_chat
                                                                                    emotion_availability=emotion_avail_signal
                                                                                    skin_preview_path=skin_path_signal
                                                                                    focus_trigger=focus_trigger_signal
                                                                                    on_focus_change=on_chat_focus_change.clone()
                                                                                />
                                                                            </div>
                                                                        </div>
                                                                    }
                                                                        .into_any()
                                                                }
                                                                None => {
                                                                    view! {
                                                                        <div class="max-w-4xl mx-auto px-4 py-8">
                                                                            <Card class="p-8 text-center">
                                                                                <p class="text-gray-400">
                                                                                    "No scenes have been created for this realm yet."
                                                                                </p>
                                                                            </Card>
                                                                        </div>
                                                                    }
                                                                        .into_any()
                                                                }
                                                            }
                                                        })
                                                }}
                                            </Suspense>
                                        </main>

                                        // Inventory popup
                                        {
                                            #[cfg(feature = "hydrate")]
                                            let ws_sender_for_inv = ws_sender.clone();
                                            #[cfg(not(feature = "hydrate"))]
                                            let ws_sender_for_inv: StoredValue<Option<WsSender>, LocalStorage> = StoredValue::new_local(None);
                                            view! {
                                                <InventoryPopup
                                                    open=Signal::derive(move || inventory_open.get())
                                                    on_close=Callback::new(move |_: ()| {
                                                        set_inventory_open.set(false);
                                                    })
                                                    ws_sender=ws_sender_for_inv
                                                />
                                            }
                                        }
                                    }
                                        .into_any()
                                }
                                None => {
                                    view! {
                                        <div class="flex items-center justify-center min-h-screen">
                                            <Card class="p-8 text-center max-w-md">
                                                <div class="mx-auto w-20 h-20 rounded-full bg-red-900/20 flex items-center justify-center mb-4">
                                                    <img
                                                        src="/icons/x.svg"
                                                        alt=""
                                                        class="w-10 h-10"
                                                        aria-hidden="true"
                                                    />
                                                </div>
                                                <h2 class="text-xl font-semibold text-white mb-2">
                                                    "Realm Not Found"
                                                </h2>
                                                <p class="text-gray-400 mb-6">
                                                    "The realm you're looking for doesn't exist or you don't have access."
                                                </p>
                                                <a href="/" class="btn-primary inline-block">
                                                    "Back to Home"
                                                </a>
                                            </Card>
                                        </div>
                                    }
                                        .into_any()
                                }
                            }
                        })
                }}
            </Suspense>
        </div>
    }
}
