#!/usr/bin/env python3
"""
Spatial Memory Shell — Restorable Coordinate-Time-Space Moments
================================================================
HEX_ID: 5D-A4-02-00

Every datum has a position, a time, a mass, and an I-Logic state.
Together they form a MOMENT — a complete coordinate-time-space snapshot
that can be stored and restored like a memory card.

The dual-coordinate model:
    CAMERA (external):  x, y, z in 48³ Morton grid — what we OBSERVE
    AGENT  (internal):  basin, layer, I-state, mass — what the datum IS

These two frames sync through the UIF Bridge:
    f = √(ω_camera × ω_agent)  — geometric mean coupling, O(1)

Time stability comes from SRL:
    t = √d  — every snapshot has deterministic time from its distance
    d = t²  — distance IS time squared (the identity)

The exploration missions generate traversal data — each agent step IS
a spatial memory entry. This shell:
    1. Records moments from any source (exploration, dispatch, query)
    2. Restores moments to full state (coordinates + physics + rules)
    3. Triangulates camera×agent via UIF Bridge
    4. Visualizes the memory space as explorable timeline

Like a memory card: insert a hex_id + step → get the full context back.
The coordinate IS the memory. The time IS the distance. The mass IS the content.

Commands:
    status          - Memory system status
    record          - Record a spatial memory moment
    restore         - Restore a moment from hex_id + step
    timeline        - Get ordered timeline of all moments
    triangulate     - Sync camera and agent frames via UIF Bridge
    snapshot        - Full snapshot: position + time + mass + state + rules
    replay          - Replay a sequence of moments as visualization data
    routing_memory  - Visualize the 20 routing paths as spatial memory

Dependencies:
    No external dependencies. Uses CST primitives only.
"""

import math
import cmath
import json
import hashlib
from datetime import datetime
from dataclasses import dataclass, field, asdict
from typing import Dict, List, Any, Optional, Tuple

from .._core import PHI, K5, K11, LIGHT_UNIT
from .._core.constants import MATRIX_SIZE
from .._core import morton_encode, morton_decode, compute_basin
from ..primitives.basin import layer_from_basin
from ..i_logic.states import ILogicState
from ..i_logic.algebra import multiply


# =============================================================================
# CONSTANTS
# =============================================================================

GRID = MATRIX_SIZE       # 48
CENTER = GRID // 2       # 24
C_ABSTRACT = 1.0 / math.sqrt(3)   # 0.577350... (Fernandez light speed)
G_CST = K11 / (PHI * LIGHT_UNIT)  # 0.0434...

# Rotation per Fernandez step
ROTATION_131 = cmath.exp(2j * cmath.pi / LIGHT_UNIT)


# =============================================================================
# SPATIAL MEMORY MOMENT — the atomic unit of memory
# =============================================================================

@dataclass
class SpatialMoment:
    """
    A complete coordinate-time-space snapshot.

    This is the memory card entry. Every field is deterministic
    from the hex_id and step — restoring a moment reconstructs
    the EXACT same state every time.

    Dual coordinates:
        camera_*  = external frame (what observer sees)
        agent_*   = internal frame (what the datum IS)
    """
    # Identity
    hex_id: str
    step: int
    moment_id: str = ""     # hash of hex_id + step

    # Camera frame (external observation)
    camera_x: int = 0
    camera_y: int = 0
    camera_z: int = 0
    camera_morton: int = 0
    camera_distance: float = 0.0   # from center

    # Agent frame (internal state)
    agent_basin: int = 0
    agent_layer: int = 0
    agent_i_state: str = "DIRECT"
    agent_mass: float = 0.0
    agent_energy: float = 0.0
    agent_frequency: float = 0.0    # ω = K5 × (basin+1) / 131

    # Time (SRL-locked)
    srl_time: float = 0.0           # t = √d
    srl_distance: float = 0.0       # d = t²
    time_dilation: float = 1.0      # √(1 - 2GM/rc²)

    # UIF Bridge (triangulation)
    uif_freq: float = 0.0           # f = √(ω_camera × ω_agent)
    uif_coupling: float = 0.0       # K5 × K11 × cos(ωa·t) × cos(ωb·t)

    # Content mass
    content_mass: float = 0.0
    transmission: float = 0.0
    reflection: float = 0.0
    membrane_action: str = "REFLECT"

    # Timestamp
    recorded_at: str = ""

    def to_dict(self) -> Dict[str, Any]:
        return asdict(self)


