"""
Persistent transclusion context for Information Spaces.

This module extends the core TransclusionContext with database-backed
variable storage and space integration.
"""

from pathlib import Path
from typing import Dict, Any, Optional, List, Set
from dataclasses import dataclass, field

from markitect.packaging.transclusion.context import TransclusionContext
from ..models import SpaceVariable
from ..repositories.interfaces import IVariableRepository


class VariableScope:
    """Defines variable scope levels in order of precedence (highest first)."""

    REQUEST = "request"  # Temporary, per-request variables
    DOCUMENT = "document"  # Document-level variables
    SPACE = "space"  # Space-level variables (persisted)


@dataclass
class ScopedVariables:
    """
    Manages variables across multiple scope layers.

    Variables are resolved in order: request > document > space
    This allows local overrides of space-level defaults.
    """

    space_vars: Dict[str, Any] = field(default_factory=dict)
    document_vars: Dict[str, Any] = field(default_factory=dict)
    request_vars: Dict[str, Any] = field(default_factory=dict)

    def get(self, name: str, default: Any = None) -> Any:
        """
        Get a variable, checking scopes in order of precedence.

        Args:
            name: Variable name
            default: Default if not found in any scope

        Returns:
            Variable value from highest precedence scope, or default
        """
        if name in self.request_vars:
            return self.request_vars[name]
        if name in self.document_vars:
            return self.document_vars[name]
        if name in self.space_vars:
            return self.space_vars[name]
        return default

    def set(self, name: str, value: Any, scope: str = VariableScope.REQUEST) -> None:
        """
        Set a variable in the specified scope.

        Args:
            name: Variable name
            value: Variable value
            scope: Target scope (request, document, or space)
        """
        if scope == VariableScope.REQUEST:
            self.request_vars[name] = value
        elif scope == VariableScope.DOCUMENT:
            self.document_vars[name] = value
        elif scope == VariableScope.SPACE:
            self.space_vars[name] = value

    def get_all(self) -> Dict[str, Any]:
        """
        Get all variables merged with proper precedence.

        Returns:
            Dictionary with all variables, higher scopes overriding lower
        """
        merged = {}
        merged.update(self.space_vars)
        merged.update(self.document_vars)
        merged.update(self.request_vars)
        return merged

    def clear_scope(self, scope: str) -> None:
        """Clear all variables in a scope."""
        if scope == VariableScope.REQUEST:
            self.request_vars.clear()
        elif scope == VariableScope.DOCUMENT:
            self.document_vars.clear()
        elif scope == VariableScope.SPACE:
            self.space_vars.clear()


