"""
Value container types for RDF objects.

This module provides dataclasses that represent RDF object values (IRIs and
literals) with their associated metadata. These are distinct from field
descriptors (in model.py) which define how model attributes map to RDF
predicates.

RDF Object Hierarchy:
    - IRI: Resource identifier (e.g., <http://example.org/foo>)
    - Literal: Plain literal value (e.g., "hello")
        - LangLiteral: Language-tagged literal (e.g., "Hello"@en)
        - TypedLiteral: Typed literal with XSD datatype (e.g., "42"^^xsd:integer)
"""

from dataclasses import dataclass
from typing import Any

from .security import escape_sparql_iri, escape_sparql_literal


@dataclass(frozen=True)
class IRI:
    """
    An IRI (Internationalized Resource Identifier) value.

    This dataclass represents an RDF resource reference. IRIs are formatted
    with angle brackets in SPARQL.

    Attributes:
        value: The IRI string.

    Example:
        >>> iri = IRI("http://example.org/resource")
        >>> print(iri.to_sparql())  # "<http://example.org/resource>"
    """

    value: str

    def __str__(self) -> str:
        """Return the IRI value."""
        return self.value

    def __repr__(self) -> str:
        """Return a detailed representation."""
        return f"IRI({self.value!r})"

    def to_sparql(self) -> str:
        """Format this IRI for SPARQL output.

        Returns:
            SPARQL IRI with angle brackets.

        Example:
            >>> IRI("http://example.org/foo").to_sparql()
            '<http://example.org/foo>'
        """
        escaped: str = escape_sparql_iri(self.value)
        return f"<{escaped}>"

    def to_sparql_filter(self) -> str:
        """Format this IRI for SPARQL FILTER expressions.

        IRIs use the same format in filters as elsewhere.
        """
        return self.to_sparql()


@dataclass(frozen=True)
class Literal:
    """
    A plain RDF literal value.

    This is the base class for all literal types. Plain literals are
    simple string values without language tags or datatypes.

    Attributes:
        value: The literal value (will be converted to string for SPARQL).

    Example:
        >>> literal = Literal("hello")
        >>> print(literal.to_sparql())  # '"hello"'
    """

    value: Any

    def __str__(self) -> str:
        """Return the value as string."""
        return str(self.value)

    def __repr__(self) -> str:
        """Return a detailed representation."""
        return f"Literal({self.value!r})"

    def to_sparql(self) -> str:
        """Format this literal for SPARQL output.

        Returns:
            Quoted and escaped SPARQL literal string.

        Example:
            >>> Literal("hello").to_sparql()
            '"hello"'
        """
        escaped: str = escape_sparql_literal(str(self.value))
        return f'"{escaped}"'

    def to_sparql_filter(self) -> str:
        """Format this literal for SPARQL FILTER expressions.

        Numbers are left unquoted for proper numeric comparisons.
        Strings are quoted and escaped.

        Example:
            >>> Literal(42).to_sparql_filter()
            '42'
            >>> Literal("hello").to_sparql_filter()
            '"hello"'
        """
        if isinstance(self.value, (int, float)):
            return str(self.value)
        return self.to_sparql()


@dataclass(frozen=True)
class LangLiteral(Literal):
    """
    A language-tagged literal value from RDF.

    This dataclass represents an RDF literal with an associated language tag.
    It is returned when querying LangString fields to preserve the language
    information from the SPARQL endpoint.

    Attributes:
        value: The string value of the literal.
        lang: The BCP 47 language tag (e.g., 'en', 'yi', 'zh-Hans'), or None
              if no language tag was present.

    Example:
        >>> literal = LangLiteral("Hello", "en")
        >>> print(literal.value)   # "Hello"
        >>> print(literal.lang)    # "en"
        >>> print(str(literal))    # "Hello"
    """

    lang: str | None = None

    def __repr__(self) -> str:
        """Return a detailed representation including language tag."""
        if self.lang:
            return f"LangLiteral({self.value!r}, lang={self.lang!r})"
        return f"LangLiteral({self.value!r})"

    def to_sparql(self) -> str:
        """Format this language-tagged literal for SPARQL output.

        Returns:
            SPARQL literal string with language tag if present.

        Example:
            >>> LangLiteral("Hello", "en").to_sparql()
            '"Hello"@en'
            >>> LangLiteral("Hello", None).to_sparql()
            '"Hello"'
        """
        escaped: str = escape_sparql_literal(str(self.value))
        if self.lang:
            return f'"{escaped}"@{self.lang}'
        return f'"{escaped}"'

    def to_sparql_filter(self) -> str:
        """Format this language-tagged literal for SPARQL FILTER expressions.

        Language-tagged literals use the same format in filters.
        """
        return self.to_sparql()


@dataclass(frozen=True)
class TypedLiteral(Literal):
    """
    A typed literal value from RDF with its XSD datatype.

    This dataclass represents an RDF literal with its associated datatype IRI.
    Values are **automatically converted** to appropriate Python types based on
    the XSD datatype during hydration.

    Type Conversion Mapping:
        - xsd:integer    → int
        - xsd:decimal    → decimal.Decimal
        - xsd:float      → float
        - xsd:double     → float
        - xsd:boolean    → bool
        - xsd:date       → datetime.date
        - xsd:dateTime   → datetime.datetime
        - Unknown types  → str (original string value preserved)

    Attributes:
        value: The converted Python value (NOT always a string - see mapping above)
        datatype: The XSD datatype IRI (e.g., "http://www.w3.org/2001/XMLSchema#integer"),
                  or None if no datatype was present in the RDF data.

    Example:
        >>> literal = TypedLiteral(42, "http://www.w3.org/2001/XMLSchema#integer")
        >>> print(literal.value)        # 42
        >>> print(type(literal.value))  # <class 'int'>
        >>> print(literal.datatype)     # "http://www.w3.org/2001/XMLSchema#integer"

        >>> # Unknown datatypes preserve the string value
        >>> custom = TypedLiteral("abc", "http://example.org/customType")
        >>> print(type(custom.value))   # <class 'str'>
    """

    datatype: str | None = None

    def __repr__(self) -> str:
        """Return a detailed representation including datatype."""
        if self.datatype:
            return f"TypedLiteral({self.value!r}, datatype={self.datatype!r})"
        return f"TypedLiteral({self.value!r})"

    def to_sparql(self) -> str:
        """Format this typed literal for SPARQL output.

        Returns:
            SPARQL literal string with datatype if present.

        Example:
            >>> TypedLiteral(42, XSD_INTEGER).to_sparql()
            '"42"^^<http://www.w3.org/2001/XMLSchema#integer>'
            >>> TypedLiteral("hello", None).to_sparql()
            '"hello"'
        """
        escaped: str = escape_sparql_literal(str(self.value))
        if self.datatype:
            return f'"{escaped}"^^<{self.datatype}>'
        return f'"{escaped}"'

    def to_sparql_filter(self) -> str:
        """Format this typed literal for SPARQL FILTER expressions.

        Typed literals use the same format in filters.
        """
        return self.to_sparql()