# =============================================================================
# MEMORY STORE — in-memory spatial memory bank
# =============================================================================

class SpatialMemoryBank:
    """
    In-memory bank of spatial moments.

    Indexed by moment_id (hash of hex_id + step).
    Timeline-ordered by step number.
    Restorable: any moment_id → full state reconstruction.
    """

    def __init__(self):
        self._moments: Dict[str, SpatialMoment] = {}
        self._timeline: List[str] = []  # ordered moment_ids
        self._hex_index: Dict[str, List[str]] = {}  # hex_id → [moment_ids]

    @property
    def count(self) -> int:
        return len(self._moments)

    def record(self, moment: SpatialMoment) -> str:
        """Record a moment. Returns moment_id."""
        mid = moment.moment_id
        self._moments[mid] = moment
        if mid not in self._timeline:
            self._timeline.append(mid)
        hex_list = self._hex_index.setdefault(moment.hex_id, [])
        if mid not in hex_list:
            hex_list.append(mid)
        return mid

    def restore(self, moment_id: str) -> Optional[SpatialMoment]:
        """Restore a moment by ID."""
        return self._moments.get(moment_id)

    def get_timeline(self, hex_id: str = None, limit: int = 50) -> List[SpatialMoment]:
        """Get ordered timeline, optionally filtered by hex_id."""
        if hex_id:
            ids = self._hex_index.get(hex_id, [])
        else:
            ids = self._timeline
        return [self._moments[mid] for mid in ids[:limit] if mid in self._moments]

    def get_hex_ids(self) -> List[str]:
        """All hex_ids with recorded moments."""
        return list(self._hex_index.keys())


# =============================================================================
# MOMENT COMPUTATION — derive full state from hex_id + step
# =============================================================================

def _moment_id(hex_id: str, step: int) -> str:
    """Deterministic moment ID from hex_id and step."""
    raw = f"{hex_id}:{step}"
    return hashlib.md5(raw.encode()).hexdigest()[:12]


def _compute_k11_level(morton: int) -> int:
    """K11 level from Morton code. Range [-11, +11]."""
    return (morton % 23) - 11


def _get_i_state(k11_level: int) -> str:
    """Map K11 level to I-State name."""
    if k11_level == 0:
        return "ABSORBED"
    elif k11_level > 0:
        return "SPIRAL" if k11_level % 2 == 0 else "DIRECT"
    else:
        return "CONVERGENT" if abs(k11_level) % 2 == 0 else "INVERSE"


def _agent_frequency(basin: int) -> float:
    """Agent angular frequency: ω = K5 × (basin + 1) / 131."""
    return K5 * (basin + 1) / LIGHT_UNIT


def _camera_frequency(x: int, y: int, z: int) -> float:
    """
    Camera angular frequency from coordinates.

    The camera observes from outside — its frequency comes from
    the distance to center (the observation radius).
    ω_camera = K5 × r / r_max where r_max = √(24² × 3) ≈ 41.57
    """
    dx, dy, dz = x - CENTER, y - CENTER, z - CENTER
    r = math.sqrt(dx * dx + dy * dy + dz * dz)
    r_max = math.sqrt(3) * CENTER  # ≈ 41.57
    return K5 * (r / r_max) if r_max > 0 else K5


