#!/usr/bin/env python3
"""
Semantic Chain Shell - Language to Geometry Decomposition
Standalone version for cubesquare package.

HEX_ID: 53454D43 (SEMC)

Decomposes natural language into geometric primitives using frequency-based
basin mapping and I-Logic state routing. This is DECOMPOSITION not TRANSLATION.

Components (embedded):
- Basin Dictionary: Maps terms to basins via frequency (Zipf's law)
- Condition Encoder: Creates ephemeral tokens with I-Logic states
- FML Sentence Parser: Decomposes language into primitives
- Chain Executor: Routes via I-Logic products

Commands:
    status   - Show semantic chain status
    parse    - Parse text into primitives
    route    - Get best agent for condition
    basin    - Get basin for term
    encode   - Encode term as ephemeral token
    wavelength - Get wavelength for term frequency

Mathematical Foundation:
    High frequency words (common) -> short wavelength -> violet (380nm) -> low basin (0-43)
    Low frequency words (rare) -> long wavelength -> red (750nm) -> high basin (88-130)
    Basin = wavelength band within 131 Fernandez basins
"""

import json
import re
import math
from typing import Dict, Any, List, Tuple, Optional
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum

# Import from circular for I-Logic operations
from ..circular.state_machine import ILogicState, ILogicStateMachine


# =============================================================================
# CONSTANTS
# =============================================================================

PHI = 1.618033988749895
PRIME_131 = 131

# Wavelength range (visible spectrum)
LAMBDA_MIN = 380.0  # nm (violet, high frequency)
LAMBDA_MAX = 750.0  # nm (red, low frequency)
LAMBDA_RANGE = LAMBDA_MAX - LAMBDA_MIN  # 370nm

# Python keywords to exclude
PYTHON_KEYWORDS = {
    'def', 'class', 'import', 'from', 'return', 'if', 'else', 'elif',
    'for', 'while', 'try', 'except', 'with', 'as', 'in', 'is', 'not',
    'and', 'or', 'True', 'False', 'None', 'self', 'lambda', 'yield',
    'pass', 'break', 'continue', 'raise', 'finally', 'global', 'assert'
}

# Common English stopwords
STOPWORDS = {
    'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
    'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could',
    'should', 'may', 'might', 'must', 'shall', 'can', 'need', 'dare',
    'to', 'of', 'in', 'for', 'on', 'with', 'at', 'by', 'from', 'up',
    'about', 'into', 'over', 'after', 'this', 'that', 'these', 'those',
    'it', 'its', 'they', 'them', 'their', 'we', 'us', 'our', 'you', 'your',
    'he', 'him', 'his', 'she', 'her', 'who', 'which', 'what', 'where',
    'when', 'why', 'how', 'all', 'each', 'every', 'both', 'few', 'more',
    'most', 'other', 'some', 'such', 'no', 'nor', 'not', 'only', 'own',
    'same', 'so', 'than', 'too', 'very', 'just', 'also', 'now', 'here'
}

# I-Logic zone mapping by wavelength
ILOGIC_ZONES = {
    'VIOLET': (380, 450, ILogicState.DIRECT),     # Basins 0-24: Stable
    'BLUE': (450, 495, ILogicState.DIRECT),        # Basins 25-40: Stable
    'GREEN': (495, 570, ILogicState.SPIRAL),       # Basins 41-67: Growth
    'YELLOW': (570, 590, ILogicState.SPIRAL),      # Basins 68-74: Growth
    'ORANGE': (590, 620, ILogicState.ANTI_SPIRAL), # Basins 75-85: Decay
    'RED': (620, 750, ILogicState.INVERSE),        # Basins 86-130: Unstable
}


class L2Class(Enum):
    """L2 semantic classification categories."""
    GEOMETRIC = 'GEOMETRIC'
    SEMANTIC = 'SEMANTIC'
    SPATIAL = 'SPATIAL'
    FUNCTIONAL = 'FUNCTIONAL'
    PROBABILISTIC = 'PROBABILISTIC'


