from __future__ import annotations
from enum import Enum
from typing import Callable, Dict, Any, Optional, List, Tuple
from pyWebLayout.core.base import Interactable


class LinkType(Enum):
    """Enumeration of different types of links for classification purposes"""
    INTERNAL = 1  # Links within the same document (e.g., chapter references, bookmarks)
    EXTERNAL = 2  # Links to external resources (e.g., websites, other documents)
    API = 3       # Links that trigger API calls (e.g., for settings management)
    FUNCTION = 4  # Links that execute a specific function


class Link(Interactable):
    """
    A link that can navigate to a location or execute a function.
    Links can be used for navigation within a document, to external resources,
    or to trigger API calls for functionality like settings management.
    """

    def __init__(self,
                 location: str,
                 link_type: LinkType = LinkType.INTERNAL,
                 callback: Optional[Callable] = None,
                 params: Optional[Dict[str, Any]] = None,
                 title: Optional[str] = None,
                 html_id: Optional[str] = None):
        """
        Initialize a link.

        Args:
            location: The target location or identifier for this link
            link_type: The type of link (internal, external, API, function)
            callback: Optional callback function to execute when the link is activated
            params: Optional parameters to pass to the callback or API
            title: Optional title/tooltip for the link
            html_id: Optional HTML id attribute (from <a id="...">) for callback binding
        """
        super().__init__(callback)
        self._location = location
        self._link_type = link_type
        self._params = params or {}
        self._title = title
        self._html_id = html_id

    @property
    def location(self) -> str:
        """Get the target location of this link"""
        return self._location

    @property
    def link_type(self) -> LinkType:
        """Get the type of this link"""
        return self._link_type

    @property
    def params(self) -> Dict[str, Any]:
        """Get the parameters for this link"""
        return self._params

    @property
    def title(self) -> Optional[str]:
        """Get the title/tooltip for this link"""
        return self._title

    @property
    def html_id(self) -> Optional[str]:
        """Get the HTML id attribute for callback binding"""
        return self._html_id

    def execute(self, point=None) -> Any:
        """
        Execute the link action based on its type.

        For internal and external links, returns the location.
        For API and function links, executes the callback with the provided parameters.

        Args:
            point: Optional interaction point passed from the interact() method

        Returns:
            The result of the link execution, which depends on the link type.
        """
        if self._link_type in (LinkType.API, LinkType.FUNCTION) and self._callback:
            return self._callback(self._location, point, **self._params)
        else:
            # For INTERNAL and EXTERNAL links, return the location
            # The renderer/browser will handle the navigation
            return self._location


class Button(Interactable):
    """
    A button that can be clicked to execute an action.
    Buttons are similar to function links but are rendered differently.
    """

    def __init__(self,
                 label: str,
                 callback: Callable,
                 params: Optional[Dict[str, Any]] = None,
                 enabled: bool = True,
                 html_id: Optional[str] = None):
        """
        Initialize a button.

        Args:
            label: The text label for the button
            callback: The function to execute when the button is clicked
            params: Optional parameters to pass to the callback
            enabled: Whether the button is initially enabled
            html_id: Optional HTML id attribute (from <button id="...">) for callback binding
        """
        super().__init__(callback)
        self._label = label
        self._params = params or {}
        self._enabled = enabled
        self._html_id = html_id

    @property
    def label(self) -> str:
        """Get the button label"""
        return self._label

    @label.setter
    def label(self, label: str):
        """Set the button label"""
        self._label = label

    @property
    def enabled(self) -> bool:
        """Check if the button is enabled"""
        return self._enabled

    @enabled.setter
    def enabled(self, enabled: bool):
        """Enable or disable the button"""
        self._enabled = enabled

    @property
    def params(self) -> Dict[str, Any]:
        """Get the button parameters"""
        return self._params

    @property
    def html_id(self) -> Optional[str]:
        """Get the HTML id attribute for callback binding"""
        return self._html_id

    def execute(self, point=None) -> Any:
        """
        Execute the button's callback function if the button is enabled.

        Args:
            point: Optional interaction point passed from the interact() method

        Returns:
            The result of the callback function, or None if the button is disabled.
        """
        if self._enabled and self._callback:
            return self._callback(point, **self._params)
        return None