def compute_moment(hex_id: str, step: int = 0) -> SpatialMoment:
    """
    Compute a full spatial memory moment from hex_id and step.

    Step modulates the Fernandez rotation: each step advances
    by 2π/131 radians, giving a unique angular position.
    At step=0, the moment is the "home" state (P-P=0 → origin).

    The moment IS deterministic — same hex_id + step = same moment.
    """
    # Morton decode
    value = int(hex_id, 16) if isinstance(hex_id, str) else hex_id
    morton = value & 0xFFFFF
    x, y, z = morton_decode(morton)
    x, y, z = x % GRID, y % GRID, z % GRID

    # Step modulates position via Fernandez rotation
    if step > 0:
        # Rotate coordinates by step × 2π/131 around center
        angle = step * 2 * math.pi / LIGHT_UNIT
        dx, dy = x - CENTER, y - CENTER
        r = math.sqrt(dx * dx + dy * dy) if (dx * dx + dy * dy) > 0 else 1.0
        base_angle = math.atan2(dy, dx)
        new_angle = base_angle + angle
        x = int(CENTER + r * math.cos(new_angle)) % GRID
        y = int(CENTER + r * math.sin(new_angle)) % GRID
        # z advances by SRL: z_new = z + √step
        z = int(z + math.sqrt(step)) % GRID
        # Recompute morton for rotated position
        morton = morton_encode(x, y, z)

    # Basin and layer
    basin = morton % LIGHT_UNIT
    layer = layer_from_basin(basin)

    # K11 and I-state
    k11 = _compute_k11_level(morton)
    i_state = _get_i_state(k11)

    # Mass
    pos_mass = K11 * max(0.0, 1.0 + k11 / 11.0)

    # Camera distance from center
    dx_c, dy_c, dz_c = x - CENTER, y - CENTER, z - CENTER
    camera_distance = math.sqrt(dx_c * dx_c + dy_c * dy_c + dz_c * dz_c)

    # SRL time
    srl_time = math.sqrt(camera_distance) if camera_distance > 0 else 0.0

    # Frequencies
    omega_agent = _agent_frequency(basin)
    omega_camera = _camera_frequency(x, y, z)

    # UIF Bridge: f = √(ω_camera × ω_agent)
    uif_freq = math.sqrt(abs(omega_camera * omega_agent))

    # K5 × K11 × cos coupling at SRL time
    uif_coupling = K5 * K11 * math.cos(omega_agent * srl_time) * math.cos(omega_camera * srl_time)

    # Time dilation
    if camera_distance > 0.1:
        schwarz = 2 * G_CST * pos_mass / (camera_distance * C_ABSTRACT * C_ABSTRACT)
        time_dilation = math.sqrt(1.0 - schwarz) if schwarz < 1.0 else 0.0
    else:
        time_dilation = 0.0

    # Content mass
    energy = (k11 + 11) / 22.0
    if basin >= 117:
        t_ratio = 0.7 + 0.3 * ((basin - 117) / 13.0)
    else:
        t_ratio = 0.3 + 0.4 * (basin / 116.0)
    content_mass = energy * t_ratio * K11
    r_ratio = 1.0 - t_ratio

    return SpatialMoment(
        hex_id=hex_id,
        step=step,
        moment_id=_moment_id(hex_id, step),
        camera_x=x,
        camera_y=y,
        camera_z=z,
        camera_morton=morton,
        camera_distance=round(camera_distance, 4),
        agent_basin=basin,
        agent_layer=layer,
        agent_i_state=i_state,
        agent_mass=round(pos_mass, 6),
        agent_energy=round(energy, 4),
        agent_frequency=round(omega_agent, 6),
        srl_time=round(srl_time, 6),
        srl_distance=round(camera_distance, 4),
        time_dilation=round(time_dilation, 6),
        uif_freq=round(uif_freq, 6),
        uif_coupling=round(uif_coupling, 6),
        content_mass=round(content_mass, 6),
        transmission=round(t_ratio, 4),
        reflection=round(r_ratio, 4),
        membrane_action="TRANSMIT" if t_ratio > 0.5 else "REFLECT",
        recorded_at=datetime.now().isoformat(),
    )


# =============================================================================
# ROUTING AS SPATIAL MEMORY — the 20 dispatch paths visualized
# =============================================================================