class PredicateType(Enum):
    """Predicate types for relationships."""
    IS = 'IS'
    HAS = 'HAS'
    DOES = 'DOES'
    CAUSES = 'CAUSES'
    RELATES_TO = 'RELATES_TO'


# =============================================================================
# DATA CLASSES
# =============================================================================

@dataclass
class EncodedCondition:
    """Ephemeral token for a condition."""
    hex_id: str
    primitive_text: str
    basin: int
    wavelength: float
    ilogic_state: ILogicState
    ephemeral: bool = True


@dataclass
class Primitive:
    """Parsed primitive from text."""
    ptype: str  # 'entity', 'state', 'action', 'relation'
    text: str
    resolved_hex: Optional[str] = None
    encoding: Optional[EncodedCondition] = None


@dataclass
class Relationship:
    """Relationship between primitives."""
    source: str
    target: str
    predicate: PredicateType
    l2_class: L2Class


@dataclass
class ParseResult:
    """Result of parsing a sentence."""
    original_text: str
    primitives: List[Primitive] = field(default_factory=list)
    encoded_conditions: List[EncodedCondition] = field(default_factory=list)
    relationships: List[Relationship] = field(default_factory=list)

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary."""
        return {
            'original_text': self.original_text,
            'primitives': [
                {
                    'type': p.ptype,
                    'text': p.text,
                    'resolved_hex': p.resolved_hex,
                    'encoding': {
                        'hex_id': p.encoding.hex_id,
                        'basin': p.encoding.basin,
                        'wavelength': p.encoding.wavelength,
                        'ilogic': p.encoding.ilogic_state.name,
                    } if p.encoding else None
                }
                for p in self.primitives
            ],
            'encoded_conditions': [
                {
                    'hex_id': c.hex_id,
                    'text': c.primitive_text,
                    'basin': c.basin,
                    'wavelength': c.wavelength,
                    'ilogic': c.ilogic_state.name,
                }
                for c in self.encoded_conditions
            ],
            'relationships': [
                {
                    'source': r.source,
                    'target': r.target,
                    'predicate': r.predicate.name,
                    'l2_class': r.l2_class.name,
                }
                for r in self.relationships
            ],
        }


@dataclass
class AgentMatch:
    """Agent match for routing."""
    hex_id: str
    name: str
    condition_ilogic: ILogicState
    agent_ilogic: ILogicState
    product: ILogicState
    alignment_score: float
    role: str

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary."""
        return {
            'hex_id': self.hex_id,
            'name': self.name,
            'condition_ilogic': self.condition_ilogic.name,
            'agent_ilogic': self.agent_ilogic.name,
            'product': self.product.name,
            'alignment_score': self.alignment_score,
            'role': self.role,
        }


