"""
helix_cdc/substrate/core.py
===========================

CDR Substrate Core - The Physics Layer

Provides:
- Substrate: The geometric manifold (vault)
- Seed: CDR symbol with depth/entropy
- CDR operators: distance, gradient_toward, merge, split, etc.
- step_glyph: Core inference primitive
"""

from __future__ import annotations

import hashlib
import numpy as np
from dataclasses import dataclass, field
from pathlib import Path
from typing import Dict, Any, List, Optional, Tuple


# ===========================================================================
# Seed: The CDR Symbol
# ===========================================================================

@dataclass
class Seed:
    """CDR symbol - geometric token with depth for multi-granularity."""
    vector: np.ndarray
    depth: int = 0
    entropy: float = 0.0
    metadata: Dict[str, Any] = field(default_factory=dict)

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

    def at_depth(self, new_depth: int) -> "Seed":
        return Seed(
            vector=self.vector.copy(),
            depth=new_depth,
            entropy=self.entropy,
            metadata={**self.metadata, "parent_depth": self.depth}
        )

    def to_dict(self) -> Dict[str, Any]:
        return {"hash": self.hash, "depth": self.depth, "entropy": self.entropy, "metadata": self.metadata}


# ===========================================================================
# Substrate: The Manifold/Vault
# ===========================================================================

@dataclass
class Substrate:
    """Geometric manifold from HXV1 vault - K basis vectors of dimension D."""
    path: Path
    K: int
    D: int
    basis: np.ndarray

    @property
    def size_bytes(self) -> int:
        return self.path.stat().st_size

    def info(self) -> Dict[str, Any]:
        return {"path": str(self.path), "size_bytes": self.size_bytes, "K": self.K, "D": self.D}

    def seed_from_text(self, text: str) -> Seed:
        h = hashlib.sha256(text.encode()).digest()
        np.random.seed(int.from_bytes(h[:4], 'little'))
        vec = np.random.randn(self.D).astype(np.float32)
        vec = vec / (np.linalg.norm(vec) + 1e-8)
        data = text.encode()
        if data:
            counts = np.zeros(256, dtype=np.float64)
            for b in data:
                counts[b] += 1
            probs = counts / len(data)
            probs = probs[probs > 0]
            entropy = float(-np.sum(probs * np.log2(probs)))
        else:
            entropy = 0.0
        return Seed(vector=vec, depth=0, entropy=entropy, metadata={"source": "text", "text_hash": h.hex()[:16]})

    def seed_from_basis(self, basis_idx: int) -> Seed:
        idx = basis_idx % self.K
        vec = self.basis[idx].copy()
        vec = vec / (np.linalg.norm(vec) + 1e-8)
        return Seed(vector=vec, depth=0, entropy=0.0, metadata={"source": "basis", "basis_idx": idx})


def load_vault(path: str) -> Substrate:
    """Load HXV1 vault into Substrate."""
    vault_path = Path(path)
    with open(vault_path, "rb") as f:
        magic = f.read(4)
        if magic != b'HXV1':
            raise ValueError(f"Not HXV1 vault: {magic}")
        K = int.from_bytes(f.read(4), 'little')
        D = int.from_bytes(f.read(4), 'little')
        data = np.frombuffer(f.read(K * D * 4), dtype=np.float32)
        basis = data.reshape(K, D)
    return Substrate(path=vault_path, K=K, D=D, basis=basis)


# ===========================================================================
# CDR Operators
# ===========================================================================

def distance(seed_a: Seed, seed_b: Seed) -> float:
    """Content-based retrieval: distance : Seed × Seed → Float"""
    return float(1.0 - np.dot(seed_a.vector, seed_b.vector))


def gradient_toward(seed: Seed, goal: Seed, step_size: float = 0.1) -> Seed:
    """Goal-directed: gradient_toward : Seed × Goal → Seed"""
    direction = goal.vector - seed.vector
    direction = direction / (np.linalg.norm(direction) + 1e-8)
    new_vec = seed.vector + step_size * direction
    new_vec = new_vec / (np.linalg.norm(new_vec) + 1e-8)
    return Seed(vector=new_vec.astype(np.float32), depth=seed.depth, entropy=seed.entropy,
                metadata={**seed.metadata, "gradient_step": step_size, "goal_hash": goal.hash})


def merge(seed_a: Seed, seed_b: Seed, weight: float = 0.5) -> Seed:
    """merge : Seed × Seed → Seed"""
    merged_vec = weight * seed_a.vector + (1 - weight) * seed_b.vector
    merged_vec = merged_vec / (np.linalg.norm(merged_vec) + 1e-8)
    return Seed(vector=merged_vec.astype(np.float32), depth=max(seed_a.depth, seed_b.depth),
                entropy=(seed_a.entropy + seed_b.entropy) / 2,
                metadata={"merged_from": [seed_a.hash, seed_b.hash], "weight": weight})


