//! Chat components for realm chat interface.

use leptos::prelude::*;

use chattyness_db::models::EmotionAvailability;
use chattyness_db::ws_messages::ClientMessage;

use super::ws_client::WsSenderStorage;

/// Emotion names indexed by emotion slot (0-11).
const EMOTIONS: &[&str] = &[
    "neutral",   // 0
    "happy",     // 1
    "sad",       // 2
    "angry",     // 3
    "surprised", // 4
    "thinking",  // 5
    "laughing",  // 6
    "crying",    // 7
    "love",      // 8
    "confused",  // 9
    "sleeping",  // 10
    "wink",      // 11
];

/// Command mode state for the chat input.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum CommandMode {
    /// Normal chat mode, no command active.
    None,
    /// Showing command hint (`:e[mote], :l[ist]`).
    ShowingHint,
    /// Showing emotion list popup.
    ShowingList,
}

/// Parse an emote command and return the emotion name if valid.
///
/// Supports `:e name`, `:emote name` with partial matching.
fn parse_emote_command(cmd: &str) -> Option<String> {
    let cmd = cmd.trim().to_lowercase();

    // Strip the leading colon if present
    let cmd = cmd.strip_prefix(':').unwrap_or(&cmd);

    // Check for `:e <name>` or `:emote <name>`
    let name = cmd
        .strip_prefix("emote ")
        .or_else(|| cmd.strip_prefix("e "))
        .map(str::trim);

    name.and_then(|n| {
        EMOTIONS
            .iter()
            .find(|ename| ename.starts_with(n) || n.starts_with(**ename))
            .map(|ename| (*ename).to_string())
    })
}

/// Chat input component with emote command support.
///
/// Props:
/// - `ws_sender`: WebSocket sender for emotion updates
/// - `emotion_availability`: Which emotions are available for the user's avatar
/// - `skin_preview_path`: Path to the user's skin layer center asset (for previews)
/// - `focus_trigger`: Signal that triggers focus when set to true
/// - `on_focus_change`: Callback when focus state changes
#[component]
pub fn ChatInput(
    ws_sender: WsSenderStorage,
    emotion_availability: Signal<Option<EmotionAvailability>>,
    skin_preview_path: Signal<Option<String>>,
    focus_trigger: Signal<bool>,
    on_focus_change: Callback<bool>,
) -> impl IntoView {
    let (message, set_message) = signal(String::new());
    let (command_mode, set_command_mode) = signal(CommandMode::None);
    let input_ref = NodeRef::<leptos::html::Input>::new();

    // Handle focus trigger from parent (when ':' is pressed globally)
    #[cfg(feature = "hydrate")]
    {
        Effect::new(move |_| {
            if focus_trigger.get() {
                if let Some(input) = input_ref.get() {
                    let _ = input.focus();
                    // Also set the message to ':' and show the hint
                    set_message.set(":".to_string());
                    set_command_mode.set(CommandMode::ShowingHint);
                    // Update the input value directly
                    input.set_value(":");
                }
            }
        });
    }

    // Apply emotion via WebSocket
    let apply_emotion = {
        move |emotion: String| {
            ws_sender.with_value(|sender| {
                if let Some(send_fn) = sender {
                    send_fn(ClientMessage::UpdateEmotion { emotion });
                }
            });
            // Clear input and close popup
            set_message.set(String::new());
            set_command_mode.set(CommandMode::None);
        }
    };

    // Handle input changes to detect commands
    let on_input = move |ev| {
        let value = event_target_value(&ev);
        set_message.set(value.clone());

        if value.starts_with(':') {
            let cmd = value[1..].to_lowercase();

            // Check for list command
            if cmd == "l" || cmd == "list" {
                set_command_mode.set(CommandMode::ShowingList);
            } else if cmd.is_empty()
                || cmd.starts_with('e')
                || cmd.starts_with('l')
                || cmd.starts_with("em")
                || cmd.starts_with("li")
            {
                // Show hint for incomplete commands
                set_command_mode.set(CommandMode::ShowingHint);
            } else if cmd.starts_with("e ") || cmd.starts_with("emote ") {
                // Typing an emote command - keep hint visible
                set_command_mode.set(CommandMode::ShowingHint);
            } else {
                set_command_mode.set(CommandMode::None);
            }
        } else {
            set_command_mode.set(CommandMode::None);
        }
    };

    // Handle key presses (Enter to execute, Escape to close)
    #[cfg(feature = "hydrate")]
    let on_keydown = {
        let apply_emotion = apply_emotion.clone();
        move |ev: web_sys::KeyboardEvent| {
            let key = ev.key();

            if key == "Escape" {
                set_command_mode.set(CommandMode::None);
                set_message.set(String::new());
                ev.prevent_default();
                return;
            }

            if key == "Enter" {
                let msg = message.get();
                if msg.starts_with(':') {
                    // Try to parse as emote command
                    if let Some(emotion_idx) = parse_emote_command(&msg) {
                        apply_emotion(emotion_idx);
                        ev.prevent_default();
                    }
                } else if !msg.trim().is_empty() {
                    // Send regular chat message
                    ws_sender.with_value(|sender| {
                        if let Some(send_fn) = sender {
                            send_fn(ClientMessage::SendChatMessage {
                                content: msg.trim().to_string(),
                            });
                        }
                    });
                    set_message.set(String::new());
                    ev.prevent_default();
                }
            }
        }
    };

    #[cfg(not(feature = "hydrate"))]
    let on_keydown = move |_ev| {};

    // Focus/blur handlers
    let on_focus = {
        let on_focus_change = on_focus_change.clone();
        move |_ev| {
            on_focus_change.run(true);
        }
    };

    let on_blur = {
        move |_ev| {
            on_focus_change.run(false);
            // Note: We don't close the popup on blur to allow click events on popup items to fire
            // The popup is closed when an item is selected or Escape is pressed
        }
    };

    // Popup select handler
    let on_popup_select = Callback::new(move |emotion: String| {
        apply_emotion(emotion);
    });

    let on_popup_close = Callback::new(move |_: ()| {
        set_command_mode.set(CommandMode::None);
    });

    view! {
        <div class="chat-input-container w-full max-w-4xl mx-auto pointer-events-auto relative">
            // Command hint bar
            <Show when=move || command_mode.get() == CommandMode::ShowingHint>
                <div class="absolute bottom-full left-0 mb-1 px-3 py-1 bg-gray-700/90 backdrop-blur-sm rounded text-sm">
                    <span class="text-gray-400">":"</span>
                    <span class="text-blue-400">"e"</span>
                    <span class="text-gray-500">"[mote] name"</span>
                    <span class="text-gray-600 mx-2">"|"</span>
                    <span class="text-gray-400">":"</span>
                    <span class="text-blue-400">"l"</span>
                    <span class="text-gray-500">"[ist]"</span>
                </div>
            </Show>

            // Emotion list popup
            <Show when=move || command_mode.get() == CommandMode::ShowingList>
                <EmoteListPopup
                    emotion_availability=emotion_availability
                    skin_preview_path=skin_preview_path
                    on_select=on_popup_select
                    on_close=on_popup_close
                />
            </Show>

            <div class="flex gap-2 items-center bg-gray-900/80 backdrop-blur-sm rounded-lg p-2 border border-gray-600">
                <input
                    type="text"
                    placeholder="Type a message... (: for commands)"
                    class="flex-1 bg-transparent text-white placeholder-gray-500 px-3 py-2 outline-none"
                    prop:value=move || message.get()
                    on:input=on_input
                    on:keydown=on_keydown
                    on:focus=on_focus
                    on:blur=on_blur
                    node_ref=input_ref
                    autocomplete="off"
                    aria-label="Chat message input"
                />
                <button
                    type="button"
                    class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
                    disabled=move || message.get().trim().is_empty()
                >
                    "Send"
                </button>
            </div>
        </div>
    }
}

