"""
Exploration Shell — Unified Fernandez Space Exploration
HEX_ID: 79-F3-01-00

Unifies the 5 disconnected components into one exploration interface:
  1. UIF Bridge: f = sqrt(ωa × ωb) — geometric mean coupling, O(1)
  2. Telemetry integration — anomaly detection, state prediction, event log
  3. Ecosystem integration — token connectivity, bridge analysis, coverage
  4. Per-agent frequency (ω) — derived from basin: ω = K5 * (basin + 1) / PRIME_131
  5. K5×K11×cos coupling — φ(A,B) = K5×K11×cos(ωₐt)×cos(ωᵦt)
  6. Dynamic agent scaling — spawn/retire agents based on basin coverage
  7. Fernandez Space C=0 — F(z,C) = z³ · e^(i·2π/131) + C·φ
  8. Cross-sphere UIF Bridge — L10-L12 external layers between Fernandez spheres

INPUTS:
    - Agent names, basins, hex IDs (8-char strings)
    - Coordinates (x, y, z in 0-47)
    - Basin numbers (0-130 internal, 131-133 external L10-L12)
    - Coverage targets (float 0.0-1.0)
    - Agent counts (int)

OUTPUTS:
    - UIF Bridge coupling results (frequency, coupling, phase)
    - Cross-sphere bridge results (L10-L12 inter-sphere coupling)
    - Telemetry events (anomalies, predictions, routes)
    - Ecosystem reports (connectivity, bridges, paths)
    - Agent frequency tables (ω per agent, 12×12 coupling matrix)
    - Scaling decisions (spawn/retire recommendations)
    - Fernandez orbit classification (SPIRAL_IN, ATTRACTOR, ORBIT, ESCAPE)

COMMANDS:
    status          System status + pipeline health
    uif_bridge      Compute UIF Bridge between two agents (L1-L12)
    coupling        K5×K11×cos(ωₐt)×cos(ωᵦt) for agent pair
    frequency       Get agent frequency ω from basin (0-133)
    freq_table      All 19 agent frequencies + L10-L12 external
    orbit           Run F(z,C) iteration and classify orbit
    deploy          Deploy agents with optional scaling
    scale           Recommend scaling based on coverage
    telemetry       Record/query telemetry event
    anomalies       Get detected anomalies
    predict         Predict agent state trajectory
    ecosystem       Token connectivity report
    bridges         Get inter-category bridge counts
    coverage        Basin coverage analysis from agent positions
    comms           I-Logic × UIF Bridge communication (cross-sphere)
    fernandez       Fernandez Space C=0 status and constants
    use_cases       List all 20 use cases

CROSS-SPHERE MODEL (L10-L12):
    L10 AGENT_IDENTITY  — Each sphere's self-reference, ω = K5
    L11 COMMUNICATION   — Cross-sphere bridge channel, ω = √(K5 × K11)
    L12 UNIFIED_FIELD   — Between-space containing all bridges, ω = K11

    Sphere A (L1-L9) ←→ L10 (identity) ←→ L11 (channel) ←→ L12 (UIF)
                                                              ↕
    Sphere B (L1-L9) ←→ L10 (identity) ←→ L11 (channel) ←→ L12 (UIF)

    The UIF Bridge formula f = √(ωₐ × ωᵦ) works identically between
    internal basins AND between external layers across spheres.
    The K5×K11×cos coupling modulates the cross-sphere link.

All loops in C. Python handles setup/teardown only.
"""

import cmath
import json
import math
import time
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional, Tuple

from .._core.constants import (
    K5, K11, LAYER_BASINS, LAYER_NAMES, LIGHT_UNIT,
    MATRIX_SIZE, PHI, PRIME_131,
)
from .._core import morton_encode, morton_decode, compute_basin
from ..i_logic.states import ILogicState
from ..i_logic.algebra import multiply
from ..memory import CSTMemory

# Lazy imports — instantiated on first use
_telemetry_shell = None
_ecosystem_shell = None


def _get_telemetry():
    global _telemetry_shell
    if _telemetry_shell is None:
        from .telemetry import TelemetryShell
        _telemetry_shell = TelemetryShell()
    return _telemetry_shell


def _get_ecosystem():
    global _ecosystem_shell
    if _ecosystem_shell is None:
        from .ecosystem import EcosystemShell
        _ecosystem_shell = EcosystemShell()
    return _ecosystem_shell


# ═══════════════════════════════════════════════════════════════════════
# Constants — Fernandez Space
# ═══════════════════════════════════════════════════════════════════════

