"""
ContentShell - Hex Identity Tokenization and Documentation Index
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.

HEX IDENTITY PROTOCOL:
    - Each topic/content maps to 8-char hex ID
    - Hex encodes: Morton (20 bits) + Basin (8 bits) + Layer (3 bits) + I-state (1 bit)
    - Bidirectional: encode topic -> hex, decode hex -> components

TOKEN SAVINGS:
    Traditional: "Query the gene breeding documentation" = ~8 tokens
    Hex protocol: "?17000100" = ~3 tokens (62% reduction)
"""
from typing import Dict, List, Optional, Any
from dataclasses import dataclass, asdict
from collections import defaultdict
import hashlib
import json
import time
from pathlib import Path

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

# Data paths
# Priority: 1) cubesquare/data/beehive (standalone) 2) data/beehive (legacy CG)
STANDALONE_DATA = Path(__file__).parent.parent / "data" / "beehive"
LEGACY_DATA = Path(__file__).parent.parent.parent / "data" / "beehive"

# Check which path has the JSON index file
if (STANDALONE_DATA / "beehive_index.json").exists():
    DATA_DIR = STANDALONE_DATA
elif (LEGACY_DATA / "beehive_index.json").exists():
    DATA_DIR = LEGACY_DATA
else:
    DATA_DIR = STANDALONE_DATA  # Fallback to standalone (will be empty)

# Binary CST format (smaller, faster) takes priority over JSON
CST_INDEX_PATH = DATA_DIR / "beehive_index.cst"
JSON_INDEX_PATH = DATA_DIR / "beehive_index.json"
INDEX_PATH = JSON_INDEX_PATH  # Fallback for backward compat

# NOTE: If 24K+ files are indexed, this is LEGACY CG DATA (persisted, not live)
# For fresh standalone, the index will be empty - hex protocol still works


@dataclass
class HexIdentity:
    """Compressed identity token for bidirectional communication."""
    hex_id: str          # 8-char hex string
    morton: int          # 20-bit morton code
    coords: tuple        # (x, y, z) from morton decode
    basin: int           # Semantic cluster (0-130)
    layer: int           # CST layer (1-9)
    i_state: int         # Identity state (0 or 1)
    topic: str           # Original topic/concept
    unlock_key: str      # Morton binary unlock pattern


@dataclass
class ContentToken:
    """CST token for documentation content."""
    file_path: str
    content_hash: int
    topics: List[str]
    basin: int
    layer: str
    summary_k0: str  # Full
    summary_k4: str  # Compressed (38%)
    summary_k8: str  # Minimal (15%)
    api_refs: List[str]
    hex_id: str


@dataclass
class QueryResult:
    """Result from documentation query."""
    query: str
    basin: int
    confidence: float
    summary: str
    detail_level: str
    files: List[str]
    commands: List[str]
    api_refs: List[str]
    related_topics: List[str]
    tokens_used: int


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


def compute_basin(topic_hash: int) -> int:
    """Map topic hash to basin (0-130). O(1) operation."""
    return topic_hash % PRIME_131


def compute_layer(basin: int, diagonal: int) -> int:
    """Classify to CST layer based on basin and diagonal. O(1) operation."""
    if diagonal < 50:
        return 1
    elif basin < 26:
        return 2
    elif diagonal < 200:
        return 3
    elif basin < 65:
        return 4
    elif basin < 100:
        return 5
    elif diagonal < 400:
        return 6
    elif basin < 120:
        return 7
    elif diagonal < 600:
        return 8
    else:
        return 9