/// Emote list popup component.
///
/// Shows available emotions in a 2-column grid with avatar previews.
#[component]
fn EmoteListPopup(
    emotion_availability: Signal<Option<EmotionAvailability>>,
    skin_preview_path: Signal<Option<String>>,
    on_select: Callback<String>,
    #[prop(into)] on_close: Callback<()>,
) -> impl IntoView {
    let _ = on_close; // Suppress unused warning
    // Get list of available emotions (name, preview_path)
    let available_emotions = move || {
        emotion_availability
            .get()
            .map(|avail| {
                EMOTIONS
                    .iter()
                    .enumerate()
                    .filter(|(idx, _)| avail.available.get(*idx).copied().unwrap_or(false))
                    .map(|(idx, name)| {
                        let preview = avail.preview_paths.get(idx).cloned().flatten();
                        ((*name).to_string(), preview)
                    })
                    .collect::<Vec<_>>()
            })
            .unwrap_or_default()
    };

    view! {
        <div
            class="absolute bottom-full left-0 mb-2 w-full max-w-lg bg-gray-800/95 backdrop-blur-sm rounded-lg shadow-2xl border border-gray-700 p-3 z-50"
            role="listbox"
            aria-label="Available emotions"
        >
            <div class="text-gray-400 text-xs mb-2 px-1">"Select an emotion:"</div>
            <div class="grid grid-cols-3 gap-1 max-h-64 overflow-y-auto">
                <For
                    each=move || available_emotions()
                    key=|(name, _): &(String, Option<String>)| name.clone()
                    children=move |(emotion_name, preview_path): (String, Option<String>)| {
                        let on_select = on_select.clone();
                        let emotion_name_for_click = emotion_name.clone();
                        let _skin_path = skin_preview_path.get();
                        let _emotion_path = preview_path.clone();
                        view! {
                            <button
                                type="button"
                                class="flex items-center gap-2 p-2 rounded hover:bg-gray-700 transition-colors text-left w-full"
                                on:click=move |_| on_select.run(emotion_name_for_click.clone())
                                role="option"
                            >
                                <EmotionPreview
                                    skin_path=_skin_path.clone()
                                    emotion_path=_emotion_path.clone()
                                />
                                <span class="text-white text-sm">
                                    ":e "
                                    {emotion_name.clone()}
                                </span>
                            </button>
                        }
                    }
                />
            </div>
            <Show when=move || available_emotions().is_empty()>
                <div class="text-gray-500 text-sm text-center py-4">
                    "No emotions configured for your avatar"
                </div>
            </Show>
        </div>
    }
}