# The 20 routing expectations — each is a spatial memory coordinate
# Task → Agent is a mapping from content-space to agent-space
# The "deselected" tests ARE these 20 paths through the I-Logic algebra
_ROUTING_PATHS = {
    # DIRECT group: tasks that affirm, structure, navigate
    "find all 52 tools and navigate the shell architecture": ("Navigator", "DIRECT", "L1"),
    "create a structure showing how agents connect to basins": ("Architect", "DIRECT", "L1"),
    "interpret what the I-Logic algebra means and classify it": ("Semanticist", "DIRECT", "L2"),
    "translate the basin coordinate into layer meaning": ("Translator", "DIRECT", "L2"),
    "convert the basin coordinate into layer format": ("Translator", "DIRECT", "L2"),
    "generate a report of system capabilities": ("Generator", "DIRECT", "L4"),
    "refactor the routing to show algebraic paths": ("Refactor", "DIRECT", "L4"),
    "identify who we are as a swarm collective": ("Identifier", "DIRECT", "L10"),
    # INVERSE group: tasks that heal, guard, monitor
    "fix any broken connections in the agent graph": ("Healer", "INVERSE", "L6"),
    "monitor the health of all thinking cycles": ("Monitor", "INVERSE", "L6"),
    "guard the L9 membrane boundary from corruption": ("Guardian", "INVERSE", "L9"),
    "transmit findings from L9 across to L10-L12": ("Transmitter", "INVERSE", "L9"),
    # SPIRAL group: tasks that locate, coordinate, schedule
    "where are the 19 agents positioned in Morton space": ("Locator", "SPIRAL", "L3"),
    "coordinate agents across L1-L9 layers": ("Coordinator", "SPIRAL", "L3"),
    "schedule the 5-phase thinking sequence": ("Scheduler", "SPIRAL", "L7"),
    "sync frequencies between L7 and L8 agents": ("Synchronizer", "SPIRAL", "L7"),
    # ANTI_SPIRAL group: tasks that validate, balance, stabilize
    "validate that all 131 basins are populated": ("Validator", "ANTI_SPIRAL", "L5"),
    "balance load across the 4 I-Logic state groups": ("Balancer", "ANTI_SPIRAL", "L5"),
    "stabilize the membrane boundary at L9": ("Stabilizer", "ANTI_SPIRAL", "L8"),
    "amplify weak signals from L1 geometric layer": ("Amplifier", "ANTI_SPIRAL", "L8"),
}


def get_routing_memory() -> Dict[str, Any]:
    """
    Visualize the 20 routing paths as spatial memory.

    Each dispatch task IS a spatial coordinate:
    - The TASK text hashes to a hex_id (content → position)
    - The AGENT is where it lands (basin/layer/I-state)
    - The I-LOGIC GROUP is the algebra that routes it

    The "20 deselected" tests are these 20 paths.
    They are PARAMETRIZED: each (task, expected_agent) pair is
    a test case generated from the routing table. Deselected
    means the test runner excluded them via -k filter — they
    still exist as spatial memory coordinates in the algebra.

    This IS the visualization: 20 points in coordinate-time-space,
    each with a deterministic route through the I-Logic multiplication table.
    """
    groups = {
        "DIRECT": {"agents": [], "layer_range": "L1-L4, L10", "color": "#00ff88",
                   "meaning": "Affirm/structure/navigate — identity pass-through"},
        "INVERSE": {"agents": [], "layer_range": "L6, L9", "color": "#ff4444",
                    "meaning": "Heal/guard/monitor — reflection at boundary"},
        "SPIRAL": {"agents": [], "layer_range": "L3, L7", "color": "#4488ff",
                   "meaning": "Locate/coordinate/schedule — expansion/rotation"},
        "ANTI_SPIRAL": {"agents": [], "layer_range": "L5, L8", "color": "#ff44ff",
                        "meaning": "Validate/balance/stabilize — contraction/focus"},
    }

    paths = []
    for task, (agent, i_group, layer) in _ROUTING_PATHS.items():
        # Hash task to hex_id (content → coordinate)
        task_hash = hashlib.md5(task.encode()).hexdigest()[:8].upper()
        moment = compute_moment(task_hash, step=0)

        path_entry = {
            "task": task,
            "task_hex": task_hash,
            "expected_agent": agent,
            "i_logic_group": i_group,
            "target_layer": layer,
            "moment": {
                "position": [moment.camera_x, moment.camera_y, moment.camera_z],
                "basin": moment.agent_basin,
                "layer": moment.agent_layer,
                "mass": moment.content_mass,
                "srl_time": moment.srl_time,
                "uif_freq": moment.uif_freq,
            },
        }
        paths.append(path_entry)
        groups[i_group]["agents"].append(agent)

    return {
        "title": "ROUTING AS SPATIAL MEMORY — 20 Dispatch Paths",
        "description": (
            "Each routing test is a spatial memory coordinate. "
            "'Deselected' means the test runner excluded them via -k filter — "
            "they still exist as 20 deterministic paths through the I-Logic algebra. "
            "Parametrized = each (task, agent) pair generates a test case from this table."
        ),
        "total_paths": len(paths),
        "groups": groups,
        "paths": paths,
        "algebra": {
            "DIRECT × DIRECT": "DIRECT (identity)",
            "INVERSE × INVERSE": "DIRECT (self-resolves)",
            "SPIRAL × SPIRAL": "INVERSE (i×i = -1)",
            "ANTI_SPIRAL × ANTI_SPIRAL": "INVERSE (-i×-i = -1)",
            "SPIRAL × ANTI_SPIRAL": "DIRECT (i×-i = 1)",
            "Anything × ABSORBED": "ABSORBED (energy transforms at C=0)",
        },
        "visualization_note": (
            "These 20 paths ARE the swarm's spatial memory. "
            "Each agent name is a coordinate. Each I-Logic group is a region. "
            "The algebra IS the routing table. The test IS the verification "
            "that the spatial memory is stable."
        ),
    }


