"""
Interaction handler for managing button/link press-release lifecycle with visual feedback.

This module provides utilities for handling interactive element states and rendering
frames at different stages of interaction (pressed, released).
"""

from typing import Optional, Tuple, Callable, Any
from PIL import Image
import time
import numpy as np

from pyWebLayout.concrete.functional import LinkText, ButtonText
from pyWebLayout.concrete.page import Page


class InteractionHandler:
    """
    Manages the press-release lifecycle for interactive elements.

    This class handles the timing and state management needed to show
    visual feedback when buttons or links are clicked. It can generate
    multiple rendered frames showing the pressed and released states.

    Usage patterns:

    Pattern A - Simple one-shot with automatic frames:
        handler = InteractionHandler(page)
        frames = handler.execute_with_feedback(button_element, point)
        # Returns: [pressed_frame, released_frame]
        # Show frames in sequence with brief delay

    Pattern B - Manual state management for custom event loops:
        handler = InteractionHandler(page)
        handler.set_pressed_state(button_element, True)
        pressed_frame = handler.render_current_state()
        # ... show frame, wait, execute action ...
        handler.set_pressed_state(button_element, False)
        released_frame = handler.render_current_state()
    """

    def __init__(self, page: Page, press_duration_ms: int = 150):
        """
        Initialize the interaction handler.

        Args:
            page: The Page object containing the interactive elements
            press_duration_ms: How long to show the pressed state (default: 150ms)
        """
        self._page = page
        self._press_duration_ms = press_duration_ms

    def set_pressed_state(self, element, pressed: bool):
        """
        Set the pressed state of an interactive element.

        Args:
            element: A LinkText or ButtonText object
            pressed: True to show pressed, False to show released
        """
        if isinstance(element, (LinkText, ButtonText)):
            # Ensure element has page reference for dirty flag
            if not hasattr(element, '_page') or element._page is None:
                element.set_page(self._page)
            element.set_pressed(pressed)
        else:
            raise TypeError(
                f"Element must be LinkText or ButtonText, got {type(element)}")

    def set_hovered_state(self, element, hovered: bool):
        """
        Set the hovered state of an interactive element.

        Args:
            element: A LinkText or ButtonText object
            hovered: True to show hovered, False for normal
        """
        if isinstance(element, (LinkText, ButtonText)):
            # Ensure element has page reference for dirty flag
            if not hasattr(element, '_page') or element._page is None:
                element.set_page(self._page)
            element.set_hovered(hovered)
        else:
            raise TypeError(
                f"Element must be LinkText or ButtonText, got {type(element)}")

    def render_current_state(self) -> Image.Image:
        """
        Render the page with current element states.

        Returns:
            PIL Image of the rendered page
        """
        return self._page.render()

    def execute_with_feedback(
            self,
            element,
            point: Optional[np.ndarray] = None,
            callback: Optional[Callable] = None) -> Tuple[Image.Image, Image.Image, Any]:
        """
        Execute an interaction with visual feedback at each stage.

        This is the high-level "all-in-one" method that:
        1. Sets pressed state and renders
        2. Waits for press_duration_ms
        3. Executes the element's callback (or provided callback)
        4. Sets released state and renders

        Args:
            element: A LinkText or ButtonText object
            point: Optional point where interaction occurred
            callback: Optional custom callback (overrides element's callback)

        Returns:
            Tuple of (pressed_frame, released_frame, callback_result)
        """
        # Step 1: Render pressed state
        self.set_pressed_state(element, True)
        pressed_frame = self.render_current_state()

        # Step 2: Wait for visual feedback duration
        time.sleep(self._press_duration_ms / 1000.0)

        # Step 3: Execute callback
        callback_result = None
        if callback:
            callback_result = callback(point) if point is not None else callback()
        elif hasattr(element, 'interact'):
            callback_result = element.interact(point)

        # Step 4: Render released state
        self.set_pressed_state(element, False)
        released_frame = self.render_current_state()

        return pressed_frame, released_frame, callback_result

    def execute_async_with_feedback(
            self,
            element,
            point: Optional[np.ndarray] = None) -> Tuple[Image.Image, Callable, Image.Image]:
        """
        Execute an interaction with visual feedback, returning frames immediately
        without blocking.

        This method returns the frames and a callback to execute later, allowing
        the caller to control when the action actually happens.

        Args:
            element: A LinkText or ButtonText object
            point: Optional point where interaction occurred

        Returns:
            Tuple of (pressed_frame, execute_callback, released_frame)
            where execute_callback is a function that will execute the interaction
        """
        # Render pressed state
        self.set_pressed_state(element, True)
        pressed_frame = self.render_current_state()

        # Create callback that will execute the interaction and reset state
        def execute_callback():
            result = None
            if hasattr(element, 'interact'):
                result = element.interact(point)
            self.set_pressed_state(element, False)
            return result

        # Pre-render the released state (element state is still pressed)
        # We'll return this frame but the caller controls when to show it
        self.set_pressed_state(element, False)
        released_frame = self.render_current_state()

        # Reset back to pressed for consistency
        # (caller will call execute_callback which sets to False)
        self.set_pressed_state(element, True)

        return pressed_frame, execute_callback, released_frame


