"""
Inventory UI for STARWASTE
Visual overlay for displaying and managing inventory
"""

import arcade
from arcade.types import Color
from typing import Optional, Tuple
import sys
import os

from PIL import Image, ImageDraw

# Add parent directory to path
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from shared.inventory import Inventory, ItemStack, EquipmentSlot
from shared.items import get_item
from shared.constants import SCREEN_WIDTH, SCREEN_HEIGHT, COLORS

from client.i18n import get_i18n


class InventoryUI:
    """
    Inventory UI overlay with drag & drop support.
    
    Layout:
    - Left side: Equipment slots (8 slots)
    - Center: Main inventory grid (40 slots, 8x5)
    - Bottom: Hotbar (10 slots, always visible)
    """
    
    def __init__(self, inventory: Inventory, window=None):
        self.inventory = inventory
        self.window = window  # Reference to window for dynamic positioning
        self.visible = False

        self.i18n = get_i18n()
        
        # Rendering mode:
        # - Textured UI (default): panels/slot frames are pre-rendered once to a texture and drawn as sprites.
        #   Much less CPU/driver overhead (especially on macOS).
        # - Shape cache (fallback): ShapeElementList geometry.
        self.use_texture_ui = True
        self._shape_cache = None
        self._shape_cache_dirty = True

        self._ui_textures_dirty = True
        self._inv_panel_texture: Optional[arcade.Texture] = None
        self._equip_panel_texture: Optional[arcade.Texture] = None
        self._hotbar_texture: Optional[arcade.Texture] = None
        self._inv_panel_sprite: Optional[arcade.Sprite] = None
        self._equip_panel_sprite: Optional[arcade.Sprite] = None
        self._hotbar_sprite: Optional[arcade.Sprite] = None
        
        # 🔥 NEW: Frame throttling - update only every N frames
        self._frame_counter = 0
        self._update_interval = 5  # Update every 5 frames (12Hz at 60fps)
        
        # 🔥 NEW: Cache for quantity Text objects
        self._quantity_text_cache = {}  # {slot_index: Text object}
        
        # UI positioning
        self.slot_size = 50
        self.slot_padding = 5
        self.panel_padding = 20
        
        # Inventory layout: 10 columns (matches hotbar) x 4 rows
        self.inv_cols = 10  # Changed from 8 to 10
        self.inv_rows = 4   # Changed from 5 to 4 (still 40 slots total)
        
        # These will be calculated dynamically based on window size
        self.inv_width = self.inv_cols * (self.slot_size + self.slot_padding) + self.panel_padding * 2
        self.inv_height = self.inv_rows * (self.slot_size + self.slot_padding) + self.panel_padding * 2 + 50
        
        # Hotbar (bottom, always visible) - 10 slots
        self.hotbar_height = self.slot_size + 20
        self.hotbar_y = 20
        
        # Update positions based on window size
        self.update_positions()
        
        # Equipment panel (left side, 2 columns x 4 rows)
        self.equip_cols = 2
        self.equip_rows = 4
        self.equip_width = self.equip_cols * (self.slot_size + self.slot_padding) + self.panel_padding * 2 + 80  # Extra space for labels
        self.equip_height = self.inv_height
        self.equip_x = self.panel_x - self.equip_width - 20
        self.equip_y = self.panel_y
        
        # Drag & drop
        self.dragging_slot: Optional[Tuple[str, int]] = None  # ("main", slot_index) or ("equip", EquipmentSlot)
        self.dragging_stack: Optional[ItemStack] = None
        self.drag_offset_x = 0
        self.drag_offset_y = 0

        # Current mouse position (screen coords)
        self.mouse_x = 0
        self.mouse_y = 0

        # Hover tooltip (item name)
        self._hover_item_name: Optional[str] = None
        self._tooltip_text: Optional[arcade.Text] = None
        self._tooltip_shadow_text: Optional[arcade.Text] = None

        # Optional callback to request server-authoritative inventory changes
        # Signature: callback(src_type, src_slot, dst_type, dst_slot)
        self.move_request_callback = None
        # Optional callback to request dropping a stack to the ground
        # Signature: callback(src_type, src_slot)
        self.drop_request_callback = None
        
        # Colors
        self.bg_color = Color(20, 20, 25, 230)  # Semi-transparent dark
        self.slot_color = Color(40, 40, 50, 255)
        self.slot_hover_color = Color(60, 60, 70, 255)
        self.slot_selected_color = Color(80, 100, 120, 255)
        self.text_color = arcade.color.WHITE
        self.text_color_gray = Color(150, 150, 150, 255)
        
        # Title
        self.title_color = Color.from_hex_string(COLORS["rust_orange"])

    def _invalidate_ui_cache(self):
        self._shape_cache_dirty = True
        self._ui_textures_dirty = True

    def _ensure_ui_textures(self):
        """Build/update textured UI sprites (one-time or on resize/layout changes)."""
        if not self.use_texture_ui:
            return
        if not self._ui_textures_dirty and self._inv_panel_sprite and self._hotbar_sprite:
            return

        # Build textures once. Sizes are layout-dependent, not window-dependent.
        self._inv_panel_texture = self._build_inventory_panel_texture()
        self._equip_panel_texture = self._build_equipment_panel_texture()
        self._hotbar_texture = self._build_hotbar_texture()

        # Create/update sprites
        if self._inv_panel_sprite is None:
            self._inv_panel_sprite = arcade.Sprite()
        if self._equip_panel_sprite is None:
            self._equip_panel_sprite = arcade.Sprite()
        if self._hotbar_sprite is None:
            self._hotbar_sprite = arcade.Sprite()

        self._inv_panel_sprite.texture = self._inv_panel_texture
        self._equip_panel_sprite.texture = self._equip_panel_texture
        self._hotbar_sprite.texture = self._hotbar_texture

        # Position sprites
        self._inv_panel_sprite.center_x = self.panel_x + self.inv_width / 2
        self._inv_panel_sprite.center_y = self.panel_y + self.inv_height / 2

        self._equip_panel_sprite.center_x = self.equip_x + self.equip_width / 2
        self._equip_panel_sprite.center_y = self.equip_y + self.equip_height / 2

        hotbar_width = self.inventory.hotbar_size * (self.slot_size + self.slot_padding) + 20
        self._hotbar_sprite.center_x = self.hotbar_x + hotbar_width / 2 - 10
        self._hotbar_sprite.center_y = self.hotbar_y + (self.slot_size + 20) / 2 - 10

        self._ui_textures_dirty = False

    def _build_inventory_panel_texture(self) -> arcade.Texture:
        """Pre-render main inventory panel + slot frames into a single texture."""
        w = int(self.inv_width)
        h = int(self.inv_height)
        img = Image.new("RGBA", (w, h), (0, 0, 0, 0))
        draw = ImageDraw.Draw(img)

        def rect_arcade(x0: int, y0: int, x1: int, y1: int, *, fill=None, outline=None, width: int = 1):
            """Draw rectangle using Arcade-like local coords (origin bottom-left) into PIL (origin top-left)."""
            # Convert to PIL coords (top-left origin). PIL's rectangle uses inclusive bounds.
            pil_y0 = h - y1
            pil_y1 = h - y0
            draw.rectangle([x0, pil_y0, x1, pil_y1], fill=fill, outline=outline, width=width)

        # Panel background + border
        bg = (self.bg_color.r, self.bg_color.g, self.bg_color.b, self.bg_color.a)
        border = (self.text_color_gray.r, self.text_color_gray.g, self.text_color_gray.b, 220)
        rect_arcade(0, 0, w - 1, h - 1, fill=bg, outline=border, width=2)

        # Slot grid frames (subtle; baked once)
        start_x = int(self.panel_padding)
        start_y = int(h - 70)
        fill_col = (self.slot_color.r, self.slot_color.g, self.slot_color.b, 255)
        outline_col = (self.text_color_gray.r, self.text_color_gray.g, self.text_color_gray.b, 180)

        for slot in range(self.inventory.main_slots):
            col = slot % self.inv_cols
            row = slot // self.inv_cols
            x0 = start_x + int(col * (self.slot_size + self.slot_padding))
            y0 = start_y - int(row * (self.slot_size + self.slot_padding))
            x1 = x0 + int(self.slot_size)
            y1 = y0 + int(self.slot_size)

            is_hotbar = slot < self.inventory.hotbar_size
            is_selected = is_hotbar and slot == self.inventory.selected_hotbar_slot

            if is_selected:
                sel = self.slot_selected_color
                fill = (sel.r, sel.g, sel.b, sel.a)
                outline = (255, 255, 255, 220)
            elif is_hotbar:
                fill = (50, 50, 60, 255)
                outline = outline_col
            else:
                fill = fill_col
                outline = outline_col

            rect_arcade(x0, y0, x1, y1, fill=fill, outline=outline, width=2)

        return arcade.Texture(name="ui_inventory_panel", image=img)

    def _build_equipment_panel_texture(self) -> arcade.Texture:
        """Pre-render equipment panel + slot frames into a single texture."""
        w = int(self.equip_width)
        h = int(self.equip_height)
        img = Image.new("RGBA", (w, h), (0, 0, 0, 0))
        draw = ImageDraw.Draw(img)

        def rect_arcade(x0: int, y0: int, x1: int, y1: int, *, fill=None, outline=None, width: int = 1):
            pil_y0 = h - y1
            pil_y1 = h - y0
            draw.rectangle([x0, pil_y0, x1, pil_y1], fill=fill, outline=outline, width=width)

        bg = (self.bg_color.r, self.bg_color.g, self.bg_color.b, self.bg_color.a)
        border = (self.text_color_gray.r, self.text_color_gray.g, self.text_color_gray.b, 220)
        rect_arcade(0, 0, w - 1, h - 1, fill=bg, outline=border, width=2)

        fill_col = (self.slot_color.r, self.slot_color.g, self.slot_color.b, 255)
        outline_col = (self.text_color_gray.r, self.text_color_gray.g, self.text_color_gray.b, 180)

        equipment_layout = [
            (EquipmentSlot.HELMET, 0, 0), (EquipmentSlot.CHEST, 0, 1),
            (EquipmentSlot.LEGS, 0, 2), (EquipmentSlot.WEAPON, 0, 3),
            (EquipmentSlot.TOOL, 1, 0), (EquipmentSlot.ACCESSORY, 1, 1),
            (EquipmentSlot.JETPACK, 1, 2), (EquipmentSlot.SHIELD, 1, 3),
        ]

        start_x = int(self.panel_padding)
        start_y = int(h - 80)
        for slot_type, col, row in equipment_layout:
            x0 = start_x + int(col * (self.slot_size + 70))
            y0 = start_y - int(row * (self.slot_size + 10))
            x1 = x0 + int(self.slot_size)
            y1 = y0 + int(self.slot_size)
            rect_arcade(x0, y0, x1, y1, fill=fill_col, outline=outline_col, width=2)

        return arcade.Texture(name="ui_equipment_panel", image=img)

    def _build_hotbar_texture(self) -> arcade.Texture:
        """Pre-render hotbar background + slot frames into a single texture."""
        hotbar_width = int(self.inventory.hotbar_size * (self.slot_size + self.slot_padding) + 20)
        hotbar_height = int(self.slot_size + 20)
        img = Image.new("RGBA", (hotbar_width, hotbar_height), (0, 0, 0, 0))
        draw = ImageDraw.Draw(img)

        def rect_arcade(x0: int, y0: int, x1: int, y1: int, *, fill=None, outline=None, width: int = 1):
            pil_y0 = hotbar_height - y1
            pil_y1 = hotbar_height - y0
            draw.rectangle([x0, pil_y0, x1, pil_y1], fill=fill, outline=outline, width=width)

        bg_alpha = 230 if self.visible else 180
        bg = (20, 20, 25, bg_alpha)
        border = (self.text_color_gray.r, self.text_color_gray.g, self.text_color_gray.b, 220)
        rect_arcade(0, 0, hotbar_width - 1, hotbar_height - 1, fill=bg, outline=border, width=2)

        fill_col = (self.slot_color.r, self.slot_color.g, self.slot_color.b, 255)
        outline_col = (self.text_color_gray.r, self.text_color_gray.g, self.text_color_gray.b, 180)
        sel = self.slot_selected_color
        sel_fill = (sel.r, sel.g, sel.b, sel.a)

        # Slots are drawn inside a 10px padding box in the current layout
        pad_x = 10
        pad_y = 10
        for i in range(self.inventory.hotbar_size):
            x0 = pad_x + int(i * (self.slot_size + self.slot_padding))
            y0 = pad_y
            x1 = x0 + int(self.slot_size)
            y1 = y0 + int(self.slot_size)
            is_selected = i == self.inventory.selected_hotbar_slot
            fill = sel_fill if is_selected else fill_col
            outline = (255, 255, 255, 220) if is_selected else outline_col
            rect_arcade(x0, y0, x1, y1, fill=fill, outline=outline, width=2)

        return arcade.Texture(name="ui_hotbar", image=img)
    
    def _draw_hotbar_content(self, should_update=True):
        """Draw hotbar numbers and items (cannot batch these)"""
        for i in range(self.inventory.hotbar_size):
            slot_x = self.hotbar_x + i * (self.slot_size + self.slot_padding)
            slot_y = self.hotbar_y
            
            # 🔥 OPTIMIZED: Cache hotbar number text objects
            if not hasattr(self, '_hotbar_num_texts'):
                self._hotbar_num_texts = {}
            if not hasattr(self, '_hotbar_num_shadow_texts'):
                self._hotbar_num_shadow_texts = {}
            
            num = (i + 1) % 10
            if i not in self._hotbar_num_texts:
                self._hotbar_num_texts[i] = arcade.Text(
                    str(num), slot_x + 5, slot_y + self.slot_size - 15,
                    self.text_color_gray, 10
                )
                self._hotbar_num_shadow_texts[i] = arcade.Text(
                    str(num), slot_x + 6, slot_y + self.slot_size - 16,
                    (0, 0, 0, 210), 10
                )
            else:
                # Update position
                self._hotbar_num_texts[i].x = slot_x + 5
                self._hotbar_num_texts[i].y = slot_y + self.slot_size - 15
                self._hotbar_num_shadow_texts[i].x = slot_x + 6
                self._hotbar_num_shadow_texts[i].y = slot_y + self.slot_size - 16

            # Improve readability on selected slot background
            is_selected = i == self.inventory.selected_hotbar_slot
            self._hotbar_num_texts[i].color = arcade.color.WHITE if is_selected else self.text_color_gray
            # Keep shadow in sync if text changes (should be stable)
            if self._hotbar_num_shadow_texts[i].text != self._hotbar_num_texts[i].text:
                self._hotbar_num_shadow_texts[i].text = self._hotbar_num_texts[i].text

            self._hotbar_num_shadow_texts[i].draw()
            
            self._hotbar_num_texts[i].draw()
            
            # Draw item (always draw, throttling is internal in draw_item_stack)
            stack = self.inventory.get_hotbar_slot(i)
            if stack:
                self.draw_item_stack(slot_x, slot_y, stack, f"hotbar_{i}", small=True, should_update=should_update)
    
    def _rebuild_shape_cache(self):
        """🔥 Rebuild shape cache - all rectangles in ONE batch!"""
        from arcade import shape_list
        
        shapes = shape_list.ShapeElementList()
        
        if self.visible:
            # Equipment panel background
            shapes.append(shape_list.create_rectangle_filled(
                self.equip_x + self.equip_width / 2,
                self.equip_y + self.equip_height / 2,
                self.equip_width, self.equip_height,
                self.bg_color
            ))
            
            # Main inventory panel background
            shapes.append(shape_list.create_rectangle_filled(
                self.panel_x + self.inv_width / 2,
                self.panel_y + self.inv_height / 2,
                self.inv_width, self.inv_height,
                self.bg_color
            ))
            
            # All inventory slot backgrounds (40 slots)
            start_x = self.panel_x + self.panel_padding
            start_y = self.panel_y + self.inv_height - 70
            
            for slot in range(self.inventory.main_slots):
                col = slot % self.inv_cols
                row = slot // self.inv_cols
                slot_x = start_x + col * (self.slot_size + self.slot_padding)
                slot_y = start_y - row * (self.slot_size + self.slot_padding)
                
                is_hotbar = slot < self.inventory.hotbar_size
                is_selected = is_hotbar and slot == self.inventory.selected_hotbar_slot
                
                if is_selected:
                    color = self.slot_selected_color
                elif is_hotbar:
                    color = Color(50, 50, 60, 255)
                else:
                    color = self.slot_color
                
                # Filled rectangle
                shapes.append(shape_list.create_rectangle_filled(
                    slot_x + self.slot_size / 2, slot_y + self.slot_size / 2,
                    self.slot_size, self.slot_size, color
                ))
                # Outline
                shapes.append(shape_list.create_rectangle_outline(
                    slot_x + self.slot_size / 2, slot_y + self.slot_size / 2,
                    self.slot_size, self.slot_size, self.text_color_gray, 2
                ))
            
            # Equipment slots (8 slots)
            equipment_layout = [
                (EquipmentSlot.HELMET, 0, 0), (EquipmentSlot.CHEST, 0, 1),
                (EquipmentSlot.LEGS, 0, 2), (EquipmentSlot.WEAPON, 0, 3),
                (EquipmentSlot.TOOL, 1, 0), (EquipmentSlot.ACCESSORY, 1, 1),
                (EquipmentSlot.JETPACK, 1, 2), (EquipmentSlot.SHIELD, 1, 3),
            ]
            start_x = self.equip_x + self.panel_padding
            start_y = self.equip_y + self.equip_height - 80
            
            for slot_type, col, row in equipment_layout:
                slot_x = start_x + col * (self.slot_size + 70)
                slot_y = start_y - row * (self.slot_size + 10)
                
                shapes.append(shape_list.create_rectangle_filled(
                    slot_x + self.slot_size / 2, slot_y + self.slot_size / 2,
                    self.slot_size, self.slot_size, self.slot_color
                ))
                shapes.append(shape_list.create_rectangle_outline(
                    slot_x + self.slot_size / 2, slot_y + self.slot_size / 2,
                    self.slot_size, self.slot_size, self.text_color_gray, 2
                ))
        
        # Hotbar (always visible)
        hotbar_shapes = shape_list.ShapeElementList()
        hotbar_width = self.inventory.hotbar_size * (self.slot_size + self.slot_padding) + 20
        bg_alpha = 230 if self.visible else 180
        
        hotbar_shapes.append(shape_list.create_rectangle_filled(
            self.hotbar_x + hotbar_width / 2 - 10,
            self.hotbar_y + (self.slot_size + 20) / 2 - 10,
            hotbar_width, self.slot_size + 20,
            Color(20, 20, 25, bg_alpha)
        ))
        
        for i in range(self.inventory.hotbar_size):
            slot_x = self.hotbar_x + i * (self.slot_size + self.slot_padding)
            slot_y = self.hotbar_y
            is_selected = i == self.inventory.selected_hotbar_slot
            color = self.slot_selected_color if is_selected else self.slot_color
            
            hotbar_shapes.append(shape_list.create_rectangle_filled(
                slot_x + self.slot_size / 2, slot_y + self.slot_size / 2,
                self.slot_size, self.slot_size, color
            ))
            hotbar_shapes.append(shape_list.create_rectangle_outline(
                slot_x + self.slot_size / 2, slot_y + self.slot_size / 2,
                self.slot_size, self.slot_size, self.text_color_gray, 2
            ))
        
        self._shape_cache = shapes
        self._hotbar_shapes = hotbar_shapes
    
    def update_positions(self):
        """Update UI positions based on window size (like PixelPiratesBoom)"""
        if self.window:
            screen_w = self.window.width
            screen_h = self.window.height
        else:
            screen_w = SCREEN_WIDTH
            screen_h = SCREEN_HEIGHT
        
        # Hotbar - centered at bottom (fixed position)
        self.hotbar_x = (screen_w - (self.inventory.hotbar_size * (self.slot_size + self.slot_padding))) // 2
        
        # Main inventory panel (center, above hotbar)
        self.panel_x = (screen_w - self.inv_width) // 2
        self.panel_y = self.hotbar_y + self.hotbar_height + 30  # 30px gap above hotbar
        
        # Mark caches as dirty when positions change
        self._invalidate_ui_cache()
    
    def toggle(self):
        """Toggle inventory visibility"""
        self.visible = not self.visible
        self._invalidate_ui_cache()  # Rebuild caches when visibility changes
    
    def open(self):
        """Open inventory"""
        self.visible = True
        self._invalidate_ui_cache()
    
    def close(self):
        """Close inventory"""
        self.visible = False
        self._invalidate_ui_cache()
    
    def draw(self):
        """Draw inventory UI"""
        # Keep tooltip state fresh even if mouse didn't move.
        self._update_hover_tooltip()

        # 🔥 FRAME THROTTLING: Update content only every N frames
        self._frame_counter += 1
        should_update = (self._frame_counter % self._update_interval == 0)
        
        # Build UI backing only when needed
        if self.use_texture_ui:
            self._ensure_ui_textures()
        else:
            # ShapeElementList fallback
            if self._shape_cache_dirty or self._shape_cache is None:
                self._rebuild_shape_cache()
                self._shape_cache_dirty = False
        
        if not self.visible:
            # Draw only hotbar backing
            if self.use_texture_ui:
                if self._hotbar_sprite:
                    arcade.draw_sprite(self._hotbar_sprite)
            else:
                if hasattr(self, '_hotbar_shapes'):
                    self._hotbar_shapes.draw()
            # Draw hotbar text and items
            self._draw_hotbar_content(should_update)
            # Draw hover tooltip on top
            self._draw_hover_tooltip()
            return
        
        # Draw inventory/equipment backing
        if self.use_texture_ui:
            if self._equip_panel_sprite:
                arcade.draw_sprite(self._equip_panel_sprite)
            if self._inv_panel_sprite:
                arcade.draw_sprite(self._inv_panel_sprite)
            if self._hotbar_sprite:
                arcade.draw_sprite(self._hotbar_sprite)
        else:
            if self._shape_cache:
                self._shape_cache.draw()
            if hasattr(self, '_hotbar_shapes'):
                self._hotbar_shapes.draw()
        
        # Draw text and items (already optimized with caching)
        self.draw_equipment_panel(should_update)
        self.draw_main_inventory_panel(should_update)
        self._draw_hotbar_content(should_update)
        
        # Draw dragging item on top
        if self.dragging_stack:
            self.draw_dragging_item()

        # Tooltip should be above everything (but hidden while dragging)
        self._draw_hover_tooltip()

    def _get_hovered_stack(self, x: int, y: int) -> Optional[ItemStack]:
        """Return the ItemStack currently under the cursor (hotbar always, inventory/equip when open)."""
        # Hotbar is always visible
        hotbar_slot = self._get_hotbar_slot_at(x, y)
        if hotbar_slot is not None:
            return self.inventory.get_item_stack(hotbar_slot)

        if not self.visible:
            return None

        equip_slot = self._get_equipment_slot_at(x, y)
        if equip_slot is not None:
            return self.inventory.get_equipped(equip_slot)

        main_slot = self._get_main_slot_at(x, y)
        if main_slot is not None:
            return self.inventory.get_item_stack(main_slot)

        return None

    def _update_hover_tooltip(self):
        """Update hover tooltip text based on current mouse position."""
        # Hide tooltip while dragging
        if self.dragging_stack is not None:
            self._hover_item_name = None
            return

        stack = self._get_hovered_stack(self.mouse_x, self.mouse_y)
        if not stack or not getattr(stack, "item", None):
            self._hover_item_name = None
            return

        item = stack.item
        item_id = getattr(item, "item_id", None)
        fallback_name = getattr(item, "name", None) or item_id or None
        if item_id:
            name = self.i18n.t(f"items.{item_id}.name", default=str(fallback_name) if fallback_name else None)
        else:
            name = fallback_name
        if not name:
            self._hover_item_name = None
            return

        self._hover_item_name = str(name)

    def _draw_hover_tooltip(self):
        """Draw a simple name tooltip near the cursor."""
        name = self._hover_item_name
        if not name:
            return

        # Position near cursor, with a small offset
        x = int(self.mouse_x + 14)
        y = int(self.mouse_y + 18)

        if self._tooltip_text is None:
            self._tooltip_text = arcade.Text(
                name,
                x,
                y,
                arcade.color.WHITE,
                12,
                anchor_x="left",
                anchor_y="bottom",
                bold=True,
            )
            self._tooltip_shadow_text = arcade.Text(
                name,
                x + 1,
                y - 1,
                (0, 0, 0, 220),
                12,
                anchor_x="left",
                anchor_y="bottom",
                bold=True,
            )
        else:
            self._tooltip_text.text = name
            self._tooltip_text.x = x
            self._tooltip_text.y = y
            if self._tooltip_shadow_text is not None:
                self._tooltip_shadow_text.text = name
                self._tooltip_shadow_text.x = x + 1
                self._tooltip_shadow_text.y = y - 1

        if self._tooltip_shadow_text is not None:
            self._tooltip_shadow_text.draw()
        self._tooltip_text.draw()
    
    def draw_equipment_panel(self, should_update=True):
        """Draw equipment panel"""
        # Background + slot frames are drawn via cached ShapeElementList in _rebuild_shape_cache()
        # Keep this method for dynamic content only (text + items).

        # 🔥 OPTIMIZED: Cache title text object
        if not hasattr(self, '_equip_title_text'):
            self._equip_title_text = arcade.Text(
                self.i18n.t("ui.inventory.equipment_title", default="EQUIPMENT"),
                self.equip_x + self.equip_width // 2, self.equip_y + self.equip_height - 30,
                self.title_color, 16, anchor_x="center", bold=True
            )
        else:
            # Update position in case window resized
            self._equip_title_text.text = self.i18n.t("ui.inventory.equipment_title", default="EQUIPMENT")
            self._equip_title_text.x = self.equip_x + self.equip_width // 2
            self._equip_title_text.y = self.equip_y + self.equip_height - 30
        self._equip_title_text.draw()
        
        # Equipment slots in 2 columns x 4 rows
        equipment_layout = [
            # Column 1 (left)
            (EquipmentSlot.HELMET, self.i18n.t("ui.equipment.head", default="Head"), 0, 0),
            (EquipmentSlot.CHEST, self.i18n.t("ui.equipment.chest", default="Chest"), 0, 1),
            (EquipmentSlot.LEGS, self.i18n.t("ui.equipment.legs", default="Legs"), 0, 2),
            (EquipmentSlot.WEAPON, self.i18n.t("ui.equipment.weapon", default="Weapon"), 0, 3),
            # Column 2 (right)
            (EquipmentSlot.TOOL, self.i18n.t("ui.equipment.tool", default="Tool"), 1, 0),
            (EquipmentSlot.ACCESSORY, self.i18n.t("ui.equipment.extra", default="Extra"), 1, 1),
            (EquipmentSlot.JETPACK, self.i18n.t("ui.equipment.jetpack", default="Jetpack"), 1, 2),
            (EquipmentSlot.SHIELD, self.i18n.t("ui.equipment.shield", default="Shield"), 1, 3),
        ]
        
        start_x = self.equip_x + self.panel_padding
        start_y = self.equip_y + self.equip_height - 80
        
        for slot_type, label, col, row in equipment_layout:
            slot_x = start_x + col * (self.slot_size + 70)  # 70px spacing for labels
            slot_y = start_y - row * (self.slot_size + 10)
            
            # Draw slot
            self.draw_equipment_slot(slot_x, slot_y, slot_type, label, should_update)
    
    def draw_equipment_slot(self, x: int, y: int, slot_type: EquipmentSlot, label: str, should_update=True):
        """Draw single equipment slot"""
        # Slot frames are drawn via cached ShapeElementList; draw only label + item.

        # 🔥 OPTIMIZED: Cache label text objects
        if not hasattr(self, '_equip_label_cache'):
            self._equip_label_cache = {}
        
        cache_key = slot_type
        if cache_key not in self._equip_label_cache:
            self._equip_label_cache[cache_key] = arcade.Text(
                label, x + self.slot_size + 5, y + self.slot_size // 2,
                self.text_color_gray, 10, anchor_y="center"
            )
        else:
            # Update position
            text_obj = self._equip_label_cache[cache_key]
            text_obj.text = label
            text_obj.x = x + self.slot_size + 5
            text_obj.y = y + self.slot_size // 2
        
        self._equip_label_cache[cache_key].draw()
        
        # Draw equipped item (always draw, throttling is internal)
        stack = self.inventory.get_equipped(slot_type)
        if stack:
            self.draw_item_stack(x, y, stack, f"equip_{slot_type.value}", should_update=should_update)
    
    def draw_main_inventory_panel(self, should_update=True):
        """Draw main inventory panel"""
        # Background + slot frames are drawn via cached ShapeElementList in _rebuild_shape_cache().
        # Keep this method for dynamic content only (text + items).

        # 🔥 OPTIMIZED: Cache title and stats text objects
        if not hasattr(self, '_inv_title_text'):
            self._inv_title_text = arcade.Text(
                self.i18n.t("ui.inventory.title", default="INVENTORY"),
                self.panel_x + self.inv_width // 2, self.panel_y + self.inv_height - 30,
                self.title_color, 16, anchor_x="center", bold=True
            )
            self._inv_stats_text = arcade.Text(
                "", self.panel_x + self.inv_width - 30, self.panel_y + self.inv_height - 30,
                self.text_color, 12, anchor_x="right"
            )
        else:
            # Update positions
            self._inv_title_text.text = self.i18n.t("ui.inventory.title", default="INVENTORY")
            self._inv_title_text.x = self.panel_x + self.inv_width // 2
            self._inv_title_text.y = self.panel_y + self.inv_height - 30
            self._inv_stats_text.x = self.panel_x + self.inv_width - 30
            self._inv_stats_text.y = self.panel_y + self.inv_height - 30
        
        # Update stats text
        armor = self.inventory.get_total_armor()
        self._inv_stats_text.text = self.i18n.t("ui.inventory.armor", default="Armor: {value}", value=armor)
        
        self._inv_title_text.draw()
        self._inv_stats_text.draw()
        
        # Draw inventory slots (10 columns x 4 rows) - text and items only
        start_x = self.panel_x + self.panel_padding
        start_y = self.panel_y + self.inv_height - 70
        
        for slot in range(self.inventory.main_slots):
            col = slot % self.inv_cols
            row = slot // self.inv_cols
            
            slot_x = start_x + col * (self.slot_size + self.slot_padding)
            slot_y = start_y - row * (self.slot_size + self.slot_padding)
            
            # Draw item if present (always draw, throttling is internal)
            stack = self.inventory.get_item_stack(slot)
            if stack:
                self.draw_item_stack(slot_x, slot_y, stack, f"inv_{slot}", should_update=should_update)
    

    
    def draw_item_stack(self, x: int, y: int, stack: ItemStack, slot_key: str, small: bool = False, should_update: bool = True):
        """Draw item stack in slot
        
        Args:
            slot_key: Unique identifier for this slot (e.g., 'inv_0', 'hotbar_1', 'equip_helmet')
            should_update: Whether to update cached data (throttled from parent)
        """
        item = stack.item
        if not item:
            return
        
        # 🔥 OPTIMIZED: Cache item data to avoid re-computing every frame
        if not hasattr(self, '_item_data_cache'):
            self._item_data_cache = {}
        
        # Only recompute item data when should_update=True
        if should_update or slot_key not in self._item_data_cache:
            # Color by category
            if item.category.value == "resource":
                item_color = Color(100, 100, 100, 255)  # Gray
            elif item.category.value == "weapon":
                item_color = Color.from_hex_string(COLORS["rust_orange"])
            elif item.category.value == "armor":
                item_color = Color(120, 120, 150, 255)  # Blue-ish
            elif item.category.value == "tool":
                item_color = Color(150, 120, 80, 255)  # Brown
            else:
                item_color = Color(80, 80, 80, 255)
            
            # Cache the data
            self._item_data_cache[slot_key] = {
                'color': item_color,
                'quantity': stack.quantity,
                'durability': stack.durability,
                'max_durability': stack.item.stats.max_durability if stack.item and stack.item.stats else None
            }
        
        # Use cached data
        cached = self._item_data_cache[slot_key]
        item_color = cached['color']
        
        padding = 8

        # Draw item sprite if available (and skip placeholder background)
        sprite_path = getattr(item, "sprite", None)
        drew_sprite = False
        if sprite_path:
            if not hasattr(self, "_item_texture_cache"):
                self._item_texture_cache = {}
            if not hasattr(self, "_item_sprite_cache"):
                self._item_sprite_cache = {}

            texture = self._item_texture_cache.get(sprite_path)
            if texture is None:
                try:
                    texture = arcade.load_texture(sprite_path)
                    self._item_texture_cache[sprite_path] = texture
                except Exception:
                    texture = None

            if texture is not None:
                sprite = self._item_sprite_cache.get(sprite_path)
                if sprite is None:
                    sprite = arcade.Sprite()
                    sprite.texture = texture
                    self._item_sprite_cache[sprite_path] = sprite

                icon_size = self.slot_size - padding * 2
                sprite.center_x = x + self.slot_size / 2
                sprite.center_y = y + self.slot_size / 2
                sprite.width = icon_size
                sprite.height = icon_size
                arcade.draw_sprite(sprite)
                drew_sprite = True

        # Fallback: draw colored placeholder if we didn't draw a sprite
        if not drew_sprite:
            arcade.draw_lbwh_rectangle_filled(
                x + padding, y + padding,
                self.slot_size - padding * 2, self.slot_size - padding * 2,
                item_color
            )
        
        # 🔥 OPTIMIZED: Cache quantity Text objects instead of creating new every frame
        quantity = cached['quantity']
        if quantity > 1:
            cache_key = f"{slot_key}_qty"

            if not hasattr(self, "_quantity_shadow_text_cache"):
                self._quantity_shadow_text_cache = {}
            
            if cache_key not in self._quantity_text_cache:
                self._quantity_text_cache[cache_key] = arcade.Text(
                    str(quantity),
                    x + self.slot_size - 5, y + 5,
                    self.text_color, 10 if not small else 8,
                    anchor_x="right", bold=True
                )
                self._quantity_shadow_text_cache[cache_key] = arcade.Text(
                    str(quantity),
                    x + self.slot_size - 4, y + 4,
                    (0, 0, 0, 220), 10 if not small else 8,
                    anchor_x="right", bold=True
                )
            else:
                # Update position always, text only when should_update
                text_obj = self._quantity_text_cache[cache_key]
                text_obj.x = x + self.slot_size - 5
                text_obj.y = y + 5
                shadow_obj = self._quantity_shadow_text_cache.get(cache_key)
                if shadow_obj is not None:
                    shadow_obj.x = x + self.slot_size - 4
                    shadow_obj.y = y + 4
                if should_update:
                    text_obj.text = str(quantity)
                    if shadow_obj is not None:
                        shadow_obj.text = str(quantity)
            
            shadow_obj = self._quantity_shadow_text_cache.get(cache_key)
            if shadow_obj is not None:
                shadow_obj.draw()
            self._quantity_text_cache[cache_key].draw()
        
        # Draw durability bar if applicable
        durability = cached['durability']
        max_durability = cached['max_durability']
        if durability is not None and max_durability:
            durability_percent = durability / max_durability
            bar_width = self.slot_size - padding * 2
            bar_height = 3
            
            # Background (red)
            arcade.draw_lbwh_rectangle_filled(
                x + padding, y + padding,
                bar_width, bar_height,
                Color(100, 30, 30, 255)
            )
            
            # Foreground (green to red gradient)
            if durability_percent > 0.5:
                bar_color = Color(50, 200, 50, 255)  # Green
            elif durability_percent > 0.25:
                bar_color = Color(200, 200, 50, 255)  # Yellow
            else:
                bar_color = Color(200, 50, 50, 255)  # Red
            
            arcade.draw_lbwh_rectangle_filled(
                x + padding, y + padding,
                bar_width * durability_percent, bar_height,
                bar_color
            )
    
    def draw_dragging_item(self):
        """Draw item being dragged"""
        if not self.dragging_stack:
            return

        x = int(self.mouse_x - self.drag_offset_x)
        y = int(self.mouse_y - self.drag_offset_y)
        
        # Draw semi-transparent slot
        arcade.draw_lbwh_rectangle_filled(x, y, self.slot_size, self.slot_size, Color(40, 40, 50, 200))
        self.draw_item_stack(x, y, self.dragging_stack, "dragging")

    def set_move_request_callback(self, callback):
        """Set callback invoked after a successful local move."""
        self.move_request_callback = callback

    def set_drop_request_callback(self, callback):
        """Set callback invoked to request dropping a stack on server."""
        self.drop_request_callback = callback

    def _get_main_slot_at(self, x: int, y: int) -> Optional[int]:
        """Return main inventory slot index under mouse (screen coords)."""
        start_x = self.panel_x + self.panel_padding
        start_y = self.panel_y + self.inv_height - 70

        for slot in range(self.inventory.main_slots):
            col = slot % self.inv_cols
            row = slot // self.inv_cols
            slot_x = start_x + col * (self.slot_size + self.slot_padding)
            slot_y = start_y - row * (self.slot_size + self.slot_padding)
            if slot_x <= x <= slot_x + self.slot_size and slot_y <= y <= slot_y + self.slot_size:
                return slot
        return None

    def _get_hotbar_slot_at(self, x: int, y: int) -> Optional[int]:
        """Return hotbar slot index under mouse (screen coords)."""
        start_x = self.hotbar_x
        start_y = self.hotbar_y
        for i in range(self.inventory.hotbar_size):
            slot_x = start_x + i * (self.slot_size + self.slot_padding)
            slot_y = start_y
            if slot_x <= x <= slot_x + self.slot_size and slot_y <= y <= slot_y + self.slot_size:
                return i
        return None

    def _get_equipment_slot_at(self, x: int, y: int) -> Optional[EquipmentSlot]:
        """Return equipment slot under mouse (screen coords)."""
        equipment_layout = [
            (EquipmentSlot.HELMET, 0, 0),
            (EquipmentSlot.CHEST, 0, 1),
            (EquipmentSlot.LEGS, 0, 2),
            (EquipmentSlot.WEAPON, 0, 3),
            (EquipmentSlot.TOOL, 1, 0),
            (EquipmentSlot.ACCESSORY, 1, 1),
            (EquipmentSlot.JETPACK, 1, 2),
            (EquipmentSlot.SHIELD, 1, 3),
        ]

        start_x = self.equip_x + self.panel_padding
        start_y = self.equip_y + self.equip_height - 80

        for slot_type, col, row in equipment_layout:
            slot_x = start_x + col * (self.slot_size + 70)
            slot_y = start_y - row * (self.slot_size + 10)
            if slot_x <= x <= slot_x + self.slot_size and slot_y <= y <= slot_y + self.slot_size:
                return slot_type
        return None

    def _apply_local_move(self, src_type: str, src_slot, dst_type: str, dst_slot, moving: ItemStack) -> bool:
        """Apply move locally (optimistic); server will re-sync authoritative state."""
        if src_type == dst_type and src_slot == dst_slot:
            return False

        inv = self.inventory

        def get_stack(loc_type: str, loc_slot):
            if loc_type == "main":
                return inv.get_item_stack(loc_slot)
            return inv.get_equipped(loc_slot)

        def set_stack(loc_type: str, loc_slot, stack: Optional[ItemStack]):
            if loc_type == "main":
                inv.set_item_stack(loc_slot, stack)
            else:
                inv.equipment[loc_slot] = stack

        target = get_stack(dst_type, dst_slot)

        if dst_type == "equip":
            old = inv.equip_item(dst_slot, moving)
            if old is None and moving.item and moving.item.equipment_slot != dst_slot:
                # Wrong slot, restore
                set_stack(src_type, src_slot, moving)
                return False
            if old:
                set_stack(src_type, src_slot, old)
            return True

        # dst_type == main
        if target and target.item_id == moving.item_id and (moving.item and moving.item.stack_size > 1):
            overflow = target.add_quantity(moving.quantity)
            if overflow <= 0:
                return True
            moving.quantity = overflow
            set_stack(src_type, src_slot, moving)
            return True

        set_stack(dst_type, dst_slot, moving)
        if target:
            set_stack(src_type, src_slot, target)
        return True
    
    # ========================================================================
    # INTERACTION
    # ========================================================================
    
    def on_mouse_press(self, x: int, y: int, button: int):
        """Handle mouse press"""
        if button != arcade.MOUSE_BUTTON_LEFT:
            return False

        self.mouse_x = x
        self.mouse_y = y

        # Hotbar is always visible: allow selecting it even when inventory is closed.
        hotbar_slot = self._get_hotbar_slot_at(x, y)
        if hotbar_slot is not None:
            self.inventory.select_hotbar_slot(hotbar_slot)
            self._invalidate_ui_cache()

            # If inventory is closed, hotbar click is selection-only.
            if not self.visible:
                return True

            # If inventory is open, allow dragging from the hotbar slot.
            stack = self.inventory.get_item_stack(hotbar_slot)
            if stack:
                self.dragging_slot = ("main", hotbar_slot)
                self.dragging_stack = stack
                self.inventory.set_item_stack(hotbar_slot, None)
                slot_x = self.hotbar_x + hotbar_slot * (self.slot_size + self.slot_padding)
                slot_y = self.hotbar_y
                self.drag_offset_x = x - slot_x
                self.drag_offset_y = y - slot_y
            return True

        if not self.visible:
            return False

        equip_slot = self._get_equipment_slot_at(x, y)
        if equip_slot is not None:
            stack = self.inventory.get_equipped(equip_slot)
            if stack:
                # Start dragging
                self.dragging_slot = ("equip", equip_slot)
                self.dragging_stack = stack
                self.inventory.equipment[equip_slot] = None
                # Compute offset within slot
                start_x = self.equip_x + self.panel_padding
                start_y = self.equip_y + self.equip_height - 80
                # Find exact slot rect to compute offset
                # (recompute similarly to _get_equipment_slot_at)
                equipment_layout = [
                    (EquipmentSlot.HELMET, 0, 0),
                    (EquipmentSlot.CHEST, 0, 1),
                    (EquipmentSlot.LEGS, 0, 2),
                    (EquipmentSlot.WEAPON, 0, 3),
                    (EquipmentSlot.TOOL, 1, 0),
                    (EquipmentSlot.ACCESSORY, 1, 1),
                    (EquipmentSlot.JETPACK, 1, 2),
                    (EquipmentSlot.SHIELD, 1, 3),
                ]
                for st, col, row in equipment_layout:
                    if st == equip_slot:
                        slot_x = start_x + col * (self.slot_size + 70)
                        slot_y = start_y - row * (self.slot_size + 10)
                        self.drag_offset_x = x - slot_x
                        self.drag_offset_y = y - slot_y
                        break
                return True

        main_slot = self._get_main_slot_at(x, y)
        if main_slot is not None:
            stack = self.inventory.get_item_stack(main_slot)
            if stack:
                self.dragging_slot = ("main", main_slot)
                self.dragging_stack = stack
                self.inventory.set_item_stack(main_slot, None)
                start_x = self.panel_x + self.panel_padding
                start_y = self.panel_y + self.inv_height - 70
                col = main_slot % self.inv_cols
                row = main_slot // self.inv_cols
                slot_x = start_x + col * (self.slot_size + self.slot_padding)
                slot_y = start_y - row * (self.slot_size + self.slot_padding)
                self.drag_offset_x = x - slot_x
                self.drag_offset_y = y - slot_y
                return True

        return False
    
    def on_mouse_release(self, x: int, y: int, button: int):
        """Handle mouse release"""
        if not self.visible:
            return False

        if button != arcade.MOUSE_BUTTON_LEFT:
            return False

        self.mouse_x = x
        self.mouse_y = y

        if not self.dragging_stack or not self.dragging_slot:
            return False

        src_type, src_slot = self.dragging_slot

        dst_equip = self._get_equipment_slot_at(x, y)
        dst_main = self._get_main_slot_at(x, y)
        if dst_main is None:
            dst_main = self._get_hotbar_slot_at(x, y)

        if dst_equip is None and dst_main is None:
            self.cancel_drag()
            return True

        if dst_equip is not None:
            dst_type, dst_slot = "equip", dst_equip
        else:
            dst_type, dst_slot = "main", dst_main

        # Dropped back onto the same slot -> cancel drag (restore)
        if src_type == dst_type and src_slot == dst_slot:
            self.cancel_drag()
            return True

        # Apply locally
        success = self._apply_local_move(src_type, src_slot, dst_type, dst_slot, self.dragging_stack)

        # Clear drag state
        self.dragging_slot = None
        self.dragging_stack = None

        if success and self.move_request_callback:
            # Convert equip slots to their .value for wire format
            wire_src_slot = src_slot.value if src_type == "equip" else src_slot
            wire_dst_slot = dst_slot.value if dst_type == "equip" else dst_slot
            self.move_request_callback(src_type, wire_src_slot, dst_type, wire_dst_slot)

        return True
    
    def on_mouse_motion(self, x: int, y: int, dx: int, dy: int):
        """Handle mouse motion"""
        self.mouse_x = x
        self.mouse_y = y
        self._update_hover_tooltip()
        if not self.visible:
            return False
        return self.dragging_stack is not None
    
    def on_key_press(self, symbol: int, modifiers: int):
        """Handle key press"""
        # Hotbar selection (1-0 keys) - using arcade.key constants for cross-platform support
        if symbol == arcade.key.KEY_1:
            self.inventory.select_hotbar_slot(0)
            self._ui_textures_dirty = True
            return True
        elif symbol == arcade.key.KEY_2:
            self.inventory.select_hotbar_slot(1)
            self._ui_textures_dirty = True
            return True
        elif symbol == arcade.key.KEY_3:
            self.inventory.select_hotbar_slot(2)
            self._ui_textures_dirty = True
            return True
        elif symbol == arcade.key.KEY_4:
            self.inventory.select_hotbar_slot(3)
            self._ui_textures_dirty = True
            return True
        elif symbol == arcade.key.KEY_5:
            self.inventory.select_hotbar_slot(4)
            self._ui_textures_dirty = True
            return True
        elif symbol == arcade.key.KEY_6:
            self.inventory.select_hotbar_slot(5)
            self._ui_textures_dirty = True
            return True
        elif symbol == arcade.key.KEY_7:
            self.inventory.select_hotbar_slot(6)
            self._ui_textures_dirty = True
            return True
        elif symbol == arcade.key.KEY_8:
            self.inventory.select_hotbar_slot(7)
            self._ui_textures_dirty = True
            return True
        elif symbol == arcade.key.KEY_9:
            self.inventory.select_hotbar_slot(8)
            self._ui_textures_dirty = True
            return True
        elif symbol == arcade.key.KEY_0:
            self.inventory.select_hotbar_slot(9)
            self._ui_textures_dirty = True
            return True
        
        # Toggle inventory
        if symbol == arcade.key.TAB:
            self.toggle()
            return True
        
        # Close inventory
        if symbol == arcade.key.ESCAPE and self.visible:
            self.close()
            return True

        # Drop stack (common in games)
        if symbol == arcade.key.F:
            if not self.drop_request_callback:
                return True

            # Inventory closed: drop currently selected hotbar slot (0-9)
            if not self.visible:
                src_type = "main"
                src_slot = int(getattr(self.inventory, "selected_hotbar_slot", 0))
                if not self.inventory.get_item_stack(src_slot):
                    return True
                self.drop_request_callback(src_type, src_slot)
                return True

            # If dragging, drop the dragged stack from its original source slot.
            if self.dragging_slot and self.dragging_stack:
                src_type, src_slot = self.dragging_slot
                # Clear drag state without restoring (server will authoritatively remove).
                self.dragging_slot = None
                self.dragging_stack = None
            else:
                main_slot = self._get_main_slot_at(self.mouse_x, self.mouse_y)
                if main_slot is None:
                    main_slot = self._get_hotbar_slot_at(self.mouse_x, self.mouse_y)
                equip_slot = self._get_equipment_slot_at(self.mouse_x, self.mouse_y)

                if equip_slot is not None:
                    src_type, src_slot = "equip", equip_slot
                elif main_slot is not None:
                    src_type, src_slot = "main", main_slot
                else:
                    return True

                # Only request drop if there's something in that slot.
                if src_type == "main":
                    if not self.inventory.get_item_stack(src_slot):
                        return True
                else:
                    if not self.inventory.get_equipped(src_slot):
                        return True

            wire_src_slot = src_slot.value if src_type == "equip" else src_slot
            self.drop_request_callback(src_type, wire_src_slot)
            return True
        
        return False
    
    def cancel_drag(self):
        """Cancel current drag operation"""
        # Return item to original slot
        if self.dragging_slot and self.dragging_stack:
            slot_type, slot_id = self.dragging_slot
            if slot_type == "main":
                self.inventory.set_item_stack(slot_id, self.dragging_stack)
            elif slot_type == "equip":
                self.inventory.equipment[slot_id] = self.dragging_stack
        
        self.dragging_slot = None
        self.dragging_stack = None
