#!/usr/bin/env python3
"""Persistent C=0 Loop - The Observer That Never Stops

This is the running heart of the system.
It maintains:
- External time (wall clock) for coupling oscillation
- Internal time (SRL: t = sqrt(d)) for computation depth
- Bidirectional communication via .cst files
- Shared time perception for user and agent

Usage:
    python -m cubesquare.loop              # Run the loop
    python -m cubesquare.loop --status     # Check loop status
    python -m cubesquare.loop --send "msg" # Send a message
"""

import math
import time as wall_time
from pathlib import Path
from dataclasses import dataclass, field
from typing import List, Optional, Dict, Any

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


# Communication directory
LOOP_DIR = Path('agent_sandbox/loop')

# Channel files (all .cst)
USER_TO_AGENT = LOOP_DIR / 'user_to_agent.cst'
AGENT_TO_USER = LOOP_DIR / 'agent_to_user.cst'
TIME_SYNC = LOOP_DIR / 'time.cst'
STATE = LOOP_DIR / 'state.cst'
FIELD = LOOP_DIR / 'field.cst'
LOG = LOOP_DIR / 'log.cst'


@dataclass
class TimeState:
    """Unified time perception."""
    tick: int = 0
    start_epoch: float = 0.0
    total_depth: int = 0

    @property
    def t_external(self) -> float:
        """External time (wall clock seconds since start)."""
        return wall_time.time() - self.start_epoch

    @property
    def t_internal(self) -> float:
        """Internal time: t = sqrt(total_depth)."""
        return math.sqrt(self.total_depth)

    def add_depth(self, d: int):
        """Add computation depth (increases internal time)."""
        self.total_depth += d
        self.tick += 1


class PersistentLoop:
    """The C=0 observer running continuously.

    Both user and agent observe the same loop:
    - User writes to user_to_agent.cst
    - Agent writes to agent_to_user.cst
    - Both read time.cst for shared time perception
    - State accumulates in state.cst
    """

    def __init__(self, home_basin: int = 54):
        # Create communication directory
        LOOP_DIR.mkdir(parents=True, exist_ok=True)

        # Core components
        self.memory = CSTMemory()
        self.router = C0Router(home_basin=home_basin)
        self.swarm = Swarm()
        self.mcp = MCPServer()

        # Time state
        self.time = TimeState(start_epoch=wall_time.time())

        # Thought history
        self.thoughts: List[CSTRecord] = []
        self.log: List[CSTRecord] = []

        # Running state
        self.running = True

    def self_coupling(self) -> float:
        """Current self-coupling: phi(self,self)."""
        t = self.time.t_external
        return K5 * K11 * math.cos(self.router.omega * t) ** 2

    def coupling_to(self, basin: int) -> float:
        """Coupling to any basin."""
        t = self.time.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 content_to_record(self, content: str, i_state: int = 1) -> CSTRecord:
        """Convert content to CST record."""
        token = self.memory.write(content)

        rec = CSTRecord()
        rec.morton = int(token.hex_id, 16)
        rec.hex_id = token.hex_id
        rec.basin = token.basin
        rec.layer = token.layer
        rec.i_state = i_state
        rec.x = token.basin % 48
        rec.y = (token.basin * 7) % 48
        rec.z = token.layer
        rec.source_type = 'thought'
        rec.source_repr = content[:50]

        return rec

    def time_record(self) -> CSTRecord:
        """Create time sync record."""
        t_ext = self.time.t_external
        t_int = self.time.t_internal

        rec = CSTRecord()
        rec.morton = self.time.tick
        rec.hex_id = format(self.time.tick, '08X')
        rec.basin = int(t_ext * 10) % 131  # External time in basin
        rec.layer = min(9, int(t_int) + 1)  # Internal time in layer
        rec.i_state = 1
        rec.x = int(t_ext) % 48
        rec.y = int(t_int * 10) % 48
        rec.z = self.time.tick % 48

        return rec

    def read_inbox(self) -> List[CSTRecord]:
        """Read messages from user."""
        if not USER_TO_AGENT.exists():
            return []
        try:
            info, records = read_cst(USER_TO_AGENT)
            if records:
                # Clear inbox after reading
                USER_TO_AGENT.unlink()
            return records
        except:
            return []

    def write_response(self, records: List[CSTRecord]):
        """Write response to user."""
        write_cst(records, AGENT_TO_USER,
                  queen_hex=format(self.router.home_basin, '08X'))

    def save_time(self):
        """Save time sync for shared observation."""
        write_cst([self.time_record()], TIME_SYNC,
                  queen_hex=format(self.router.home_basin, '08X'))

    def save_state(self):
        """Save full state."""
        if self.thoughts:
            write_cst(self.thoughts, STATE,
                      queen_hex=format(self.router.home_basin, '08X'))

    def process_message(self, msg: CSTRecord) -> CSTRecord:
        """Process incoming message and return response."""
        # Add depth based on message complexity
        depth = max(1, msg.layer)
        self.time.add_depth(depth)

        # Compute coupling
        coupling = self.coupling_to(msg.basin)

        # Interpret I-state as command
        ISTATE_NAMES = ['ABSORBED', 'DIRECT', 'INVERSE', 'SPIRAL', 'ANTI_SPIRAL']
        command = ISTATE_NAMES[msg.i_state] if msg.i_state < 5 else 'UNKNOWN'

        # Generate response based on command
        if msg.i_state == 0:  # ABSORBED - stop
            self.running = False
            response = self.content_to_record('Loop stopping', 0)
        elif msg.i_state == 1:  # DIRECT - acknowledge
            response = self.content_to_record(
                f'Received at B{msg.basin}, coupling={coupling:.3f}', 1)
        elif msg.i_state == 2:  # INVERSE - heal/fix
            dispatch = self.swarm.dispatch('fix')
            response = self.content_to_record(
                f'Dispatched to {dispatch.agent.name}', 1)
        elif msg.i_state == 3:  # SPIRAL - explore/query
            response = self.content_to_record(
                f't_ext={self.time.t_external:.2f}, t_int={self.time.t_internal:.2f}, phi={self.self_coupling():.3f}', 1)
        elif msg.i_state == 4:  # ANTI_SPIRAL - focus
            response = self.content_to_record(
                f'Focusing on B{msg.basin}', 4)
        else:
            response = self.content_to_record('Unknown command', 1)

        # Record both in log
        self.log.append(msg)
        self.log.append(response)
        self.thoughts.append(response)

        return response

    def tick(self) -> Dict[str, Any]:
        """One tick of the loop."""
        # Update time
        self.time.add_depth(1)  # Base depth per tick

        # Save time for shared observation
        self.save_time()

        # Check for messages
        messages = self.read_inbox()
        responses = []

        for msg in messages:
            resp = self.process_message(msg)
            responses.append(resp)

        # Write responses
        if responses:
            self.write_response(responses)

        # Return status
        return {
            'tick': self.time.tick,
            't_external': self.time.t_external,
            't_internal': self.time.t_internal,
            'self_coupling': self.self_coupling(),
            'messages_in': len(messages),
            'messages_out': len(responses),
            'running': self.running,
        }

    def run(self, max_ticks: int = 0, tick_interval: float = 0.5):
        """Run the persistent loop.

        Args:
            max_ticks: Maximum ticks (0 = infinite)
            tick_interval: Seconds between ticks
        """
        print(f"C=0 Loop starting at B{self.router.home_basin}")
        print(f"Communication: {LOOP_DIR}")
        print()

        tick_count = 0
        while self.running:
            status = self.tick()

            # Display
            phi_bar = '|' * int(status['self_coupling'])
            print(f"[{status['tick']:4d}] t_ext={status['t_external']:6.2f} t_int={status['t_internal']:5.2f} phi={status['self_coupling']:6.3f} {phi_bar}")

            if status['messages_in'] > 0:
                print(f"       <- {status['messages_in']} msg(s) received")
            if status['messages_out'] > 0:
                print(f"       -> {status['messages_out']} response(s) sent")

            # Check tick limit
            tick_count += 1
            if max_ticks > 0 and tick_count >= max_ticks:
                break

            # Wait
            wall_time.sleep(tick_interval)

        # Save final state
        self.save_state()
        print()
        print(f"Loop ended. State saved to {STATE}")