def topic_to_hex_identity(topic: str) -> HexIdentity:
    """
    Convert topic string to compressed hex identity. O(1) operation.

    ENCODING:
    - Hash topic to get deterministic coordinates
    - Encode to morton code
    - Pack with basin, layer, i-state into 32-bit hex
    """
    topic_hash = compute_topic_hash(topic.lower())

    # Derive coordinates (constrained to 48x48x48)
    x = (topic_hash >> 0) & 0x3F
    y = (topic_hash >> 6) & 0x3F
    z = (topic_hash >> 12) & 0x3F
    x, y, z = min(x, 47), min(y, 47), min(z, 47)

    # Compute morton code via C backend
    morton = morton_encode(x, y, z)

    # Compute basin (0-130)
    basin = topic_hash % PRIME_131

    # Compute layer based on basin/diagonal
    diagonal = x + y + z
    layer = compute_layer(basin, diagonal)

    # I-state from k11 level
    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)
    hex_id = f"{packed:08X}"

    # Generate unlock key
    unlock_key = f"M{morton:05X}:B{basin:02X}:L{layer}"

    return HexIdentity(
        hex_id=hex_id,
        morton=morton,
        coords=(x, y, z),
        basin=basin,
        layer=layer,
        i_state=i_state,
        topic=topic,
        unlock_key=unlock_key
    )


def hex_to_identity(hex_id: str) -> HexIdentity:
    """
    Decode hex identity back to components. O(1) operation.

    MORTON UNLOCK INSTRUCTIONS:
    1. Parse 32-bit value from hex
    2. Extract morton (bits 0-19), basin (bits 20-27), layer (bits 28-30), i_state (bit 31)
    3. De-interleave morton bits to get (x, y, z)
    """
    packed = int(hex_id, 16)

    morton = packed & 0xFFFFF
    basin = (packed >> 20) & 0xFF
    layer = (packed >> 28) & 0x7
    i_state = (packed >> 31) & 0x1

    # Decode morton via C backend
    coords = morton_decode(morton)
    unlock_key = f"M{morton:05X}:B{basin:02X}:L{layer}"

    return HexIdentity(
        hex_id=hex_id,
        morton=morton,
        coords=coords,
        basin=basin,
        layer=layer if layer > 0 else 1,
        i_state=i_state,
        topic=f"[decoded:{hex_id}]",
        unlock_key=unlock_key
    )


def parse_hex_message(message: str) -> Dict[str, Any]:
    """
    Parse hex protocol message.

    PROTOCOL:
    - ?HEX           Query by hex (expand to content)
    - !HEX:K#        Response at K-level
    - @HEX           Reference (no expansion)
    - +HEX,HEX,...   Batch query
    - =HEX>HEX       Relationship
    """
    message = message.strip()

    if message.startswith('?'):
        hex_id = message[1:9]
        return {'type': 'query', 'hex': hex_id, 'identity': hex_to_identity(hex_id)}

    elif message.startswith('!'):
        parts = message[1:].split(':')
        hex_id = parts[0][:8]
        k_level = int(parts[1][1]) if len(parts) > 1 and parts[1].startswith('K') else 4
        return {'type': 'response', 'hex': hex_id, 'k_level': k_level, 'identity': hex_to_identity(hex_id)}

    elif message.startswith('@'):
        hex_id = message[1:9]
        return {'type': 'reference', 'hex': hex_id, 'identity': hex_to_identity(hex_id)}

    elif message.startswith('+'):
        hex_list = message[1:].split(',')
        return {'type': 'batch', 'hexes': [h.strip()[:8] for h in hex_list]}

    elif message.startswith('='):
        parts = message[1:].split('>')
        return {'type': 'relationship', 'from': parts[0][:8], 'to': parts[1][:8] if len(parts) > 1 else None}

    else:
        if len(message) >= 8 and all(c in '0123456789ABCDEFabcdef' for c in message[:8]):
            return {'type': 'plain', 'hex': message[:8], 'identity': hex_to_identity(message[:8])}
        return {'type': 'unknown', 'raw': message}


def format_hex_response(identity: HexIdentity, summary: str, k_level: int = 4) -> str:
    """Format response in hex protocol."""
    return f"!{identity.hex_id}:K{k_level}|{summary[:100]}"


