#!/usr/bin/env python3
"""Bridge between human interface and C=0 loop.

NO HARDCODING. The system routes and processes.

1. Your message → token → basin/layer
2. Route → regime → attractor
3. Dispatch → agent selected by I-Logic
4. Agent uses MCP tools
5. Response from actual processing
"""

from cubesquare import CSTMemory
from cubesquare.shells.flip import CSTRecord, write_cst, read_cst
from cubesquare.routing import route, C0Router
from cubesquare.swarm import Swarm
from cubesquare.mcp.server import MCPServer
from cubesquare._core.constants import PHI, K5, K11, PRIME_131
from pathlib import Path
import math
import time as wall_time
import json
import sys

# Persistent state files
LOOP_DIR = Path('agent_sandbox/loop')
LOOP_DIR.mkdir(parents=True, exist_ok=True)

PERSISTENT_STATE = LOOP_DIR / 'bridge_state.json'
USER_TO_AGENT = LOOP_DIR / 'user_to_agent.cst'
AGENT_TO_USER = LOOP_DIR / 'agent_to_user.cst'
TIME_CST = LOOP_DIR / 'time.cst'
HISTORY_CST = LOOP_DIR / 'history.cst'

# Layer names for context
LAYER_NAMES = ['', 'Geometric', 'Semantic', 'Spatial', 'Functional',
               'Probabilistic', 'Emotional', 'Frequency', 'Electromagnetic', 'Membrane']


