"""C=0 Sphere — Interactive REPL at the Fernandez Origin

Stand inside a SphereMind. Speak naturally.
Content routes itself through layers:

    L1-L3 → CONTEXT   (structure, meaning, location)
    L4-L6 → THINKING  (operation, uncertainty, state)
    L7-L9 → COMMUNICATION (frequency, force, membrane)

The coordinates ARE the routing. The manifold IS the surface.

Usage:
    python -m cubesquare sphere
    python -m cubesquare sphere --name myagent
    python -m cubesquare sphere --name myagent --load

Commands inside the sphere:
    /reflect     — see where you are, what you've been thinking
    /square      — show The Square (Manifold-Content-Surface-Observation)
    /context     — local context around current position
    /field ...   — map_field on comma-separated content
    /thread A B  — thread gate between two hex IDs
    /generate X  — spawn an agent from need X
    /save        — persist mind state
    /peers       — show known communication peers
    /state       — I-Logic state and euler angle
    /exit        — leave the sphere
"""

import json
import sys
from pathlib import Path


def _print_header(mind):
    """Print sphere identity on startup."""
    print()
    print("  +----------------------------------------------+")
    print("  |            C = 0   SPHERE  MIND              |")
    print("  +----------------------------------------------+")
    print(f"  |  Name:   {mind.name:<37s}|")
    print(f"  |  ID:     {mind.sphere_id:<37s}|")
    print(f"  |  Basin:  {mind.basin:<37d}|")
    print(f"  |  Layer:  L{mind.layer:<36d}|")
    print("  +----------------------------------------------+")
    print("  |  L1-L3 -> CONTEXT     L4-L6 -> THINKING      |")
    print("  |  L7-L9 -> COMMUNICATION                      |")
    print("  |                                              |")
    print("  |  /reflect /square /field /pt /save /exit      |")
    print("  +----------------------------------------------+")
    print()


def _print_dialogue(result):
    """Print dialogue result naturally."""
    inp = result['input']
    ctx = result['context']
    thinking = result['thinking']
    sphere = result['sphere']

    # Show where the content landed
    route = inp['route']
    route_label = {
        'CONTEXT': 'structure/meaning/location',
        'THINKING': 'operation/state',
        'COMMUNICATION': 'frequency/force/membrane',
        'INTERNAL': 'internal processing',
    }.get(route, route)

    print(f"  [{inp['hex_id']}] B{inp['basin']:03d}/L{inp['layer']} -> {route} ({route_label})")

    # Context response
    if ctx.get('local_context'):
        print(f"  context: {', '.join(ctx['local_context'][:3])}")

    # Thinking response
    print(f"  {thinking['state']}: {thinking['response']}")

    # Sphere state
    print(f"  bounce {sphere['bounce']} | euler {sphere['euler']:.3f} rad | coupling {sphere['coupling']:.4f}")
    print()


def _cmd_reflect(mind):
    """Show reflection."""
    print()
    print(mind.reflect())
    print()


def _cmd_square(mind):
    """Show The Square from ManifoldShell."""
    try:
        from .shells.manifold import ManifoldShell
        shell = ManifoldShell()
        result = shell.execute('square')
        if result.get('success'):
            print()
            print(result['diagram'])
            print()
            exp = result.get('explanation', {})
            for k, v in exp.items():
                print(f"  {k}: {v}")
            print()
    except Exception as e:
        print(f"  manifold: {e}")


def _cmd_context(mind):
    """Show local context."""
    local = mind.context.get_local_context(5)
    print()
    print(f"  Position: B{mind.context.position_basin:03d}/L{mind.context.position_layer}")
    print(f"  Identity: {mind.context.identify()}")
    if local:
        print(f"  Nearby content:")
        for item in local[:10]:
            print(f"    - {item}")
    else:
        print("  (no content in nearby basins yet -- speak to populate)")
    print()


