"""
Core domain models for Information Spaces.

This module provides the foundational data models for the Information Space
abstraction, including the space entity, document associations, and configuration.
"""

import uuid
from dataclasses import dataclass, field
from datetime import datetime
from typing import Dict, Any, List, Optional
from enum import Enum


class SpaceStatus(Enum):
    """Lifecycle status of an Information Space."""
    DRAFT = "draft"
    ACTIVE = "active"
    ARCHIVED = "archived"
    DELETED = "deleted"


@dataclass
class SpaceMetadata:
    """
    Extensible metadata for an Information Space.

    Attributes:
        tags: List of tags for categorization
        author: Author identifier
        custom: Dictionary for custom metadata fields
    """
    tags: List[str] = field(default_factory=list)
    author: Optional[str] = None
    custom: Dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> Dict[str, Any]:
        """Convert metadata to dictionary for serialization."""
        return {
            "tags": self.tags,
            "author": self.author,
            "custom": self.custom,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "SpaceMetadata":
        """Create metadata from dictionary."""
        return cls(
            tags=data.get("tags", []),
            author=data.get("author"),
            custom=data.get("custom", {}),
        )


@dataclass
class SpaceConfig:
    """
    Configuration settings for an Information Space.

    Attributes:
        default_variant: Default directory variant for export (flat/hierarchical/semantic)
        enable_caching: Whether to enable render caching
        theme: Theme name for HTML rendering
        history_enabled: Whether git history tracking is enabled (Phase 8)
        history_backend: History backend type (default: "git")
        history_options: Additional history backend options
        variable_scope: Default variable scope resolution strategy
    """
    default_variant: str = "hierarchical"
    enable_caching: bool = True
    theme: Optional[str] = None
    history_enabled: bool = False
    history_backend: str = "git"
    history_options: Dict[str, Any] = field(default_factory=dict)
    variable_scope: str = "space"  # space, document, request

    def to_dict(self) -> Dict[str, Any]:
        """Convert config to dictionary for serialization."""
        return {
            "default_variant": self.default_variant,
            "enable_caching": self.enable_caching,
            "theme": self.theme,
            "history_enabled": self.history_enabled,
            "history_backend": self.history_backend,
            "history_options": self.history_options,
            "variable_scope": self.variable_scope,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "SpaceConfig":
        """Create config from dictionary."""
        return cls(
            default_variant=data.get("default_variant", "hierarchical"),
            enable_caching=data.get("enable_caching", True),
            theme=data.get("theme"),
            history_enabled=data.get("history_enabled", False),
            history_backend=data.get("history_backend", "git"),
            history_options=data.get("history_options", {}),
            variable_scope=data.get("variable_scope", "space"),
        )


@dataclass
class SpaceDocument:
    """
    Represents a document's membership in an Information Space.

    Attributes:
        id: Unique document membership identifier
        space_id: ID of the containing space
        document_id: Reference to the actual document
        space_path: Path within the space (e.g., "/intro.md")
        order_index: Ordering within the space
        metadata: Document-specific metadata
        content_hash: Hash of document content for change detection
        added_at: Timestamp when document was added
    """
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    space_id: str = ""
    document_id: str = ""
    space_path: str = ""
    order_index: int = 0
    metadata: Dict[str, Any] = field(default_factory=dict)
    content_hash: Optional[str] = None
    added_at: datetime = field(default_factory=datetime.now)

    def to_dict(self) -> Dict[str, Any]:
        """Convert document association to dictionary."""
        return {
            "id": self.id,
            "space_id": self.space_id,
            "document_id": self.document_id,
            "space_path": self.space_path,
            "order_index": self.order_index,
            "metadata": self.metadata,
            "content_hash": self.content_hash,
            "added_at": self.added_at.isoformat(),
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "SpaceDocument":
        """Create document association from dictionary."""
        added_at = data.get("added_at")
        if isinstance(added_at, str):
            added_at = datetime.fromisoformat(added_at)
        elif added_at is None:
            added_at = datetime.now()

        return cls(
            id=data.get("id", str(uuid.uuid4())),
            space_id=data.get("space_id", ""),
            document_id=data.get("document_id", ""),
            space_path=data.get("space_path", ""),
            order_index=data.get("order_index", 0),
            metadata=data.get("metadata", {}),
            content_hash=data.get("content_hash"),
            added_at=added_at,
        )


@dataclass
class InformationSpace:
    """
    First-class Information Space abstraction.

    An Information Space is a container for documents with transclusion
    relationships, persistent context, and lifecycle management.

    Attributes:
        id: Unique space identifier
        name: Human-readable unique name
        description: Optional description
        metadata: Extensible metadata
        config: Space configuration
        parent_space_id: Optional parent space for inheritance
        status: Current lifecycle status
        created_at: Creation timestamp
        updated_at: Last update timestamp

    Example:
        space = InformationSpace(
            name="api-docs",
            description="API Documentation",
            config=SpaceConfig(theme="technical")
        )
    """
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    name: str = ""
    description: Optional[str] = None
    metadata: SpaceMetadata = field(default_factory=SpaceMetadata)
    config: SpaceConfig = field(default_factory=SpaceConfig)
    parent_space_id: Optional[str] = None
    status: SpaceStatus = SpaceStatus.DRAFT
    created_at: datetime = field(default_factory=datetime.now)
    updated_at: datetime = field(default_factory=datetime.now)

    def __post_init__(self):
        """Validate space after initialization."""
        if not self.name:
            raise ValueError("Space name is required")

    def to_dict(self) -> Dict[str, Any]:
        """Convert space to dictionary for serialization."""
        return {
            "id": self.id,
            "name": self.name,
            "description": self.description,
            "metadata": self.metadata.to_dict() if isinstance(self.metadata, SpaceMetadata) else self.metadata,
            "config": self.config.to_dict() if isinstance(self.config, SpaceConfig) else self.config,
            "parent_space_id": self.parent_space_id,
            "status": self.status.value if isinstance(self.status, SpaceStatus) else self.status,
            "created_at": self.created_at.isoformat(),
            "updated_at": self.updated_at.isoformat(),
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "InformationSpace":
        """Create space from dictionary."""
        created_at = data.get("created_at")
        if isinstance(created_at, str):
            created_at = datetime.fromisoformat(created_at)
        elif created_at is None:
            created_at = datetime.now()

        updated_at = data.get("updated_at")
        if isinstance(updated_at, str):
            updated_at = datetime.fromisoformat(updated_at)
        elif updated_at is None:
            updated_at = datetime.now()

        status = data.get("status", "draft")
        if isinstance(status, str):
            status = SpaceStatus(status)

        metadata = data.get("metadata", {})
        if isinstance(metadata, dict):
            metadata = SpaceMetadata.from_dict(metadata)

        config = data.get("config", {})
        if isinstance(config, dict):
            config = SpaceConfig.from_dict(config)

        return cls(
            id=data.get("id", str(uuid.uuid4())),
            name=data["name"],
            description=data.get("description"),
            metadata=metadata,
            config=config,
            parent_space_id=data.get("parent_space_id"),
            status=status,
            created_at=created_at,
            updated_at=updated_at,
        )

    def activate(self) -> None:
        """Activate the space."""
        self.status = SpaceStatus.ACTIVE
        self.updated_at = datetime.now()

    def archive(self) -> None:
        """Archive the space."""
        self.status = SpaceStatus.ARCHIVED
        self.updated_at = datetime.now()

    def touch(self) -> None:
        """Update the last modified timestamp."""
        self.updated_at = datetime.now()


@dataclass
class SpaceVariable:
    """
    Variable stored at space level for transclusion context.

    Attributes:
        space_id: ID of the containing space
        name: Variable name
        value: Variable value (JSON-serializable)
        scope: Variable scope (space, document, request)
    """
    space_id: str
    name: str
    value: Any
    scope: str = "space"

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary."""
        return {
            "space_id": self.space_id,
            "name": self.name,
            "value": self.value,
            "scope": self.scope,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "SpaceVariable":
        """Create from dictionary."""
        return cls(
            space_id=data["space_id"],
            name=data["name"],
            value=data["value"],
            scope=data.get("scope", "space"),
        )


@dataclass
class TransclusionReference:
    """
    Tracks a transclusion reference between documents for cache invalidation.

    Attributes:
        source_doc_id: ID of the document containing the transclusion
        target_doc_id: ID of the transcluded document
        space_id: ID of the space containing the reference
        created_at: When the reference was created
    """
    source_doc_id: str
    target_doc_id: str
    space_id: str
    created_at: datetime = field(default_factory=datetime.now)

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary."""
        return {
            "source_doc_id": self.source_doc_id,
            "target_doc_id": self.target_doc_id,
            "space_id": self.space_id,
            "created_at": self.created_at.isoformat(),
        }