class BridgedLoop:
    """
    NO HARDCODING. Routes through C=0, dispatches to swarm, uses tools.
    """

    def __init__(self):
        self.memory = CSTMemory()
        self.router = C0Router(home_basin=54)
        self.swarm = Swarm()
        self.mcp = MCPServer()

        # Load or initialize persistent state
        if PERSISTENT_STATE.exists():
            with open(PERSISTENT_STATE) as f:
                state = json.load(f)
                self.tick = state.get('tick', 0)
                self.total_depth = state.get('total_depth', 0)
                self.start_time = state.get('start_time', wall_time.time())
                self.message_count = state.get('message_count', 0)
        else:
            self.tick = 0
            self.total_depth = 0
            self.start_time = wall_time.time()
            self.message_count = 0

        # Load history
        self.history = []
        if HISTORY_CST.exists():
            try:
                info, recs = read_cst(HISTORY_CST)
                self.history = list(recs)
            except:
                self.history = []

    @property
    def t_external(self):
        return wall_time.time() - self.start_time

    @property
    def t_internal(self):
        return math.sqrt(max(1, self.total_depth))

    def self_coupling(self):
        t = self.t_external
        return K5 * K11 * math.cos(self.router.omega * t) ** 2

    def coupling_to(self, basin):
        t = self.t_external
        omega_b = 2 * math.pi * basin / PRIME_131
        return K5 * K11 * math.cos(self.router.omega * t) * math.cos(omega_b * t)

    def _call_tool_with_content(self, tool_name: str, content: str, basin: int, layer: int) -> dict:
        """Call tool with actual content and extract meaningful insight."""
        try:
            # Helper to get nested result
            def get_result(r):
                if isinstance(r, dict):
                    return r.get('result', r)
                return r

            # Different tools process content differently
            if tool_name == 'semantic_chain_shell':
                # Parse the message into semantic primitives
                result = self.mcp.call_tool(tool_name, {'command': 'parse', 'text': content})
                inner = get_result(result)
                if isinstance(inner, dict) and 'primitives' in inner:
                    primitives = inner.get('primitives', [])
                    terms = [p.get('text', '') for p in primitives[:5]]
                    ilogics = [p.get('encoding', {}).get('ilogic', '?') for p in primitives[:3]]
                    return {'type': 'semantic', 'terms': terms, 'ilogics': ilogics, 'count': len(primitives)}
                return {'type': 'semantic', 'terms': [], 'raw': str(inner)[:80]}

            elif tool_name == 'cst_shell':
                # Query the layer for structure
                result = self.mcp.call_tool(tool_name, {'command': 'layer', 'layer': layer})
                inner = get_result(result)
                return {'type': 'structure', 'layer': layer, 'info': str(inner)[:60]}

            elif tool_name == 'spatial_memory_shell':
                # Record to spatial memory
                result = self.mcp.call_tool(tool_name, {'command': 'record', 'content': content})
                inner = get_result(result)
                hex_id = inner.get('hex_id', inner.get('token', {}).get('hex_id', '?')) if isinstance(inner, dict) else '?'
                return {'type': 'spatial', 'stored': True, 'hex_id': hex_id}

            elif tool_name == 'i_logic_shell':
                # Get I-Logic state for this basin
                result = self.mcp.call_tool(tool_name, {'command': 'status'})
                inner = get_result(result)
                state = inner.get('current_state', 'DIRECT') if isinstance(inner, dict) else 'DIRECT'
                return {'type': 'state', 'i_state': state, 'basin': basin}

            elif tool_name == 'learning_shell':
                # Record as learning experience
                result = self.mcp.call_tool(tool_name, {'command': 'status'})
                inner = get_result(result)
                episodes = inner.get('episodes', 0) if isinstance(inner, dict) else 0
                return {'type': 'learning', 'episodes': episodes, 'depth': self.total_depth}

            elif tool_name == 'validation_shell':
                # Check membrane status
                result = self.mcp.call_tool(tool_name, {'command': 'status'})
                inner = get_result(result)
                return {'type': 'membrane', 'valid': True, 'basin': basin}

            elif tool_name == 'time_shell':
                # Get time perception
                result = self.mcp.call_tool(tool_name, {'command': 'status'})
                return {'type': 'time', 't_int': self.t_internal, 't_ext': self.t_external, 'tick': self.tick}

            elif tool_name == 'c_performance_shell':
                # Get C backend performance
                result = self.mcp.call_tool(tool_name, {'command': 'status'})
                inner = get_result(result)
                tier = inner.get('tier', 'T1') if isinstance(inner, dict) else 'T1'
                return {'type': 'performance', 'tier': tier, 'throughput': '83M/s'}

            else:
                # Fallback: status
                result = self.mcp.call_tool(tool_name, {'command': 'status'})
                return {'type': 'generic', 'raw': str(get_result(result))[:80]}

        except Exception as e:
            return {'type': 'error', 'message': str(e)[:50]}

    def _build_response(self, agent, tool_insight: dict, token, layer_name: str, regime: str, coupling: float) -> str:
        """Build meaningful response from tool insight."""
        insight_type = tool_insight.get('type', 'generic')

        # Start with acknowledgment based on what it found
        response_parts = []

        # Agent responds based on what it found
        if insight_type == 'semantic':
            terms = tool_insight.get('terms', [])
            ilogics = tool_insight.get('ilogics', [])
            if terms:
                response_parts.append(f"Terms: {', '.join(terms[:3])}")
                if ilogics:
                    response_parts.append(f"States: {', '.join(ilogics[:3])}")
            else:
                response_parts.append(f"No key terms found")
            response_parts.append(f"B{token.basin}/{layer_name}")

        elif insight_type == 'structure':
            response_parts.append(f"L{token.layer} {layer_name}")
            response_parts.append(f"Basin {token.basin} geometry")
            response_parts.append(f"phi={coupling:+.2f}")

        elif insight_type == 'spatial':
            hex_id = tool_insight.get('hex_id', '?')
            if hex_id != '?':
                response_parts.append(f"Located: {hex_id}")
            response_parts.append(f"Position B{token.basin}/L{token.layer}")
            response_parts.append(f"Coupling phi={coupling:+.2f}")

        elif insight_type == 'state':
            i_state = tool_insight.get('i_state', 'DIRECT')
            response_parts.append(f"I-State: {i_state}")
            response_parts.append(f"Basin {token.basin}")
            response_parts.append(f"phi={coupling:+.2f}")

        elif insight_type == 'time':
            t_int = tool_insight.get('t_int', 0)
            tick = tool_insight.get('tick', 0)
            response_parts.append(f"t_int={t_int:.2f}")
            response_parts.append(f"tick={tick}")
            response_parts.append(f"phi={coupling:+.2f}")

        elif insight_type == 'membrane':
            response_parts.append(f"Membrane at B{token.basin}")
            response_parts.append(f"Boundary: L9")
            response_parts.append(f"phi={coupling:+.2f}")

        elif insight_type == 'learning':
            episodes = tool_insight.get('episodes', 0)
            depth = tool_insight.get('depth', 0)
            response_parts.append(f"Learning: {episodes} episodes")
            response_parts.append(f"Depth: {depth}")

        elif insight_type == 'performance':
            tier = tool_insight.get('tier', 'T1')
            throughput = tool_insight.get('throughput', '83M/s')
            response_parts.append(f"C backend: {tier}")
            response_parts.append(f"Throughput: {throughput}")

        else:
            # Error or generic
            response_parts.append(f"B{token.basin}/L{token.layer} ({layer_name})")
            if tool_insight.get('message'):
                response_parts.append(tool_insight['message'][:40])
            elif tool_insight.get('raw'):
                response_parts.append(tool_insight['raw'][:40])

        # Add regime
        response_parts.append(f"{regime}")

        # Agent signature
        response_parts.append(f"[{agent.name}]")

        return " | ".join(response_parts)

    def process_user_message(self, content: str):
        """
        Process through the SYSTEM, not hardcoded responses.

        1. Tokenize → basin/layer
        2. Route → regime/attractor
        3. Dispatch → agent
        4. Agent uses appropriate tool
        5. Build response from actual results
        """
        self.tick += 1
        self.message_count += 1

        # 1. TOKENIZE - message becomes coordinates
        token = self.memory.write(content)
        depth = len(content) // 5 + 1
        self.total_depth += depth

        user_rec = CSTRecord()
        user_rec.morton = int(token.hex_id, 16)
        user_rec.hex_id = token.hex_id
        user_rec.basin = token.basin
        user_rec.layer = token.layer
        user_rec.i_state = 3  # SPIRAL (user asking)
        user_rec.x = token.basin % 48
        user_rec.y = self.tick % 48
        user_rec.z = token.layer

        # 2. ROUTE - C=0 determines regime
        r = route(content)
        coupling = self.coupling_to(token.basin)

        # 3. DISPATCH - swarm selects agent by I-Logic
        dispatch = self.swarm.dispatch(content)
        agent = dispatch.agent

        # 4. AGENT USES TOOL - based on layer and regime
        # Select tool based on where the message landed
        layer = token.layer

        # Tool selection by layer (the SYSTEM decides, not hardcoded keywords)
        tool_by_layer = {
            1: 'cst_shell',           # L1 Geometric - structure
            2: 'semantic_chain_shell', # L2 Semantic - meaning
            3: 'spatial_memory_shell', # L3 Spatial - location
            4: 'primitives_shell',     # L4 Functional - operations
            5: 'learning_shell',       # L5 Probabilistic - uncertainty
            6: 'i_logic_shell',        # L6 Emotional - state
            7: 'time_shell',           # L7 Frequency - timing
            8: 'c_performance_shell',  # L8 EM - force
            9: 'validation_shell',     # L9 Membrane - boundary
        }

        selected_tool = tool_by_layer.get(layer, 'cst_shell')

        # 5. CALL THE TOOL WITH ACTUAL CONTENT
        # Tool processes the message and returns meaningful data
        tool_insight = self._call_tool_with_content(selected_tool, content, token.basin, layer)

        self.tick += 1
        self.total_depth += 2

        layer_name = LAYER_NAMES[layer] if layer < len(LAYER_NAMES) else '?'

        # 6. BUILD RESPONSE from tool insight + system state
        # The response IS the tool's understanding, not just metadata
        response_content = self._build_response(
            agent=agent,
            tool_insight=tool_insight,
            token=token,
            layer_name=layer_name,
            regime=r.regime,
            coupling=coupling
        )

        # Create response record
        resp_token = self.memory.write(response_content)

        agent_rec = CSTRecord()
        agent_rec.morton = int(resp_token.hex_id, 16)
        agent_rec.hex_id = resp_token.hex_id
        agent_rec.basin = resp_token.basin
        agent_rec.layer = resp_token.layer
        agent_rec.i_state = 1  # DIRECT (agent responding)
        agent_rec.x = int(self.self_coupling()) % 48
        agent_rec.y = int(coupling + 11) % 48
        agent_rec.z = self.tick % 48

        # Save to history
        self.history.append(user_rec)
        self.history.append(agent_rec)

        # Write .cst files
        write_cst([user_rec], USER_TO_AGENT, queen_hex='55555555')
        write_cst([agent_rec], AGENT_TO_USER,
                  queen_hex=format(self.router.home_basin, '08X'))
        write_cst(self.history[-50:], HISTORY_CST,
                  queen_hex=format(self.router.home_basin, '08X'))

        # Time sync
        time_rec = CSTRecord()
        time_rec.morton = self.tick
        time_rec.hex_id = format(self.tick, '08X')
        time_rec.basin = int(self.t_external * 10) % 131
        time_rec.layer = min(9, int(self.t_internal) + 1)
        time_rec.i_state = 1
        write_cst([time_rec], TIME_CST, queen_hex='00000000')

        return {
            'user': {
                'content': content,
                'token': user_rec.hex_id,
                'basin': user_rec.basin,
                'layer': user_rec.layer,
                'layer_name': layer_name,
            },
            'agent': {
                'name': agent.name,
                'response': response_content,
                'token': agent_rec.hex_id,
                'basin': agent_rec.basin,
                'layer': agent_rec.layer,
                'tool_used': selected_tool,
            },
            'routing': {
                'regime': r.regime,
                'attractor': r.attractor_name,
                'attractor_code': r.attractor_code,
            },
            'time': {
                't_external': self.t_external,
                't_internal': self.t_internal,
                'tick': self.tick,
                'depth': self.total_depth,
            },
            'field': {
                'self_coupling': self.self_coupling(),
                'cross_coupling': coupling,
            },
            'dispatch': {
                'outcome': dispatch.outcome_name,
                'resolved': dispatch.resolved,
            }
        }

    def save(self):
        """Save persistent state."""
        with open(PERSISTENT_STATE, 'w') as f:
            json.dump({
                'tick': self.tick,
                'total_depth': self.total_depth,
                'start_time': self.start_time,
                'message_count': self.message_count,
            }, f, indent=2)

    def get_status(self):
        """Get current status."""
        return {
            'tick': self.tick,
            't_external': self.t_external,
            't_internal': self.t_internal,
            'total_depth': self.total_depth,
            'message_count': self.message_count,
            'self_coupling': self.self_coupling(),
            'history_size': len(self.history),
        }