class Form(Interactable):
    """
    A form that can contain input fields and be submitted.
    Forms can be used for user input and settings configuration.
    """

    def __init__(self,
                 form_id: str,
                 action: Optional[str] = None,
                 callback: Optional[Callable] = None,
                 html_id: Optional[str] = None):
        """
        Initialize a form.

        Args:
            form_id: The unique identifier for this form
            action: The action URL or endpoint for form submission
            callback: Optional callback function to execute on form submission
            html_id: Optional HTML id attribute (from <form id="...">) for callback binding
        """
        super().__init__(callback)
        self._form_id = form_id
        self._action = action
        self._fields: Dict[str, FormField] = {}
        self._html_id = html_id

    @property
    def form_id(self) -> str:
        """Get the form ID"""
        return self._form_id

    @property
    def action(self) -> Optional[str]:
        """Get the form action"""
        return self._action

    @property
    def html_id(self) -> Optional[str]:
        """Get the HTML id attribute for callback binding"""
        return self._html_id

    def add_field(self, field: FormField):
        """
        Add a field to this form.

        Args:
            field: The FormField to add
        """
        self._fields[field.name] = field
        field.form = self

    def get_field(self, name: str) -> Optional[FormField]:
        """
        Get a field by name.

        Args:
            name: The name of the field to get

        Returns:
            The FormField with the specified name, or None if not found
        """
        return self._fields.get(name)

    def get_values(self) -> Dict[str, Any]:
        """
        Get the current values of all fields in this form.

        Returns:
            A dictionary mapping field names to their current values
        """
        return {name: field.value for name, field in self._fields.items()}

    def execute(self) -> Any:
        """
        Submit the form, executing the callback with the form values.

        Returns:
            The result of the callback function, or the form values if no callback is provided.
        """
        values = self.get_values()

        if self._callback:
            return self._callback(self._form_id, values)

        return values


class FormFieldType(Enum):
    """Enumeration of different types of form fields"""
    TEXT = 1
    PASSWORD = 2
    CHECKBOX = 3
    RADIO = 4
    SELECT = 5
    TEXTAREA = 6
    NUMBER = 7
    DATE = 8
    TIME = 9
    EMAIL = 10
    URL = 11
    COLOR = 12
    RANGE = 13
    HIDDEN = 14


class FormField:
    """
    A field in a form that can accept user input.
    """

    def __init__(self,
                 name: str,
                 field_type: FormFieldType,
                 label: Optional[str] = None,
                 value: Any = None,
                 required: bool = False,
                 options: Optional[List[Tuple[str, str]]] = None):
        """
        Initialize a form field.

        Args:
            name: The name of this field
            field_type: The type of this field
            label: Optional label for this field
            value: Initial value for this field
            required: Whether this field is required
            options: Options for select, radio, or checkbox fields (list of (value, label) tuples)
        """
        self._name = name
        self._field_type = field_type
        self._label = label or name
        self._value = value
        self._required = required
        self._options = options or []
        self._form: Optional[Form] = None

    @property
    def name(self) -> str:
        """Get the field name"""
        return self._name

    @property
    def field_type(self) -> FormFieldType:
        """Get the field type"""
        return self._field_type

    @property
    def label(self) -> str:
        """Get the field label"""
        return self._label

    @property
    def value(self) -> Any:
        """Get the current field value"""
        return self._value

    @value.setter
    def value(self, value: Any):
        """Set the field value"""
        self._value = value

    @property
    def required(self) -> bool:
        """Check if the field is required"""
        return self._required

    @property
    def options(self) -> List[Tuple[str, str]]:
        """Get the field options"""
        return self._options

    @property
    def form(self) -> Optional[Form]:
        """Get the form containing this field"""
        return self._form

    @form.setter
    def form(self, form: Form):
        """Set the form containing this field"""
        self._form = form
