from abc import ABC
from typing import Optional, Tuple, List, TYPE_CHECKING
import numpy as np

from pyWebLayout.style.alignment import Alignment

if TYPE_CHECKING:
    from pyWebLayout.core.query import QueryResult


class Renderable(ABC):
    """
    Abstract base class for any object that can be rendered to an image.
    All renderable objects must implement the render method.
    """
    def render(self):
        """
        Render the object to an image.
        
        Returns:
            PIL.Image: The rendered image
        """
        pass
    @property
    def origin(self):
        return self._origin

class Interactable(ABC):
    """
    Abstract base class for any object that can be interacted with.
    Interactable objects must have a callback that is executed when interacted with.
    """
    def __init__(self, callback=None):
        """
        Initialize an interactable object.
        
        Args:
            callback: The function to call when this object is interacted with
        """
        self._callback = callback
    
    def interact(self, point: np.generic):
        """
        Handle interaction at the given point.
        
        Args:
            point: The coordinates of the interaction
            
        Returns:
            The result of calling the callback function with the point
        """
        if self._callback is None:
            return None
        return self._callback(point)
    
class Layoutable(ABC):
    """
    Abstract base class for any object that can be laid out.
    Layoutable objects must implement the layout method which arranges their contents.
    """
    def layout(self):
        """
        Layout the object's contents.
        This method should be called before rendering to properly arrange the object's contents.
        """
        pass

class Queriable(ABC):

    def in_object(self, point: np.generic):
        """
        check if a point is in the object
        """
        point_array = np.array(point)
        relative_point = point_array - self._origin
        return np.all((0 <= relative_point) & (relative_point < self.size))