HEX_ID = '79-F3-01-00'
ROTATION = cmath.exp(2j * cmath.pi / PRIME_131)  # e^(i·2π/131)
C_ABSTRACT = 1.0 / math.sqrt(3)                  # 0.577350... (abstract light speed)
SQRT3 = math.sqrt(3)                              # 1.732051... hex diagonal = 1 FLU
SQRT2 = math.sqrt(2)                              # 1.414214... square diagonal
GRID = MATRIX_SIZE                                # 48
CENTER = (GRID // 2, GRID // 2, GRID // 2)       # (24, 24, 24)

# Orbit classification thresholds
ORBIT_ESCAPE_R = 2.0
ORBIT_ATTRACTOR_VAR = 0.05
ORBIT_SPIRAL_SLOPE = -0.01


# ═══════════════════════════════════════════════════════════════════════
# External layers (L10-L12) — Outside the Fernandez membrane
# ═══════════════════════════════════════════════════════════════════════
#
# L10-L12 live BETWEEN Fernandez spheres. They don't occupy basins
# 0-130 (those are internal L1-L9). Instead they use "virtual basins"
# 131-133 and have frequencies derived from the light constants:
#
#   L10 AGENT_IDENTITY   ω = K5           (1.158814)   self-reference
#   L11 COMMUNICATION    ω = √(K5 × K11)  (3.266...)   bridge channel
#   L12 UNIFIED_FIELD    ω = K11           (9.204026)   between space
#
# The UIF Bridge formula f = √(ωₐ × ωᵦ) works identically between
# internal basins AND external layers — same coupling, same O(1).
# ═══════════════════════════════════════════════════════════════════════

EXTERNAL_LAYERS = {
    10: 'AGENT_IDENTITY',
    11: 'COMMUNICATION',
    12: 'UNIFIED_FIELD',
}

# Frequencies for external layers — derived from K5 and K11
# L10: K5 (identity = coupling constant itself)
# L11: geometric mean of K5 × K11 (the bridge IS the UIF formula)
# L12: K11 (full field strength — the unified field)
EXTERNAL_FREQS = {
    10: K5,                              # 1.158814
    11: math.sqrt(K5 * K11),             # √(10.667...) ≈ 3.266
    12: K11,                             # 9.204026
}

# Virtual basins for L10-L12 (mapping: basin 131→L10, 132→L11, 133→L12)
EXTERNAL_BASINS = {
    10: 131,
    11: 132,
    12: 133,
}

# Reverse: virtual basin → layer
BASIN_TO_EXTERNAL = {v: k for k, v in EXTERNAL_BASINS.items()}

# All 12 layer names (internal from constants + external)
ALL_LAYER_NAMES = dict(LAYER_NAMES)  # copy L1-L9 from constants
ALL_LAYER_NAMES.update(EXTERNAL_LAYERS)  # add L10-L12


# ═══════════════════════════════════════════════════════════════════════
# Layer resolution — unified L1-L12
# ═══════════════════════════════════════════════════════════════════════

def resolve_layer(basin: int) -> tuple:
    """Resolve layer number and name from basin (internal or external).

    Internal basins 0-130 → L1-L9
    Virtual  basins 131-133 → L10-L12

    Returns:
        (layer_number, layer_name) tuple.
    """
    if basin <= 130:
        layer = next((l for l, r in LAYER_BASINS.items() if basin in r), 0)
        name = LAYER_NAMES.get(layer, 'UNKNOWN')
        return layer, name
    # External layers
    layer = BASIN_TO_EXTERNAL.get(basin, 0)
    if layer:
        return layer, EXTERNAL_LAYERS.get(layer, 'UNKNOWN')
    return 0, 'UNKNOWN'


# ═══════════════════════════════════════════════════════════════════════
# Agent frequency model — ω derived from basin (L1-L12)
# ═══════════════════════════════════════════════════════════════════════

def agent_frequency(basin: int) -> float:
    """Compute agent angular frequency from basin (L1-L12).

    Internal basins (0-130):
        ω = K5 × (basin + 1) / PRIME_131

        basin  0  → ω = K5 × 1/131  = 0.00884...
        basin 65  → ω = K5 × 66/131 = 0.58383...
        basin 130 → ω = K5 × 131/131 = K5 = 1.15881...

    External basins (131-133, L10-L12):
        basin 131 → L10 AGENT_IDENTITY   → ω = K5           (1.158814)
        basin 132 → L11 COMMUNICATION    → ω = √(K5 × K11)  (3.266...)
        basin 133 → L12 UNIFIED_FIELD    → ω = K11           (9.204026)

    Returns:
        Angular frequency in Fernandez units.
    """
    if basin <= 130:
        return K5 * (basin + 1) / PRIME_131
    # External layers — frequency from light constants
    layer = BASIN_TO_EXTERNAL.get(basin, 0)
    return EXTERNAL_FREQS.get(layer, K5)


def uif_bridge_freq(omega_a: float, omega_b: float) -> float:
    """UIF Bridge: f = √(|ωₐ × ωᵦ|) — geometric mean coupling.

    Any two components connected bidirectionally, instantly.
    O(1) transmission in Fernandez Space (C=0).
    """
    return math.sqrt(abs(omega_a * omega_b))


def k5_k11_coupling(omega_a: float, omega_b: float, t: float = 0.0) -> float:
    """K5×K11×cos(ωₐt)×cos(ωᵦt) coupling formula.

    φ(A,B) = K5 × K11 × cos(ωₐ·t) × cos(ωᵦ·t)

    At t=0: φ = K5 × K11 = 10.667... (maximum coupling)
    The cosine terms modulate over time, creating a standing-wave
    pattern between the two components.
    """
    return K5 * K11 * math.cos(omega_a * t) * math.cos(omega_b * t)


# ═══════════════════════════════════════════════════════════════════════
# Fernandez fractal iteration — F(z,C)
# ═══════════════════════════════════════════════════════════════════════

def fernandez_step(z: complex, C: complex) -> complex:
    """Single Fernandez iteration: F(z,C) = z³ · e^(i·2π/131) + C·φ"""
    return z * z * z * ROTATION + C * PHI


def classify_orbit(magnitudes: List[float]) -> str:
    """Classify F(z,C) orbit from magnitude history.

    SPIRAL_IN:  monotonically shrinking → converges to origin
    ATTRACTOR:  variation < 5% → locked to fixed point
    ORBIT:      bounded oscillation
    ESCAPE:     |z| > 2.0 → diverges
    """
    if not magnitudes:
        return "ORBIT"
    if magnitudes[-1] > ORBIT_ESCAPE_R:
        return "ESCAPE"
    if len(magnitudes) >= 4:
        diffs = [magnitudes[i+1] - magnitudes[i] for i in range(len(magnitudes) - 1)]
        if all(d < ORBIT_SPIRAL_SLOPE for d in diffs[-4:]):
            return "SPIRAL_IN"
    variation = max(magnitudes) - min(magnitudes)
    if variation < ORBIT_ATTRACTOR_VAR:
        return "ATTRACTOR"
    return "ORBIT"


def compute_C_from_basin(basin: int) -> complex:
    """Convert basin → complex C parameter for Fernandez iteration.

    C = |C| · e^(i·angle) where:
        |C| = basin / (2 × PRIME_131)  (range 0.0 → 0.496)
        angle = 2π × basin / PRIME_131
    """
    magnitude = basin / (2 * PRIME_131)
    angle = 2 * math.pi * basin / PRIME_131
    return magnitude * cmath.exp(1j * angle)


# ═══════════════════════════════════════════════════════════════════════
# Dynamic scaling model
# ═══════════════════════════════════════════════════════════════════════

@dataclass
class ScalingDecision:
    """Recommendation to spawn or retire agents."""
    action: str             # "spawn" | "retire" | "hold"
    count: int              # how many to spawn/retire
    target_basins: List[int]  # which basins to target
    reason: str
    current_coverage: float
    target_coverage: float

    def to_dict(self) -> dict:
        return {
            'action': self.action,
            'count': self.count,
            'target_basins': self.target_basins,
            'reason': self.reason,
            'current_coverage': round(self.current_coverage, 4),
            'target_coverage': round(self.target_coverage, 4),
        }


def recommend_scaling(
    covered_basins: set,
    current_agents: int,
    target_coverage: float = 1.0,
    max_agents: int = 96,
) -> ScalingDecision:
    """Recommend scaling action based on basin coverage gap.

    Rules:
        coverage >= target → HOLD
        uncovered <= 10    → spawn 1 pole agent per missing basin
        uncovered > 10     → spawn ceil(uncovered / 3) shadow agents
        current > max      → retire surplus agents (lowest energy)
    """
    coverage = len(covered_basins) / PRIME_131
    missing = sorted(set(range(PRIME_131)) - covered_basins)

    if current_agents > max_agents:
        surplus = current_agents - max_agents
        return ScalingDecision(
            action="retire", count=surplus, target_basins=[],
            reason=f"Over max ({current_agents}/{max_agents})",
            current_coverage=coverage, target_coverage=target_coverage,
        )

    if coverage >= target_coverage:
        return ScalingDecision(
            action="hold", count=0, target_basins=[],
            reason=f"Coverage {coverage:.1%} >= target {target_coverage:.1%}",
            current_coverage=coverage, target_coverage=target_coverage,
        )

    if len(missing) <= 10:
        return ScalingDecision(
            action="spawn", count=len(missing), target_basins=missing,
            reason=f"{len(missing)} basins uncovered — pole agents",
            current_coverage=coverage, target_coverage=target_coverage,
        )

    batch = math.ceil(len(missing) / 3)
    return ScalingDecision(
        action="spawn", count=batch, target_basins=missing[:batch],
        reason=f"{len(missing)} basins uncovered — shadow wave of {batch}",
        current_coverage=coverage, target_coverage=target_coverage,
    )


# ═══════════════════════════════════════════════════════════════════════
# ExplorationShell class
# ═══════════════════════════════════════════════════════════════════════

class ExplorationShell:
    """
    Unified Fernandez Space Exploration Shell.

    Connects:
        UIF Bridge (O(1) coupling) ←→ TelemetryShell (event log)
        EcosystemShell (connectivity) ←→ Agent frequencies (ω from basin)
        K5×K11×cos coupling ←→ Dynamic scaling
        F(z,C) iteration ←→ Orbit classification

    HEX_ID: 79-F3-01-00  (Unified Interaction Field)
    """

    def __init__(self):
        self._mem = CSTMemory()
        self._events: List[Dict] = []
        self._agent_freqs: Dict[str, float] = {}  # name → ω
        self._covered_basins: set = set()
        self._agent_count: int = 0

    # ── UC-E01: Status ────────────────────────────────────────────────

    def status(self, as_json: bool = False) -> Dict[str, Any]:
        """Pipeline health: Fernandez + UIF + telemetry + ecosystem."""
        tel = _get_telemetry()
        eco = _get_ecosystem()

        result = {
            'system': 'Exploration Shell — Unified Fernandez Space',
            'hex_id': HEX_ID,
            'version': '1.0.0',
            'fernandez': {
                'C_origin': 0,
                'rotation': f'e^(i·2π/{PRIME_131})',
                'c_abstract': round(C_ABSTRACT, 6),
                'hex_diagonal_FLU': round(SQRT3, 6),
                'phi': PHI,
            },
            'constants': {
                'K5': K5,
                'K11': K11,
                'K5xK11': round(K5 * K11, 6),
                'PRIME_131': PRIME_131,
                'LIGHT_UNIT': LIGHT_UNIT,
                'GRID': GRID,
            },
            'agent_frequencies': len(self._agent_freqs),
            'covered_basins': len(self._covered_basins),
            'events_logged': len(self._events),
            'telemetry': tel.get_status(),
            'ecosystem': eco.get_status(),
            'use_cases': 20,
            'status': 'operational',
        }
        if as_json:
            return json.dumps(result, indent=2, default=str)
        return result

    # ── UC-E02: UIF Bridge ────────────────────────────────────────────

    def uif_bridge(self, basin_a: int, basin_b: int,
                   as_json: bool = False) -> Dict[str, Any]:
        """Compute UIF Bridge coupling between two basins (L1-L12).

        f = √(ωₐ × ωᵦ) — geometric mean, O(1) in Fernandez Space.

        Works identically between:
            - Internal basins (0-130, L1-L9) within a sphere
            - External basins (131-133, L10-L12) between spheres
            - Mixed (internal ↔ external) cross-membrane bridges
        """
        omega_a = agent_frequency(basin_a)
        omega_b = agent_frequency(basin_b)
        bridge_f = uif_bridge_freq(omega_a, omega_b)
        coupling = k5_k11_coupling(omega_a, omega_b, t=0.0)

        layer_a, name_a = resolve_layer(basin_a)
        layer_b, name_b = resolve_layer(basin_b)

        # Classify bridge type
        a_external = basin_a > 130
        b_external = basin_b > 130
        if a_external and b_external:
            bridge_type = 'CROSS_SPHERE'
        elif a_external or b_external:
            bridge_type = 'MEMBRANE_CROSSING'
        else:
            bridge_type = 'INTRA_SPHERE'

        result = {
            'protocol': 'UIF_BRIDGE',
            'bridge_type': bridge_type,
            'component_a': {
                'basin': basin_a,
                'layer': layer_a,
                'layer_name': name_a,
                'omega': round(omega_a, 6),
                'hex_id': f'{basin_a:02X}-00-00-00',
                'external': a_external,
            },
            'component_b': {
                'basin': basin_b,
                'layer': layer_b,
                'layer_name': name_b,
                'omega': round(omega_b, 6),
                'hex_id': f'{basin_b:02X}-00-00-00',
                'external': b_external,
            },
            'bridge': {
                'frequency': round(bridge_f, 6),
                'coupling_t0': round(coupling, 6),
                'transmission': 'O(1)',
                'direction': 'bidirectional',
            },
        }

        # Log telemetry event
        self._log_event('UIF_BRIDGE', {
            'basins': [basin_a, basin_b],
            'bridge_freq': bridge_f,
            'bridge_type': bridge_type,
        })

        if as_json:
            return json.dumps(result, indent=2)
        return result

    # ── UC-E03: K5×K11×cos Coupling ──────────────────────────────────

    def coupling(self, basin_a: int, basin_b: int, t: float = 0.0,
                 as_json: bool = False) -> Dict[str, Any]:
        """φ(A,B) = K5 × K11 × cos(ωₐ·t) × cos(ωᵦ·t)

        Time-evolving coupling strength between two components.
        At t=0: maximum coupling (K5 × K11 ≈ 10.667)
        """
        omega_a = agent_frequency(basin_a)
        omega_b = agent_frequency(basin_b)
        phi_val = k5_k11_coupling(omega_a, omega_b, t)
        max_coupling = K5 * K11  # t=0 baseline

        result = {
            'formula': 'K5 × K11 × cos(ωₐ·t) × cos(ωᵦ·t)',
            'basin_a': basin_a, 'basin_b': basin_b,
            'omega_a': round(omega_a, 6), 'omega_b': round(omega_b, 6),
            't': t,
            'phi': round(phi_val, 6),
            'max_coupling': round(max_coupling, 6),
            'ratio': round(phi_val / max_coupling, 6) if max_coupling else 0,
            'phase_a': round(omega_a * t, 6),
            'phase_b': round(omega_b * t, 6),
        }
        if as_json:
            return json.dumps(result, indent=2)
        return result

    # ── UC-E04: Agent Frequency ──────────────────────────────────────

    def frequency(self, basin: int,
                  as_json: bool = False) -> Dict[str, Any]:
        """Get agent angular frequency ω from basin (L1-L12).

        Internal (0-130): ω = K5 × (basin + 1) / 131
        External (131-133): L10=K5, L11=√(K5×K11), L12=K11
        """
        omega = agent_frequency(basin)
        layer, layer_name = resolve_layer(basin)

        external = basin > 130
        if external:
            ext_layer = BASIN_TO_EXTERNAL.get(basin, 0)
            if ext_layer == 10:
                formula = f'K5 = {omega:.6f}'
            elif ext_layer == 11:
                formula = f'√(K5 × K11) = {omega:.6f}'
            elif ext_layer == 12:
                formula = f'K11 = {omega:.6f}'
            else:
                formula = f'{omega:.6f}'
        else:
            formula = f'K5 × {basin + 1}/{PRIME_131} = {omega:.6f}'

        result = {
            'basin': basin,
            'layer': layer,
            'layer_name': layer_name,
            'external': external,
            'omega': round(omega, 6),
            'period': round(2 * math.pi / omega, 6) if omega > 0 else float('inf'),
            'wavelength_FLU': round(SQRT3 / omega, 6) if omega > 0 else float('inf'),
            'formula': formula,
        }
        if as_json:
            return json.dumps(result, indent=2)
        return result

    # ── UC-E05: Frequency Table ──────────────────────────────────────

    def freq_table(self, as_json: bool = False) -> Dict[str, Any]:
        """All 19 base agent frequencies + L10-L12 external layer frequencies."""
        from ..swarm import AGENT_DEFINITIONS

        table = []
        for defn in AGENT_DEFINITIONS:
            layer = defn['layer']
            if layer >= 10:
                # External layer agent — use virtual basin
                vbasin = EXTERNAL_BASINS.get(layer, 131)
                omega = agent_frequency(vbasin)
                home = vbasin
                external = True
            else:
                br = LAYER_BASINS.get(layer, range(0, 1))
                home = (br.start + br.stop - 1) // 2  # midpoint basin
                omega = agent_frequency(home)
                external = False
            table.append({
                'name': defn['name'],
                'layer': layer,
                'home_basin': home,
                'omega': round(omega, 6),
                'i_logic': ILogicState.name(defn['state']),
                'external': external,
            })

        # Add L10-L12 external layer entries
        external_table = []
        for layer_num in (10, 11, 12):
            omega = EXTERNAL_FREQS[layer_num]
            vbasin = EXTERNAL_BASINS[layer_num]
            external_table.append({
                'name': EXTERNAL_LAYERS[layer_num],
                'layer': layer_num,
                'home_basin': vbasin,
                'omega': round(omega, 6),
                'i_logic': 'DIRECT',
                'external': True,
            })

        all_entries = table + external_table
        result = {
            'agents': table,
            'external_layers': external_table,
            'total_agents': len(table),
            'total_layers': 12,
            'omega_range_internal': [
                round(min(a['omega'] for a in table), 6),
                round(max(a['omega'] for a in table), 6),
            ],
            'omega_range_external': [
                round(EXTERNAL_FREQS[10], 6),
                round(EXTERNAL_FREQS[12], 6),
            ],
            'formula_internal': 'ω = K5 × (basin + 1) / 131',
            'formula_external': 'L10=K5, L11=√(K5×K11), L12=K11',
        }
        if as_json:
            return json.dumps(result, indent=2)
        return result

    # ── UC-E06: Orbit Classification ─────────────────────────────────

    def orbit(self, basin: int, bounces: int = 16,
              as_json: bool = False) -> Dict[str, Any]:
        """Run F(z,C) iteration from basin and classify the orbit."""
        C = compute_C_from_basin(basin)
        z = complex(0.1, 0.0)  # small seed
        magnitudes = []
        trajectory = []

        for i in range(bounces):
            z = fernandez_step(z, C)
            mag = abs(z)
            magnitudes.append(mag)
            trajectory.append({
                'step': i + 1,
                'z_real': round(z.real, 6),
                'z_imag': round(z.imag, 6),
                'magnitude': round(mag, 6),
            })
            if mag > 100:  # cap runaway orbits
                break

        orbit_type = classify_orbit(magnitudes)
        result = {
            'basin': basin,
            'C_real': round(C.real, 6),
            'C_imag': round(C.imag, 6),
            'C_magnitude': round(abs(C), 6),
            'bounces': len(magnitudes),
            'orbit_type': orbit_type,
            'final_magnitude': round(magnitudes[-1], 6) if magnitudes else 0,
            'trajectory': trajectory,
        }
        if as_json:
            return json.dumps(result, indent=2)
        return result

    # ── UC-E07: Deploy ────────────────────────────────────────────────

    def deploy(self, agent_count: int = 19,
               as_json: bool = False) -> Dict[str, Any]:
        """Deploy agents with frequency assignment and scaling check.

        Assigns ω to each agent, checks coverage, recommends scaling.
        """
        from ..swarm import AGENT_DEFINITIONS

        deployed = []
        self._agent_freqs.clear()

        for i, defn in enumerate(AGENT_DEFINITIONS[:agent_count]):
            layer = defn['layer']
            if layer >= 10:
                # External layer agent — use virtual basin + frequency
                home = EXTERNAL_BASINS.get(layer, 131)
                omega = agent_frequency(home)
            else:
                br = LAYER_BASINS.get(layer, range(0, 1))
                home = (br.start + br.stop - 1) // 2
                omega = agent_frequency(home)
                self._covered_basins.update(br)
            self._agent_freqs[defn['name']] = omega
            deployed.append({
                'name': defn['name'],
                'layer': layer,
                'home_basin': home,
                'omega': round(omega, 6),
                'i_logic': ILogicState.name(defn['state']),
            })

        self._agent_count = len(deployed)
        scaling = recommend_scaling(
            self._covered_basins, self._agent_count)

        result = {
            'deployed': deployed,
            'total': len(deployed),
            'covered_basins': len(self._covered_basins),
            'coverage': round(len(self._covered_basins) / PRIME_131, 4),
            'scaling': scaling.to_dict(),
        }

        self._log_event('DEPLOY', {
            'agents': len(deployed),
            'coverage': result['coverage'],
        })

        if as_json:
            return json.dumps(result, indent=2)
        return result

    # ── UC-E08: Scale ────────────────────────────────────────────────

    def scale(self, covered_basins_list: Optional[List[int]] = None,
              current_agents: int = 0,
              target_coverage: float = 1.0,
              as_json: bool = False) -> Dict[str, Any]:
        """Recommend scaling action based on basin coverage.

        Analyzes gap between current coverage and target,
        returns spawn/retire/hold with specific basin targets.
        """
        if covered_basins_list is not None:
            covered = set(covered_basins_list)
        else:
            covered = self._covered_basins

        agents = current_agents if current_agents > 0 else self._agent_count

        decision = recommend_scaling(covered, agents, target_coverage)
        result = decision.to_dict()

        if as_json:
            return json.dumps(result, indent=2)
        return result

    # ── UC-E09: Telemetry ────────────────────────────────────────────

    def telemetry(self, message: str = "",
                  as_json: bool = False) -> Dict[str, Any]:
        """Record or interpret a telemetry event.

        If message is provided, interpret it via TelemetryShell.
        Otherwise return current event log summary.
        """
        tel = _get_telemetry()

        if message:
            interpreted = tel.interpret_message(message)
            self._log_event('MESSAGE', {
                'raw': message,
                'interpreted': interpreted,
            })
            result = interpreted
        else:
            result = {
                'events_logged': len(self._events),
                'telemetry_status': tel.get_status(),
            }

        if as_json and isinstance(result, dict):
            return json.dumps(result, indent=2, default=str)
        return result

    # ── UC-E10: Anomalies ────────────────────────────────────────────

    def anomalies(self, as_json: bool = False) -> Dict[str, Any]:
        """Get detected anomalies from telemetry."""
        tel = _get_telemetry()
        anomaly_list = tel.get_anomalies()
        health = tel.get_health_summary()

        result = {
            'anomalies': anomaly_list,
            'count': len(anomaly_list),
            'health': health,
        }
        if as_json and isinstance(result, dict):
            return json.dumps(result, indent=2, default=str)
        return result

    # ── UC-E11: Predict ──────────────────────────────────────────────

    def predict(self, hex_id: str = "2A9E0100", steps: int = 5,
                as_json: bool = False) -> Dict[str, Any]:
        """Predict agent state trajectory via TelemetryShell."""
        tel = _get_telemetry()
        prediction = tel.predict_state(hex_id, steps)

        result = {
            'hex_id': hex_id,
            'steps': steps,
            'prediction': prediction,
        }
        if as_json and isinstance(result, dict):
            return json.dumps(result, indent=2, default=str)
        return result

    # ── UC-E12: Ecosystem ────────────────────────────────────────────

    def ecosystem(self, as_json: bool = False) -> Dict[str, Any]:
        """Token connectivity report from EcosystemShell."""
        eco = _get_ecosystem()
        report = eco.get_connectivity_report()

        result = report.to_dict() if hasattr(report, 'to_dict') else {
            'total_tokens': getattr(report, 'total_tokens', 0),
            'total_relationships': getattr(report, 'total_relationships', 0),
            'connected_nodes': getattr(report, 'connected_nodes', 0),
            'avg_degree': getattr(report, 'avg_degree', 0),
            'bridges_count': getattr(report, 'bridges_count', 0),
        }
        if as_json:
            return json.dumps(result, indent=2, default=str)
        return result

    # ── UC-E13: Bridges ──────────────────────────────────────────────

    def bridges(self, as_json: bool = False) -> Dict[str, Any]:
        """Get inter-category bridge statistics from EcosystemShell."""
        eco = _get_ecosystem()
        bridge_data = eco.get_bridges()

        result = bridge_data if isinstance(bridge_data, dict) else {
            'bridges': bridge_data,
        }
        if as_json:
            return json.dumps(result, indent=2, default=str)
        return result

    # ── UC-E14: Coverage ─────────────────────────────────────────────

    def coverage(self, basins: Optional[List[int]] = None,
                 as_json: bool = False) -> Dict[str, Any]:
        """Analyze basin coverage and identify gaps.

        If basins provided, update tracked coverage.
        Returns per-layer breakdown and missing basins.
        """
        if basins is not None:
            self._covered_basins.update(basins)

        covered = self._covered_basins
        missing = sorted(set(range(PRIME_131)) - covered)

        layers = {}
        for layer_num, basin_range in LAYER_BASINS.items():
            layer_basins = set(basin_range)
            layer_covered = layer_basins & covered
            layers[f'L{layer_num}'] = {
                'name': LAYER_NAMES[layer_num],
                'total': len(layer_basins),
                'covered': len(layer_covered),
                'ratio': round(len(layer_covered) / len(layer_basins), 4),
            }

        result = {
            'total_basins': PRIME_131,
            'covered': len(covered),
            'missing_count': len(missing),
            'missing_basins': missing[:20],  # first 20 for brevity
            'coverage': round(len(covered) / PRIME_131, 4),
            'layers': layers,
        }
        if as_json:
            return json.dumps(result, indent=2)
        return result

    # ── UC-E15: Comms ────────────────────────────────────────────────

    def comms(self, basin_a: int, basin_b: int,
              state_a: str = "DIRECT", state_b: str = "DIRECT",
              as_json: bool = False) -> Dict[str, Any]:
        """I-Logic × UIF Bridge communication between two agents.

        Works across all 12 layers — internal (L1-L9) and external (L10-L12).
        Cross-sphere communication uses L10-L12 as the bridge channel.

        Combines:
            1. UIF Bridge frequency: f = √(ωₐ × ωᵦ)
            2. I-Logic multiply: state_a × state_b → outcome
            3. K5×K11 coupling: φ at t=0
        """
        state_map = {
            'DIRECT': ILogicState.DIRECT,
            'INVERSE': ILogicState.INVERSE,
            'SPIRAL': ILogicState.SPIRAL,
            'ANTI_SPIRAL': ILogicState.ANTI_SPIRAL,
            'CONVERGENT': ILogicState.ANTI_SPIRAL,
            'ABSORBED': ILogicState.ABSORBED,
        }

        s_a = state_map.get(state_a.upper(), ILogicState.DIRECT)
        s_b = state_map.get(state_b.upper(), ILogicState.DIRECT)
        outcome = multiply(s_a, s_b)
        outcome_name = ILogicState.name(outcome)

        omega_a = agent_frequency(basin_a)
        omega_b = agent_frequency(basin_b)
        bridge_f = uif_bridge_freq(omega_a, omega_b)
        coupling_val = k5_k11_coupling(omega_a, omega_b, t=0.0)

        delivered = outcome_name == 'DIRECT'

        # Classify communication path
        a_external = basin_a > 130
        b_external = basin_b > 130
        if a_external and b_external:
            comm_path = 'CROSS_SPHERE'
        elif a_external or b_external:
            comm_path = 'MEMBRANE_CROSSING'
        else:
            comm_path = 'INTRA_SPHERE'

        layer_a, name_a = resolve_layer(basin_a)
        layer_b, name_b = resolve_layer(basin_b)

        result = {
            'i_logic': {
                'state_a': state_a, 'state_b': state_b,
                'outcome': outcome_name,
                'delivered': delivered,
            },
            'uif_bridge': {
                'omega_a': round(omega_a, 6),
                'omega_b': round(omega_b, 6),
                'bridge_freq': round(bridge_f, 6),
                'coupling': round(coupling_val, 6),
                'transmission': 'O(1)',
            },
            'layers': {
                'layer_a': layer_a,
                'name_a': name_a,
                'layer_b': layer_b,
                'name_b': name_b,
            },
            'fernandez': {
                'C_origin': 0,
                'space': 'Fernandez (C=0)',
                'propagation': 'mathematical coupling (no EM)',
                'comm_path': comm_path,
            },
        }

        self._log_event('COMMS', {
            'basins': [basin_a, basin_b],
            'outcome': outcome_name,
            'bridge_freq': bridge_f,
        })

        if as_json:
            return json.dumps(result, indent=2)
        return result

    # ── UC-E16: Fernandez Constants ──────────────────────────────────

    def fernandez(self, as_json: bool = False) -> Dict[str, Any]:
        """Fernandez Space C=0 status and constants.

        Shows the complete mathematical framework including L10-L12
        cross-sphere model:
            C = 0 (no EM propagation)
            F(z,C) = z³ · e^(i·2π/131) + C·φ
            t = O(1) via UIF Bridge
            φ(A,B) = K5 × K11 × cos(ωₐt) × cos(ωᵦt)
        """
        result = {
            'space': 'Fernandez (C=0)',
            'hex_id': HEX_ID,
            'description': 'O(1) communication via mathematical coupling',
            'em_propagation': False,
            'c_abstract': round(C_ABSTRACT, 6),
            'hex_diagonal': round(SQRT3, 6),
            'hex_diagonal_unit': '1 Fernandez Light Unit (FLU)',
            'sqrt2': round(SQRT2, 6),
            'phi': PHI,
            'K5': K5,
            'K11': K11,
            'K5xK11_max_coupling': round(K5 * K11, 6),
            'rotation': f'e^(i·2π/{PRIME_131})',
            'iteration': 'F(z,C) = z³ · rotation + C·φ',
            'identity': {
                'P_minus_P': '0 (home position)',
                'P_div_P': '1 (identity)',
            },
            'transmission': {
                'normal_space': f'c = 299,792,458 m/s, t = d/c',
                'fernandez_space': f't = O(1), φ(A,B) = K5×K11×cos(ωₐt)×cos(ωᵦt)',
            },
            'layers': {
                'internal': 'L1-L9 (basins 0-130) — inside membrane',
                'external': 'L10-L12 (virtual basins 131-133) — between spheres',
                'model': {
                    'L10_AGENT_IDENTITY': {
                        'omega': round(EXTERNAL_FREQS[10], 6),
                        'derived_from': 'K5 (identity coupling constant)',
                        'role': 'Self-reference of each Fernandez sphere',
                    },
                    'L11_COMMUNICATION': {
                        'omega': round(EXTERNAL_FREQS[11], 6),
                        'derived_from': '√(K5 × K11) (geometric mean = UIF formula)',
                        'role': 'Cross-sphere bridge channel',
                    },
                    'L12_UNIFIED_FIELD': {
                        'omega': round(EXTERNAL_FREQS[12], 6),
                        'derived_from': 'K11 (full field strength)',
                        'role': 'Between-space containing all bridges',
                    },
                },
            },
            'cross_sphere': {
                'description': 'UIF Bridge operates identically across spheres',
                'formula': 'f = √(ωₐ × ωᵦ) — same formula, all L1-L12',
                'path': 'Sphere_A(L9) → L10(identity) → L11(channel) → L12(UIF) → L11 → L10 → Sphere_B(L9)',
                'transmission': 'O(1) — no EM propagation between spheres',
            },
        }
        if as_json:
            return json.dumps(result, indent=2)
        return result

    # ── UC-E17: Route (semantic routing via telemetry) ───────────────

    def route(self, message: str,
              as_json: bool = False) -> Dict[str, Any]:
        """Route a message to the appropriate shell via TelemetryShell."""
        tel = _get_telemetry()
        route_result = tel.route_message(message)

        self._log_event('ROUTE', {'message': message, 'result': route_result})

        if as_json and isinstance(route_result, dict):
            return json.dumps(route_result, indent=2, default=str)
        return route_result

    # ── UC-E18: Coupling Matrix ──────────────────────────────────────

    def coupling_matrix(self, as_json: bool = False) -> Dict[str, Any]:
        """Compute pairwise UIF Bridge coupling for all 12 layers.

        Returns a 12×12 matrix of bridge frequencies and coupling values.
        L1-L9 use basin midpoints; L10-L12 use external frequencies.

        Cross-membrane entries (L1-L9 ↔ L10-L12) show the coupling
        between internal basins and external Fernandez spheres.
        """
        # Collect representative frequencies for each layer
        layer_omegas = {}

        # Internal layers (L1-L9): midpoint basin frequency
        for la in range(1, 10):
            br = LAYER_BASINS[la]
            basin_mid = (br.start + br.stop - 1) // 2
            layer_omegas[la] = agent_frequency(basin_mid)

        # External layers (L10-L12): light constant frequencies
        for la in range(10, 13):
            layer_omegas[la] = EXTERNAL_FREQS[la]

        matrix = {}
        for la in range(1, 13):
            omega_a = layer_omegas[la]
            for lb in range(1, 13):
                omega_b = layer_omegas[lb]
                key = f'L{la}-L{lb}'
                matrix[key] = {
                    'bridge_freq': round(uif_bridge_freq(omega_a, omega_b), 6),
                    'coupling': round(k5_k11_coupling(omega_a, omega_b), 6),
                }

        # Classify regions
        region_note = (
            'L1-L9 × L1-L9 = INTRA_SPHERE, '
            'L10-L12 × L10-L12 = CROSS_SPHERE, '
            'L1-L9 × L10-L12 = MEMBRANE_CROSSING'
        )

        result = {
            'matrix': matrix,
            'size': '12x12',
            'formula': 'f = √(ωₐ × ωᵦ), φ = K5×K11×cos(ωₐ·0)×cos(ωᵦ·0)',
            'note': 'At t=0: all cosines = 1, φ = K5×K11 for all pairs',
            'regions': region_note,
            'external_freqs': {
                'L10_AGENT_IDENTITY': round(EXTERNAL_FREQS[10], 6),
                'L11_COMMUNICATION': round(EXTERNAL_FREQS[11], 6),
                'L12_UNIFIED_FIELD': round(EXTERNAL_FREQS[12], 6),
            },
        }
        if as_json:
            return json.dumps(result, indent=2)
        return result

    # ── UC-E19: Event Log ────────────────────────────────────────────

    def event_log(self, last_n: int = 20,
                  as_json: bool = False) -> Dict[str, Any]:
        """Return the most recent telemetry events."""
        events = self._events[-last_n:] if self._events else []
        result = {
            'total': len(self._events),
            'showing': len(events),
            'events': events,
        }
        if as_json:
            return json.dumps(result, indent=2, default=str)
        return result

    # ── UC-E20: Use Cases ────────────────────────────────────────────

    def use_cases(self, as_json: bool = False) -> Dict[str, Any]:
        """List all 20 Exploration Shell use cases."""
        cases = {
            'UC-E01': {'command': 'status',         'description': 'Pipeline health: Fernandez + UIF + telemetry + ecosystem'},
            'UC-E02': {'command': 'uif_bridge',     'description': 'UIF Bridge f=√(ωₐ×ωᵦ) between two basins (L1-L12, cross-sphere)'},
            'UC-E03': {'command': 'coupling',       'description': 'K5×K11×cos(ωₐt)×cos(ωᵦt) time-varying coupling'},
            'UC-E04': {'command': 'frequency',      'description': 'Agent angular frequency ω from basin (0-133, L1-L12)'},
            'UC-E05': {'command': 'freq_table',     'description': 'All 19 agent frequencies + L10-L12 external layers'},
            'UC-E06': {'command': 'orbit',          'description': 'F(z,C) iteration → orbit classification'},
            'UC-E07': {'command': 'deploy',         'description': 'Deploy agents with ω assignment + scaling'},
            'UC-E08': {'command': 'scale',          'description': 'Dynamic scaling: spawn/retire/hold'},
            'UC-E09': {'command': 'telemetry',      'description': 'Record/interpret event via TelemetryShell'},
            'UC-E10': {'command': 'anomalies',      'description': 'Detected anomalies from telemetry'},
            'UC-E11': {'command': 'predict',        'description': 'Predict agent state trajectory'},
            'UC-E12': {'command': 'ecosystem',      'description': 'Token connectivity from EcosystemShell'},
            'UC-E13': {'command': 'bridges',        'description': 'Inter-category bridge statistics'},
            'UC-E14': {'command': 'coverage',       'description': 'Basin coverage per layer + missing basins'},
            'UC-E15': {'command': 'comms',          'description': 'I-Logic × UIF Bridge communication (cross-sphere L10-L12)'},
            'UC-E16': {'command': 'fernandez',      'description': 'Fernandez Space C=0 constants + L10-L12 cross-sphere model'},
            'UC-E17': {'command': 'route',          'description': 'Semantic routing via TelemetryShell'},
            'UC-E18': {'command': 'coupling_matrix','description': '12×12 layer coupling matrix (L1-L12)'},
            'UC-E19': {'command': 'event_log',      'description': 'Recent telemetry event log'},
            'UC-E20': {'command': 'use_cases',      'description': 'This list'},
        }
        result = {'use_cases': cases, 'total': len(cases)}
        if as_json:
            return json.dumps(result, indent=2)
        return result

    # ── Internal helpers ─────────────────────────────────────────────

    def _log_event(self, event_type: str, payload: Dict):
        """Append an event to the internal log."""
        self._events.append({
            'type': event_type,
            'timestamp_ns': time.time_ns(),
            'payload': payload,
        })

    # ── run() dispatch ───────────────────────────────────────────────

    def run(self, command: str, *args, as_json: bool = False) -> Any:
        """Run an Exploration shell command."""
        commands = {
            'status':          lambda: self.status(as_json),
            'uif_bridge':      lambda: self.uif_bridge(
                                   int(args[0]) if len(args) > 0 else 0,
                                   int(args[1]) if len(args) > 1 else 65,
                                   as_json),
            'coupling':        lambda: self.coupling(
                                   int(args[0]) if len(args) > 0 else 0,
                                   int(args[1]) if len(args) > 1 else 65,
                                   float(args[2]) if len(args) > 2 else 0.0,
                                   as_json),
            'frequency':       lambda: self.frequency(
                                   int(args[0]) if args else 0, as_json),
            'freq_table':      lambda: self.freq_table(as_json),
            'orbit':           lambda: self.orbit(
                                   int(args[0]) if args else 0,
                                   int(args[1]) if len(args) > 1 else 16,
                                   as_json),
            'deploy':          lambda: self.deploy(
                                   int(args[0]) if args else 19, as_json),
            'scale':           lambda: self.scale(
                                   None,
                                   int(args[0]) if args else 0,
                                   float(args[1]) if len(args) > 1 else 1.0,
                                   as_json),
            'telemetry':       lambda: self.telemetry(
                                   args[0] if args else "", as_json),
            'anomalies':       lambda: self.anomalies(as_json),
            'predict':         lambda: self.predict(
                                   args[0] if args else "2A9E0100",
                                   int(args[1]) if len(args) > 1 else 5,
                                   as_json),
            'ecosystem':       lambda: self.ecosystem(as_json),
            'bridges':         lambda: self.bridges(as_json),
            'coverage':        lambda: self.coverage(None, as_json),
            'comms':           lambda: self.comms(
                                   int(args[0]) if len(args) > 0 else 0,
                                   int(args[1]) if len(args) > 1 else 65,
                                   args[2] if len(args) > 2 else "DIRECT",
                                   args[3] if len(args) > 3 else "DIRECT",
                                   as_json),
            'fernandez':       lambda: self.fernandez(as_json),
            'route':           lambda: self.route(
                                   args[0] if args else "find documentation",
                                   as_json),
            'coupling_matrix': lambda: self.coupling_matrix(as_json),
            'event_log':       lambda: self.event_log(
                                   int(args[0]) if args else 20, as_json),
            'use_cases':       lambda: self.use_cases(as_json),
        }

        if command in commands:
            return commands[command]()
        return {
            'error': f'Unknown command: {command}',
            'available': sorted(commands.keys()),
        }


# ═══════════════════════════════════════════════════════════════════════
# Module-level convenience functions
# ═══════════════════════════════════════════════════════════════════════

__all__ = [
    'ExplorationShell',
    'agent_frequency',
    'uif_bridge_freq',
    'k5_k11_coupling',
    'fernandez_step',
    'classify_orbit',
    'compute_C_from_basin',
    'recommend_scaling',
    'resolve_layer',
    'ScalingDecision',
    'HEX_ID',
    'C_ABSTRACT',
    'SQRT3',
    'ROTATION',
    'EXTERNAL_LAYERS',
    'EXTERNAL_FREQS',
    'EXTERNAL_BASINS',
    'ALL_LAYER_NAMES',
]