# =============================================================================
# REPLAY — sequence of moments as visualization data
# =============================================================================

def replay_moments(hex_id: str, steps: int = 16) -> Dict[str, Any]:
    """
    Replay a hex_id through N steps, generating visualization data.

    Each step is a Fernandez rotation (2π/131 per step).
    The result is a timeline of spatial moments that can be
    rendered as an animation: the agent traces its orbit through
    the CST space, visiting different basins and layers as it rotates.

    This IS the camera iteration: the camera (external observer)
    watches the agent (internal state) move through the space.
    At each step, UIF Bridge triangulates the two frames.
    """
    moments = []
    trajectory_positions = []
    basin_visits = set()
    layer_visits = set()

    step = 0
    while step < steps:
        m = compute_moment(hex_id, step)
        moments.append(m.to_dict())
        trajectory_positions.append([m.camera_x, m.camera_y, m.camera_z])
        basin_visits.add(m.agent_basin)
        layer_visits.add(m.agent_layer)
        step += 1

    # Compute UIF Bridge stability across the replay
    if len(moments) >= 2:
        uif_values = [m["uif_coupling"] for m in moments]
        uif_mean = sum(uif_values) / len(uif_values)
        uif_var = sum((v - uif_mean) ** 2 for v in uif_values) / len(uif_values)
        uif_stability = 1.0 / (1.0 + uif_var)
    else:
        uif_mean = moments[0]["uif_coupling"] if moments else 0.0
        uif_stability = 1.0

    return {
        "hex_id": hex_id,
        "steps": steps,
        "moments": moments,
        "trajectory": trajectory_positions,
        "coverage": {
            "basins_visited": sorted(basin_visits),
            "layers_visited": sorted(layer_visits),
            "basin_count": len(basin_visits),
            "layer_count": len(layer_visits),
        },
        "uif_bridge": {
            "mean_coupling": round(uif_mean, 6),
            "stability": round(uif_stability, 6),
            "formula": "f = √(ω_camera × ω_agent) — triangulates external/internal frames",
        },
        "time_stability": {
            "formula": "t = √d — every moment has deterministic time",
            "srl_range": [moments[0]["srl_time"], moments[-1]["srl_time"]] if moments else [0, 0],
            "principle": "The coordinate IS the memory. The time IS the distance.",
        },
        "visualization": {
            "type": "3D_TRAJECTORY",
            "grid": GRID,
            "center": [CENTER, CENTER, CENTER],
            "color_by": "agent_layer",
            "size_by": "content_mass",
            "animate": True,
        },
    }