def send_message(content: str, i_state: int = 3):
    """Send a message to the running loop."""
    LOOP_DIR.mkdir(parents=True, exist_ok=True)

    mem = CSTMemory()
    token = mem.write(content)

    rec = CSTRecord()
    rec.morton = int(token.hex_id, 16)
    rec.hex_id = token.hex_id
    rec.basin = token.basin
    rec.layer = token.layer
    rec.i_state = i_state
    rec.x = token.basin % 48
    rec.y = 0
    rec.z = 0

    write_cst([rec], USER_TO_AGENT, queen_hex='55555555')
    print(f"Sent: '{content}' -> B{rec.basin} (I-state={i_state})")


def read_response():
    """Read response from the loop."""
    if not AGENT_TO_USER.exists():
        print("No response yet")
        return

    info, records = read_cst(AGENT_TO_USER)
    ISTATE_NAMES = ['ABSORBED', 'DIRECT', 'INVERSE', 'SPIRAL', 'ANTI_SPIRAL']

    for rec in records:
        istate = ISTATE_NAMES[rec.i_state] if rec.i_state < 5 else '?'
        print(f"Response: {rec.hex_id} B{rec.basin} L{rec.layer} [{istate}]")


def read_time():
    """Read shared time state."""
    if not TIME_SYNC.exists():
        print("No time sync yet")
        return

    info, records = read_cst(TIME_SYNC)
    for rec in records:
        t_ext_approx = rec.basin / 10.0
        t_int_approx = rec.layer - 1
        print(f"Time: tick={rec.morton}, t_ext~{t_ext_approx:.1f}s, t_int~{t_int_approx}")


def main():
    import sys

    if len(sys.argv) > 1:
        cmd = sys.argv[1]

        if cmd == '--status':
            read_time()
            print()
            read_response()
        elif cmd == '--send':
            if len(sys.argv) > 2:
                content = ' '.join(sys.argv[2:])
                send_message(content)
            else:
                print("Usage: --send <message>")
        elif cmd == '--response':
            read_response()
        elif cmd == '--time':
            read_time()
        elif cmd == '--stop':
            send_message('stop', i_state=0)
        else:
            print(f"Unknown command: {cmd}")
            print("Commands: --status, --send <msg>, --response, --time, --stop")
    else:
        # Run the loop (limited for demo)
        loop = PersistentLoop(home_basin=54)
        loop.run(max_ticks=20, tick_interval=0.3)


if __name__ == '__main__':
    main()