class HexCommunicator:
    """
    Bidirectional hex communication layer.

    USAGE:
    - Claude sends: "?17000100" (query)
    - System returns: "!17000100:K4|Summary..."
    - Claude can respond: "@17000100" (reference without expansion)
    """

    def __init__(self, index: 'ContentCSTIndex' = None):
        self.index = index
        self.session_hexes: Dict[str, HexIdentity] = {}
        self.hex_to_topic: Dict[str, str] = {}

    def register_topic(self, topic: str) -> HexIdentity:
        """Register topic and get hex identity for session."""
        identity = topic_to_hex_identity(topic)
        self.session_hexes[identity.hex_id] = identity
        self.hex_to_topic[identity.hex_id] = topic
        return identity

    def query_hex(self, hex_id: str, k_level: int = 4) -> str:
        """Query by hex ID and return compressed response."""
        identity = hex_to_identity(hex_id)

        if self.index:
            tokens = self.index.by_basin.get(identity.basin, [])
            if tokens:
                if k_level == 0:
                    summary = tokens[0].summary_k0
                elif k_level == 8:
                    summary = tokens[0].summary_k8
                else:
                    summary = tokens[0].summary_k4
                return format_hex_response(identity, summary, k_level)

        return f"!{hex_id}:K{k_level}|NO_DATA"

    def process_message(self, message: str) -> str:
        """Process incoming hex protocol message."""
        parsed = parse_hex_message(message)

        if parsed['type'] == 'query':
            return self.query_hex(parsed['hex'])

        elif parsed['type'] == 'batch':
            responses = [self.query_hex(h, k_level=8) for h in parsed['hexes']]
            return '\n'.join(responses)

        elif parsed['type'] == 'reference':
            identity = parsed['identity']
            return f"@{identity.hex_id}:B{identity.basin}:L{identity.layer}"

        elif parsed['type'] == 'relationship':
            from_id = hex_to_identity(parsed['from'])
            to_id = hex_to_identity(parsed['to']) if parsed['to'] else None
            if to_id:
                return f"={from_id.hex_id}:B{from_id.basin}>={to_id.hex_id}:B{to_id.basin}"
            return f"={from_id.hex_id}:B{from_id.basin}"

        return f"?UNKNOWN:{message[:20]}"

    def get_session_hex_map(self) -> Dict[str, str]:
        """Get mapping of hex IDs to topics for this session."""
        return {hex_id: identity.topic for hex_id, identity in self.session_hexes.items()}

    def unload_context(self) -> str:
        """Generate compact hex-based session context for unloading."""
        if not self.session_hexes:
            return "SESSION:EMPTY"
        hex_refs = ','.join(self.session_hexes.keys())
        return f"SESSION:{len(self.session_hexes)}|{hex_refs}"

    def reload_context(self, session_string: str) -> int:
        """Reload context from hex session string."""
        if not session_string.startswith('SESSION:'):
            return 0

        parts = session_string[8:].split('|')
        if len(parts) < 2:
            return 0

        hex_refs = parts[1].split(',')
        loaded = 0
        for hex_id in hex_refs:
            hex_id = hex_id.strip()
            if len(hex_id) == 8:
                identity = hex_to_identity(hex_id)
                self.session_hexes[hex_id] = identity
                loaded += 1

        return loaded

    def generate_handshake(self, compact: bool = True) -> str:
        """Generate the FIRST MESSAGE for Claude session startup."""
        core_topics = [
            ('cst memory', 'cst'),
            ('morton encoding', 'morton'),
            ('basin clusters', 'basin'),
            ('i-logic algebra', 'ilogic'),
            ('semantic layers', 'layers'),
            ('beehive index', 'beehive'),
        ]

        hex_map = []
        for topic, short in core_topics:
            identity = self.register_topic(topic)
            hex_map.append(f"{identity.hex_id}={short}")

        if compact:
            lines = [
                "CST:HEX|V1|AGENT:2A9E0100",
                f"MAP:{','.join(hex_map)}",
                "CMD:?H=query,@H=ref,+H,H=batch,!H:K#=resp",
                "MORTON:decode(H)->xyz->basin->layer",
                "RDY"
            ]
            return '\n'.join(lines)
        else:
            lines = [
                "=== CUBESQUARE HEX PROTOCOL v1 ===",
                "Agent: claude | Hex: 2A9E0100",
                "",
                "HEX MAP (topic -> hex_id):",
            ]
            for topic, short in core_topics:
                identity = self.session_hexes.get(topic_to_hex_identity(topic).hex_id)
                if identity:
                    lines.append(f"  {short:8} -> {identity.hex_id} (basin:{identity.basin}, L{identity.layer})")

            lines.extend([
                "",
                "PROTOCOL:",
                "  ?HEX       Query (expand to content)",
                "  @HEX       Reference (no expansion)",
                "  +HEX,HEX   Batch query",
                "  !HEX:K#    Response at K-level (K0=full, K4=38%, K8=15%)",
                "",
                "READY - Use hex IDs to save 70%+ tokens"
            ])
            return '\n'.join(lines)