@dataclass
class RoutingPlan:
    """Plan for routing a condition to agents."""
    parsed: ParseResult
    best_agent: Optional[AgentMatch]
    all_matches: List[AgentMatch]
    wave_dominant: bool
    execution_time_ms: float
    routing_reason: str

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary."""
        return {
            'parsed': self.parsed.to_dict(),
            'best_agent': self.best_agent.to_dict() if self.best_agent else None,
            'all_matches': [m.to_dict() for m in self.all_matches],
            'wave_dominant': self.wave_dominant,
            'execution_time_ms': self.execution_time_ms,
            'routing_reason': self.routing_reason,
        }


# =============================================================================
# AGENT REGISTRY (built-in agents for routing)
# =============================================================================

AGENT_REGISTRY = {
    '2A9E0100': {'name': 'Healer', 'ilogic': ILogicState.INVERSE, 'role': 'Self-healing, problem resolution'},
    '2A9E0200': {'name': 'Validator', 'ilogic': ILogicState.DIRECT, 'role': 'Verification, integrity checking'},
    '2A9E0300': {'name': 'Expander', 'ilogic': ILogicState.SPIRAL, 'role': 'Growth, abstraction'},
    '2A9E0400': {'name': 'Crystallizer', 'ilogic': ILogicState.ANTI_SPIRAL, 'role': 'Focus, consolidation'},
    '2A9E0500': {'name': 'Absorber', 'ilogic': ILogicState.ABSORBED, 'role': 'Loop termination, completion'},
}


# =============================================================================
# CORE FUNCTIONS
# =============================================================================

def extract_terms(text: str) -> List[str]:
    """Extract meaningful terms from text, excluding keywords and stopwords."""
    if not text:
        return []

    tokens = re.findall(r'[a-zA-Z_][a-zA-Z0-9_]*', text.lower())

    terms = []
    for t in tokens:
        if len(t) < 3:
            continue
        if t in PYTHON_KEYWORDS or t in STOPWORDS:
            continue
        parts = re.findall(r'[a-z]+', t)
        for p in parts:
            if len(p) >= 3 and p not in STOPWORDS:
                terms.append(p)

    return terms


def frequency_to_wavelength(rank: int, vocab_size: int) -> float:
    """
    Map Zipf rank to wavelength (380-750nm).

    High rank (rare word) -> long wavelength (red)
    Low rank (common word) -> short wavelength (violet)
    """
    normalized = (rank - 1) / max(vocab_size - 1, 1)
    wavelength = LAMBDA_MIN + (LAMBDA_RANGE * normalized)
    return wavelength


def wavelength_to_basin(wavelength: float) -> int:
    """Map wavelength to basin (0-130)."""
    normalized = (wavelength - LAMBDA_MIN) / LAMBDA_RANGE
    normalized = max(0, min(1, normalized))
    basin = int(normalized * (PRIME_131 - 1))
    return basin


def wavelength_to_ilogic(wavelength: float) -> ILogicState:
    """Map wavelength to I-Logic state based on color zones."""
    for zone_name, (low, high, state) in ILOGIC_ZONES.items():
        if low <= wavelength < high:
            return state
    return ILogicState.INVERSE  # Default to INVERSE for edge cases


def generate_hex_id(text: str, basin: int) -> str:
    """Generate ephemeral hex ID from text and basin."""
    text_hash = hash(text) & 0xFFFFFFFF
    # Format: XXXX YYYY where XX = basin-derived, YY = hash-derived
    area = basin % 256
    category = (basin // 2) % 256
    layer = (text_hash >> 8) & 0xFF
    index = text_hash & 0xFF
    return f'{area:02X}{category:02X}{layer:02X}{index:02X}'


# =============================================================================
# SEMANTIC SHELL
# =============================================================================

class SemanticShell:
    """
    Semantic Chain Shell.

    Decomposes language into geometric primitives:
    - Text -> Terms -> Frequency ranks -> Wavelengths -> Basins -> I-Logic states

    Commands:
        status     - Show system status
        parse      - Parse text into primitives
        route      - Get best agent for text condition
        basin      - Get basin for a term
        encode     - Encode term as ephemeral token
        wavelength - Get wavelength for term
    """

    def __init__(self):
        """Initialize semantic shell."""
        self.hex_id = '53454D43'
        self.state_machine = ILogicStateMachine()
        self.vocabulary: Dict[str, int] = {}  # term -> rank
        self.vocab_size = 0

        # Build default vocabulary from common semantic terms
        self._build_default_vocabulary()

    def _build_default_vocabulary(self):
        """Build default vocabulary for common terms."""
        # Common terms ranked by estimated frequency
        common_terms = [
            # High frequency (low rank = violet)
            'file', 'data', 'type', 'value', 'name', 'path', 'key', 'node',
            'list', 'dict', 'class', 'function', 'method', 'object', 'string',
            # Medium frequency (green)
            'token', 'basin', 'layer', 'agent', 'state', 'logic', 'chain',
            'parse', 'encode', 'decode', 'validate', 'process', 'execute',
            # Lower frequency (yellow/orange)
            'healer', 'validator', 'expander', 'crystallizer', 'absorber',
            'semantic', 'geometric', 'fernandez', 'wavelength', 'frequency',
            # Low frequency (red = high rank)
            'stuck', 'corrupted', 'broken', 'isolated', 'orphaned', 'missing',
            'degraded', 'unstable', 'failing', 'error', 'problem', 'issue',
        ]

        for rank, term in enumerate(common_terms, 1):
            self.vocabulary[term] = rank

        self.vocab_size = len(self.vocabulary)

    def _get_term_rank(self, term: str) -> int:
        """Get rank for a term (1 = most common)."""
        term_lower = term.lower()
        if term_lower in self.vocabulary:
            return self.vocabulary[term_lower]
        # Unknown terms get median rank (neutral wavelength)
        return self.vocab_size // 2

    def _get_term_wavelength(self, term: str) -> float:
        """Get wavelength for a term."""
        rank = self._get_term_rank(term)
        return frequency_to_wavelength(rank, self.vocab_size)

    def _encode_term(self, term: str) -> EncodedCondition:
        """Encode a term as an ephemeral token."""
        wavelength = self._get_term_wavelength(term)
        basin = wavelength_to_basin(wavelength)
        ilogic = wavelength_to_ilogic(wavelength)
        hex_id = generate_hex_id(term, basin)

        return EncodedCondition(
            hex_id=hex_id,
            primitive_text=term,
            basin=basin,
            wavelength=wavelength,
            ilogic_state=ilogic,
            ephemeral=True,
        )

    def status(self, as_json: bool = False) -> Dict[str, Any]:
        """Get semantic chain status."""
        result = {
            'hex_id': self.hex_id,
            'system': 'Semantic Chain Shell',
            'version': '1.0.0',
            'components': {
                'basin_dictionary': {'status': 'ACTIVE', 'vocab_size': self.vocab_size},
                'condition_encoder': {'status': 'ACTIVE'},
                'chain_executor': {'status': 'ACTIVE', 'agents': len(AGENT_REGISTRY)},
            },
            'principle': 'DECOMPOSITION not TRANSLATION',
            'l2_classes': [c.name for c in L2Class],
            'ilogic_zones': {
                name: f'{low}-{high}nm -> {state.name}'
                for name, (low, high, state) in ILOGIC_ZONES.items()
            },
            'wavelength_range': f'{LAMBDA_MIN}-{LAMBDA_MAX}nm',
            'basin_count': PRIME_131,
        }

        if as_json:
            return json.dumps(result, indent=2)
        return result

    def parse(self, text: str, as_json: bool = False) -> Dict[str, Any]:
        """Parse text into semantic primitives."""
        terms = extract_terms(text)

        primitives = []
        encoded_conditions = []

        for term in terms:
            encoding = self._encode_term(term)
            primitive = Primitive(
                ptype='term',
                text=term,
                encoding=encoding,
            )
            primitives.append(primitive)
            encoded_conditions.append(encoding)

        result = ParseResult(
            original_text=text,
            primitives=primitives,
            encoded_conditions=encoded_conditions,
        )

        output = result.to_dict()

        if as_json:
            return json.dumps(output, indent=2)
        return output

    def route(self, text: str, as_json: bool = False) -> Dict[str, Any]:
        """Route text to best agent based on I-Logic product."""
        import time
        start = time.perf_counter()

        # Parse text
        terms = extract_terms(text)
        if not terms:
            return {'error': 'No meaningful terms found'}

        # Get dominant condition from first substantive term
        # (In full implementation, would aggregate all terms)
        condition_term = terms[-1]  # Last term often contains condition
        encoding = self._encode_term(condition_term)
        condition_ilogic = encoding.ilogic_state

        # Compute matches against all agents
        matches = []
        for hex_id, agent in AGENT_REGISTRY.items():
            agent_ilogic = agent['ilogic']
            product = self.state_machine.multiply(condition_ilogic, agent_ilogic)

            # Alignment score (DIRECT is best, ABSORBED is loop-exit)
            alignment_scores = {
                ILogicState.DIRECT: 1.0,
                ILogicState.ABSORBED: 0.8,
                ILogicState.SPIRAL: 0.5,
                ILogicState.ANTI_SPIRAL: 0.3,
                ILogicState.INVERSE: 0.2,
            }

            matches.append(AgentMatch(
                hex_id=hex_id,
                name=agent['name'],
                condition_ilogic=condition_ilogic,
                agent_ilogic=agent_ilogic,
                product=product,
                alignment_score=alignment_scores.get(product, 0.1),
                role=agent['role'],
            ))

        # Sort by alignment score
        matches.sort(key=lambda m: -m.alignment_score)
        best = matches[0] if matches else None

        elapsed = (time.perf_counter() - start) * 1000

        # Determine wave dominance (short wavelength = wave-like)
        wave_dominant = encoding.wavelength < 550

        result = {
            'text': text,
            'condition_term': condition_term,
            'condition': {
                'hex_id': encoding.hex_id,
                'basin': encoding.basin,
                'wavelength': round(encoding.wavelength, 2),
                'ilogic': encoding.ilogic_state.name,
            },
            'best_agent': best.to_dict() if best else None,
            'all_matches': [m.to_dict() for m in matches],
            'wave_dominant': wave_dominant,
            'execution_time_ms': round(elapsed, 3),
            'routing_reason': f'{condition_ilogic.name} x {best.agent_ilogic.name} = {best.product.name}' if best else 'No agent found',
        }

        if as_json:
            return json.dumps(result, indent=2)
        return result

    def basin(self, term: str, as_json: bool = False) -> Dict[str, Any]:
        """Get basin for a term."""
        wavelength = self._get_term_wavelength(term)
        basin_num = wavelength_to_basin(wavelength)
        ilogic = wavelength_to_ilogic(wavelength)
        rank = self._get_term_rank(term)

        result = {
            'term': term,
            'rank': rank,
            'wavelength': round(wavelength, 2),
            'basin': basin_num,
            'ilogic': ilogic.name,
            'in_vocabulary': term.lower() in self.vocabulary,
        }

        if as_json:
            return json.dumps(result, indent=2)
        return result

    def encode(self, term: str, ptype: str = 'state_condition', as_json: bool = False) -> Dict[str, Any]:
        """Encode term as ephemeral token."""
        encoding = self._encode_term(term)

        result = {
            'term': term,
            'type': ptype,
            'hex_id': encoding.hex_id,
            'basin': encoding.basin,
            'wavelength': round(encoding.wavelength, 2),
            'ilogic': encoding.ilogic_state.name,
            'ephemeral': encoding.ephemeral,
        }

        if as_json:
            return json.dumps(result, indent=2)
        return result

    def wavelength(self, term: str, as_json: bool = False) -> Dict[str, Any]:
        """Get wavelength for a term."""
        wavelength = self._get_term_wavelength(term)
        rank = self._get_term_rank(term)

        # Determine color zone
        color = 'UNKNOWN'
        for zone_name, (low, high, _) in ILOGIC_ZONES.items():
            if low <= wavelength < high:
                color = zone_name
                break

        result = {
            'term': term,
            'rank': rank,
            'wavelength': round(wavelength, 2),
            'color_zone': color,
            'frequency_type': 'HIGH' if wavelength < 500 else 'MEDIUM' if wavelength < 600 else 'LOW',
        }

        if as_json:
            return json.dumps(result, indent=2)
        return result

    def run(self, command: str, *args, as_json: bool = False) -> Any:
        """Run a semantic shell command."""
        commands = {
            'status': lambda: self.status(as_json),
            'parse': lambda: self.parse(args[0], as_json) if args else {'error': 'Text required'},
            'route': lambda: self.route(args[0], as_json) if args else {'error': 'Text required'},
            'basin': lambda: self.basin(args[0], as_json) if args else {'error': 'Term required'},
            'encode': lambda: self.encode(args[0], args[1] if len(args) > 1 else 'state_condition', as_json) if args else {'error': 'Term required'},
            'wavelength': lambda: self.wavelength(args[0], as_json) if args else {'error': 'Term required'},
        }

        if command in commands:
            return commands[command]()
        else:
            return {'error': f'Unknown command: {command}', 'available': list(commands.keys())}
