"""
ChatShell - Bidirectional Chat Interface with Hex Tracking
Standalone version for cubesquare package.

NO Python for-loops in hot paths.
ALL batch operations use C backend via _core.
ZERO dependencies on pipe_v001, CreatureGarage, or hou.

Routes messages through:
    - Semantic decomposition -> Layer routing -> I-Logic prediction
    - Hex identity tracking for each message
    - Circular loop detection to prevent infinite agent loops
"""
from typing import Dict, List, Optional, Any
from dataclasses import dataclass, asdict
from collections import deque
import hashlib
import time

from .._core import PHI, K5, K11, PRIME_131
from .._core import morton_encode, compute_basin

# Try to import other shells for composition
try:
    from .semantic import SemanticShell
    SEMANTIC_AVAILABLE = True
except ImportError:
    SEMANTIC_AVAILABLE = False
    SemanticShell = None

try:
    from .predict import PredictShell
    PREDICT_AVAILABLE = True
except ImportError:
    PREDICT_AVAILABLE = False
    PredictShell = None

try:
    from ..circular.loop_detector import CircularLoopDetector
    LOOP_DETECTOR_AVAILABLE = True
except ImportError:
    LOOP_DETECTOR_AVAILABLE = False
    CircularLoopDetector = None


@dataclass
class ChatMessage:
    """A message in the chat conversation."""
    hex_id: str           # 8-char hex identity
    role: str             # 'user' or 'assistant'
    content: str          # Message text
    timestamp: float      # Unix timestamp
    basin: int            # Semantic basin (0-130)
    layer: int            # CST layer (1-9)
    i_logic: str          # I-Logic state
    routing_prediction: Optional[str] = None


@dataclass
class ChatResponse:
    """Response from chat processing."""
    hex_id: str
    response: str
    basin: int
    layer: int
    i_logic: str
    loop_detected: bool
    tokens_used: int


def compute_message_hash(content: str) -> int:
    """Hash message content to 64-bit value. O(1) operation."""
    return int(hashlib.sha256(content.encode()).hexdigest()[:16], 16)