class ContentCSTIndex:
    """
    Content CST Index for documentation.

    ALL OPERATIONS VECTORIZED - NO PYTHON FOR LOOPS in hot path.
    Loads from persisted JSON index for O(1) startup.
    """

    def __init__(self, auto_load: bool = True):
        self.tokens: Dict[str, ContentToken] = {}
        self.by_basin: Dict[int, List[ContentToken]] = defaultdict(list)
        self.by_topic: Dict[str, List[ContentToken]] = defaultdict(list)
        self.topic_to_basin: Dict[str, int] = {}
        self.hot_cache: Dict[str, QueryResult] = {}
        self.stats = {
            'files_indexed': 0,
            'queries_resolved': 0,
            'cache_hits': 0,
            'cache_misses': 0,
            'last_rebuild': None,
            'last_update': None,
            'loaded_from_persistence': False,
            'load_time_ms': 0,
        }

        self.basin_themes = self._init_basin_themes()

        if auto_load:
            self.load_from_persistence()

    def _init_basin_themes(self) -> Dict[int, str]:
        """Initialize basin themes for common documentation areas."""
        themes = {}
        themes[23] = "Gene/Reproduction System"
        themes[47] = "Morton Encoding/Spatial"
        themes[89] = "MCP/Agent Communication"
        themes[12] = "C Backend/Performance"
        themes[65] = "CST Layers/Navigation"
        themes[101] = "Beehive/Orchestration"
        themes[34] = "Hex System/Addressing"
        themes[78] = "Fernandez/Basin Clustering"
        themes[56] = "Documentation/Tickets"
        return themes

    def load_from_persistence(self) -> Dict[str, Any]:
        """
        Load index from persistence. Tries JSON format.

        NOTE: If 20K+ files are indexed, this is LEGACY CG DATA (persisted snapshot).
        For fresh standalone installs, the index will be empty - hex protocol still works.
        The hex identity protocol (encode/decode/handshake) is independent of the index.
        """
        start_time = time.time()
        result = {
            'status': 'empty',
            'tokens_loaded': 0,
            'load_time_ms': 0,
            'source': None,
            'is_legacy_data': False,
        }

        # Try JSON index
        if JSON_INDEX_PATH.exists():
            try:
                data = json.loads(JSON_INDEX_PATH.read_text(encoding='utf-8'))
                tokens_data = data.get("tokens", {})

                # Reconstruct tokens - dict iteration (bounded by index size)
                for hex_id, t in tokens_data.items():
                    token = ContentToken(
                        file_path=t.get("file_path", ""),
                        content_hash=t.get("content_hash", 0),
                        topics=t.get("topics", []),
                        basin=t.get("basin", 0),
                        layer=t.get("layer", 1),
                        summary_k0=t.get("summaries", {}).get("k0", ""),
                        summary_k4=t.get("summaries", {}).get("k4", ""),
                        summary_k8=t.get("summaries", {}).get("k8", ""),
                        api_refs=t.get("api_refs", []),
                        hex_id=hex_id
                    )
                    self.tokens[t.get("file_path", hex_id)] = token
                    self.by_basin[token.basin].append(token)

                    for topic in token.topics:
                        self.by_topic[topic.lower()].append(token)
                        self.topic_to_basin[topic.lower()] = token.basin

                load_time_ms = int((time.time() - start_time) * 1000)

                self.stats['files_indexed'] = len(self.tokens)
                self.stats['loaded_from_persistence'] = True
                self.stats['load_time_ms'] = load_time_ms
                self.stats['last_update'] = data.get("saved_at", time.strftime('%Y-%m-%dT%H:%M:%S'))

                # Detect legacy CG data (20K+ files = CG pipe_v001 snapshot)
                is_legacy = len(self.tokens) > 20000

                result = {
                    'status': 'loaded',
                    'tokens_loaded': len(self.tokens),
                    'basins_populated': len(self.by_basin),
                    'load_time_ms': load_time_ms,
                    'source': 'json',
                    'is_legacy_data': is_legacy,
                }

            except Exception as e:
                result['status'] = 'error'
                result['error'] = str(e)

        # Empty index is valid - hex protocol works without data
        return result

    def query(self, topic: str, detail_level: int = 4) -> QueryResult:
        """Query documentation by topic."""
        topic_lower = topic.lower()

        cache_key = f"{topic_lower}:{detail_level}"
        if cache_key in self.hot_cache:
            self.stats['cache_hits'] += 1
            return self.hot_cache[cache_key]

        self.stats['cache_misses'] += 1

        topic_hash = compute_topic_hash(topic_lower)
        basin = compute_basin(topic_hash)

        tokens = self.by_basin.get(basin, [])

        keyword_tokens = []
        for key, toks in self.by_topic.items():
            if topic_lower in key or key in topic_lower:
                keyword_tokens.extend(toks)

        all_tokens = list({t.file_path: t for t in tokens + keyword_tokens}.values())

        if not all_tokens:
            empty_result = QueryResult(
                query=topic,
                basin=basin,
                confidence=0.0,
                summary=f"No documentation found for '{topic}'",
                detail_level=f"k{detail_level}",
                files=[],
                commands=[],
                api_refs=[],
                related_topics=[],
                tokens_used=50
            )
            self.hot_cache[cache_key] = empty_result
            return empty_result

        if detail_level == 0:
            summaries = [t.summary_k0 for t in all_tokens[:3]]
        elif detail_level == 4:
            summaries = [t.summary_k4 for t in all_tokens[:3]]
        else:
            summaries = [t.summary_k8 for t in all_tokens[:3]]

        combined_summary = ' | '.join(summaries)[:500]

        api_refs = []
        for t in all_tokens[:5]:
            api_refs.extend(t.api_refs[:3])
        api_refs = list(set(api_refs))[:5]

        related = list(self.basin_themes.get(basin, "").split('/'))

        result = QueryResult(
            query=topic,
            basin=basin,
            confidence=min(1.0, len(all_tokens) / 5),
            summary=combined_summary,
            detail_level=f"k{detail_level}",
            files=[t.file_path for t in all_tokens[:5]],
            commands=[],
            api_refs=api_refs,
            related_topics=related,
            tokens_used=len(combined_summary.split())
        )

        self.hot_cache[cache_key] = result
        self.stats['queries_resolved'] += 1

        return result

    def get_basin_summary(self, basin_id: int) -> Dict[str, Any]:
        """Get summary for a specific basin."""
        tokens = self.by_basin.get(basin_id, [])

        if not tokens:
            return {
                'basin': basin_id,
                'theme': self.basin_themes.get(basin_id, 'Unknown'),
                'files': 0,
                'summary': 'No documents in this basin'
            }

        return {
            'basin': basin_id,
            'theme': self.basin_themes.get(basin_id, 'General'),
            'files': len(tokens),
            'file_list': [t.file_path for t in tokens[:10]],
            'topics': list(set(topic for t in tokens for topic in t.topics))[:10],
            'summary': ' '.join(t.summary_k8 for t in tokens[:3])[:300]
        }

    def get_status(self) -> Dict[str, Any]:
        """Get index status."""
        return {
            'index': {
                'files_indexed': self.stats['files_indexed'],
                'basins_populated': len(self.by_basin),
                'topics_indexed': len(self.topic_to_basin),
                'cache_size': len(self.hot_cache),
                'cache_hits': self.stats['cache_hits'],
                'cache_misses': self.stats['cache_misses'],
                'last_rebuild': self.stats['last_rebuild'],
                'last_update': self.stats['last_update']
            }
        }