class SpaceTransclusionContext(TransclusionContext):
    """
    Transclusion context integrated with Information Spaces.

    Extends the base TransclusionContext with:
    - Space-aware variable resolution with scope layers
    - Reference tracking for cache invalidation
    - Optional persistence of space-level variables
    """

    def __init__(
        self,
        space_id: str,
        base_path: Optional[Path] = None,
        variables: Optional[Dict[str, Any]] = None,
        max_depth: int = 10,
        variable_repo: Optional[IVariableRepository] = None,
    ):
        """
        Initialize a space-aware transclusion context.

        Args:
            space_id: The space ID this context belongs to
            base_path: Base path for relative file resolution
            variables: Initial request-level variables
            max_depth: Maximum inclusion depth
            variable_repo: Optional repository for persisting space variables
        """
        # Initialize scoped vars BEFORE super().__init__() because
        # the base class sets self.variables which triggers our property setter
        self._scoped_vars = ScopedVariables()
        self.space_id = space_id
        self._variable_repo = variable_repo

        # Track references during processing
        self._current_document_id: Optional[str] = None
        self._references: List[tuple] = []  # (source_doc_id, target_doc_id)

        # Now call parent init (which may set variables via property)
        super().__init__(base_path=base_path, variables={}, max_depth=max_depth)

        # Load space variables from repository if available
        if variable_repo:
            self._load_space_variables()

        # Set initial request variables
        if variables:
            for name, value in variables.items():
                self._scoped_vars.set(name, value, VariableScope.REQUEST)

    def _load_space_variables(self) -> None:
        """Load space-level variables from the repository."""
        if not self._variable_repo:
            return

        space_vars = self._variable_repo.list_variables(self.space_id, scope="space")
        for var in space_vars:
            self._scoped_vars.set(var.name, var.value, VariableScope.SPACE)

        doc_vars = self._variable_repo.list_variables(self.space_id, scope="document")
        for var in doc_vars:
            self._scoped_vars.set(var.name, var.value, VariableScope.DOCUMENT)

    def set_current_document(self, document_id: str) -> None:
        """
        Set the current document being processed.

        Args:
            document_id: The document ID
        """
        self._current_document_id = document_id

    def get_current_document(self) -> Optional[str]:
        """Get the current document being processed."""
        return self._current_document_id

    def track_reference(self, target_doc_id: str) -> None:
        """
        Track a reference from current document to target.

        Args:
            target_doc_id: The document being referenced
        """
        if self._current_document_id:
            self._references.append((self._current_document_id, target_doc_id))

    def get_tracked_references(self) -> List[tuple]:
        """
        Get all tracked references.

        Returns:
            List of (source_doc_id, target_doc_id) tuples
        """
        return list(self._references)

    def clear_tracked_references(self) -> None:
        """Clear all tracked references."""
        self._references.clear()

    # Override variable methods to use scoped storage

    def set_variable(self, name: str, value: Any, scope: str = VariableScope.REQUEST) -> None:
        """
        Set a variable in the specified scope.

        Args:
            name: Variable name
            value: Variable value
            scope: Variable scope (request, document, or space)
        """
        self._scoped_vars.set(name, value, scope)

        # Persist space-level variables if repository available
        if scope == VariableScope.SPACE and self._variable_repo:
            var = SpaceVariable(
                space_id=self.space_id,
                name=name,
                value=value,
                scope=scope,
            )
            self._variable_repo.set_variable(var)

    def get_variable(self, name: str, default: Any = None) -> Any:
        """
        Get a variable from the scoped storage.

        Args:
            name: Variable name
            default: Default value if not found

        Returns:
            Variable value or default
        """
        return self._scoped_vars.get(name, default)

    def substitute_variables(self, text: str) -> str:
        """
        Substitute variables in text using scoped variable resolution.

        Args:
            text: Text containing {{variable}} references

        Returns:
            Text with variables substituted
        """
        import re

        def replace_var(match):
            var_name = match.group(1).strip()
            value = self._scoped_vars.get(var_name)
            return str(value) if value is not None else match.group(0)

        return re.sub(r'\{\{([^}]+)\}\}', replace_var, text)

    @property
    def variables(self) -> Dict[str, Any]:
        """Get all variables merged with proper precedence."""
        return self._scoped_vars.get_all()

    @variables.setter
    def variables(self, value: Dict[str, Any]) -> None:
        """Set request-level variables."""
        self._scoped_vars.request_vars = value

    def create_child_context(
        self, new_base_path: Optional[Path] = None
    ) -> "SpaceTransclusionContext":
        """
        Create a child context for nested processing.

        Args:
            new_base_path: New base path for the child context

        Returns:
            New SpaceTransclusionContext with inherited state
        """
        child = SpaceTransclusionContext(
            space_id=self.space_id,
            base_path=new_base_path or self.base_path,
            max_depth=self.max_depth,
            variable_repo=self._variable_repo,
        )

        # Copy scoped variables
        child._scoped_vars.space_vars = self._scoped_vars.space_vars.copy()
        child._scoped_vars.document_vars = self._scoped_vars.document_vars.copy()
        child._scoped_vars.request_vars = self._scoped_vars.request_vars.copy()

        # Copy processing state
        child.current_depth = self.current_depth
        child.inclusion_stack = self.inclusion_stack.copy()
        child.processed_files = self.processed_files.copy()

        # Share reference tracking
        child._current_document_id = self._current_document_id
        child._references = self._references  # Shared list

        return child


class CrossSpaceResolver:
    """
    Resolves references across space boundaries.

    Enables transclusion from one space to reference content in another space.
    """

    def __init__(self, contexts: Dict[str, SpaceTransclusionContext]):
        """
        Initialize the cross-space resolver.

        Args:
            contexts: Dictionary mapping space_id to SpaceTransclusionContext
        """
        self._contexts = contexts

    def add_context(self, space_id: str, context: SpaceTransclusionContext) -> None:
        """Add a space context."""
        self._contexts[space_id] = context

    def get_context(self, space_id: str) -> Optional[SpaceTransclusionContext]:
        """Get the context for a space."""
        return self._contexts.get(space_id)

    def resolve_variable(
        self,
        space_id: str,
        var_name: str,
        default: Any = None,
    ) -> Any:
        """
        Resolve a variable from a specific space.

        Args:
            space_id: The space to look up
            var_name: Variable name
            default: Default value if not found

        Returns:
            Variable value or default
        """
        context = self._contexts.get(space_id)
        if context:
            return context.get_variable(var_name, default)
        return default

    def resolve_cross_space_reference(
        self,
        reference: str,
        current_space_id: str,
    ) -> Optional[tuple]:
        """
        Parse and resolve a cross-space reference.

        Reference format: "space:other-space/path/to/doc.md"
        or just "path/to/doc.md" for current space.

        Args:
            reference: The reference string
            current_space_id: Current space ID for relative references

        Returns:
            Tuple of (space_id, path) or None if invalid
        """
        if ":" in reference and reference.startswith("space:"):
            # Cross-space reference
            _, rest = reference.split(":", 1)
            if "/" in rest:
                space_name, path = rest.split("/", 1)
                return (space_name, "/" + path)
            return None
        else:
            # Same-space reference
            return (current_space_id, reference)