/// Emotion preview component.
///
/// Renders a small canvas with the avatar skin and emotion overlay.
#[component]
fn EmotionPreview(skin_path: Option<String>, emotion_path: Option<String>) -> impl IntoView {
    let canvas_ref = NodeRef::<leptos::html::Canvas>::new();

    #[cfg(feature = "hydrate")]
    {
        use wasm_bindgen::{closure::Closure, JsCast};

        let skin_path_clone = skin_path.clone();
        let emotion_path_clone = emotion_path.clone();

        Effect::new(move |_| {
            let Some(canvas) = canvas_ref.get() else {
                return;
            };

            let canvas_el: &web_sys::HtmlCanvasElement = &canvas;
            canvas_el.set_width(32);
            canvas_el.set_height(32);

            let Ok(Some(ctx)) = canvas_el.get_context("2d") else {
                return;
            };
            let ctx: web_sys::CanvasRenderingContext2d = ctx.dyn_into().unwrap();

            // Clear canvas
            ctx.clear_rect(0.0, 0.0, 32.0, 32.0);

            // Helper to normalize asset paths
            fn normalize_path(path: &str) -> String {
                if path.starts_with('/') {
                    path.to_string()
                } else {
                    format!("/static/{}", path)
                }
            }

            // Draw skin layer first
            if let Some(ref skin) = skin_path_clone {
                let skin_url = normalize_path(skin);
                let ctx_clone = ctx.clone();

                let img = web_sys::HtmlImageElement::new().unwrap();
                let img_clone = img.clone();

                // Load and draw emotion on top after skin loads
                let emotion_path_for_closure = emotion_path_clone.clone();

                let onload = Closure::once(Box::new(move || {
                    ctx_clone.draw_image_with_html_image_element_and_dw_and_dh(
                        &img_clone, 0.0, 0.0, 32.0, 32.0,
                    ).ok();

                    // Now draw emotion overlay
                    if let Some(ref emotion) = emotion_path_for_closure {
                        let emotion_url = normalize_path(emotion);
                        let ctx_emotion = ctx_clone.clone();

                        let emotion_img = web_sys::HtmlImageElement::new().unwrap();
                        let emotion_img_clone = emotion_img.clone();

                        let emotion_onload = Closure::once(Box::new(move || {
                            ctx_emotion.draw_image_with_html_image_element_and_dw_and_dh(
                                &emotion_img_clone, 0.0, 0.0, 32.0, 32.0,
                            ).ok();
                        }) as Box<dyn FnOnce()>);

                        emotion_img.set_onload(Some(emotion_onload.as_ref().unchecked_ref()));
                        emotion_onload.forget();
                        emotion_img.set_src(&emotion_url);
                    }
                }) as Box<dyn FnOnce()>);

                img.set_onload(Some(onload.as_ref().unchecked_ref()));
                onload.forget();
                img.set_src(&skin_url);
            } else if let Some(ref emotion) = emotion_path_clone {
                // No skin, just draw emotion
                let emotion_url = normalize_path(emotion);
                let ctx_clone = ctx.clone();

                let img = web_sys::HtmlImageElement::new().unwrap();
                let img_clone = img.clone();

                let onload = Closure::once(Box::new(move || {
                    ctx_clone.draw_image_with_html_image_element_and_dw_and_dh(
                        &img_clone, 0.0, 0.0, 32.0, 32.0,
                    ).ok();
                }) as Box<dyn FnOnce()>);

                img.set_onload(Some(onload.as_ref().unchecked_ref()));
                onload.forget();
                img.set_src(&emotion_url);
            } else {
                // No assets - draw placeholder circle
                ctx.set_fill_style_str("#4B5563");
                ctx.begin_path();
                ctx.arc(16.0, 16.0, 14.0, 0.0, std::f64::consts::PI * 2.0).ok();
                ctx.fill();
            }
        });
    }

    view! {
        <canvas
            node_ref=canvas_ref
            width="32"
            height="32"
            class="w-8 h-8 rounded"
            aria-hidden="true"
        />
    }
}
