"""
helix_cdc/substrate/api.py - Simplified Cognitive Substrate API (WO#COG_02)
============================================================================

Minimal API for embedding Helix substrate under cognitive architectures
like Soar, ACT-R, or custom planners.

Example:
    from helix_cdc.substrate.api import new_seed, regen, measure_entropy, crossover

    # Create a seed (symbol)
    seed = new_seed("concept_alpha")

    # Regenerate at different depths
    coarse = regen(seed, depth=64)
    fine = regen(seed, depth=1024)

    # Measure entropy
    h = measure_entropy(fine)

    # Breed two seeds
    child = crossover(seed_a, seed_b, mode="hybrid")
"""

import hashlib
import numpy as np
from dataclasses import dataclass, field
from typing import Dict, Any, Optional, Union

# Try to use C pistons for entropy and regen
try:
    from helix_cdc.native import (
        entropy_bytes as _c_entropy,
        sha256_chain_step as _c_sha256_chain_step,
        is_available as _pistons_available,
    )
    HAS_PISTONS = _pistons_available()
except ImportError:
    HAS_PISTONS = False
    _c_entropy = None
    _c_sha256_chain_step = None

# Allow forcing Python path for testing
import os
FORCE_PYTHON_PATH = os.getenv("HELIX_FORCE_PYTHON", "0") == "1"


@dataclass
class Seed:
    """
    A Helix seed - the compressed symbol representation.

    Seeds can be:
    - Created from strings/bytes
    - Regenerated at different depths
    - Combined via crossover
    """
    content: bytes
    name: str = ""
    metadata: Dict[str, Any] = field(default_factory=dict)

    @property
    def hash(self) -> str:
        """SHA256 hash (truncated to 16 hex chars)."""
        return hashlib.sha256(self.content).hexdigest()[:16]

    @property
    def size(self) -> int:
        return len(self.content)

    def __repr__(self) -> str:
        return f"Seed(name='{self.name}', hash='{self.hash}', size={self.size})"


@dataclass
class Manifold:
    """
    A regenerated manifold from a seed at a specific depth.

    Depth controls granularity:
    - Low depth = coarse summary
    - High depth = fine detail
    """
    data: bytes
    seed_hash: str
    depth: int
    entropy: float
    metadata: Dict[str, Any] = field(default_factory=dict)

    @property
    def size(self) -> int:
        return len(self.data)

    @property
    def hash(self) -> str:
        return hashlib.sha256(self.data).hexdigest()[:16]

    def as_array(self, dtype=np.uint8) -> np.ndarray:
        """Convert to numpy array."""
        return np.frombuffer(self.data, dtype=dtype)

    def __repr__(self) -> str:
        return f"Manifold(seed='{self.seed_hash}', depth={self.depth}, size={self.size}, H={self.entropy:.2f})"


def new_seed(source: Union[str, bytes], name: str = "") -> Seed:
    """
    Create a new seed from a string or bytes.

    Args:
        source: String or bytes to encode as seed
        name: Optional name for the seed

    Returns:
        Seed object
    """
    if isinstance(source, str):
        content = hashlib.sha256(source.encode()).digest()
        if not name:
            name = source[:32]
    else:
        content = hashlib.sha256(source).digest()
        if not name:
            name = f"bytes_{len(source)}"

    return Seed(
        content=content,
        name=name,
        metadata={"source_type": type(source).__name__}
    )


def regen(seed: Seed, depth: int = 256, use_c: Optional[bool] = None) -> Manifold:
    """
    Regenerate a manifold from a seed at the specified depth.

    Depth controls the "resolution" of the unfolding:
    - 64: Coarse (~2KB output)
    - 256: Medium (~8KB output)
    - 1024: Fine (~32KB output)
    - 4096: Very fine (~128KB output)

    Args:
        seed: The seed to regenerate from
        depth: Number of hash chain iterations
        use_c: Force C path (True), Python path (False), or auto (None)

    Returns:
        Manifold containing the regenerated data
    """
    # Determine whether to use C path
    if use_c is None:
        use_c = HAS_PISTONS and not FORCE_PYTHON_PATH

    if use_c and HAS_PISTONS and _c_sha256_chain_step is not None:
        # C piston path - uses optimized SHA256 chain
        data = _regen_c_path(seed.content, depth)
        backend = "c_pistons"
    else:
        # Python path - pure Python implementation
        data = _regen_python_path(seed.content, depth)
        backend = "python"

    entropy = measure_entropy_bytes(data)

    return Manifold(
        data=data,
        seed_hash=seed.hash,
        depth=depth,
        entropy=entropy,
        metadata={"seed_name": seed.name, "backend": backend}
    )


def _regen_python_path(state: bytes, depth: int) -> bytes:
    """Pure Python regen via SHA256 hash chain."""
    chunks = []
    for i in range(depth):
        state = hashlib.sha256(state + i.to_bytes(4, 'little')).digest()
        chunks.append(state)
    return b''.join(chunks)


