# McRogueFace Cookbook - Scrollable List Widget
"""
Scrolling list with arbitrary item rendering.

Example:
    from lib.scrollable_list import ScrollableList

    def render_item(item, frame, index, selected):
        # Add item content to frame
        label = mcrfpy.Caption(text=item['name'], pos=(10, 5))
        frame.children.append(label)

    items = [{'name': f'Item {i}'} for i in range(50)]

    scroll_list = ScrollableList(
        pos=(100, 100),
        size=(300, 400),
        items=items,
        render_item=render_item,
        item_height=40
    )
    scene.children.append(scroll_list.frame)
"""
import mcrfpy


class ScrollableList:
    """Scrolling list with arbitrary item rendering.

    Args:
        pos: (x, y) position tuple
        size: (width, height) tuple
        items: List of items to display
        item_height: Height of each item row (default: 30)
        render_item: Callback(item, frame, index, selected) to render item content
        on_select: Callback(index, item) when item is selected
        bg_color: Background color (default: dark)
        item_bg_color: Normal item background
        selected_bg_color: Selected item background
        hover_bg_color: Hovered item background
        outline_color: Border color
        outline: Border thickness

    Attributes:
        frame: The outer frame (add this to scene)
        items: List of items
        selected_index: Currently selected index
        scroll_offset: Current scroll position
    """

    DEFAULT_BG = mcrfpy.Color(30, 30, 35)
    DEFAULT_ITEM_BG = mcrfpy.Color(35, 35, 40)
    DEFAULT_SELECTED_BG = mcrfpy.Color(60, 80, 120)
    DEFAULT_HOVER_BG = mcrfpy.Color(50, 50, 60)
    DEFAULT_OUTLINE = mcrfpy.Color(80, 80, 90)

    def __init__(self, pos, size, items=None, item_height=30,
                 render_item=None, on_select=None,
                 bg_color=None, item_bg_color=None,
                 selected_bg_color=None, hover_bg_color=None,
                 outline_color=None, outline=1):
        self.pos = pos
        self.size = size
        self._items = list(items) if items else []
        self.item_height = item_height
        self.render_item = render_item or self._default_render
        self.on_select = on_select

        # Colors
        self.bg_color = bg_color or self.DEFAULT_BG
        self.item_bg_color = item_bg_color or self.DEFAULT_ITEM_BG
        self.selected_bg_color = selected_bg_color or self.DEFAULT_SELECTED_BG
        self.hover_bg_color = hover_bg_color or self.DEFAULT_HOVER_BG
        self.outline_color = outline_color or self.DEFAULT_OUTLINE

        # State
        self._selected_index = -1
        self._scroll_offset = 0
        self._hovered_index = -1
        self._visible_count = int(size[1] / item_height)

        # Create outer frame with clipping
        self.frame = mcrfpy.Frame(
            pos=pos,
            size=size,
            fill_color=self.bg_color,
            outline_color=self.outline_color,
            outline=outline
        )
        self.frame.clip_children = True

        # Content container (scrolls within frame)
        self._content = mcrfpy.Frame(
            pos=(0, 0),
            size=(size[0], max(size[1], len(self._items) * item_height)),
            fill_color=mcrfpy.Color(0, 0, 0, 0),  # Transparent
            outline=0
        )
        self.frame.children.append(self._content)

        # Set up scroll handler
        self.frame.on_move = self._on_mouse_move

        # Build initial items
        self._rebuild_items()

    def _default_render(self, item, frame, index, selected):
        """Default item renderer - just shows string representation."""
        text = str(item) if not isinstance(item, dict) else item.get('name', str(item))
        label = mcrfpy.Caption(
            text=text,
            pos=(10, (self.item_height - 14) / 2),
            fill_color=mcrfpy.Color(200, 200, 200),
            font_size=14
        )
        frame.children.append(label)

    def _rebuild_items(self):
        """Rebuild all item frames."""
        # Clear content
        while len(self._content.children) > 0:
            self._content.children.pop()

        # Update content size
        total_height = len(self._items) * self.item_height
        self._content.h = max(self.size[1], total_height)

        # Create item frames
        self._item_frames = []
        for i, item in enumerate(self._items):
            item_frame = mcrfpy.Frame(
                pos=(0, i * self.item_height),
                size=(self.size[0], self.item_height),
                fill_color=self.item_bg_color,
                outline=0
            )

            # Set up click handler
            def make_click_handler(index):
                def handler(pos, button, action):
                    if button == mcrfpy.MouseButton.LEFT and action == mcrfpy.InputState.RELEASED:
                        self.select(index)
                return handler

            def make_enter_handler(index):
                def handler(pos, button, action):
                    self._on_item_enter(index)
                return handler

            def make_exit_handler(index):
                def handler(pos, button, action):
                    self._on_item_exit(index)
                return handler

            item_frame.on_click = make_click_handler(i)
            item_frame.on_enter = make_enter_handler(i)
            item_frame.on_exit = make_exit_handler(i)

            # Render item content
            is_selected = i == self._selected_index
            self.render_item(item, item_frame, i, is_selected)

            self._item_frames.append(item_frame)
            self._content.children.append(item_frame)

    def _on_item_enter(self, index):
        """Handle mouse entering an item."""
        self._hovered_index = index
        if index != self._selected_index:
            self._item_frames[index].fill_color = self.hover_bg_color

    def _on_item_exit(self, index):
        """Handle mouse leaving an item."""
        if self._hovered_index == index:
            self._hovered_index = -1
        if index != self._selected_index:
            self._item_frames[index].fill_color = self.item_bg_color

    def _on_mouse_move(self, pos, button, action):
        """Handle mouse movement for scroll wheel detection."""
        # Note: This is a placeholder - actual scroll wheel handling
        # depends on the engine's input system
        pass

    def _update_item_display(self):
        """Update visual state of items."""
        for i, frame in enumerate(self._item_frames):
            if i == self._selected_index:
                frame.fill_color = self.selected_bg_color
            elif i == self._hovered_index:
                frame.fill_color = self.hover_bg_color
            else:
                frame.fill_color = self.item_bg_color

    @property
    def items(self):
        """List of items."""
        return list(self._items)

    @items.setter
    def items(self, value):
        """Set new items list."""
        self._items = list(value)
        self._selected_index = -1
        self._scroll_offset = 0
        self._rebuild_items()

    @property
    def selected_index(self):
        """Currently selected index (-1 if none)."""
        return self._selected_index

    @property
    def selected_item(self):
        """Currently selected item (None if none)."""
        if 0 <= self._selected_index < len(self._items):
            return self._items[self._selected_index]
        return None

    @property
    def scroll_offset(self):
        """Current scroll position in pixels."""
        return self._scroll_offset

    def select(self, index):
        """Select an item by index.

        Args:
            index: Item index to select
        """
        if not self._items:
            return

        old_index = self._selected_index
        self._selected_index = max(-1, min(index, len(self._items) - 1))

        if old_index != self._selected_index:
            self._update_item_display()
            if self.on_select and self._selected_index >= 0:
                self.on_select(self._selected_index, self._items[self._selected_index])

    def scroll(self, delta):
        """Scroll the list by a number of items.

        Args:
            delta: Number of items to scroll (positive = down)
        """
        max_scroll = max(0, len(self._items) * self.item_height - self.size[1])
        new_offset = self._scroll_offset + delta * self.item_height
        new_offset = max(0, min(new_offset, max_scroll))

        if new_offset != self._scroll_offset:
            self._scroll_offset = new_offset
            self._content.y = -self._scroll_offset

    def scroll_to(self, index):
        """Scroll to make an item visible.

        Args:
            index: Item index to scroll to
        """
        if not 0 <= index < len(self._items):
            return

        item_top = index * self.item_height
        item_bottom = item_top + self.item_height

        if item_top < self._scroll_offset:
            self._scroll_offset = item_top
        elif item_bottom > self._scroll_offset + self.size[1]:
            self._scroll_offset = item_bottom - self.size[1]

        self._content.y = -self._scroll_offset

    def navigate(self, direction):
        """Navigate selection up or down.

        Args:
            direction: +1 for down, -1 for up
        """
        if not self._items:
            return

        if self._selected_index < 0:
            new_index = 0 if direction > 0 else len(self._items) - 1
        else:
            new_index = self._selected_index + direction
            # Wrap around
            if new_index < 0:
                new_index = len(self._items) - 1
            elif new_index >= len(self._items):
                new_index = 0

        self.select(new_index)
        self.scroll_to(new_index)

    def add_item(self, item, index=None):
        """Add an item to the list.

        Args:
            item: Item to add
            index: Position to insert (None = end)
        """
        if index is None:
            self._items.append(item)
        else:
            self._items.insert(index, item)
        self._rebuild_items()

    def remove_item(self, index):
        """Remove an item by index.

        Args:
            index: Index of item to remove
        """
        if 0 <= index < len(self._items):
            del self._items[index]
            if self._selected_index >= len(self._items):
                self._selected_index = len(self._items) - 1
            self._rebuild_items()

    def clear(self):
        """Remove all items."""
        self._items.clear()
        self._selected_index = -1
        self._scroll_offset = 0
        self._rebuild_items()

    def filter(self, predicate):
        """Filter displayed items.

        Args:
            predicate: Function(item) -> bool to filter items

        Note: This creates a new filtered view, not modifying original items.
        """
        # Store original items if not already stored
        if not hasattr(self, '_original_items'):
            self._original_items = list(self._items)

        self._items = [item for item in self._original_items if predicate(item)]
        self._selected_index = -1
        self._rebuild_items()

    def reset_filter(self):
        """Reset to show all items (undo filter)."""
        if hasattr(self, '_original_items'):
            self._items = list(self._original_items)
            del self._original_items
            self._selected_index = -1
            self._rebuild_items()