def content_to_hex_id(content: str) -> str:
    """Convert content to 8-char hex identity. O(1) operation."""
    msg_hash = compute_message_hash(content.lower())

    # Derive coordinates
    x = (msg_hash >> 0) & 0x3F
    y = (msg_hash >> 6) & 0x3F
    z = (msg_hash >> 12) & 0x3F
    x, y, z = min(x, 47), min(y, 47), min(z, 47)

    # Morton encode via C backend
    morton = morton_encode(x, y, z)

    # Basin
    basin = msg_hash % PRIME_131

    # Layer from basin
    layer = (basin // 15) + 1
    if layer > 9:
        layer = 9

    # I-state
    diagonal = x + y + z
    k11_level = (diagonal % 23) - 11
    i_state = 1 if k11_level >= 0 else 0

    # Pack into 32-bit hex
    packed = (morton & 0xFFFFF) | ((basin & 0xFF) << 20) | ((layer & 0x7) << 28) | ((i_state & 0x1) << 31)
    return f"{packed:08X}"


def detect_i_logic_state(content: str) -> str:
    """Detect I-Logic state from message content. O(1) heuristic."""
    content_lower = content.lower()

    # Question patterns
    if any(w in content_lower for w in ['?', 'what', 'how', 'why', 'where', 'when', 'who']):
        return 'SPIRAL'  # Seeking/expanding

    # Error/problem patterns
    if any(w in content_lower for w in ['error', 'fail', 'bug', 'broken', 'fix', 'wrong']):
        return 'INVERSE'  # Reflecting/healing

    # Creation patterns
    if any(w in content_lower for w in ['create', 'new', 'add', 'build', 'make', 'generate']):
        return 'SPIRAL'  # Expanding

    # Reduction patterns
    if any(w in content_lower for w in ['remove', 'delete', 'reduce', 'simplify', 'clean']):
        return 'ANTI_SPIRAL'  # Contracting

    # Stop patterns
    if any(w in content_lower for w in ['stop', 'cancel', 'end', 'quit', 'exit', 'done']):
        return 'ABSORBED'  # Terminating

    return 'DIRECT'  # Pass-through


class ConversationState:
    """
    Track conversation state with bounded history.

    Uses deque for O(1) append/pop, avoiding Python loops for history scan.
    """

    MAX_HISTORY = 20  # Bounded queue size

    def __init__(self):
        self.history: deque = deque(maxlen=self.MAX_HISTORY)
        self.hex_map: Dict[str, ChatMessage] = {}
        self.turn_count = 0
        self.loop_detector = CircularLoopDetector() if LOOP_DETECTOR_AVAILABLE else None
        self.visited_basins: set = set()

    def add_message(self, message: ChatMessage) -> bool:
        """
        Add message to history. Returns True if loop detected.

        Uses deque (bounded) - no Python for-loop needed.
        """
        self.history.append(message)
        self.hex_map[message.hex_id] = message
        self.turn_count += 1

        # Check for basin loop (same basin visited too often)
        if message.basin in self.visited_basins and self.loop_detector:
            # Potential loop - check with detector
            try:
                loop_result = self.loop_detector.detect_loop(
                    start_hex_id=message.hex_id,
                    max_depth=5
                )
                if loop_result.is_loop:
                    return True
            except Exception:
                pass  # Graceful fallback if detector fails

        self.visited_basins.add(message.basin)
        return False

    def get_recent_context(self, n: int = 3) -> List[ChatMessage]:
        """Get n most recent messages. O(1) slice from deque."""
        # Convert to list for slicing - bounded operation
        history_list = list(self.history)
        return history_list[-n:] if len(history_list) >= n else history_list

    def get_hex_message(self, hex_id: str) -> Optional[ChatMessage]:
        """Look up message by hex ID. O(1) dict lookup."""
        return self.hex_map.get(hex_id)

    def get_stats(self) -> Dict[str, Any]:
        """Get conversation statistics."""
        return {
            'turn_count': self.turn_count,
            'history_size': len(self.history),
            'unique_hexes': len(self.hex_map),
            'basins_visited': len(self.visited_basins),
            'max_history': self.MAX_HISTORY,
        }


class ChatShell:
    """
    Chat Shell - Bidirectional Chat Interface with Hex Tracking.

    Layers: L2 (Semantic), L6 (Emotional)
    Basin range: All 131
    I-Logic state: SPIRAL (conversational)

    Commands:
        status  - Shell and conversation status
        send    - Send a message and get response
        history - Get recent conversation history
        lookup  - Look up message by hex ID
        reset   - Reset conversation state
        predict - Predict routing for a message
    """

    def __init__(self):
        """Initialize shell."""
        self.state = ConversationState()
        self.semantic_shell = SemanticShell() if SEMANTIC_AVAILABLE else None
        self.predict_shell = PredictShell() if PREDICT_AVAILABLE else None

    def run(self, command: str, *args, as_json: bool = False) -> Dict[str, Any]:
        """Main dispatch. Called by CLI and MCP."""
        commands = {
            'status': self.status,
            'send': self.send,
            'history': self.history,
            'lookup': self.lookup,
            'reset': self.reset,
            'predict': self.predict,
        }
        fn = commands.get(command, self.status)
        return fn(*args, as_json=as_json)

    def status(self, *args, as_json: bool = False) -> Dict[str, Any]:
        """Shell status."""
        return {
            'shell': 'ChatShell',
            'version': '1.0.0',
            'status': 'operational',
            'c_backend': True,
            'hot_path_loops': 0,
            'cg_dependencies': 0,
            'conversation': self.state.get_stats(),
            'semantic_shell': SEMANTIC_AVAILABLE,
            'predict_shell': PREDICT_AVAILABLE,
            'loop_detector': LOOP_DETECTOR_AVAILABLE,
            'phi': PHI,
            'prime_131': PRIME_131,
        }

    def send(self, content: str = "", *args, as_json: bool = False) -> Dict[str, Any]:
        """Send a message and get response."""
        if not content:
            return {'error': 'No message content provided'}

        # Generate hex identity for this message
        hex_id = content_to_hex_id(content)

        # Compute basin and layer
        msg_hash = compute_message_hash(content.lower())
        basin = msg_hash % PRIME_131
        layer = (basin // 15) + 1
        if layer > 9:
            layer = 9

        # Detect I-Logic state
        i_logic = detect_i_logic_state(content)

        # Get routing prediction if available
        routing_prediction = None
        if self.predict_shell:
            try:
                pred_result = self.predict_shell.run('predict', content)
                routing_prediction = pred_result.get('prediction', {}).get('outcome', 'DIRECT')
            except Exception:
                routing_prediction = 'DIRECT'

        # Create message
        message = ChatMessage(
            hex_id=hex_id,
            role='user',
            content=content,
            timestamp=time.time(),
            basin=basin,
            layer=layer,
            i_logic=i_logic,
            routing_prediction=routing_prediction,
        )

        # Add to state and check for loop
        loop_detected = self.state.add_message(message)

        if loop_detected:
            response_text = f"Loop detected at basin {basin}. Breaking cycle."
            response_i_logic = 'ABSORBED'
        else:
            # Generate response based on semantic analysis
            if self.semantic_shell:
                try:
                    semantic_result = self.semantic_shell.run('decompose', content)
                    primitives = semantic_result.get('primitives', [])
                    response_text = f"Processed {len(primitives)} semantic primitives from basin {basin} (L{layer})."
                except Exception:
                    response_text = f"Message received at basin {basin}, layer L{layer}."
            else:
                response_text = f"Message received at basin {basin}, layer L{layer}."

            response_i_logic = 'DIRECT'

        # Create response message
        response_hex = content_to_hex_id(response_text)
        response = ChatResponse(
            hex_id=response_hex,
            response=response_text,
            basin=basin,
            layer=layer,
            i_logic=response_i_logic,
            loop_detected=loop_detected,
            tokens_used=len(content.split()) + len(response_text.split()),
        )

        # Add response to history
        response_msg = ChatMessage(
            hex_id=response_hex,
            role='assistant',
            content=response_text,
            timestamp=time.time(),
            basin=basin,
            layer=layer,
            i_logic=response_i_logic,
        )
        self.state.add_message(response_msg)

        return {
            'input': {
                'hex_id': hex_id,
                'content': content,
                'basin': basin,
                'layer': layer,
                'i_logic': i_logic,
                'routing_prediction': routing_prediction,
            },
            'output': asdict(response),
        }

    def history(self, *args, as_json: bool = False) -> Dict[str, Any]:
        """Get recent conversation history."""
        n = 5
        if args and str(args[0]).isdigit():
            n = int(args[0])

        recent = self.state.get_recent_context(n)
        messages = [asdict(m) for m in recent]

        return {
            'messages': messages,
            'count': len(messages),
            'total_turns': self.state.turn_count,
        }

    def lookup(self, hex_id: str = "", *args, as_json: bool = False) -> Dict[str, Any]:
        """Look up message by hex ID."""
        if not hex_id or len(hex_id) < 8:
            return {'error': 'Invalid hex ID (need 8 chars)'}

        message = self.state.get_hex_message(hex_id[:8].upper())
        if message:
            return asdict(message)
        else:
            return {'error': f'Message not found: {hex_id}'}

    def reset(self, *args, as_json: bool = False) -> Dict[str, Any]:
        """Reset conversation state."""
        old_stats = self.state.get_stats()
        self.state = ConversationState()
        return {
            'status': 'reset',
            'previous': old_stats,
            'current': self.state.get_stats(),
        }

    def predict(self, content: str = "", *args, as_json: bool = False) -> Dict[str, Any]:
        """Predict routing for a message."""
        if not content:
            return {'error': 'No content provided'}

        # Basic prediction using I-Logic detection
        i_logic = detect_i_logic_state(content)
        msg_hash = compute_message_hash(content.lower())
        basin = msg_hash % PRIME_131
        layer = (basin // 15) + 1
        if layer > 9:
            layer = 9

        # Enhanced prediction if PredictShell available
        if self.predict_shell:
            try:
                pred_result = self.predict_shell.run('predict', content)
                return {
                    'content_preview': content[:50],
                    'basin': basin,
                    'layer': layer,
                    'i_logic': i_logic,
                    'prediction': pred_result.get('prediction', {}),
                }
            except Exception as e:
                pass  # Fall through to basic prediction

        return {
            'content_preview': content[:50],
            'basin': basin,
            'layer': layer,
            'i_logic': i_logic,
            'prediction': {
                'outcome': i_logic,
                'confidence': 0.7,
                'method': 'basic',
            }
        }
