//! Avatar Editor popup component.
//!
//! Provides a two-column interface for customizing avatars:
//! - Left side: Navigation (layers/emotions) and 3x3 grid editor
//! - Right side: Inventory props for drag-and-drop

use leptos::prelude::*;
use leptos::web_sys;
use uuid::Uuid;

use chattyness_db::models::{AvatarWithPaths, InventoryItem};
#[cfg(feature = "hydrate")]
use chattyness_db::ws_messages::ClientMessage;

use super::modals::GuestLockedOverlay;
use super::ws_client::WsSenderStorage;
#[cfg(feature = "hydrate")]
use crate::utils::normalize_asset_path;
use crate::utils::use_escape_key;

/// Tab selection for the editor
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum EditorTab {
    BaseLayers,
    Emotions,
}

/// Base layer selection
#[derive(Clone, Copy, PartialEq, Eq, Default)]
pub enum BaseLayer {
    #[default]
    Skin,
    Clothes,
    Accessories,
}

impl BaseLayer {
    /// Get display name for the layer
    pub fn display_name(&self) -> &'static str {
        match self {
            BaseLayer::Skin => "Skin",
            BaseLayer::Clothes => "Clothes",
            BaseLayer::Accessories => "Accessories",
        }
    }

    /// Get the database column prefix
    pub fn column_prefix(&self) -> &'static str {
        match self {
            BaseLayer::Skin => "l_skin",
            BaseLayer::Clothes => "l_clothes",
            BaseLayer::Accessories => "l_accessories",
        }
    }
}

/// Inventory sub-tab selection
#[derive(Clone, Copy, PartialEq, Eq, Default)]
pub enum InventoryTab {
    #[default]
    Suggested,
    AllProps,
}

/// Context menu state for clearing slots
#[derive(Clone)]
pub struct ContextMenuState {
    pub x: i32,
    pub y: i32,
    pub position: u8,
}

/// All 12 emotion display names
const EMOTIONS: [&str; 12] = [
    "Neutral",
    "Happy",
    "Sad",
    "Angry",
    "Surprised",
    "Thinking",
    "Laughing",
    "Crying",
    "Love",
    "Confused",
    "Sleeping",
    "Wink",
];