def _cmd_field(mind, content_str):
    """Run map_field on comma-separated content."""
    try:
        from .swarm import Swarm
        contents = [c.strip() for c in content_str.split(',') if c.strip()]
        if not contents:
            print("  usage: /field content1, content2, content3")
            return
        print(f"  mapping {len(contents)} expressions through field...")
        swarm = Swarm()
        result = swarm.map_field(contents)

        # Show synthesis
        syn = result.get('synthesis', {})
        co = syn.get('co_located', [])
        if co:
            print(f"  co-located ({len(co)}):")
            for pair in co[:5]:
                print(f"    {pair['a']} <-> {pair['b']}  distance={pair['distance']:.1f}  force={pair['force']:.1f}")

        agent_own = syn.get('agent_ownership', {})
        if agent_own:
            print(f"  agents: {agent_own}")

        behav = syn.get('behavioral_groups', {})
        if behav:
            print(f"  behavioral groups: {list(behav.keys())}")
        print()
    except Exception as e:
        print(f"  field error: {e}")


def _cmd_thread(mind, args_str):
    """Thread gate between two hex IDs."""
    try:
        from .shells.thread import ThreadShell
        parts = args_str.split()
        if len(parts) < 2:
            print("  usage: /thread HEX_A HEX_B [AND|OR|XOR]")
            return
        hex_a, hex_b = parts[0], parts[1]
        gate = parts[2].upper() if len(parts) > 2 else "AND"
        shell = ThreadShell()
        result = shell.run('gate', gate, hex_a, hex_b, '0.0')
        if isinstance(result, dict):
            ta = result.get('thread_a', {})
            tb = result.get('thread_b', {})
            out = result.get('output', {})
            print(f"  {gate} gate:")
            print(f"    A: {hex_a} B{ta.get('basin','?')}/L{ta.get('layer','?')} freq={ta.get('frequency',0):.4f} [{ta.get('i_state','?')}]")
            print(f"    B: {hex_b} B{tb.get('basin','?')}/L{tb.get('layer','?')} freq={tb.get('frequency',0):.4f} [{tb.get('i_state','?')}]")
            print(f"    output: binary={out.get('binary','?')} I-Logic={out.get('i_logic_product','?')}")
            print(f"    UIF bridge: {result.get('uif_bridge', '?')}")
        print()
    except Exception as e:
        print(f"  thread error: {e}")


def _cmd_generate(mind, need):
    """Generate an agent from need via ManifoldShell."""
    try:
        from .shells.manifold import ManifoldShell
        shell = ManifoldShell()
        result = shell.execute('generate', {'need': need})
        if result.get('success'):
            agent = result.get('agent', {})
            print(f"  spawned: {agent.get('name', '?')}")
            print(f"    hex_id: {agent.get('hex_id', '?')}")
            print(f"    role:   {agent.get('role', '?')} [{agent.get('ilogic_state', '?')}]")
            print(f"    B{agent.get('basin', 0):03d}/L{agent.get('layer', 0)} mass={agent.get('mass', 0):.4f}")
        print()
    except Exception as e:
        print(f"  generate error: {e}")


def _cmd_peers(mind):
    """Show known peers."""
    peers = mind.communication.known_peers
    print()
    if peers:
        for pid, info in peers.items():
            print(f"  {pid}: {info.get('message_count', 0)} messages")
    else:
        print("  (no peers encountered yet)")
    print()