class InteractionStateManager:
    """
    Manages interaction states for multiple elements on a page.

    Useful for applications that need to track hover/press states
    across many interactive elements simultaneously.
    """

    def __init__(self, page: Page):
        """
        Initialize the state manager.

        Args:
            page: The Page object containing interactive elements
        """
        self._page = page
        self._hovered_element = None
        self._pressed_element = None

    def update_hover(self, point: Tuple[int, int]) -> Optional[Image.Image]:
        """
        Update hover state based on cursor position.

        Queries the page to find what's under the cursor and updates
        hover states accordingly.

        Args:
            point: Cursor position (x, y)

        Returns:
            New rendered frame if hover state changed, None otherwise
        """
        # Query what's at this point
        result = self._page.query_point(point)

        if not result or not result.is_interactive:
            # Nothing interactive under cursor
            if self._hovered_element:
                # Clear previous hover
                if isinstance(self._hovered_element, (LinkText, ButtonText)):
                    self._hovered_element.set_hovered(False)
                self._hovered_element = None
                return self._page.render()
            return None

        # Something interactive is under cursor
        element = result.object
        if element != self._hovered_element:
            # Hover changed
            # Clear old hover
            if self._hovered_element and isinstance(
                    self._hovered_element, (LinkText, ButtonText)):
                self._hovered_element.set_hovered(False)

            # Set new hover
            if isinstance(element, (LinkText, ButtonText)):
                element.set_hovered(True)

            self._hovered_element = element
            return self._page.render()

        return None

    def handle_mouse_down(self, point: Tuple[int, int]) -> Optional[Image.Image]:
        """
        Handle mouse button press at a point.

        Args:
            point: Click position (x, y)

        Returns:
            New rendered frame showing pressed state, or None if nothing interactive
        """
        result = self._page.query_point(point)

        if not result or not result.is_interactive:
            return None

        element = result.object
        if isinstance(element, (LinkText, ButtonText)):
            element.set_pressed(True)
            self._pressed_element = element
            return self._page.render()

        return None

    def handle_mouse_up(
            self,
            point: Tuple[int,
                         int]) -> Tuple[Optional[Image.Image],
                                        Any]:
        """
        Handle mouse button release at a point.

        Args:
            point: Release position (x, y)

        Returns:
            Tuple of (rendered_frame, callback_result)
            Frame shows released state, result is from executing the callback
        """
        if not self._pressed_element:
            return None, None

        # Execute the interaction
        callback_result = None
        if hasattr(self._pressed_element, 'interact'):
            callback_result = self._pressed_element.interact(
                np.array(point))

        # Release the pressed state
        if isinstance(self._pressed_element, (LinkText, ButtonText)):
            self._pressed_element.set_pressed(False)

        self._pressed_element = None

        return self._page.render(), callback_result

    def reset(self):
        """Reset all interaction states."""
        if self._hovered_element and isinstance(
                self._hovered_element, (LinkText, ButtonText)):
            self._hovered_element.set_hovered(False)

        if self._pressed_element and isinstance(
                self._pressed_element, (LinkText, ButtonText)):
            self._pressed_element.set_pressed(False)

        self._hovered_element = None
        self._pressed_element = None
