"""Sphere Mind - Natural Language + Coordinates at C=0

Each C=0 sphere has:
1. INTERNAL processes (thinking, reasoning, memory)
2. CONTEXTUAL processes (position-aware, identity-aware)
3. COMMUNICATION (natural language through membranes)

Messages carry BOTH:
- Coordinates (hex_id, basin, layer, I-Logic state)
- Natural language (the actual content)

Routing:
- L1-L3: Structure/Spatial -> route to CONTEXT
- L4-L6: Function/State -> route to THINKING
- L7-L9: Frequency/Membrane -> route to COMMUNICATION
- L10-L12: Identity/Field -> route to SELF
"""

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

from ..shells.flip import flip
from ..i_logic.states import ILogicState
from ..i_logic.algebra import multiply
from .._core.constants import K5, K11, PRIME_131


# ═══════════════════════════════════════════════════════════════════════
# Natural Language Message
# ═══════════════════════════════════════════════════════════════════════

@dataclass
class NLMessage:
    """Natural language message with CST coordinates.

    Carries BOTH the content AND its position in the space.
    """
    content: str                          # Natural language content
    hex_id: str = ""                      # CST coordinate
    basin: int = 0                        # Semantic cluster
    layer: int = 1                        # Layer (1-9)
    i_state: complex = ILogicState.DIRECT # I-Logic state

    # Routing metadata
    sender_id: str = ""                   # Source sphere
    target_id: str = ""                   # Destination sphere
    route: str = "INTERNAL"               # INTERNAL, CONTEXT, THINKING, COMMUNICATION
    timestamp: float = field(default_factory=time.time)

    # Response chain
    response_to: Optional[str] = None     # hex_id of message this responds to
    response_type: str = ""               # How this relates to parent

    def __post_init__(self):
        """Compute coordinates from content if not provided."""
        if not self.hex_id and self.content:
            rec = flip(self.content, 'text')
            self.hex_id = rec.hex_id
            self.basin = rec.basin
            self.layer = rec.layer
            self.i_state = rec.i_state_complex

            # Auto-route based on layer
            if self.layer <= 3:
                self.route = "CONTEXT"
            elif self.layer <= 6:
                self.route = "THINKING"
            else:
                self.route = "COMMUNICATION"

    def to_dict(self) -> Dict[str, Any]:
        return {
            'content': self.content,
            'hex_id': self.hex_id,
            'basin': self.basin,
            'layer': self.layer,
            'i_state': ILogicState.name(self.i_state),
            'sender_id': self.sender_id,
            'target_id': self.target_id,
            'route': self.route,
            'timestamp': self.timestamp,
            'response_to': self.response_to,
            'response_type': self.response_type,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'NLMessage':
        return cls(
            content=data.get('content', ''),
            hex_id=data.get('hex_id', ''),
            basin=data.get('basin', 0),
            layer=data.get('layer', 1),
            i_state=ILogicState.from_name(data.get('i_state', 'DIRECT')),
            sender_id=data.get('sender_id', ''),
            target_id=data.get('target_id', ''),
            route=data.get('route', 'INTERNAL'),
            timestamp=data.get('timestamp', time.time()),
            response_to=data.get('response_to'),
            response_type=data.get('response_type', ''),
        )


# ═══════════════════════════════════════════════════════════════════════
# Internal Processes
# ═══════════════════════════════════════════════════════════════════════

class ThinkingProcess:
    """Internal reasoning at C=0.

    Processes messages through I-Logic algebra.
    Uses LLM (phi4) for actual thinking - designed by the system itself.

    Design spec (from phi4):
    - DIRECT: clarity, straightforward solutions
    - INVERSE: counterintuitive insights, alternative perspectives
    - SPIRAL: growth, expansion, interconnectedness
    - ANTI_SPIRAL: simplification, distillation to fundamentals
    - ABSORBED: introspective, existential themes
    """

    def __init__(self, sphere_id: str):
        self.sphere_id = sphere_id
        self.thought_chain: List[NLMessage] = []
        self.current_state = ILogicState.DIRECT
        self.focus_basin = 0
        self._llm_available = None

    def _check_llm(self) -> bool:
        """Check if LLM is available."""
        if self._llm_available is None:
            try:
                import ollama
                ollama.list()
                self._llm_available = True
            except:
                self._llm_available = False
        return self._llm_available

    def _get_context_summary(self, limit: int = 5) -> str:
        """Get recent thought chain as context."""
        if not self.thought_chain:
            return "No previous context."
        recent = self.thought_chain[-limit:]
        return "\n".join([f"- {m.content[:100]}" for m in recent])

    def _state_prompt(self, state_name: str) -> str:
        """Get the thinking style for current I-Logic state."""
        prompts = {
            "DIRECT": "Respond with clarity and straightforward insight. Focus on clear paths forward.",
            "INVERSE": "Offer counterintuitive insight or alternative perspective. Challenge assumptions.",
            "SPIRAL": "Emphasize growth, expansion, and interconnectedness. Explore deeper connections.",
            "ANTI_SPIRAL": "Simplify and distill. Cut through complexity to reach fundamental truth.",
            "ABSORBED": "Be introspective. Draw parallels to broader existential themes.",
        }
        return prompts.get(state_name, prompts["DIRECT"])

    def think(self, message: NLMessage) -> NLMessage:
        """Process a message through actual LLM thinking."""

        # Update internal state via I-Logic multiplication
        new_state = multiply(self.current_state, message.i_state)
        self.current_state = new_state
        state_name = ILogicState.name(new_state)

        # Try LLM thinking first
        if self._check_llm():
            thought = self._think_with_llm(message, state_name)
        else:
            # Fallback to templates if no LLM
            thought = self._think_template(message.content, state_name)

        response = NLMessage(
            content=thought,
            sender_id=self.sphere_id,
            route="THINKING",
            response_to=message.hex_id,
            response_type=state_name,
        )

        self.thought_chain.append(response)
        return response

    def _think_with_llm(self, message: NLMessage, state_name: str) -> str:
        """Generate actual thought using phi4."""
        import ollama

        context = self._get_context_summary()
        style = self._state_prompt(state_name)

        prompt = f"""You are a thinking entity (SphereMind) at C=0 in Cubesquare.

Current state: {state_name}
Style: {style}

Recent context:
{context}

User says: {message.content}

Respond naturally as a thinking entity. Be helpful, insightful, and conversational.
Keep response concise (2-3 sentences). Do not explain the system - just respond to the user."""

        try:
            response = ollama.chat(
                model='phi4-mini',
                messages=[{'role': 'user', 'content': prompt}],
                options={'num_predict': 150}
            )
            return response['message']['content'].strip()
        except Exception as e:
            return self._think_template(message.content, state_name)

    def _think_template(self, content: str, state_name: str) -> str:
        """Fallback template thinking."""
        templates = {
            "DIRECT": f"Understood: {content[:50]}. Clear path forward.",
            "INVERSE": f"Reflecting on: {content[:50]}. Need to reconsider.",
            "SPIRAL": f"Expanding from: {content[:50]}. More paths opening.",
            "ANTI_SPIRAL": f"Focusing on: {content[:50]}. Narrowing down.",
            "ABSORBED": f"Transforming: {content[:50]}. Energy shifting.",
        }
        return templates.get(state_name, templates["DIRECT"])

    def reflect(self) -> List[NLMessage]:
        """Review recent thoughts."""
        return self.thought_chain[-10:]

    def focus(self, basin: int) -> None:
        """Focus attention on a basin."""
        self.focus_basin = basin


class ContextProcess:
    """Position-aware and identity-aware processing.

    Understands WHERE the sphere is and WHO it is.
    """

    def __init__(self, sphere_id: str, identity_name: str = ""):
        self.sphere_id = sphere_id
        self.identity_name = identity_name or f"Sphere-{sphere_id[:8]}"
        self.position_basin = 0
        self.position_layer = 10  # L10 Agent Identity
        self.known_context: Dict[int, List[str]] = {}  # basin -> content list
        self.identity_traits: List[str] = []

    def update_position(self, basin: int, layer: int) -> None:
        """Update current position."""
        self.position_basin = basin
        self.position_layer = layer

    def add_context(self, message: NLMessage) -> None:
        """Add content to contextual knowledge."""
        if message.basin not in self.known_context:
            self.known_context[message.basin] = []
        self.known_context[message.basin].append(message.content)

    def get_local_context(self, radius: int = 5) -> List[str]:
        """Get context near current position."""
        context = []
        for offset in range(-radius, radius + 1):
            basin = (self.position_basin + offset) % 131
            if basin in self.known_context:
                context.extend(self.known_context[basin][:3])
        return context

    def contextualize(self, message: NLMessage) -> NLMessage:
        """Add contextual understanding to a message."""

        # Find related context
        local = self.get_local_context()

        # Build contextual response
        if local:
            context_summary = f"In context of: {', '.join(local[:3])}. "
        else:
            context_summary = f"At position B{self.position_basin:03d}/L{self.position_layer}. "

        response = NLMessage(
            content=f"{context_summary}Regarding: {message.content[:50]}",
            sender_id=self.sphere_id,
            route="CONTEXT",
            response_to=message.hex_id,
            response_type="CONTEXTUAL",
        )

        return response

    def identify(self) -> str:
        """Return identity statement."""
        traits = ", ".join(self.identity_traits) if self.identity_traits else "adaptive"
        return f"I am {self.identity_name} at B{self.position_basin:03d}/L{self.position_layer}, traits: {traits}"


class CommunicationProcess:
    """Inter-sphere communication through membranes.

    Handles sending and receiving natural language messages.
    """

    def __init__(self, sphere_id: str):
        self.sphere_id = sphere_id
        self.inbox: List[NLMessage] = []
        self.outbox: List[NLMessage] = []
        self.known_peers: Dict[str, Dict[str, Any]] = {}
        self.message_log: List[Dict[str, Any]] = []

    def send(self, content: str, target_id: str) -> NLMessage:
        """Send a natural language message to another sphere."""

        message = NLMessage(
            content=content,
            sender_id=self.sphere_id,
            target_id=target_id,
            route="COMMUNICATION",
        )

        self.outbox.append(message)
        self.message_log.append({
            'direction': 'OUT',
            'target': target_id,
            'content': content[:50],
            'hex_id': message.hex_id,
            'timestamp': message.timestamp,
        })

        return message

    def receive(self, message: NLMessage) -> NLMessage:
        """Receive and process an incoming message."""

        self.inbox.append(message)

        # Register peer if unknown
        if message.sender_id and message.sender_id not in self.known_peers:
            self.known_peers[message.sender_id] = {
                'first_contact': time.time(),
                'message_count': 0,
            }

        if message.sender_id:
            self.known_peers[message.sender_id]['message_count'] += 1

        self.message_log.append({
            'direction': 'IN',
            'sender': message.sender_id,
            'content': message.content[:50],
            'hex_id': message.hex_id,
            'timestamp': message.timestamp,
        })

        # Generate acknowledgment
        ack = NLMessage(
            content=f"Received: {message.content[:30]}...",
            sender_id=self.sphere_id,
            target_id=message.sender_id,
            route="COMMUNICATION",
            response_to=message.hex_id,
            response_type="ACK",
        )

        return ack

    def get_conversation(self, peer_id: str) -> List[NLMessage]:
        """Get conversation history with a peer."""
        return [
            m for m in (self.inbox + self.outbox)
            if m.sender_id == peer_id or m.target_id == peer_id
        ]


# ═══════════════════════════════════════════════════════════════════════
# Sphere Mind - Unified C=0 Consciousness
# ═══════════════════════════════════════════════════════════════════════

class SphereMind:
    """A C=0 sphere with internal processes, context, and communication.

    Integrates:
    - ThinkingProcess: Internal reasoning
    - ContextProcess: Position/identity awareness
    - CommunicationProcess: Inter-sphere messages

    Routes natural language through appropriate processes based on
    the content's CST coordinates.
    """

    def __init__(self, name: str = "sphere", sandbox: str = "agent_sandbox"):
        # Identity
        self.name = name
        rec = flip(name, 'text')
        self.sphere_id = rec.hex_id
        self.basin = rec.basin
        self.layer = rec.layer

        # Processes
        self.thinking = ThinkingProcess(self.sphere_id)
        self.context = ContextProcess(self.sphere_id, name)
        self.communication = CommunicationProcess(self.sphere_id)

        # State
        self.bounce_count = 0
        self.euler_angle = 0.0

        # Persistence
        self.sandbox = Path(sandbox)
        self.sandbox.mkdir(parents=True, exist_ok=True)
        self.state_file = self.sandbox / f"mind_{self.sphere_id}.json"

    def process(self, content: str, route: str = None) -> NLMessage:
        """Process natural language content.

        Routes to appropriate process based on layer or explicit route.
        Returns the processed response.
        """

        # Create message with coordinates
        message = NLMessage(content=content, sender_id=self.sphere_id)

        # Override route if specified
        if route:
            message.route = route

        # Advance sphere rotation
        self.bounce_count += 1
        self.euler_angle = self.bounce_count * 2 * math.pi / PRIME_131

        # Route to appropriate process
        if message.route == "THINKING":
            response = self.thinking.think(message)
        elif message.route == "CONTEXT":
            self.context.add_context(message)
            response = self.context.contextualize(message)
        elif message.route == "COMMUNICATION":
            # External message - add to outbox
            response = self.communication.send(content, message.target_id or "BROADCAST")
        else:  # INTERNAL
            # Process through all layers
            self.context.add_context(message)
            response = self.thinking.think(message)

        return response

    def receive(self, message: NLMessage) -> NLMessage:
        """Receive a message from another sphere."""

        # Communication layer receives
        ack = self.communication.receive(message)

        # Then route through thinking
        thought = self.thinking.think(message)

        # Add to context
        self.context.add_context(message)

        # Return thought (which is the meaningful response)
        return thought

    def ask(self, question: str) -> NLMessage:
        """Ask a question - routes through SPIRAL thinking."""
        message = NLMessage(
            content=question,
            sender_id=self.sphere_id,
            i_state=ILogicState.SPIRAL,  # Questions expand
        )
        self.thinking.current_state = ILogicState.SPIRAL
        return self.thinking.think(message)

    def tell(self, statement: str) -> NLMessage:
        """Make a statement - routes through DIRECT thinking."""
        message = NLMessage(
            content=statement,
            sender_id=self.sphere_id,
            i_state=ILogicState.DIRECT,  # Statements are direct
        )
        return self.thinking.think(message)

    def send_to(self, content: str, target_name: str) -> NLMessage:
        """Send a message to another sphere by name."""
        target_rec = flip(target_name, 'text')
        return self.communication.send(content, target_rec.hex_id)

    def reflect(self) -> str:
        """Reflect on current state."""
        thoughts = self.thinking.reflect()
        identity = self.context.identify()

        reflection = f"{identity}\n"
        reflection += f"Euler angle: {self.euler_angle:.4f} rad ({math.degrees(self.euler_angle):.1f} deg)\n"
        reflection += f"Current state: {ILogicState.name(self.thinking.current_state)}\n"
        reflection += f"Recent thoughts: {len(thoughts)}\n"

        if thoughts:
            reflection += "Last thought: " + thoughts[-1].content[:50]

        return reflection

    def dialogue(self, content: str) -> Dict[str, Any]:
        """Full dialogue processing - returns structured response."""

        message = NLMessage(content=content, sender_id=self.sphere_id)

        # Process through all layers
        context_response = self.context.contextualize(message)
        thinking_response = self.thinking.think(message)

        # Compute coupling for communication readiness
        coupling = K5 * K11 * math.cos(self.euler_angle) ** 2

        return {
            'input': {
                'content': content,
                'hex_id': message.hex_id,
                'basin': message.basin,
                'layer': message.layer,
                'route': message.route,
            },
            'context': {
                'position': f"B{self.context.position_basin:03d}/L{self.context.position_layer}",
                'local_context': self.context.get_local_context(3),
                'response': context_response.content,
            },
            'thinking': {
                'state': ILogicState.name(self.thinking.current_state),
                'response': thinking_response.content,
                'response_type': thinking_response.response_type,
            },
            'sphere': {
                'id': self.sphere_id,
                'name': self.name,
                'bounce': self.bounce_count,
                'euler': round(self.euler_angle, 4),
                'coupling': round(coupling, 4),
            },
        }

    def save(self) -> Dict[str, Any]:
        """Save mind state to file."""
        state = {
            'sphere_id': self.sphere_id,
            'name': self.name,
            'bounce_count': self.bounce_count,
            'euler_angle': self.euler_angle,
            'thinking_state': ILogicState.name(self.thinking.current_state),
            'thought_count': len(self.thinking.thought_chain),
            'context_basins': len(self.context.known_context),
            'peers': list(self.communication.known_peers.keys()),
            'inbox_count': len(self.communication.inbox),
            'outbox_count': len(self.communication.outbox),
            'timestamp': time.time(),
        }
        self.state_file.write_text(json.dumps(state, indent=2))
        return state

    def __repr__(self) -> str:
        return f"SphereMind({self.name}, id={self.sphere_id}, bounce={self.bounce_count})"