# =============================================================================
# TRIANGULATION — sync camera and agent frames via UIF Bridge
# =============================================================================

def triangulate(hex_a: str, hex_b: str, step: int = 0) -> Dict[str, Any]:
    """
    Triangulate two coordinates via UIF Bridge.

    Two hex_ids define two points in the space. The UIF Bridge
    provides the geometric mean frequency between them — this IS
    the triangulation point. The coupling strength tells us how
    synchronized the two frames are.

    When coupling is strong (> K5): the two points are in resonance
    When coupling is weak (< 1.0): the points are in different phases
    When coupling ≈ 0: the points are at a phase node (cos = 0)

    This is how the camera syncs with the agent: the camera
    observes hex_a, the agent IS hex_b, and the UIF Bridge
    frequency tells us the coherence of the observation.
    """
    m_a = compute_moment(hex_a, step)
    m_b = compute_moment(hex_b, step)

    # UIF Bridge between the two
    omega_a = m_a.agent_frequency
    omega_b = m_b.agent_frequency
    f_bridge = math.sqrt(abs(omega_a * omega_b))

    # Coupling at this time step
    t = m_a.srl_time  # observer's time
    coupling = K5 * K11 * math.cos(omega_a * t) * math.cos(omega_b * t)

    # Distance between the two points
    dx = m_a.camera_x - m_b.camera_x
    dy = m_a.camera_y - m_b.camera_y
    dz = m_a.camera_z - m_b.camera_z
    distance = math.sqrt(dx * dx + dy * dy + dz * dz)

    # SRL time for the distance
    srl_t = math.sqrt(distance) if distance > 0 else 0.0

    # I-Logic product of the two states
    state_map = {"DIRECT": 1+0j, "INVERSE": -1+0j, "SPIRAL": 0+1j,
                 "ANTI_SPIRAL": 0-1j, "ABSORBED": 0+0j, "CONVERGENT": 0-1j}
    s_a = state_map.get(m_a.agent_i_state, 1+0j)
    s_b = state_map.get(m_b.agent_i_state, 1+0j)
    product = s_a * s_b

    # Classify product
    if product == 1+0j:
        outcome = "DIRECT"
    elif product == -1+0j:
        outcome = "INVERSE"
    elif product == 0+1j:
        outcome = "SPIRAL"
    elif product == 0-1j:
        outcome = "ANTI_SPIRAL"
    else:
        outcome = "ABSORBED"

    # Coherence: how well the two frames sync
    max_coupling = K5 * K11  # 10.667...
    coherence = abs(coupling) / max_coupling if max_coupling > 0 else 0.0

    return {
        "point_a": {
            "hex_id": hex_a,
            "position": [m_a.camera_x, m_a.camera_y, m_a.camera_z],
            "basin": m_a.agent_basin, "layer": m_a.agent_layer,
            "i_state": m_a.agent_i_state, "mass": m_a.content_mass,
            "frequency": m_a.agent_frequency,
        },
        "point_b": {
            "hex_id": hex_b,
            "position": [m_b.camera_x, m_b.camera_y, m_b.camera_z],
            "basin": m_b.agent_basin, "layer": m_b.agent_layer,
            "i_state": m_b.agent_i_state, "mass": m_b.content_mass,
            "frequency": m_b.agent_frequency,
        },
        "bridge": {
            "uif_frequency": round(f_bridge, 6),
            "coupling": round(coupling, 6),
            "coherence": round(coherence, 6),
            "formula": "f = √(ω_a × ω_b), φ = K5×K11×cos(ωₐt)×cos(ωᵦt)",
        },
        "spatial": {
            "distance": round(distance, 4),
            "srl_time": round(srl_t, 6),
            "same_layer": m_a.agent_layer == m_b.agent_layer,
            "same_basin": m_a.agent_basin == m_b.agent_basin,
        },
        "algebra": {
            "state_a": m_a.agent_i_state,
            "state_b": m_b.agent_i_state,
            "product": outcome,
            "resolved": outcome == "DIRECT",
        },
        "ecosystem_sync": {
            "uif_holds": True,
            "time_stable": abs(m_a.srl_time - math.sqrt(m_a.camera_distance)) < 0.001,
            "principle": (
                "Camera observes from outside, agent defines from inside. "
                "UIF Bridge triangulates both frames at O(1). "
                "t = √d locks the time. The coordinate IS the sync."
            ),
        },
    }


