//! Shared emotion picker components.
//!
//! Provides reusable components for selecting emotions with avatar previews.
//! Used by both the chat `:l` command and the keybindings popup.

use leptos::prelude::*;

use chattyness_db::models::EmotionAvailability;

/// Controls how emotion labels are displayed in the EmoteListPopup.
#[derive(Clone, Copy, PartialEq, Eq, Default)]
pub enum LabelStyle {
    /// Show as command format: `:e happy` (lowercase, no capitalization)
    Command,
    /// Show as name format: `Happy` (capitalized)
    #[default]
    Name,
}

/// Emotion names indexed by emotion slot (0-11).
pub 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
];

/// Emote list popup component.
///
/// Shows emotions in a grid with avatar previews.
/// Supports search-as-you-type filtering and keyboard navigation.
///
/// Props:
/// - `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)
/// - `on_select`: Callback when an emotion is selected (receives emotion name)
/// - `on_close`: Callback when popup should close
/// - `emotion_filter`: Signal containing the current filter text
/// - `selected_idx`: Signal containing the currently selected index
/// - `show_all_emotions`: When true, show all 12 emotions (for keybindings);
///                        when false, only show available emotions (for chat `:l`)
/// - `popup_style`: Positioning style - "above" (default, for chat input) or "static" (for modal wrapper)
/// - `label`: How to display emotion labels - `Command` for `:e happy` or `Name` for `Happy` (default)
#[component]
pub fn EmoteListPopup(
    emotion_availability: Signal<Option<EmotionAvailability>>,
    skin_preview_path: Signal<Option<String>>,
    on_select: Callback<String>,
    #[prop(into)] on_close: Callback<()>,
    #[prop(into)] emotion_filter: Signal<String>,
    #[prop(into)] selected_idx: Signal<usize>,
    #[prop(default = false)] show_all_emotions: bool,
    #[prop(default = "above")] popup_style: &'static str,
    #[prop(default = LabelStyle::Name)] label: LabelStyle,
) -> impl IntoView {
    let _ = on_close; // Suppress unused warning

    // Determine container class based on popup style
    // Both use the same max-w-lg width for consistency
    let container_class = if popup_style == "static" {
        // Static positioning for use inside a modal wrapper (keybindings)
        "max-w-lg bg-gray-800/95 backdrop-blur-sm rounded-lg shadow-2xl border border-gray-700 p-3"
    } else {
        // Absolute positioning above parent (chat input)
        "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"
    };

    // Get list of emotions (name, preview_path), filtered by search text
    // If show_all_emotions is true, show all regardless of availability
    let available_emotions = move || {
        let filter_text = emotion_filter.get().to_lowercase();
        emotion_availability
            .get()
            .map(|avail| {
                EMOTIONS
                    .iter()
                    .enumerate()
                    .filter(|(idx, _)| {
                        // If show_all_emotions is true, include all emotions
                        // Otherwise, only include available ones
                        show_all_emotions || avail.available.get(*idx).copied().unwrap_or(false)
                    })
                    .filter(|(_, name)| {
                        // Filter by prefix if filter is set
                        filter_text.is_empty() || name.starts_with(&filter_text)
                    })
                    .map(|(idx, name)| {
                        let preview = avail.preview_paths.get(idx).cloned().flatten();
                        let is_available = avail.available.get(idx).copied().unwrap_or(false);
                        ((*name).to_string(), preview, is_available)
                    })
                    .collect::<Vec<_>>()
            })
            .unwrap_or_else(|| {
                // If no availability info, show all emotions with no previews
                if show_all_emotions {
                    EMOTIONS
                        .iter()
                        .filter(|name| filter_text.is_empty() || name.starts_with(&filter_text))
                        .map(|name| ((*name).to_string(), None, true))
                        .collect()
                } else {
                    Vec::new()
                }
            })
    };

    let filter_display = move || {
        let f = emotion_filter.get();
        if f.is_empty() {
            "Type to filter...".to_string()
        } else {
            format!("Filter: {}", f)
        }
    };

    // Indexed emotions for selection tracking
    let indexed_emotions = move || {
        available_emotions()
            .into_iter()
            .enumerate()
            .collect::<Vec<_>>()
    };

    view! {
        <div
            class=container_class
            role="listbox"
            aria-label="Available emotions"
        >
            <div class="flex justify-between items-center text-xs mb-2 px-1">
                <span class="text-gray-400">"Select an emotion:"</span>
                <span class="text-blue-400 italic">{filter_display}</span>
            </div>
            <div class="grid grid-cols-3 gap-1 max-h-64 overflow-y-auto">
                {move || {
                    indexed_emotions()
                        .into_iter()
                        .map(|(idx, (emotion_name, preview_path, is_available))| {
                            let on_select = on_select.clone();
                            let emotion_name_for_click = emotion_name.clone();
                            let emotion_name_display = emotion_name.clone();
                            let skin_path = skin_preview_path.get();
                            let emotion_path = preview_path.clone();
                            let is_selected = move || selected_idx.get() == idx;
                            view! {
                                <button
                                    type="button"
                                    class=move || {
                                        let base = if is_selected() {
                                            "flex items-center gap-2 p-2 rounded bg-blue-600 text-left w-full"
                                        } else {
                                            "flex items-center gap-2 p-2 rounded hover:bg-gray-700 transition-colors text-left w-full"
                                        };
                                        // Dim unavailable emotions (only when showing all)
                                        if !is_available && !is_selected() {
                                            format!("{} opacity-50", base)
                                        } else {
                                            base.to_string()
                                        }
                                    }
                                    on:click=move |_| on_select.run(emotion_name_for_click.clone())
                                    role="option"
                                    aria-selected=is_selected
                                >
                                    <EmotionPreview
                                        skin_path=skin_path.clone()
                                        emotion_path=emotion_path.clone()
                                    />
                                    <span class=if label == LabelStyle::Name {
                                        "text-white text-sm capitalize"
                                    } else {
                                        "text-white text-sm"
                                    }>
                                        {if label == LabelStyle::Command { ":e " } else { "" }}
                                        {emotion_name_display}
                                    </span>
                                </button>
                            }
                        })
                        .collect_view()
                }}
            </div>
            <Show when=move || available_emotions().is_empty()>
                <div class="text-gray-500 text-sm text-center py-4">
                    {move || {
                        if emotion_filter.get().is_empty() {
                            "No emotions configured for your avatar"
                        } else {
                            "No matching emotions"
                        }
                    }}
                </div>
            </Show>
            <div class="mt-2 pt-2 border-t border-gray-700 text-xs text-gray-500">
                <kbd class="px-1 py-0.5 bg-gray-700 rounded font-mono">"↑↓"</kbd>
                " navigate  "
                <kbd class="px-1 py-0.5 bg-gray-700 rounded font-mono">"Enter"</kbd>
                " select  "
                <kbd class="px-1 py-0.5 bg-gray-700 rounded font-mono">"Esc"</kbd>
                " cancel"
            </div>
        </div>
    }
}

/// Emotion preview component.
///
/// Renders a small canvas with the avatar skin and emotion overlay.
#[component]
pub 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::{JsCast, closure::Closure};

        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"
        />
    }
}
