"""Cubesquare Layer Base Class

All layers share this contract for consistent API.
"""
from __future__ import annotations

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import List, Dict, Any, Optional, TYPE_CHECKING
from enum import IntEnum
from pathlib import Path
import hashlib


class LayerID(IntEnum):
    """Layer identifiers."""
    GEOMETRIC = 1
    SEMANTIC = 2
    SPATIAL = 3
    FUNCTIONAL = 4
    PROBABILISTIC = 5
    EMOTIONAL = 6
    FREQUENCY = 7
    ELECTROMAGNETIC = 8
    MEMBRANE = 9


@dataclass
class Token:
    """Universal token representation across all layers.

    This is the user-facing token class with clean interface.
    """
    # Core identity
    morton: int
    hex_id: str

    # Coordinates (derived from morton)
    x: int = 0
    y: int = 0
    z: int = 0

    # Classification
    layer: int = 0
    basin: int = 0
    diagonal: int = 0

    # Content
    path: Optional[str] = None
    content_hash: str = ""

    # Metadata
    coherence: float = 1.0
    energy: float = 100.0
    phi_depth: int = 0

    # Layer-specific data
    layer_data: Dict[str, Any] = field(default_factory=dict)

    def __hash__(self):
        return hash(self.morton)

    def __eq__(self, other):
        if isinstance(other, Token):
            return self.morton == other.morton
        return False

    @classmethod
    def from_morton(cls, morton: int, **kwargs) -> "Token":
        """Create token from morton code."""
        from .._core import morton_decode, PRIME_131

        x, y, z = morton_decode(morton)

        # Always compute basin from coordinates directly (O(1), pure Python)
        # This avoids C backend issues where basin might return 0
        x8, y8, z8 = x & 0xFF, y & 0xFF, z & 0xFF
        hash_val = (x8 * 1664525 + y8 * 22695477 + z8 * 48271) & 0xFFFFFFFF
        basin = hash_val % PRIME_131

        # Diagonal sum
        diagonal = x8 + y8 + z8

        # Phi depth (0-11)
        phi_depth = min(diagonal // 23, 11)

        # Layer classification based on basin (131 basins / 9 layers ≈ 14.5 basins per layer)
        layer = min(basin // 15 + 1, 9)

        hex_id = f"{(x >> 8) & 0xFF:02X}{x & 0xFF:02X}{(y >> 8) & 0xFF:02X}{y & 0xFF:02X}"

        return cls(
            morton=morton,
            hex_id=hex_id,
            x=x, y=y, z=z,
            layer=layer,
            basin=basin,
            diagonal=diagonal,
            phi_depth=phi_depth,
            **kwargs
        )

    @classmethod
    def from_path(cls, path: str) -> "Token":
        """Create token from file path."""
        from .._core import morton_encode

        # Create deterministic hash from path
        path_hash = hashlib.sha256(path.encode()).hexdigest()
        hash_int = int(path_hash[:16], 16)

        # Derive coordinates from hash
        x = hash_int & 0x1FFFFF
        y = (hash_int >> 21) & 0x1FFFFF
        morton = morton_encode(x, y)

        token = cls.from_morton(morton, path=path, content_hash=path_hash[:16])
        return token


class Layer(ABC):
    """Base class for all CST layers.

    Each layer provides a unique perspective on the token space:
    - Query: Find tokens by layer-specific criteria
    - Navigate: Move between tokens
    - Filter: Subset tokens by layer-specific attributes
    """

    # Layer identity
    LAYER_ID: LayerID = LayerID.GEOMETRIC
    LAYER_NAME: str = "BASE"
    LAYER_DESCRIPTION: str = "Base layer"

    def __init__(self, memory: "CSTMemory"):
        """Initialize layer with reference to parent memory."""
        self._memory = memory

    def _check_access(self):
        """Check access to this layer (no-op in standalone)."""
        pass

    def _record(self, operation: str, **details):
        """Record usage for this layer operation (no-op in standalone)."""
        pass

    # Common interface - all layers implement these

    @abstractmethod
    def query(self, pattern: str, limit: int = 100) -> List[Token]:
        """Query tokens by layer-specific pattern."""
        pass

    @abstractmethod
    def filter(self, tokens: List[Token], **criteria) -> List[Token]:
        """Filter tokens by layer-specific criteria."""
        pass

    @abstractmethod
    def navigate(self, token: Token, direction: str) -> Optional[Token]:
        """Navigate from token in a direction."""
        pass

    @abstractmethod
    def distance(self, a: Token, b: Token) -> float:
        """Compute layer-specific distance between tokens."""
        pass

    # Optional: layers can override for specialized behavior

    def related(self, token: Token, limit: int = 10) -> List[Token]:
        """Find related tokens (default: nearby by distance)."""
        all_tokens = self._memory._tokens.values()
        scored = [(t, self.distance(token, t)) for t in all_tokens if t != token]
        scored.sort(key=lambda x: x[1])
        return [t for t, _ in scored[:limit]]

    def cluster(self, tokens: List[Token]) -> Dict[int, List[Token]]:
        """Cluster tokens by layer-specific grouping (default: by basin)."""
        clusters: Dict[int, List[Token]] = {}
        for t in tokens:
            key = t.basin
            if key not in clusters:
                clusters[key] = []
            clusters[key].append(t)
        return clusters


# Forward reference for type hints
if TYPE_CHECKING:
    from ..memory import CSTMemory