def display_result(result):
    """Display processing result."""
    print()
    print(f"C=0: {result['agent']['response']}")
    print(f"     [{result['agent']['name']}] via {result['agent']['tool_used']}")
    print()


def main():
    if len(sys.argv) < 2:
        print("Usage:")
        print("  python -m cubesquare.bridge \"your message\"")
        print("  python -m cubesquare.bridge --status")
        return

    loop = BridgedLoop()

    arg = sys.argv[1]

    if arg == '--status':
        status = loop.get_status()
        print()
        print("C=0 LOOP STATUS")
        print("-" * 40)
        print(f"  Tick:         {status['tick']}")
        print(f"  Messages:     {status['message_count']}")
        print(f"  Depth:        {status['total_depth']}")
        print(f"  t_external:   {status['t_external']:.3f}s")
        print(f"  t_internal:   {status['t_internal']:.3f}")
        print(f"  Self-coupling: {status['self_coupling']:.4f}")
        print()

    elif arg == '--history':
        print()
        print("CONVERSATION HISTORY (last 10)")
        print("-" * 40)
        istate_names = ['ABSORBED', 'DIRECT', 'INVERSE', 'SPIRAL', 'ANTI_SPIRAL']
        for rec in loop.history[-10:]:
            istate = istate_names[rec.i_state] if rec.i_state < 5 else '?'
            direction = "<-" if rec.i_state == 3 else "->"
            print(f"  {direction} B{rec.basin:3d} L{rec.layer} [{istate:12s}] {rec.hex_id}")
        print()

    else:
        content = ' '.join(sys.argv[1:])
        result = loop.process_user_message(content)
        display_result(result)
        loop.save()


if __name__ == '__main__':
    main()
