from __future__ import annotations
from pyWebLayout.core import Hierarchical
from pyWebLayout.style import Font
from pyWebLayout.style.abstract_style import AbstractStyle
from typing import Tuple, Union, List, Optional, Dict, Any, Callable
import pyphen

# Import LinkType for type hints (imported at module level to avoid F821 linting error)
from pyWebLayout.abstract.functional import LinkType


class Word:
    """
    An abstract representation of a word in a document. Words can be split across
    lines or pages during rendering. This class manages the logical representation
    of a word without any rendering specifics.

    Now uses AbstractStyle objects for memory efficiency and proper style management.
    """

    def __init__(self,
                 text: str,
                 style: Union[Font,
                              AbstractStyle],
                 background=None,
                 previous: Union['Word',
                                 None] = None):
        """
        Initialize a new Word.

        Args:
            text: The text content of the word
            style: AbstractStyle object or Font object (for backward compatibility)
            background: Optional background color override
            previous: Reference to the previous word in sequence
        """
        self._text = text
        self._style = style
        self._background = background
        self._previous = previous
        self._next = None
        self.concrete = None
        if previous:
            previous.add_next(self)

    @classmethod
    def create_and_add_to(cls, text: str, container, style: Optional[Font] = None,
                          background=None) -> 'Word':
        """
        Create a new Word and add it to a container, inheriting style and language
        from the container if not explicitly provided.

        This method provides a convenient way to create words that automatically
        inherit styling from their container (Paragraph, FormattedSpan, etc.)
        without copying string values - using object references instead.

        Args:
            text: The text content of the word
            container: The container to add the word to (must have add_word method and style property)
            style: Optional Font style override. If None, inherits from container
            background: Optional background color override. If None, inherits from container

        Returns:
            The newly created Word object

        Raises:
            AttributeError: If the container doesn't have the required add_word method or style property
        """
        # Inherit style from container if not provided
        if style is None:
            if hasattr(container, 'style'):
                style = container.style
            else:
                raise AttributeError(
                    f"Container {type(container).__name__} must have a 'style' property")

        # Inherit background from container if not provided
        if background is None and hasattr(container, 'background'):
            background = container.background

        # Determine the previous word for proper linking
        previous = None
        if hasattr(container, '_words') and container._words:
            # Container has a _words list (like FormattedSpan)
            previous = container._words[-1]
        elif hasattr(container, 'words'):
            # Container has a words() method (like Paragraph)
            try:
                # Get the last word from the iterator
                for _, word in container.words():
                    previous = word
            except (StopIteration, TypeError):
                previous = None

        # Create the new word
        word = cls(text, style, background, previous)

        # Link the previous word to this new one
        if previous:
            previous.add_next(word)

        # Add the word to the container
        if hasattr(container, 'add_word'):
            # Check if add_word expects a Word object or text string
            import inspect
            sig = inspect.signature(container.add_word)
            params = list(sig.parameters.keys())

            if len(params) > 0:
                # Peek at the parameter name to guess the expected type
                param_name = params[0]
                if param_name in ['word', 'word_obj', 'word_object']:
                    # Expects a Word object
                    container.add_word(word)
                else:
                    # Might expect text string (like FormattedSpan.add_word)
                    # In this case, we can't use the container's add_word as it would create
                    # a duplicate Word. We need to add directly to the container's word
                    # list.
                    if hasattr(container, '_words'):
                        container._words.append(word)
                    else:
                        # Fallback: try calling with the Word object anyway
                        container.add_word(word)
            else:
                # No parameters, shouldn't happen with add_word methods
                container.add_word(word)
        else:
            raise AttributeError(
                f"Container {type(container).__name__} must have an 'add_word' method")

        return word

    def add_concete(self, text: Union[Any, Tuple[Any, Any]]):
        self.concrete = text

    @property
    def text(self) -> str:
        """Get the text content of the word"""
        return self._text

    @property
    def style(self) -> Font:
        """Get the font style of the word"""
        return self._style

    @property
    def background(self):
        """Get the background color of the word"""
        return self._background

    @property
    def previous(self) -> Union['Word', None]:
        """Get the previous word in sequence"""
        return self._previous

    @property
    def next(self) -> Union['Word', None]:
        """Get the next word in sequence"""
        return self._next

    def add_next(self, next_word: 'Word'):
        """Set the next word in sequence"""
        self._next = next_word

    def possible_hyphenation(self, language: str = None) -> bool:
        """
        Hyphenate the word and store the parts.

        Args:
            language: Language code for hyphenation. If None, uses the style's language.

        Returns:
            bool: True if the word was hyphenated, False otherwise.
        """

        dic = pyphen.Pyphen(lang=self._style.language)
        return list(dic.iterate(self._text))


...