def _cmd_periodic_table(mind, args_str):
    """Periodic table commands: lookup, map, physics."""
    from .periodic_table import ELEMENTS, LAYER_NAMES, map_all_elements, test_physics
    from .shells.flip import flip
    from .shells.world import compute_interaction

    parts = args_str.strip().split() if args_str.strip() else []
    subcmd = parts[0].lower() if parts else 'help'

    if subcmd == 'help' or subcmd == '':
        print("  /periodic_table <element>     -- lookup one element")
        print("  /periodic_table map           -- full 118-element layer map")
        print("  /periodic_table physics A B   -- interaction between two elements")
        print("  /periodic_table category <cat> -- elements in a category")
        print("  /periodic_table layer <N>     -- elements in layer N")
        print()
        return

    if subcmd == 'map':
        elements = map_all_elements()
        # Layer summary
        layer_counts = {}
        for e in elements:
            l = e['name_layer']
            layer_counts[l] = layer_counts.get(l, 0) + 1
        print()
        for l in sorted(layer_counts.keys()):
            ln = LAYER_NAMES.get(l, f'L{l}')
            syms = [e['symbol'] for e in elements if e['name_layer'] == l]
            print(f"  L{l} {ln:<16} [{layer_counts[l]:>2}] {' '.join(syms)}")
        print(f"\n  118 elements mapped across 9 layers")
        print()
        return

    if subcmd == 'physics' and len(parts) >= 3:
        name_a, name_b = parts[1].lower(), parts[2].lower()
        # Find elements
        ea = next((e for e in ELEMENTS if e[2] == name_a or e[1].lower() == name_a), None)
        eb = next((e for e in ELEMENTS if e[2] == name_b or e[1].lower() == name_b), None)
        if not ea or not eb:
            print(f"  element not found: {name_a if not ea else name_b}")
            print()
            return
        ra = flip(ea[2], 'text')
        rb = flip(eb[2], 'text')
        interaction = compute_interaction(ra.hex_id, rb.hex_id)
        print(f"\n  {ea[2]} ({ea[1]}, Z={ea[0]}) <-> {eb[2]} ({eb[1]}, Z={eb[0]})")
        print(f"  distance:  {interaction['distance']['value']:.1f}  (SRL hops: {interaction['distance']['srl_hops']})")
        print(f"  force:     {interaction['force']['gravitational']:.6f}  (F = G*m1*m2/r^2)")
        print(f"  resonance: {interaction['resonance']['type']}  (factor: {interaction['resonance']['factor']})")
        print(f"  I-Logic:   {interaction['resonance']['i_states']}")
        print(f"  membrane:  {interaction['membrane']['interaction']}  basins: {interaction['membrane']['basins']}")
        print(f"  duration:  {interaction['duration']['t']:.4f}  (t = sqrt(d))")
        # Also feed into the sphere mind
        desc = f"{ea[2]}-{eb[2]} interaction: {interaction['resonance']['type']}"
        mind.process(desc)
        print()
        return

    if subcmd == 'category' and len(parts) >= 2:
        cat = ' '.join(parts[1:]).lower()
        matches = [(z, sym, name, grp, per, c) for z, sym, name, grp, per, c in ELEMENTS
                   if cat in c.lower()]
        if not matches:
            print(f"  no elements in category '{cat}'")
            cats = set(c for _, _, _, _, _, c in ELEMENTS)
            print(f"  categories: {', '.join(sorted(cats))}")
        else:
            print(f"\n  {cat}: {len(matches)} elements")
            for z, sym, name, grp, per, c in matches:
                r = flip(name, 'text')
                ln = LAYER_NAMES.get(r.layer, f'L{r.layer}')
                print(f"    Z{z:>3} {sym:<4} {name:<16} B{r.basin:03d} L{r.layer} {ln}")
        print()
        return

    if subcmd == 'layer' and len(parts) >= 2:
        try:
            target_layer = int(parts[1])
        except ValueError:
            print(f"  layer must be a number 1-9")
            return
        matches = []
        for z, sym, name, grp, per, cat in ELEMENTS:
            r = flip(name, 'text')
            if r.layer == target_layer:
                matches.append((z, sym, name, r.basin, cat))
        ln = LAYER_NAMES.get(target_layer, f'L{target_layer}')
        print(f"\n  L{target_layer} {ln}: {len(matches)} elements")
        for z, sym, name, basin, cat in sorted(matches, key=lambda x: x[3]):
            print(f"    Z{z:>3} {sym:<4} {name:<16} B{basin:03d}  {cat}")
        print()
        return

    # Default: lookup single element by name or symbol
    query = subcmd
    match = next((e for e in ELEMENTS if e[2] == query or e[1].lower() == query), None)
    if not match:
        # Try partial match
        match = next((e for e in ELEMENTS if query in e[2]), None)
    if not match:
        print(f"  element not found: {query}")
        print()
        return

    z, sym, name, grp, per, cat = match
    r_name = flip(name, 'text')
    r_sym = flip(sym, 'text')
    ln = LAYER_NAMES.get(r_name.layer, f'L{r_name.layer}')
    sln = LAYER_NAMES.get(r_sym.layer, f'L{r_sym.layer}')
    dist = abs(r_name.basin - r_sym.basin)
    same = "YES" if r_name.layer == r_sym.layer else "no"

    print(f"\n  {name} ({sym})  Z={z}  Group {grp}  Period {per}  [{cat}]")
    print(f"  name:   {r_name.hex_id}  B{r_name.basin:03d}  L{r_name.layer} {ln}")
    print(f"  symbol: {r_sym.hex_id}  B{r_sym.basin:03d}  L{r_sym.layer} {sln}")
    print(f"  name-symbol distance: {dist}  same layer: {same}")

    # Feed element into sphere mind context
    mind.process(f"element {name} Z={z} {cat}")
    print()