def _regen_c_path(state: bytes, depth: int) -> bytes:
    """C piston regen via optimized SHA256 chain."""
    chunks = []
    for i in range(depth):
        state = _c_sha256_chain_step(state, i)
        chunks.append(state)
    return b''.join(chunks)


def measure_entropy(manifold: Manifold) -> float:
    """
    Measure Shannon entropy of a manifold.

    Args:
        manifold: The manifold to measure

    Returns:
        Entropy in bits (0.0 to 8.0)
    """
    return manifold.entropy


def measure_entropy_bytes(data: bytes) -> float:
    """
    Measure Shannon entropy of raw bytes.

    Uses C pistons if available for 14x speedup.

    Args:
        data: Bytes to measure

    Returns:
        Entropy in bits (0.0 to 8.0)
    """
    if HAS_PISTONS:
        return _c_entropy(data)
    else:
        import math
        if len(data) == 0:
            return 0.0
        hist = [0] * 256
        for byte in data:
            hist[byte] += 1
        entropy = 0.0
        for count in hist:
            if count > 0:
                p = count / len(data)
                entropy -= p * math.log2(p)
        return entropy


def mutate(seed: Seed, mutation_strength: float = 0.1) -> Seed:
    """
    Create a mutated variant of a seed.

    Args:
        seed: The seed to mutate
        mutation_strength: How much to mutate (0.0 to 1.0)

    Returns:
        New mutated seed
    """
    # Generate mutation noise deterministically from seed
    rng = np.random.default_rng(int.from_bytes(seed.content[:4], 'little'))
    noise = rng.integers(0, 256, size=32, dtype=np.uint8).tobytes()

    # XOR original with scaled noise
    content = bytearray(seed.content)
    n_mutate = int(32 * mutation_strength)
    for i in range(n_mutate):
        content[i] ^= noise[i]

    return Seed(
        content=bytes(content),
        name=f"{seed.name}_mut",
        metadata={
            **seed.metadata,
            "parent_hash": seed.hash,
            "mutation_strength": mutation_strength,
        }
    )


def crossover(
    seed_a: Seed,
    seed_b: Seed,
    mode: str = "hybrid",
    alpha: float = 0.5,
) -> Seed:
    """
    Combine two seeds to produce offspring.

    Modes:
    - "hybrid": Byte-wise XOR blend
    - "average": Arithmetic mean in byte space
    - "interleave": Alternating bytes from each parent

    Args:
        seed_a: First parent seed
        seed_b: Second parent seed
        mode: Crossover mode
        alpha: Blend factor (0.0 = all A, 1.0 = all B)

    Returns:
        New child seed
    """
    a = np.frombuffer(seed_a.content, dtype=np.uint8)
    b = np.frombuffer(seed_b.content, dtype=np.uint8)

    if mode == "hybrid":
        # XOR with alpha-weighted selection - RNG seeded by BOTH parents
        combined_hash = hashlib.sha256(seed_a.content + seed_b.content).digest()
        parent_seed = int.from_bytes(combined_hash[:4], 'little')
        mask = np.random.default_rng(parent_seed).random(32) < alpha
        child = np.where(mask, b, a)
    elif mode == "average":
        # Arithmetic blend
        child = ((1 - alpha) * a.astype(np.float32) + alpha * b.astype(np.float32)).astype(np.uint8)
    elif mode == "interleave":
        # Alternating bytes
        child = np.empty(32, dtype=np.uint8)
        child[0::2] = a[0::2]
        child[1::2] = b[1::2]
    else:
        raise ValueError(f"Unknown crossover mode: {mode}")

    return Seed(
        content=bytes(child),
        name=f"{seed_a.name}x{seed_b.name}",
        metadata={
            "parent_a_hash": seed_a.hash,
            "parent_b_hash": seed_b.hash,
            "crossover_mode": mode,
            "alpha": alpha,
        }
    )


def verify_seed(seed: Seed) -> Dict[str, Any]:
    """
    Verify a seed is valid and deterministic.

    Args:
        seed: The seed to verify

    Returns:
        Verification report
    """
    # Regenerate twice at depth 64
    m1 = regen(seed, depth=64)
    m2 = regen(seed, depth=64)

    return {
        "seed_hash": seed.hash,
        "seed_size": seed.size,
        "is_deterministic": m1.hash == m2.hash,
        "manifold_hash": m1.hash,
        "entropy": m1.entropy,
    }


def get_piston_status() -> Dict[str, Any]:
    """
    Get status of C pistons for substrate operations.

    Returns:
        Dict with pistons_available, force_python, effective_backend
    """
    effective = "python" if FORCE_PYTHON_PATH or not HAS_PISTONS else "c_pistons"
    return {
        "pistons_available": HAS_PISTONS,
        "force_python": FORCE_PYTHON_PATH,
        "effective_backend": effective,
    }


# Convenience exports
__all__ = [
    "Seed",
    "Manifold",
    "new_seed",
    "regen",
    "measure_entropy",
    "measure_entropy_bytes",
    "mutate",
    "crossover",
    "verify_seed",
    "get_piston_status",
    "HAS_PISTONS",
]