class FormattedSpan:
    """
    A run of words with consistent formatting.
    This represents a sequence of words that share the same style attributes.
    """

    def __init__(self, style: Font, background=None):
        """
        Initialize a new formatted span.

        Args:
            style: Font style information for all words in this span
            background: Optional background color override
        """
        self._style = style
        self._background = background if background else style.background
        self._words: List[Word] = []

    @classmethod
    def create_and_add_to(
            cls,
            container,
            style: Optional[Font] = None,
            background=None) -> 'FormattedSpan':
        """
        Create a new FormattedSpan and add it to a container, inheriting style from
        the container if not explicitly provided.

        Args:
            container: The container to add the span to (must have add_span method and style property)
            style: Optional Font style override. If None, inherits from container
            background: Optional background color override

        Returns:
            The newly created FormattedSpan object

        Raises:
            AttributeError: If the container doesn't have the required add_span method or style property
        """
        # Inherit style from container if not provided
        if style is None:
            if hasattr(container, 'style'):
                style = container.style
            else:
                raise AttributeError(
                    f"Container {type(container).__name__} must have a 'style' property")

        # Inherit background from container if not provided
        if background is None and hasattr(container, 'background'):
            background = container.background

        # Create the new span
        span = cls(style, background)

        # Add the span to the container
        if hasattr(container, 'add_span'):
            container.add_span(span)
        else:
            raise AttributeError(
                f"Container {type(container).__name__} must have an 'add_span' method")

        return span

    @property
    def style(self) -> Font:
        """Get the font style of this span"""
        return self._style

    @property
    def background(self):
        """Get the background color of this span"""
        return self._background

    @property
    def words(self) -> List[Word]:
        """Get the list of words in this span"""
        return self._words

    def add_word(self, text: str) -> Word:
        """
        Create and add a new word to this span.

        Args:
            text: The text content of the word

        Returns:
            The newly created Word object
        """
        # Get the previous word if any
        previous = self._words[-1] if self._words else None

        # Create the new word
        word = Word(text, self._style, self._background, previous)

        # Link the previous word to this new one
        if previous:
            previous.add_next(word)

        # Add the word to our list
        self._words.append(word)

        return word


class LinkedWord(Word):
    """
    A Word that is also a Link - combines text content with hyperlink functionality.

    When a word is part of a hyperlink, it becomes clickable and can trigger
    navigation or callbacks. Multiple words can share the same link destination.
    """

    def __init__(self, text: str, style: Union[Font, 'AbstractStyle'],
                 location: str, link_type: Optional['LinkType'] = None,
                 callback: Optional[Callable] = None,
                 background=None, previous: Optional[Word] = None,
                 params: Optional[Dict[str, Any]] = None,
                 title: Optional[str] = None):
        """
        Initialize a linked word.

        Args:
            text: The text content of the word
            style: The font style
            location: The link target (URL, bookmark, etc.)
            link_type: Type of link (INTERNAL, EXTERNAL, etc.)
            callback: Optional callback for link activation
            background: Optional background color
            previous: Previous word in sequence
            params: Parameters for the link
            title: Tooltip/title for the link
        """
        # Initialize Word first
        super().__init__(text, style, background, previous)

        # Store link properties
        self._location = location
        self._link_type = link_type or LinkType.EXTERNAL
        self._callback = callback
        self._params = params or {}
        self._title = title

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

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

    @property
    def link_callback(self) -> Optional[Callable]:
        """Get the link callback (distinct from word callback)"""
        return self._callback

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

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

    def execute_link(self, context: Optional[Dict[str, Any]] = None) -> Any:
        """
        Execute the link action.

        Args:
            context: Optional context dict (e.g., {'text': word.text})

        Returns:
            The result of the link execution
        """
        # Add word text to context
        full_context = {**self._params, 'text': self._text}
        if context:
            full_context.update(context)

        if self._link_type in (LinkType.API, LinkType.FUNCTION) and self._callback:
            return self._callback(self._location, **full_context)
        else:
            # For INTERNAL and EXTERNAL links, return the location
            return self._location


class LineBreak(Hierarchical):
    """
    A line break element that forces a new line within text content.
    While this is an inline element that can occur within paragraphs,
    it has block-like properties for consistency with the abstract model.

    Uses Hierarchical mixin for parent-child relationship management.
    """

    def __init__(self):
        """Initialize a line break element."""
        super().__init__()
        # Import here to avoid circular imports
        from .block import BlockType
        self._block_type = BlockType.LINE_BREAK

    @property
    def block_type(self):
        """Get the block type for this line break"""
        return self._block_type

    @classmethod
    def create_and_add_to(cls, container) -> 'LineBreak':
        """
        Create a new LineBreak and add it to a container.

        Args:
            container: The container to add the line break to

        Returns:
            The newly created LineBreak object
        """
        # Create the new line break
        line_break = cls()

        # Add the line break to the container if it has an appropriate method
        if hasattr(container, 'add_line_break'):
            container.add_line_break(line_break)
        elif hasattr(container, 'add_element'):
            container.add_element(line_break)
        elif hasattr(container, 'add_word'):
            # Some containers might treat line breaks like words
            container.add_word(line_break)
        else:
            # Set parent relationship manually
            line_break.parent = container

        return line_break