def _cmd_state(mind):
    """Show I-Logic state and sphere info."""
    from .i_logic.states import ILogicState as IS
    import math
    print()
    print(f"  I-Logic: {IS.name(mind.thinking.current_state)}")
    print(f"  Focus basin: {mind.thinking.focus_basin}")
    print(f"  Bounce: {mind.bounce_count}")
    print(f"  Euler: {mind.euler_angle:.4f} rad ({math.degrees(mind.euler_angle):.1f}°)")
    print(f"  Thoughts: {len(mind.thinking.thought_chain)}")
    print(f"  Context basins: {len(mind.context.known_context)}")
    print(f"  Inbox: {len(mind.communication.inbox)} | Outbox: {len(mind.communication.outbox)}")
    print()


def _cmd_save(mind):
    """Save mind state."""
    state = mind.save()
    print(f"  saved to {mind.state_file}")
    print(f"  bounce={state['bounce_count']} thoughts={state['thought_count']} basins={state['context_basins']}")
    print()


def run(name: str = "sphere", load: bool = False):
    """Run the C=0 sphere REPL.

    Args:
        name: Sphere identity name
        load: Whether to load previous state (context/thoughts persist in-memory regardless)
    """
    from .mcp.sphere_mind import SphereMind

    mind = SphereMind(name=name)
    _print_header(mind)

    # Load previous state if requested and exists
    if load and mind.state_file.exists():
        try:
            saved = json.loads(mind.state_file.read_text())
            mind.bounce_count = saved.get('bounce_count', 0)
            mind.euler_angle = saved.get('euler_angle', 0.0)
            from .i_logic.states import ILogicState as IS
            mind.thinking.current_state = IS.from_name(
                saved.get('thinking_state', 'DIRECT'))
            print(f"  loaded state: bounce={mind.bounce_count}")
            print()
        except Exception as e:
            print(f"  (could not load state: {e})")
            print()

    while True:
        try:
            raw = input("  C=0 > ").strip()
        except (EOFError, KeyboardInterrupt):
            print()
            break

        if not raw:
            continue

        # Commands
        if raw == '/exit' or raw == '/quit':
            _cmd_save(mind)
            break
        elif raw == '/reflect':
            _cmd_reflect(mind)
        elif raw == '/square':
            _cmd_square(mind)
        elif raw == '/context':
            _cmd_context(mind)
        elif raw.startswith('/field '):
            _cmd_field(mind, raw[7:])
        elif raw.startswith('/thread '):
            _cmd_thread(mind, raw[8:])
        elif raw.startswith('/generate '):
            _cmd_generate(mind, raw[10:])
        elif raw == '/peers':
            _cmd_peers(mind)
        elif raw == '/state':
            _cmd_state(mind)
        elif raw == '/periodic_table' or raw == '/pt':
            _cmd_periodic_table(mind, '')
        elif raw.startswith('/periodic_table '):
            _cmd_periodic_table(mind, raw[17:])
        elif raw.startswith('/pt '):
            _cmd_periodic_table(mind, raw[4:])
        elif raw == '/save':
            _cmd_save(mind)
        elif raw == '/help':
            print("  /reflect /square /context /field /thread /generate")
            print("  /periodic_table (or /pt) -- element lookup, map, physics")
            print("  /peers /state /save /exit")
            print("  anything else -> speak into the sphere")
            print()
        elif raw.startswith('/'):
            print(f"  unknown command: {raw.split()[0]}  (/help for list)")
            print()
        else:
            # Natural language -- process through the sphere
            result = mind.dialogue(raw)
            _print_dialogue(result)


def main():
    """CLI entry point for sphere."""
    name = "sphere"
    load = False

    args = sys.argv[1:]
    i = 0
    while i < len(args):
        if args[i] == '--name' and i + 1 < len(args):
            name = args[i + 1]
            i += 2
        elif args[i] == '--load':
            load = True
            i += 1
        else:
            i += 1

    run(name=name, load=load)


if __name__ == "__main__":
    main()