/// Rendered preview of the full avatar with all layers composited.
///
/// Shows all 9 positions of each layer stacked: skin -> clothes -> accessories -> current emotion
#[component]
fn RenderedPreview(#[prop(into)] avatar: Signal<Option<AvatarWithPaths>>) -> impl IntoView {
    let canvas_ref = NodeRef::<leptos::html::Canvas>::new();
    let canvas_size = 200;
    let cell_size = canvas_size / 3;

    #[cfg(feature = "hydrate")]
    {
        use std::cell::RefCell;
        use std::collections::HashMap;
        use std::rc::Rc;
        use wasm_bindgen::JsCast;
        use wasm_bindgen::closure::Closure;

        let image_cache: Rc<RefCell<HashMap<String, web_sys::HtmlImageElement>>> =
            Rc::new(RefCell::new(HashMap::new()));

        let (redraw_trigger, set_redraw_trigger) = signal(0u32);

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

            let Some(av) = avatar.get() else {
                return;
            };

            let canvas_el: &web_sys::HtmlCanvasElement = &canvas;
            canvas_el.set_width(canvas_size as u32);
            canvas_el.set_height(canvas_size as u32);

            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, canvas_size as f64, canvas_size as f64);

            // Draw background
            ctx.set_fill_style_str("#374151");
            ctx.fill_rect(0.0, 0.0, canvas_size as f64, canvas_size as f64);

            // Helper to load and draw an image at a grid position
            let draw_at_position =
                |path: &str,
                 pos: usize,
                 cache: &Rc<RefCell<HashMap<String, web_sys::HtmlImageElement>>>,
                 ctx: &web_sys::CanvasRenderingContext2d| {
                    let normalized_path = normalize_asset_path(path);
                    let mut cache_borrow = cache.borrow_mut();
                    let row = pos / 3;
                    let col = pos % 3;
                    let x = (col * cell_size) as f64;
                    let y = (row * cell_size) as f64;
                    let size = cell_size as f64;

                    if let Some(img) = cache_borrow.get(&normalized_path) {
                        if img.complete() && img.natural_width() > 0 {
                            let _ = ctx.draw_image_with_html_image_element_and_dw_and_dh(
                                img, x, y, size, size,
                            );
                        }
                    } else {
                        let img = web_sys::HtmlImageElement::new().unwrap();
                        let trigger = set_redraw_trigger;
                        let onload = Closure::once(Box::new(move || {
                            trigger.update(|v| *v += 1);
                        }) as Box<dyn FnOnce()>);
                        img.set_onload(Some(onload.as_ref().unchecked_ref()));
                        onload.forget();
                        img.set_src(&normalized_path);
                        cache_borrow.insert(normalized_path, img);
                    }
                };

            // Draw layers in order: skin -> clothes -> accessories -> current emotion
            for (pos, path) in av.skin_layer.iter().enumerate() {
                if let Some(p) = path {
                    draw_at_position(p, pos, &image_cache, &ctx);
                }
            }

            for (pos, path) in av.clothes_layer.iter().enumerate() {
                if let Some(p) = path {
                    draw_at_position(p, pos, &image_cache, &ctx);
                }
            }

            for (pos, path) in av.accessories_layer.iter().enumerate() {
                if let Some(p) = path {
                    draw_at_position(p, pos, &image_cache, &ctx);
                }
            }

            // Draw current emotion layer
            let emotion_idx = av.current_emotion as usize;
            if emotion_idx < 12 {
                for (pos, path) in av.emotions[emotion_idx].iter().enumerate() {
                    if let Some(p) = path {
                        draw_at_position(p, pos, &image_cache, &ctx);
                    }
                }
            }
        });
    }

    view! {
        <div class="bg-gray-900 p-2 rounded-lg">
            <canvas
                node_ref=canvas_ref
                style=format!("width: {}px; height: {}px;", canvas_size, canvas_size)
            />
        </div>
    }
}