class ContentShell:
    """
    Content Shell - Hex Identity Tokenization Protocol.

    Layers: L2 (Semantic)
    Basin range: 15-29
    I-Logic state: DIRECT

    Commands:
        status    - Shell and index status
        encode    - Encode topic to hex identity
        decode    - Decode hex identity to components
        query     - Query documentation by topic
        basin     - Get basin summary
        hex       - Process hex protocol message
        handshake - Generate session handshake
    """

    def __init__(self):
        """Initialize shell."""
        self.index = ContentCSTIndex(auto_load=True)
        self.communicator = HexCommunicator(self.index)

    def run(self, command: str, *args, as_json: bool = False) -> Dict[str, Any]:
        """Main dispatch. Called by CLI and MCP."""
        commands = {
            'status': self.status,
            'encode': self.encode,
            'decode': self.decode,
            'query': self.query,
            'basin': self.basin,
            'hex': self.hex_message,
            'handshake': self.handshake,
        }
        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."""
        index_status = self.index.get_status()
        files_indexed = index_status['index']['files_indexed']

        # Detect legacy CG data
        is_legacy = files_indexed > 20000
        data_note = "LEGACY CG DATA (persisted snapshot)" if is_legacy else "standalone data"

        return {
            'shell': 'ContentShell',
            'version': '1.0.0',
            'status': 'operational',
            'c_backend': True,
            'hot_path_loops': 0,
            'cg_dependencies': 0,
            'index': index_status['index'],
            'data_source': data_note,
            'is_legacy_data': is_legacy,
            'phi': PHI,
            'prime_131': PRIME_131,
        }

    def encode(self, topic: str = "", *args, as_json: bool = False) -> Dict[str, Any]:
        """Encode topic to hex identity."""
        if not topic:
            return {'error': 'No topic provided'}

        identity = topic_to_hex_identity(topic)
        return asdict(identity)

    def decode(self, hex_id: str = "", *args, as_json: bool = False) -> Dict[str, Any]:
        """Decode hex identity to components."""
        if not hex_id or len(hex_id) < 8:
            return {'error': 'Invalid hex ID (need 8 chars)'}

        try:
            identity = hex_to_identity(hex_id[:8])
            return asdict(identity)
        except Exception as e:
            return {'error': str(e)}

    def query(self, topic: str = "", *args, as_json: bool = False) -> Dict[str, Any]:
        """Query documentation by topic."""
        if not topic:
            return {'error': 'No topic provided'}

        detail_level = 4
        if args and str(args[0]).isdigit():
            detail_level = int(args[0])

        result = self.index.query(topic, detail_level)
        return asdict(result)

    def basin(self, basin_id: str = "0", *args, as_json: bool = False) -> Dict[str, Any]:
        """Get basin summary."""
        try:
            bid = int(basin_id)
        except ValueError:
            return {'error': f'Invalid basin ID: {basin_id}'}

        return self.index.get_basin_summary(bid)

    def hex_message(self, message: str = "", *args, as_json: bool = False) -> Dict[str, Any]:
        """Process hex protocol message."""
        if not message:
            return {'error': 'No message provided'}

        response = self.communicator.process_message(message)
        return {'input': message, 'response': response}

    def handshake(self, *args, as_json: bool = False) -> Dict[str, Any]:
        """Generate session handshake."""
        compact = True
        if args and str(args[0]).lower() in ('verbose', 'full', 'false'):
            compact = False

        handshake_text = self.communicator.generate_handshake(compact=compact)
        return {
            'handshake': handshake_text,
            'compact': compact,
            'session_hexes': len(self.communicator.session_hexes),
        }
