//! Chat components for realm chat interface.

use leptos::prelude::*;

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

use super::emotion_picker::{EMOTIONS, EmoteListPopup, LabelStyle};
use super::ws_client::WsSenderStorage;

/// 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 for colon commands (`:e[mote], :l[ist]`).
    ShowingColonHint,
    /// Showing command hint for slash commands (`/setting`).
    ShowingSlashHint,
    /// 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())
    })
}

/// Parse a whisper command and return (target_name, message) if valid.
///
/// Supports `/w name message` and `/whisper name message`.
#[cfg(feature = "hydrate")]
fn parse_whisper_command(cmd: &str) -> Option<(String, String)> {
    let cmd = cmd.trim();

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

    // Check for `w <name> <message>` or `whisper <name> <message>`
    let rest = cmd
        .strip_prefix("whisper ")
        .or_else(|| cmd.strip_prefix("w "))
        .map(str::trim)?;

    // Find the first space to separate name from message
    let space_idx = rest.find(' ')?;
    let name = rest[..space_idx].trim().to_string();
    let message = rest[space_idx + 1..].trim().to_string();

    if name.is_empty() || message.is_empty() {
        return None;
    }

    Some((name, message))
}

/// 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 (prefix char in value)
/// - `focus_prefix`: The prefix character that triggered focus (':' or '/')
/// - `on_focus_change`: Callback when focus state changes
/// - `on_open_settings`: Callback to open settings popup
/// - `on_open_inventory`: Callback to open inventory popup
/// - `whisper_target`: Signal containing the display name to whisper to (triggers pre-fill)
#[component]
pub fn ChatInput(
    ws_sender: WsSenderStorage,
    emotion_availability: Signal<Option<EmotionAvailability>>,
    skin_preview_path: Signal<Option<String>>,
    focus_trigger: Signal<bool>,
    #[prop(default = Signal::derive(|| ':'))] focus_prefix: Signal<char>,
    on_focus_change: Callback<bool>,
    #[prop(optional)] on_open_settings: Option<Callback<()>>,
    #[prop(optional)] on_open_inventory: Option<Callback<()>>,
    /// Signal containing the display name to whisper to. When set, pre-fills the input.
    #[prop(optional, into)]
    whisper_target: Option<Signal<Option<String>>>,
) -> impl IntoView {
    let (message, set_message) = signal(String::new());
    let (command_mode, set_command_mode) = signal(CommandMode::None);
    let (list_filter, set_list_filter) = signal(String::new());
    let (selected_index, set_selected_index) = signal(0usize);
    let input_ref = NodeRef::<leptos::html::Input>::new();

    // Compute filtered emotions for keyboard navigation
    let filtered_emotions = move || {
        let filter_text = list_filter.get().to_lowercase();
        emotion_availability
            .get()
            .map(|avail| {
                EMOTIONS
                    .iter()
                    .enumerate()
                    .filter(|(idx, _)| avail.available.get(*idx).copied().unwrap_or(false))
                    .filter(|(_, name)| filter_text.is_empty() || name.starts_with(&filter_text))
                    .map(|(_, name)| (*name).to_string())
                    .collect::<Vec<_>>()
            })
            .unwrap_or_default()
    };

    // Handle focus trigger from parent (when space, ':' or '/' is pressed globally)
    #[cfg(feature = "hydrate")]
    {
        Effect::new(move |_| {
            if focus_trigger.get() {
                if let Some(input) = input_ref.get() {
                    let _ = input.focus();
                    let prefix = focus_prefix.get();

                    // Space means just focus, no prefix
                    if prefix == ' ' {
                        return;
                    }

                    let prefix_str = prefix.to_string();
                    set_message.set(prefix_str.clone());
                    // Show appropriate hint based on prefix
                    set_command_mode.set(if prefix == '/' {
                        CommandMode::ShowingSlashHint
                    } else {
                        CommandMode::ShowingColonHint
                    });
                    // Update the input value directly
                    input.set_value(&prefix_str);
                }
            }
        });
    }

    // Handle whisper target pre-fill
    #[cfg(feature = "hydrate")]
    {
        Effect::new(move |_| {
            let Some(whisper_signal) = whisper_target else {
                return;
            };

            let Some(target_name) = whisper_signal.get() else {
                return;
            };

            // Pre-fill with /whisper command prefix only (no placeholder text)
            // User types their message after the space
            // parse_whisper_command already rejects empty messages
            let whisper_prefix = format!("/whisper {} ", target_name);

            if let Some(input) = input_ref.get() {
                // Set the message
                set_message.set(whisper_prefix.clone());
                // Don't show hint - user already knows they're whispering
                set_command_mode.set(CommandMode::None);

                // Update input value
                input.set_value(&whisper_prefix);

                // Focus the input and position cursor at end
                let _ = input.focus();
                let len = whisper_prefix.len() as u32;
                let _ = input.set_selection_range(len, len);
            }
        });
    }

    // 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 list is showing, update filter (input is the filter text)
            if command_mode.get_untracked() == CommandMode::ShowingList {
                set_list_filter.set(value.clone());
                set_selected_index.set(0); // Reset selection when filter changes
                return;
            }

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

                // Show hint for colon commands
                if cmd.is_empty()
                    || "list".starts_with(&cmd)
                    || "emote".starts_with(&cmd)
                    || cmd.starts_with("e ")
                    || cmd.starts_with("emote ")
                {
                    set_command_mode.set(CommandMode::ShowingColonHint);
                } else {
                    set_command_mode.set(CommandMode::None);
                }
            } else if value.starts_with('/') {
                let cmd = value[1..].to_lowercase();

                // Show hint for slash commands (don't execute until Enter)
                // Match: /s[etting], /i[nventory], /w[hisper]
                // But NOT when whisper command is complete (has name + space for message)
                let is_complete_whisper = {
                    // Check if it's "/w name " or "/whisper name " (name followed by space)
                    let rest = cmd.strip_prefix("whisper ").or_else(|| cmd.strip_prefix("w "));
                    if let Some(after_cmd) = rest {
                        // If there's content after the command and it contains a space,
                        // user has typed "name " and is now typing the message
                        after_cmd.contains(' ')
                    } else {
                        false
                    }
                };

                if is_complete_whisper {
                    // User is typing the message part, no hint needed
                    set_command_mode.set(CommandMode::None);
                } else if cmd.is_empty()
                    || "setting".starts_with(&cmd)
                    || "inventory".starts_with(&cmd)
                    || "whisper".starts_with(&cmd)
                    || cmd == "setting"
                    || cmd == "settings"
                    || cmd == "inventory"
                    || cmd.starts_with("w ")
                    || cmd.starts_with("whisper ")
                {
                    set_command_mode.set(CommandMode::ShowingSlashHint);
                } else {
                    set_command_mode.set(CommandMode::None);
                }
            } else {
                set_command_mode.set(CommandMode::None);
            }
        }
    };

    // Handle key presses (Tab for autocomplete, Enter to execute, Escape to close and blur)
    #[cfg(feature = "hydrate")]
    let on_keydown = {
        let apply_emotion = apply_emotion.clone();
        let on_open_settings = on_open_settings.clone();
        let on_open_inventory = on_open_inventory.clone();
        move |ev: web_sys::KeyboardEvent| {
            let key = ev.key();
            let current_mode = command_mode.get_untracked();

            if key == "Escape" {
                set_command_mode.set(CommandMode::None);
                set_list_filter.set(String::new());
                set_selected_index.set(0);
                set_message.set(String::new());
                // Blur the input to unfocus chat
                if let Some(input) = input_ref.get() {
                    let _ = input.blur();
                }
                ev.prevent_default();
                return;
            }

            // Arrow key navigation when list is showing
            if current_mode == CommandMode::ShowingList {
                let emotions = filtered_emotions();
                let count = emotions.len();

                if key == "ArrowDown" && count > 0 {
                    set_selected_index.update(|idx| {
                        *idx = (*idx + 1) % count;
                    });
                    ev.prevent_default();
                    return;
                }

                if key == "ArrowUp" && count > 0 {
                    set_selected_index.update(|idx| {
                        *idx = if *idx == 0 { count - 1 } else { *idx - 1 };
                    });
                    ev.prevent_default();
                    return;
                }

                if key == "Enter" && count > 0 {
                    // Select the currently highlighted emotion
                    let idx = selected_index.get_untracked();
                    if let Some(emotion) = emotions.get(idx) {
                        set_list_filter.set(String::new());
                        set_selected_index.set(0);
                        apply_emotion(emotion.clone());
                    }
                    ev.prevent_default();
                    return;
                }

                // Any other key in list mode is handled by on_input
                if key == "Enter" {
                    ev.prevent_default();
                    return;
                }
            }

            // Tab for autocomplete
            if key == "Tab" {
                let msg = message.get();
                if msg.starts_with('/') {
                    let cmd = msg[1..].to_lowercase();
                    // Autocomplete to /setting if /s, /se, /set, etc.
                    if !cmd.is_empty() && "setting".starts_with(&cmd) && cmd != "setting" {
                        set_message.set("/setting".to_string());
                        if let Some(input) = input_ref.get() {
                            input.set_value("/setting");
                        }
                        ev.prevent_default();
                        return;
                    }
                    // Autocomplete to /inventory if /i, /in, /inv, etc.
                    if !cmd.is_empty() && "inventory".starts_with(&cmd) && cmd != "inventory" {
                        set_message.set("/inventory".to_string());
                        if let Some(input) = input_ref.get() {
                            input.set_value("/inventory");
                        }
                        ev.prevent_default();
                        return;
                    }
                }
                // Always prevent Tab from moving focus when in input
                ev.prevent_default();
                return;
            }

            if key == "Enter" {
                let msg = message.get();

                // Handle slash commands
                if msg.starts_with('/') {
                    let cmd = msg[1..].to_lowercase();

                    // /s, /se, /set, /sett, /setti, /settin, /setting, /settings
                    if !cmd.is_empty() && ("setting".starts_with(&cmd) || cmd == "settings") {
                        if let Some(ref callback) = on_open_settings {
                            callback.run(());
                        }
                        set_message.set(String::new());
                        set_command_mode.set(CommandMode::None);
                        if let Some(input) = input_ref.get() {
                            input.set_value("");
                            let _ = input.blur();
                        }
                        ev.prevent_default();
                        return;
                    }

                    // /i, /in, /inv, /inve, /inven, /invent, /invento, /inventor, /inventory
                    if !cmd.is_empty() && "inventory".starts_with(&cmd) {
                        if let Some(ref callback) = on_open_inventory {
                            callback.run(());
                        }
                        set_message.set(String::new());
                        set_command_mode.set(CommandMode::None);
                        if let Some(input) = input_ref.get() {
                            input.set_value("");
                            let _ = input.blur();
                        }
                        ev.prevent_default();
                        return;
                    }

                    // /w NAME message or /whisper NAME message
                    if let Some((target_name, whisper_content)) = parse_whisper_command(&msg) {
                        if !whisper_content.trim().is_empty() {
                            ws_sender.with_value(|sender| {
                                if let Some(send_fn) = sender {
                                    send_fn(ClientMessage::SendChatMessage {
                                        content: whisper_content.trim().to_string(),
                                        target_display_name: Some(target_name),
                                    });
                                }
                            });
                            set_message.set(String::new());
                            set_command_mode.set(CommandMode::None);
                            if let Some(input) = input_ref.get() {
                                input.set_value("");
                            }
                        }
                        ev.prevent_default();
                        return;
                    }

                    // Invalid slash command - just ignore, don't send
                    ev.prevent_default();
                    return;
                }

                // Handle colon commands - NEVER send as message
                if msg.starts_with(':') {
                    let cmd = msg[1..].to_lowercase();

                    // :l, :li, :lis, :list - open the emotion list
                    if !cmd.is_empty() && "list".starts_with(&cmd) {
                        set_command_mode.set(CommandMode::ShowingList);
                        set_list_filter.set(String::new());
                        set_message.set(String::new());
                        if let Some(input) = input_ref.get() {
                            input.set_value("");
                        }
                        ev.prevent_default();
                        return;
                    }

                    // :e <name> or :emote <name> - apply emotion if valid
                    if let Some(emotion) = parse_emote_command(&msg) {
                        apply_emotion(emotion);
                    }
                    // Invalid colon command - just ignore, don't send
                    ev.prevent_default();
                    return;
                }

                // Send regular chat message (only if not a command)
                if !msg.trim().is_empty() {
                    ws_sender.with_value(|sender| {
                        if let Some(send_fn) = sender {
                            send_fn(ClientMessage::SendChatMessage {
                                content: msg.trim().to_string(),
                                target_display_name: None, // Broadcast to scene
                            });
                        }
                    });
                    set_message.set(String::new());
                    if let Some(input) = input_ref.get() {
                        input.set_value("");
                    }
                    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| {
        set_list_filter.set(String::new());
        apply_emotion(emotion);
    });

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

    let filter_signal = Signal::derive(move || list_filter.get());

    view! {
        <div class="chat-input-container w-full max-w-4xl mx-auto pointer-events-auto relative">
            // Colon command hint bar (:e[mote], :l[ist])
            <Show when=move || command_mode.get() == CommandMode::ShowingColonHint>
                <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>

            // Slash command hint bar (/s[etting], /i[nventory], /w[hisper])
            <Show when=move || command_mode.get() == CommandMode::ShowingSlashHint>
                <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">"s"</span>
                    <span class="text-gray-500">"[etting]"</span>
                    <span class="text-gray-600 mx-2">"|"</span>
                    <span class="text-gray-400">"/"</span>
                    <span class="text-blue-400">"i"</span>
                    <span class="text-gray-500">"[nventory]"</span>
                    <span class="text-gray-600 mx-2">"|"</span>
                    <span class="text-gray-400">"/"</span>
                    <span class="text-blue-400">"w"</span>
                    <span class="text-gray-500">"[hisper] name"</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
                    emotion_filter=filter_signal
                    selected_idx=Signal::derive(move || selected_index.get())
                    label=LabelStyle::Command
                />
            </Show>

            <div class="flex items-center bg-gray-900/80 backdrop-blur-sm rounded-lg p-2 border border-gray-600">
                <input
                    type="text"
                    placeholder="Type a message... (: or / 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"
                />
            </div>
        </div>
    }
}
