"""
Prefix registry for managing namespace prefixes in SPARQL queries.

Features:
- Pre-registered common prefixes (schema, foaf, rdf, rdfs, owl, xsd)
- Custom prefix registration
- Short-form IRI expansion
- SPARQL PREFIX clause generation
"""

from functools import lru_cache
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    pass

# Import for IRI validation
from ..exc.exceptions import IRIError
from .security import validate_iri


class PrefixRegistry:
    """Registry for managing namespace prefixes and IRI expansion."""

    def __init__(self) -> None:
        """Initialize prefix registry with common prefixes."""
        self.prefixes: dict[str, str] = {
            # Common RDF/RDFS/OWL prefixes
            "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
            "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
            "owl": "http://www.w3.org/2002/07/owl#",
            "xsd": "http://www.w3.org/2001/XMLSchema#",
            # Common vocabulary prefixes
            "schema": "http://schema.org/",
            "foaf": "http://xmlns.com/foaf/0.1/",
            "dc": "http://purl.org/dc/elements/1.1/",
            "dcterms": "http://purl.org/dc/terms/",
            "skos": "http://www.w3.org/2004/02/skos/core#",
            # Common example/test prefixes
            "ex": "http://example.org/",
        }
        # Create cached expansion function
        self._expand_cached = lru_cache(maxsize=128)(self._expand_impl)

    def register_prefix(self, prefix: str, namespace: str) -> None:
        """Register a new prefix mapping.

        Args:
            prefix: The prefix (e.g., 'schema')
            namespace: The namespace URI (e.g., 'http://schema.org/')

        Raises:
            ValueError: If prefix contains invalid characters or namespace is invalid
        """
        if not prefix or not isinstance(prefix, str):
            raise ValueError("Prefix must be a non-empty string")

        if not namespace or not isinstance(namespace, str):
            raise ValueError("Namespace must be a non-empty string")

        # Validate prefix format
        if ":" in prefix:
            raise ValueError("Prefix cannot contain colon (:)")

        if not prefix.isidentifier():
            raise ValueError("Prefix must be a valid identifier")

        # Validate namespace format using the same validation as the rest of the
        # codebase
        try:
            validate_iri(namespace, strict=True)
        except IRIError as e:
            raise ValueError(f"Invalid namespace IRI: {e}") from e

        # Store the namespace as-is, preserving trailing # or /
        self.prefixes[prefix] = namespace
        # Clear cache when prefixes change
        self._expand_cached.cache_clear()

    def _expand_impl(self, short_iri: str) -> str:
        """Expand a short-form IRI to full IRI using registered prefixes.

        Args:
            short_iri: Short-form IRI (e.g., 'schema:Person')

        Returns:
            Expanded IRI (e.g., 'http://schema.org/Person')

        Raises:
            ValueError: If short_iri format is invalid
        """
        if not short_iri or not isinstance(short_iri, str):
            raise ValueError("Short IRI must be a non-empty string")

        # Check if this is a full IRI (contains "://" which indicates a scheme)
        if "://" in short_iri:
            # This is a full IRI, return as-is
            return short_iri

        if ":" not in short_iri:
            # No prefix found, return as-is
            return short_iri

        prefix, local = short_iri.split(":", 1)

        if not prefix:
            raise ValueError("Prefix cannot be empty")

        if not local:
            raise ValueError("Local part cannot be empty")

        if prefix in self.prefixes:
            namespace = self.prefixes[prefix]
            # Determine appropriate separator based on original namespace format
            # Check the original namespace before normalization
            original_namespace = self.prefixes[prefix]
            if original_namespace.endswith("#"):
                return namespace + local
            elif original_namespace.endswith("/"):
                return namespace + local
            else:
                # If namespace doesn't end with # or /, add # as default
                return namespace + "#" + local

        # Prefix not found, return original
        return short_iri

    def expand(self, short_iri: str) -> str:
        """Expand a short-form IRI to full IRI using registered prefixes (cached).

        Args:
            short_iri: Short-form IRI (e.g., 'schema:Person')

        Returns:
            Expanded IRI (e.g., 'http://schema.org/Person')

        Raises:
            ValueError: If short_iri format is invalid
        """
        return self._expand_cached(short_iri)

    def contract(self, full_iri: str) -> str | None:
        """Contract a full IRI to short-form using registered prefixes.

        Args:
            full_iri: Full IRI (e.g., 'http://schema.org/Person')

        Returns:
            Short-form IRI if prefix found (e.g., 'schema:Person'),
            None if no matching prefix found
        """
        if not full_iri or not isinstance(full_iri, str):
            return None

        # Try to find matching prefix - prioritize longer matches first
        matches = []
        for prefix, namespace in self.prefixes.items():
            # Try exact match first (namespace as stored)
            if full_iri.startswith(namespace):
                remaining = full_iri[len(namespace) :]
                if remaining:  # Only return if there's a local part
                    matches.append((len(namespace), f"{prefix}:{remaining}"))

            # Try with # terminator if namespace doesn't end with #
            if not namespace.endswith("#"):
                expected_namespace_hash = namespace + "#"
                if full_iri.startswith(expected_namespace_hash):
                    local_part = full_iri[len(expected_namespace_hash) :]
                    if local_part:  # Only return if there's a local part
                        matches.append(
                            (len(expected_namespace_hash), f"{prefix}:{local_part}")
                        )

            # Try with / terminator if namespace doesn't end with /
            if not namespace.endswith("/"):
                expected_namespace_slash = namespace + "/"
                if full_iri.startswith(expected_namespace_slash):
                    local_part = full_iri[len(expected_namespace_slash) :]
                    if local_part:  # Only return if there's a local part
                        matches.append(
                            (len(expected_namespace_slash), f"{prefix}:{local_part}")
                        )

        # Return the match with the longest namespace (most specific)
        if matches:
            # Sort by namespace length (longest first) and return first
            matches.sort(reverse=True, key=lambda x: x[0])
            return matches[0][1]

        return None

    def get_prefix_declarations(self) -> str:
        """Generate SPARQL PREFIX declarations for all registered prefixes.

        Returns:
            SPARQL PREFIX declarations as string
        """
        declarations = []
        for prefix, namespace in sorted(self.prefixes.items()):
            # For SPARQL PREFIX declarations, ensure namespace ends with # or /
            # This is the standard SPARQL format
            if not namespace.endswith(("#", "/")):
                namespace += "#"
            declarations.append(f"PREFIX {prefix}: <{namespace}>")

        return "\n".join(declarations)

    def get_prefixes(self) -> dict[str, str]:
        """Get all registered prefixes.

        Returns:
            Dictionary of prefix to namespace mappings
        """
        return dict(self.prefixes)

    def clear(self) -> None:
        """Clear all registered prefixes."""
        self.prefixes.clear()
        # Clear cache when prefixes change
        self._expand_cached.cache_clear()

    def __contains__(self, prefix: str) -> bool:
        """Check if prefix is registered."""
        return prefix in self.prefixes

    def __getitem__(self, prefix: str) -> str:
        """Get namespace for prefix."""
        return self.prefixes[prefix]

    def __setitem__(self, prefix: str, namespace: str) -> None:
        """Set namespace for prefix."""
        self.register_prefix(prefix, namespace)
        # Cache clearing is handled by register_prefix()