/// Avatar Editor popup component.
///
/// Props:
/// - `open`: Signal controlling visibility
/// - `on_close`: Callback when popup should close
/// - `avatar`: Current avatar data with paths
/// - `realm_slug`: Current realm slug for API calls
/// - `on_avatar_update`: Callback when avatar is updated
/// - `ws_sender`: WebSocket sender for broadcasting avatar changes
/// - `is_guest`: Whether the current user is a guest (shows locked overlay)
#[component]
pub fn AvatarEditorPopup(
    #[prop(into)] open: Signal<bool>,
    on_close: Callback<()>,
    #[prop(into)] avatar: Signal<Option<AvatarWithPaths>>,
    #[prop(into)] realm_slug: Signal<String>,
    on_avatar_update: Callback<AvatarWithPaths>,
    ws_sender: WsSenderStorage,
    /// Whether the current user is a guest. Guests see a locked overlay.
    #[prop(optional, into)]
    is_guest: Option<Signal<bool>>,
) -> impl IntoView {
    let is_guest = is_guest.unwrap_or_else(|| Signal::derive(|| false));
    // Tab state
    let (active_tab, set_active_tab) = signal(EditorTab::BaseLayers);

    // Base Layer selection
    let (selected_layer, set_selected_layer) = signal(BaseLayer::default());

    // Emotion selection (0-11)
    let (selected_emotion, set_selected_emotion) = signal(0usize);

    // Display Rendered toggle
    let (display_rendered, set_display_rendered) = signal(false);

    // Inventory sub-tab
    let (inventory_tab, set_inventory_tab) = signal(InventoryTab::default());

    // Inventory state
    let (inventory_items, set_inventory_items) = signal(Vec::<InventoryItem>::new());
    let (inventory_loading, set_inventory_loading) = signal(false);
    let (inventory_loaded, set_inventory_loaded) = signal(false);

    // Drag state
    let (dragging_item, set_dragging_item) = signal(Option::<Uuid>::None);
    let (hover_cell, set_hover_cell) = signal(Option::<usize>::None);

    // Context menu for clearing
    let (context_menu, set_context_menu) = signal(Option::<ContextMenuState>::None);

    // Saving state
    let (_saving, set_saving) = signal(false);

    // Helper to get current layer name for API calls
    let get_current_layer_name = move || -> String {
        match active_tab.get() {
            EditorTab::BaseLayers => match selected_layer.get() {
                BaseLayer::Skin => "skin".to_string(),
                BaseLayer::Clothes => "clothes".to_string(),
                BaseLayer::Accessories => "accessories".to_string(),
            },
            EditorTab::Emotions => EMOTIONS[selected_emotion.get()].to_lowercase(),
        }
    };

    // Fetch inventory when popup opens
    #[cfg(feature = "hydrate")]
    {
        use gloo_net::http::Request;
        use leptos::task::spawn_local;

        Effect::new(move |_| {
            if !open.get() {
                // Reset state when closing
                set_inventory_loaded.set(false);
                set_context_menu.set(None);
                return;
            }

            if inventory_loaded.get() {
                return;
            }

            set_inventory_loading.set(true);

            spawn_local(async move {
                let response = Request::get("/api/inventory").send().await;
                match response {
                    Ok(resp) if resp.ok() => {
                        if let Ok(data) = resp
                            .json::<chattyness_db::models::InventoryResponse>()
                            .await
                        {
                            set_inventory_items.set(data.items);
                            set_inventory_loaded.set(true);
                        }
                    }
                    _ => {}
                }
                set_inventory_loading.set(false);
            });
        });
    }

    // Handle escape key to close
    use_escape_key(open, on_close.clone());

    // Close context menu when clicking elsewhere
    let close_context_menu = move |_| {
        set_context_menu.set(None);
    };

    // Get current layer's grid paths based on selection
    let current_grid = Memo::new(move |_| {
        let av = avatar.get();
        let Some(av) = av else {
            return [None, None, None, None, None, None, None, None, None];
        };

        match active_tab.get() {
            EditorTab::BaseLayers => match selected_layer.get() {
                BaseLayer::Skin => av.skin_layer.clone(),
                BaseLayer::Clothes => av.clothes_layer.clone(),
                BaseLayer::Accessories => av.accessories_layer.clone(),
            },
            EditorTab::Emotions => {
                let emotion_idx = selected_emotion.get();
                if emotion_idx < 12 {
                    av.emotions[emotion_idx].clone()
                } else {
                    Default::default()
                }
            }
        }
    });

    // Helper function to filter inventory items based on current selection
    let get_filtered_inventory = move || -> Vec<InventoryItem> {
        let items = inventory_items.get();
        if inventory_tab.get() == InventoryTab::AllProps {
            return items;
        }

        // Filter based on current selection
        match active_tab.get() {
            EditorTab::BaseLayers => {
                let layer = selected_layer.get();
                items
                    .into_iter()
                    .filter(|item| {
                        item.layer
                            .map(|l| match (l, layer) {
                                (chattyness_db::models::AvatarLayer::Skin, BaseLayer::Skin) => true,
                                (
                                    chattyness_db::models::AvatarLayer::Clothes,
                                    BaseLayer::Clothes,
                                ) => true,
                                (
                                    chattyness_db::models::AvatarLayer::Accessories,
                                    BaseLayer::Accessories,
                                ) => true,
                                _ => false,
                            })
                            .unwrap_or(false)
                    })
                    .collect()
            }
            EditorTab::Emotions => {
                // For emotions, we don't have a default_emotion field on InventoryItem
                // so we show all items for now (or could filter by some other criteria)
                items
            }
        }
    };

    // Handle layer click - toggle display_rendered if same layer
    let on_layer_click = move |layer: BaseLayer| {
        if selected_layer.get() == layer {
            set_display_rendered.update(|v| *v = !*v);
        } else {
            set_selected_layer.set(layer);
        }
    };

    // Handle emotion click - toggle display_rendered if same emotion
    let on_emotion_click = move |emotion_idx: usize| {
        if selected_emotion.get() == emotion_idx {
            set_display_rendered.update(|v| *v = !*v);
        } else {
            set_selected_emotion.set(emotion_idx);
        }
    };

    let on_close_backdrop = on_close.clone();
    let on_close_button = on_close.clone();

    view! {
        <Show when=move || open.get()>
            <div
                class="fixed inset-0 z-50 flex items-center justify-center"
                role="dialog"
                aria-modal="true"
                aria-labelledby="avatar-editor-title"
                on:click=close_context_menu
            >
                // Backdrop
                <div
                    class="absolute inset-0 bg-black/70 backdrop-blur-sm"
                    on:click=move |_| on_close_backdrop.run(())
                    aria-hidden="true"
                />

                // Modal content
                <div class="relative bg-gray-800 rounded-lg shadow-2xl max-w-4xl w-full mx-4 border border-gray-700 max-h-[85vh] flex flex-col">
                    // Header
                    <div class="flex items-center justify-between p-4 border-b border-gray-700">
                        <h2 id="avatar-editor-title" class="text-xl font-bold text-white">
                            "Avatar Editor"
                        </h2>
                        <button
                            type="button"
                            class="text-gray-400 hover:text-white transition-colors"
                            on:click=move |_| on_close_button.run(())
                            aria-label="Close avatar editor"
                        >
                            <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
                            </svg>
                        </button>
                    </div>

                    // Tab bar
                    <div class="flex border-b border-gray-700 px-4" role="tablist">
                        <button
                            type="button"
                            role="tab"
                            aria-selected=move || active_tab.get() == EditorTab::BaseLayers
                            class=move || format!(
                                "px-4 py-2 font-medium transition-colors border-b-2 -mb-px {}",
                                if active_tab.get() == EditorTab::BaseLayers {
                                    "text-blue-400 border-blue-400"
                                } else {
                                    "text-gray-400 border-transparent hover:text-gray-300"
                                }
                            )
                            on:click=move |_| set_active_tab.set(EditorTab::BaseLayers)
                        >
                            "Base Layers"
                        </button>
                        <button
                            type="button"
                            role="tab"
                            aria-selected=move || active_tab.get() == EditorTab::Emotions
                            class=move || format!(
                                "px-4 py-2 font-medium transition-colors border-b-2 -mb-px {}",
                                if active_tab.get() == EditorTab::Emotions {
                                    "text-blue-400 border-blue-400"
                                } else {
                                    "text-gray-400 border-transparent hover:text-gray-300"
                                }
                            )
                            on:click=move |_| set_active_tab.set(EditorTab::Emotions)
                        >
                            "Emotions"
                        </button>
                    </div>

                    // Main content - two columns
                    <div class="flex-1 flex overflow-hidden p-4 gap-4">
                        // Left column - Navigation + Grid
                        <div class="flex gap-4 min-w-0">
                            // Left navigation
                            <div class="w-28 flex flex-col gap-1">
                                <Show when=move || active_tab.get() == EditorTab::BaseLayers>
                                    {[BaseLayer::Skin, BaseLayer::Clothes, BaseLayer::Accessories]
                                        .into_iter()
                                        .map(|layer| {
                                            let is_selected = move || selected_layer.get() == layer;
                                            view! {
                                                <button
                                                    type="button"
                                                    class=move || format!(
                                                        "px-3 py-2 text-left text-sm rounded transition-colors {}",
                                                        if is_selected() {
                                                            "bg-blue-600 text-white"
                                                        } else {
                                                            "text-gray-400 hover:bg-gray-700 hover:text-white"
                                                        }
                                                    )
                                                    on:click=move |_| on_layer_click(layer)
                                                >
                                                    {layer.display_name()}
                                                </button>
                                            }
                                        })
                                        .collect_view()}
                                </Show>
                                <Show when=move || active_tab.get() == EditorTab::Emotions>
                                    <div class="overflow-y-auto max-h-64">
                                        {EMOTIONS
                                            .iter()
                                            .enumerate()
                                            .map(|(idx, name)| {
                                                let is_selected = move || selected_emotion.get() == idx;
                                                let av = avatar.clone();
                                                let is_available = move || {
                                                    av.get().map(|a| a.emotions_available[idx]).unwrap_or(false)
                                                };
                                                view! {
                                                    <button
                                                        type="button"
                                                        class=move || format!(
                                                            "px-3 py-1.5 text-left text-sm rounded transition-colors {}",
                                                            if is_selected() {
                                                                "bg-blue-600 text-white"
                                                            } else if is_available() {
                                                                "text-gray-300 hover:bg-gray-700 hover:text-white"
                                                            } else {
                                                                "text-gray-500 hover:bg-gray-700"
                                                            }
                                                        )
                                                        on:click=move |_| on_emotion_click(idx)
                                                    >
                                                        {*name}
                                                    </button>
                                                }
                                            })
                                            .collect_view()}
                                    </div>
                                </Show>
                            </div>

                            // 3x3 Grid / Rendered Preview
                            <div class="flex flex-col items-center">
                                // Rendered preview (when display_rendered is true)
                                <Show when=move || display_rendered.get()>
                                    <RenderedPreview avatar=avatar.clone() />
                                </Show>

                                // Regular 3x3 grid (when display_rendered is false)
                                <Show when=move || !display_rendered.get()>
                                <div
                                    class="grid grid-cols-3 gap-1 bg-gray-900 p-2 rounded-lg"
                                    role="grid"
                                    aria-label="Avatar grid"
                                >
                                    {(0..9)
                                        .map(|pos| {
                                            let grid = current_grid.clone();
                                            let path = move || grid.get()[pos].clone();
                                            let is_hover = move || hover_cell.get() == Some(pos);

                                            view! {
                                                <div
                                                    class=move || format!(
                                                        "w-16 h-16 border-2 rounded flex items-center justify-center transition-all {}",
                                                        if is_hover() {
                                                            "border-blue-400 bg-blue-900/30"
                                                        } else if path().is_some() {
                                                            "border-gray-600 bg-gray-700"
                                                        } else {
                                                            "border-gray-700 border-dashed bg-gray-800"
                                                        }
                                                    )
                                                    on:dragover=move |ev: web_sys::DragEvent| {
                                                        ev.prevent_default();
                                                        set_hover_cell.set(Some(pos));
                                                    }
                                                    on:dragleave=move |_| {
                                                        set_hover_cell.set(None);
                                                    }
                                                    on:drop=move |ev: web_sys::DragEvent| {
                                                        ev.prevent_default();
                                                        set_hover_cell.set(None);

                                                        #[cfg(feature = "hydrate")]
                                                        {
                                                            use gloo_net::http::Request;
                                                            use leptos::task::spawn_local;

                                                            if let Some(item_id) = dragging_item.get() {
                                                                let slug = realm_slug.get();
                                                                let layer = get_current_layer_name();
                                                                let position = pos as u8;
                                                                let on_update = on_avatar_update.clone();

                                                                set_saving.set(true);

                                                                spawn_local(async move {
                                                                    let body = serde_json::json!({
                                                                        "inventory_item_id": item_id,
                                                                        "layer": layer,
                                                                        "position": position,
                                                                    });

                                                                    let result = Request::put(&format!("/api/realms/{}/avatar/slot", slug))
                                                                        .header("Content-Type", "application/json")
                                                                        .body(body.to_string())
                                                                        .unwrap()
                                                                        .send()
                                                                        .await;

                                                                    if let Ok(resp) = result {
                                                                        if resp.ok() {
                                                                            if let Ok(avatar) = resp.json::<AvatarWithPaths>().await {
                                                                                on_update.run(avatar);
                                                                            }
                                                                        }
                                                                    }
                                                                    set_saving.set(false);
                                                                });
                                                            }
                                                        }
                                                    }
                                                    on:contextmenu=move |ev: web_sys::MouseEvent| {
                                                        ev.prevent_default();
                                                        if path().is_some() {
                                                            set_context_menu.set(Some(ContextMenuState {
                                                                x: ev.client_x(),
                                                                y: ev.client_y(),
                                                                position: pos as u8,
                                                            }));
                                                        }
                                                    }
                                                    role="gridcell"
                                                    aria-label=move || format!("Position {}", pos)
                                                >
                                                    {move || {
                                                        path().map(|p| {
                                                            let asset_path = if p.starts_with('/') {
                                                                p.clone()
                                                            } else {
                                                                format!("/static/{}", p)
                                                            };
                                                            view! {
                                                                <img
                                                                    src=asset_path
                                                                    alt=""
                                                                    class="w-full h-full object-contain"
                                                                />
                                                            }
                                                        })
                                                    }}
                                                </div>
                                            }
                                        })
                                        .collect_view()}
                                </div>
                                </Show>

                                // Display Rendered toggle
                                <label class="flex items-center gap-2 mt-3 cursor-pointer">
                                    <input
                                        type="checkbox"
                                        class="w-4 h-4 rounded bg-gray-700 border-gray-600 text-blue-500 focus:ring-blue-500"
                                        prop:checked=move || display_rendered.get()
                                        on:change=move |ev: web_sys::Event| {
                                            use leptos::wasm_bindgen::JsCast;
                                            let checked = ev
                                                .target()
                                                .and_then(|t| t.dyn_into::<web_sys::HtmlInputElement>().ok())
                                                .map(|el| el.checked())
                                                .unwrap_or(false);
                                            set_display_rendered.set(checked);
                                        }
                                    />
                                    <span class="text-sm text-gray-400">"Display Rendered"</span>
                                </label>
                            </div>
                        </div>

                        // Sync button to broadcast avatar changes
                        <div class="flex justify-end mt-4">
                            <button
                                type="button"
                                class="px-4 py-2 bg-green-600 hover:bg-green-500 text-white rounded transition-colors font-medium"
                                on:click=move |_| {
                                    #[cfg(feature = "hydrate")]
                                    {
                                        #[cfg(debug_assertions)]
                                        web_sys::console::log_1(&"[AvatarEditor] Sync button clicked".into());
                                        ws_sender.with_value(|sender| {
                                            match sender {
                                                Some(send_fn) => {
                                                    #[cfg(debug_assertions)]
                                                    web_sys::console::log_1(&"[AvatarEditor] Sending SyncAvatar".into());
                                                    send_fn(ClientMessage::SyncAvatar);
                                                }
                                                None => {
                                                    #[cfg(debug_assertions)]
                                                    web_sys::console::error_1(&"[AvatarEditor] ERROR: ws_sender is None!".into());
                                                }
                                            }
                                        });
                                    }
                                }
                            >
                                "Save & Sync"
                            </button>
                        </div>

                        // Right column - Inventory
                        <div class="flex-1 flex flex-col min-w-0 border-l border-gray-700 pl-4">
                            // Inventory sub-tabs
                            <div class="flex gap-2 mb-3">
                                <button
                                    type="button"
                                    class=move || format!(
                                        "px-3 py-1 text-sm rounded-full transition-colors {}",
                                        if inventory_tab.get() == InventoryTab::Suggested {
                                            "bg-blue-600 text-white"
                                        } else {
                                            "bg-gray-700 text-gray-400 hover:text-white"
                                        }
                                    )
                                    on:click=move |_| set_inventory_tab.set(InventoryTab::Suggested)
                                >
                                    "Suggested"
                                </button>
                                <button
                                    type="button"
                                    class=move || format!(
                                        "px-3 py-1 text-sm rounded-full transition-colors {}",
                                        if inventory_tab.get() == InventoryTab::AllProps {
                                            "bg-blue-600 text-white"
                                        } else {
                                            "bg-gray-700 text-gray-400 hover:text-white"
                                        }
                                    )
                                    on:click=move |_| set_inventory_tab.set(InventoryTab::AllProps)
                                >
                                    "All Props"
                                </button>
                            </div>

                            // Inventory grid
                            <div class="flex-1 overflow-y-auto">
                                <Show when=move || inventory_loading.get()>
                                    <div class="flex items-center justify-center py-8">
                                        <p class="text-gray-400">"Loading props..."</p>
                                    </div>
                                </Show>

                                <Show when=move || !inventory_loading.get() && get_filtered_inventory().is_empty()>
                                    <div class="flex flex-col items-center justify-center py-8 text-center">
                                        <p class="text-gray-400">"No props available"</p>
                                        <p class="text-gray-500 text-sm mt-1">"Drag props here to customize your avatar"</p>
                                    </div>
                                </Show>

                                <Show when=move || !inventory_loading.get() && !get_filtered_inventory().is_empty()>
                                    <div
                                        class="grid grid-cols-4 gap-2"
                                        role="listbox"
                                        aria-label="Available props"
                                    >
                                        <For
                                            each=move || get_filtered_inventory()
                                            key=|item| item.id
                                            children=move |item: InventoryItem| {
                                                let item_id = item.id;
                                                let item_name = item.prop_name.clone();
                                                let asset_path = if item.prop_asset_path.starts_with('/') {
                                                    item.prop_asset_path.clone()
                                                } else {
                                                    format!("/static/{}", item.prop_asset_path)
                                                };

                                                view! {
                                                    <div
                                                        class="aspect-square rounded-lg border-2 border-gray-600 hover:border-gray-500 bg-gray-700/50 p-1 cursor-grab transition-all"
                                                        draggable="true"
                                                        on:dragstart=move |_ev: web_sys::DragEvent| {
                                                            set_dragging_item.set(Some(item_id));
                                                        }
                                                        on:dragend=move |_| {
                                                            set_dragging_item.set(None);
                                                            set_hover_cell.set(None);
                                                        }
                                                        role="option"
                                                        aria-label=item_name
                                                    >
                                                        <img
                                                            src=asset_path
                                                            alt=""
                                                            class="w-full h-full object-contain pointer-events-none"
                                                        />
                                                    </div>
                                                }
                                            }
                                        />
                                    </div>
                                </Show>
                            </div>
                        </div>
                    </div>

                    // Guest locked overlay
                    <Show when=move || is_guest.get()>
                        <GuestLockedOverlay />
                    </Show>
                </div>

                // Context menu
                {move || {
                    context_menu.get().map(|menu| {
                        let layer_name = match active_tab.get() {
                            EditorTab::BaseLayers => selected_layer.get().display_name().to_lowercase(),
                            EditorTab::Emotions => EMOTIONS[selected_emotion.get()].to_lowercase(),
                        };
                        let layer_for_api = get_current_layer_name();
                        let position = menu.position;

                        view! {
                            <div
                                class="fixed bg-gray-900 border border-gray-600 rounded-lg shadow-xl py-1 z-[60]"
                                style=move || format!("left: {}px; top: {}px;", menu.x, menu.y)
                            >
                                <button
                                    type="button"
                                    class="w-full px-4 py-2 text-left text-sm text-red-400 hover:bg-gray-800 transition-colors"
                                    on:click=move |_| {
                                        set_context_menu.set(None);

                                        #[cfg(feature = "hydrate")]
                                        {
                                            use gloo_net::http::Request;
                                            use leptos::task::spawn_local;

                                            let slug = realm_slug.get();
                                            let layer = layer_for_api.clone();
                                            let on_update = on_avatar_update.clone();

                                            set_saving.set(true);

                                            spawn_local(async move {
                                                let body = serde_json::json!({
                                                    "layer": layer,
                                                    "position": position,
                                                });

                                                let result = Request::delete(&format!("/api/realms/{}/avatar/slot", slug))
                                                    .header("Content-Type", "application/json")
                                                    .body(body.to_string())
                                                    .unwrap()
                                                    .send()
                                                    .await;

                                                if let Ok(resp) = result {
                                                    if resp.ok() {
                                                        if let Ok(avatar) = resp.json::<AvatarWithPaths>().await {
                                                            on_update.run(avatar);
                                                        }
                                                    }
                                                }
                                                set_saving.set(false);
                                            });
                                        }
                                    }
                                >
                                    {format!("Clear prop from {} layer", layer_name)}
                                </button>
                            </div>
                        }
                    })
                }}
            </div>
        </Show>
    }
}