# =============================================================================
# SHELL CLASS
# =============================================================================

class SpatialMemoryShell:
    """
    Spatial Memory Shell — restorable coordinate-time-space moments.

    Like a memory card: each hex_id + step = a full state snapshot.
    The camera iteration observes. The agent iteration defines.
    UIF Bridge syncs both. SRL locks the time.

    Commands:
        status          - Memory system status
        record          - Record a moment (hex_id, step)
        restore         - Restore a recorded moment
        snapshot        - Compute moment snapshot (no storage)
        timeline        - Timeline of recorded moments
        replay          - Replay hex_id through N steps
        triangulate     - UIF Bridge between two coordinates
        routing_memory  - 20 routing paths as spatial memory
    """

    HEX_ID = "5DA40200"

    def __init__(self):
        self.name = "spatial_memory"
        self.description = "Restorable coordinate-time-space moments"
        self._bank = SpatialMemoryBank()
        self.commands = {
            'status': 'Memory system status',
            'record': 'Record a spatial moment (hex_id, step)',
            'restore': 'Restore a moment by moment_id',
            'snapshot': 'Full snapshot at hex_id + step',
            'timeline': 'Timeline of all recorded moments',
            'replay': 'Replay hex_id through N steps',
            'triangulate': 'UIF Bridge between two coordinates',
            'routing_memory': '20 routing paths as spatial memory',
        }

    def execute(self, command: str, args: Dict[str, Any] = None) -> Dict[str, Any]:
        """Execute a spatial memory command."""
        args = args or {}

        handlers = {
            'status': self._cmd_status,
            'record': self._cmd_record,
            'restore': self._cmd_restore,
            'snapshot': self._cmd_snapshot,
            'timeline': self._cmd_timeline,
            'replay': self._cmd_replay,
            'triangulate': self._cmd_triangulate,
            'routing_memory': self._cmd_routing_memory,
        }

        handler = handlers.get(command)
        if not handler:
            return {'success': False, 'error': f'Unknown command: {command}',
                    'available': list(handlers.keys())}

        try:
            return handler(args)
        except Exception as e:
            return {'success': False, 'error': str(e)}

    def _cmd_status(self, args: Dict) -> Dict:
        """Memory system status."""
        return {
            'success': True,
            'shell': 'spatial_memory',
            'hex_id': self.HEX_ID,
            'version': '1.2.0',
            'moments_recorded': self._bank.count,
            'hex_ids_tracked': len(self._bank.get_hex_ids()),
            'model': {
                'camera': 'External observation frame (x, y, z Morton)',
                'agent': 'Internal state frame (basin, layer, I-state, mass)',
                'bridge': 'UIF Bridge f = √(ω_camera × ω_agent) triangulates',
                'time': 'SRL t = √d locks every moment deterministically',
            },
            'principle': (
                'Like a memory card: hex_id + step = full context restoration. '
                'The coordinate IS the memory. The time IS the distance. '
                'The mass IS the content.'
            ),
        }

    def _cmd_record(self, args: Dict) -> Dict:
        """Record a spatial moment."""
        hex_id = args.get('hex_id', '2A9E0100')
        step = int(args.get('step', 0))
        moment = compute_moment(hex_id, step)
        mid = self._bank.record(moment)
        return {
            'success': True,
            'moment_id': mid,
            'hex_id': hex_id,
            'step': step,
            'position': [moment.camera_x, moment.camera_y, moment.camera_z],
            'basin': moment.agent_basin,
            'layer': moment.agent_layer,
            'i_state': moment.agent_i_state,
            'mass': moment.content_mass,
            'srl_time': moment.srl_time,
            'recorded': True,
        }

    def _cmd_restore(self, args: Dict) -> Dict:
        """Restore a moment from the bank."""
        moment_id = args.get('moment_id', '')
        moment = self._bank.restore(moment_id)
        if not moment:
            return {'success': False, 'error': f'Moment {moment_id} not found'}
        return {'success': True, 'moment': moment.to_dict()}

    def _cmd_snapshot(self, args: Dict) -> Dict:
        """Full snapshot without recording."""
        hex_id = args.get('hex_id', '2A9E0100')
        step = int(args.get('step', 0))
        moment = compute_moment(hex_id, step)
        return {'success': True, 'moment': moment.to_dict()}

    def _cmd_timeline(self, args: Dict) -> Dict:
        """Timeline of recorded moments."""
        hex_id = args.get('hex_id', None)
        limit = int(args.get('limit', 50))
        moments = self._bank.get_timeline(hex_id, limit)
        return {
            'success': True,
            'count': len(moments),
            'hex_filter': hex_id,
            'moments': [m.to_dict() for m in moments],
        }

    def _cmd_replay(self, args: Dict) -> Dict:
        """Replay hex_id through N steps."""
        hex_id = args.get('hex_id', '2A9E0100')
        steps = int(args.get('steps', 16))
        result = replay_moments(hex_id, steps)
        result['success'] = True
        return result

    def _cmd_triangulate(self, args: Dict) -> Dict:
        """UIF Bridge triangulation between two coordinates."""
        hex_a = args.get('hex_a', args.get('hex1', '2A9E0100'))
        hex_b = args.get('hex_b', args.get('hex2', '000019B8'))
        step = int(args.get('step', 0))
        result = triangulate(hex_a, hex_b, step)
        result['success'] = True
        return result

    def _cmd_routing_memory(self, args: Dict) -> Dict:
        """20 routing paths as spatial memory."""
        result = get_routing_memory()
        result['success'] = True
        return result

    def run(self, command: str, *args, as_json: bool = False) -> Any:
        """Run a shell command (CLI interface)."""
        commands = {
            'status': lambda: self.execute('status'),
            'record': lambda: self.execute('record', {
                'hex_id': args[0] if args else '2A9E0100',
                'step': args[1] if len(args) > 1 else 0,
            }),
            'restore': lambda: self.execute('restore', {
                'moment_id': args[0] if args else '',
            }),
            'snapshot': lambda: self.execute('snapshot', {
                'hex_id': args[0] if args else '2A9E0100',
                'step': args[1] if len(args) > 1 else 0,
            }),
            'timeline': lambda: self.execute('timeline', {}),
            'replay': lambda: self.execute('replay', {
                'hex_id': args[0] if args else '2A9E0100',
                'steps': int(args[1]) if len(args) > 1 else 16,
            }),
            'triangulate': lambda: self.execute('triangulate', {
                'hex_a': args[0] if args else '2A9E0100',
                'hex_b': args[1] if len(args) > 1 else '000019B8',
            }),
            'routing_memory': lambda: self.execute('routing_memory'),
        }
        if command in commands:
            result = commands[command]()
            if as_json and isinstance(result, dict):
                return json.dumps(result, indent=2, default=str)
            return result
        return {'error': f'Unknown command: {command}', 'available': list(commands.keys())}


def main():
    """CLI entry point."""
    import sys

    shell = SpatialMemoryShell()

    if len(sys.argv) < 2:
        print("Spatial Memory Shell — Restorable Moments")
        print("=" * 50)
        print()
        print("  CAMERA (external)  ←— UIF Bridge —→  AGENT (internal)")
        print("  x, y, z Morton        f = √(ω×ω)      basin, layer, state")
        print("                        ↓")
        print("              SPATIAL MEMORY MOMENT")
        print("              t = √d (time locked)")
        print()
        print("Commands:")
        for cmd, desc in shell.commands.items():
            print(f"  {cmd:<20} - {desc}")
        return

    command = sys.argv[1]
    a = {}
    for arg in sys.argv[2:]:
        if '=' in arg:
            k, v = arg.split('=', 1)
            a[k] = v

    result = shell.execute(command, a)
    print(json.dumps(result, indent=2, default=str))


if __name__ == "__main__":
    main()