def split(seed: Seed, D: int) -> Tuple[Seed, Seed]:
    """split : Seed → (Seed, Seed)"""
    mid = D // 2
    vec_a = np.zeros(D, dtype=np.float32)
    vec_a[:mid] = seed.vector[:mid]
    vec_a = vec_a / (np.linalg.norm(vec_a) + 1e-8)
    vec_b = np.zeros(D, dtype=np.float32)
    vec_b[mid:] = seed.vector[mid:]
    vec_b = vec_b / (np.linalg.norm(vec_b) + 1e-8)
    return (Seed(vector=vec_a, depth=seed.depth, entropy=seed.entropy/2, metadata={"split_from": seed.hash, "component": "A"}),
            Seed(vector=vec_b, depth=seed.depth, entropy=seed.entropy/2, metadata={"split_from": seed.hash, "component": "B"}))


def interpolate(seed_a: Seed, seed_b: Seed, t: float) -> Seed:
    """interpolate : Seed × Seed × Float → Seed"""
    interp_vec = (1 - t) * seed_a.vector + t * seed_b.vector
    interp_vec = interp_vec / (np.linalg.norm(interp_vec) + 1e-8)
    return Seed(vector=interp_vec.astype(np.float32), depth=max(seed_a.depth, seed_b.depth),
                entropy=(1 - t) * seed_a.entropy + t * seed_b.entropy,
                metadata={"interpolated_from": [seed_a.hash, seed_b.hash], "t": t})


# ===========================================================================
# Glyph Stepping
# ===========================================================================

VOCAB_LANES = [
    ["world","space","time","light","force","energy","matter","wave","field","particle","atom","cell","mind","soul","spirit","body",
     "heart","brain","eye","hand","voice","word","thought","dream","memory","hope","fear","love","hate","joy","pain","peace",
     "war","life","death","birth","growth","change","flow","cycle","pattern","form","shape","color","sound","touch","taste","smell",
     "earth","water","fire","air","metal","wood","stone","crystal","star","moon","sun","sky","cloud","rain","wind","storm"],
    ["be","become","exist","emerge","arise","appear","vanish","fade","grow","shrink","expand","contract","move","flow","drift","float",
     "rise","fall","spin","orbit","vibrate","resonate","pulse","breathe","think","feel","sense","perceive","know","understand","believe","doubt",
     "create","destroy","build","break","form","shape","mold","carve","give","take","share","keep","find","lose","seek","hide",
     "speak","listen","watch","wait","act","react","respond","adapt","learn","teach","guide","follow","lead","serve","help","heal"],
    ["new","old","young","ancient","eternal","fleeting","lasting","brief","large","small","vast","tiny","infinite","finite","whole","part",
     "bright","dark","clear","cloudy","sharp","soft","hard","gentle","fast","slow","quick","steady","still","moving","fixed","free",
     "true","false","real","ideal","pure","mixed","simple","complex","good","bad","right","wrong","fair","just","kind","cruel",
     "strong","weak","bold","shy","wise","foolish","calm","wild","deep","shallow","high","low","near","far","inner","outer"],
    ["in","on","at","to","from","with","without","through","above","below","before","after","between","among","within","beyond",
     "for","against","toward","away","into","onto","upon","under","like","unlike","as","than","so","thus","hence","therefore",
     "and","or","but","yet","while","when","where","why","if","then","else","unless","until","since","because","although",
     "all","some","none","each","every","any","both","either","one","two","many","few","more","less","most","least"],
]


@dataclass
class GlyphState:
    seed: Seed
    basis_idx: int
    word: str
    position: int


def step_glyph(substrate: Substrate, seed: Seed, position: int = 0, temperature: float = 0.8,
               goal_seed: Optional[Seed] = None, goal_step: float = 0.05) -> GlyphState:
    """Execute one glyph step: attention + sample + evolve."""
    scores = seed.vector @ substrate.basis.T
    scores = scores / max(temperature, 0.1)
    scores = scores - scores.max()
    probs = np.exp(scores) / np.exp(scores).sum()
    basis_idx = int(np.random.choice(substrate.K, p=probs))
    word = VOCAB_LANES[position % 4][basis_idx % 64]
    new_vec = seed.vector + 0.1 * substrate.basis[basis_idx]
    new_vec = new_vec / (np.linalg.norm(new_vec) + 1e-8)
    new_seed = Seed(vector=new_vec.astype(np.float32), depth=seed.depth, entropy=seed.entropy,
                    metadata={**seed.metadata, "last_basis_idx": basis_idx})
    if goal_seed is not None:
        new_seed = gradient_toward(new_seed, goal_seed, step_size=goal_step)
    return GlyphState(seed=new_seed, basis_idx=basis_idx, word=word, position=position)
