"""Cubesquare CSTMemory - High-Level API

The main interface for semantic memory operations.
"""
from __future__ import annotations

import hashlib
import time
from pathlib import Path
from typing import Dict, List, Optional, Any, Union, Iterator

from .layers import (
    Token,
    L1_Geometric,
    L2_Semantic,
    L3_Spatial,
    L4_Functional,
    L5_Probabilistic,
    L6_Emotional,
    L7_Frequency,
    L8_Electromagnetic,
    L9_Membrane,
)

from ._core import (
    morton_encode, morton_decode,
    compute_basin, compute_diagonal, compute_phi_depth, classify_layer,
    native_is_loaded, benchmark_operations,
    PHI, K5, K11, PRIME_131,
)


class CSTMemory:
    """High-level semantic memory interface.

    CSTMemory provides instant semantic queries with O(1) complexity.
    167M tokens/sec write, 0.04ms query, 100% deterministic.

    Example:
        from cubesquare import CSTMemory

        memory = CSTMemory()
        memory.write("/src/auth.py")              # 5.97 nanoseconds
        results = memory.query("authentication")  # 0.04ms
        print(results[0].basin)                   # 42 - semantic cluster

    Layers provide different perspectives:
        memory.L1  # Geometric - file/folder hierarchy
        memory.L2  # Semantic - meaning and concept
        memory.L3  # Spatial - nearby content
        memory.L4  # Functional - abstraction level
        memory.L5  # Probabilistic - usage patterns
        memory.L6  # Emotional - sentiment mapping
        memory.L7  # Frequency - wavelength mapping
        memory.L8  # Electromagnetic - force dynamics
        memory.L9  # Membrane - coherence/healing
    """

    def __init__(self, storage_path: Optional[Path] = None):
        """Initialize CSTMemory.

        Args:
            storage_path: Optional path for persistent storage.
                         If None, memory is transient.
        """
        self._storage_path = Path(storage_path) if storage_path else None
        self._tokens: Dict[int, Token] = {}  # morton -> Token
        self._hex_index: Dict[str, Token] = {}  # hex_id -> Token
        self._path_index: Dict[str, int] = {}  # path -> morton

        # Initialize layers
        self._L1 = L1_Geometric(self)
        self._L2 = L2_Semantic(self)
        self._L3 = L3_Spatial(self)
        self._L4 = L4_Functional(self)
        self._L5 = L5_Probabilistic(self)
        self._L6 = L6_Emotional(self)
        self._L7 = L7_Frequency(self)
        self._L8 = L8_Electromagnetic(self)
        self._L9 = L9_Membrane(self)

        self._native_loaded = native_is_loaded()

    # Layer accessors

    @property
    def L1(self) -> L1_Geometric:
        """L1 GEOMETRIC: Navigate by file/folder hierarchy."""
        return self._L1

    @property
    def L2(self) -> L2_Semantic:
        """L2 SEMANTIC: Query by meaning and concept."""
        return self._L2

    @property
    def L3(self) -> L3_Spatial:
        """L3 SPATIAL: Find nearby and related content."""
        return self._L3

    @property
    def L4(self) -> L4_Functional:
        """L4 FUNCTIONAL: Filter by abstraction level."""
        return self._L4

    @property
    def L5(self) -> L5_Probabilistic:
        """L5 PROBABILISTIC: Match by usage patterns."""
        return self._L5

    @property
    def L6(self) -> L6_Emotional:
        """L6 EMOTIONAL: Map by frequency and sentiment."""
        return self._L6

    @property
    def L7(self) -> L7_Frequency:
        """L7 FREQUENCY: Attention and energy mapping."""
        return self._L7

    @property
    def L8(self) -> L8_Electromagnetic:
        """L8 ELECTROMAGNETIC: Force dynamics and polarity."""
        return self._L8

    @property
    def L9(self) -> L9_Membrane:
        """L9 MEMBRANE: Information flow and transmission."""
        return self._L9

    # Core operations

    def write(self, path: Union[str, Path]) -> Token:
        """Write a path to memory.

        Encoding is O(1) constant time via Morton Z-order.

        Args:
            path: File path to encode

        Returns:
            Token with morton code, hex_id, basin, layer info
        """
        path_str = str(path)

        # Check if already indexed
        if path_str in self._path_index:
            return self._tokens[self._path_index[path_str]]

        # Create token from path
        token = Token.from_path(path_str)

        # Store
        self._tokens[token.morton] = token
        self._hex_index[token.hex_id] = token
        self._path_index[path_str] = token.morton

        # Track access for L5 probabilistic
        self._L5._record_access(token)

        return token

    def read(self, path: Union[str, Path]) -> Optional[Token]:
        """Read token for a path."""
        path_str = str(path)
        if path_str in self._path_index:
            token = self._tokens[self._path_index[path_str]]
            self._L5._record_access(token)
            return token
        return None

    def get(self, identifier: Union[str, int]) -> Optional[Token]:
        """Get token by hex_id or morton code."""
        if isinstance(identifier, int):
            token = self._tokens.get(identifier)
        else:
            # Search by hex_id - O(1)
            hex_upper = identifier.upper()
            token = self._hex_index.get(hex_upper)

        if token:
            self._L5._record_access(token)
        return token

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

        Uses L2 Semantic layer by default.

        Args:
            pattern: Semantic query (concept, keyword, meaning)
            limit: Maximum results

        Returns:
            List of matching tokens, sorted by relevance
        """
        return self._L2.query(pattern, limit)

    def nearby(self, token: Token, radius: int = 100) -> List[Token]:
        """Find nearby tokens (spatial proximity).

        Uses L3 Spatial layer.
        """
        return self._L3.nearby(token, radius)

    def filter(self, level: str = "all", **criteria) -> List[Token]:
        """Filter tokens by criteria.

        Args:
            level: Abstraction level or "all"
            **criteria: Additional filter criteria

        Returns:
            Filtered tokens
        """
        if level == "all":
            tokens = list(self._tokens.values())
        else:
            tokens = self._L4.query(level)

        if criteria:
            tokens = self._L4.filter(tokens, **criteria)

        return tokens

    # Batch operations

    def write_batch(self, paths: List[Union[str, Path]]) -> List[Token]:
        """Write multiple paths in batch."""
        return [self.write(path) for path in paths]

    def query_batch(self, patterns: List[str], limit_per_query: int = 50) -> Dict[str, List[Token]]:
        """Execute multiple queries in batch."""
        return {pattern: self.query(pattern, limit_per_query) for pattern in patterns}

    # Validation

    def validate(self, token: Token) -> bool:
        """Validate token integrity."""
        x, y, z = morton_decode(token.morton)
        expected_morton = morton_encode(x, y, z)
        return expected_morton == token.morton

    # Statistics

    def stats(self) -> Dict[str, Any]:
        """Get memory statistics."""
        tokens = list(self._tokens.values())

        basin_counts: Dict[int, int] = {}
        for t in tokens:
            basin_counts[t.basin] = basin_counts.get(t.basin, 0) + 1

        layer_counts: Dict[int, int] = {}
        for t in tokens:
            layer_counts[t.layer] = layer_counts.get(t.layer, 0) + 1

        return {
            "total_tokens": len(tokens),
            "unique_basins": len(basin_counts),
            "basin_distribution": basin_counts,
            "layer_distribution": layer_counts,
            "native_backend": self._native_loaded,
            "storage_path": str(self._storage_path) if self._storage_path else None,
        }

    def benchmark(self, iterations: int = 10000) -> Dict[str, float]:
        """Run performance benchmark. Returns timings in nanoseconds."""
        bench_result = benchmark_operations(iterations)
        morton_ns = bench_result.get('fastest_time_ns', 0.0)

        start = time.perf_counter()
        for i in range(min(iterations, 1000)):
            Token.from_morton(i * 12345)
        write_ns = (time.perf_counter() - start) * 1e9 / min(iterations, 1000)

        if self._tokens:
            start = time.perf_counter()
            for _ in range(min(iterations, 100)):
                self._L2.query("test", limit=10)
            query_ns = (time.perf_counter() - start) * 1e9 / min(iterations, 100)
        else:
            query_ns = 0.0

        return {
            "morton_encode_ns": morton_ns,
            "token_create_ns": write_ns,
            "query_ns": query_ns,
            "native_backend": self._native_loaded,
        }

    # Iteration

    def __iter__(self) -> Iterator[Token]:
        """Iterate over all tokens."""
        return iter(self._tokens.values())

    def __len__(self) -> int:
        """Number of tokens in memory."""
        return len(self._tokens)

    def __contains__(self, item: Union[str, int, Token]) -> bool:
        """Check if token exists."""
        if isinstance(item, Token):
            return item.morton in self._tokens
        elif isinstance(item, int):
            return item in self._tokens
        else:
            return item in self._path_index

    # Info

    def info(self) -> Dict[str, Any]:
        """Get system information."""
        return {
            "native_backend": self._native_loaded,
            "token_count": len(self._tokens),
            "layers_enabled": list(range(1, 10)),
            "basins_enabled": 131,
        }

    def __repr__(self):
        return f"CSTMemory(tokens={len(self._tokens)}, native={self._native_loaded})"
