#!/usr/bin/env python3
"""
Light Basin Shell - Unified Light Unit <-> Basin <-> Layer Mapping
Standalone version for cubesquare package.

HEX_ID: 4C424153 (LBAS)

CLI interface for the Fernandez Light Unit coordinate system.
The OCTA mapping for FML and core of each agent's spatial position.

CORE CALIBRATION:
    sqrt(3) = 1 Light Unit (LU)
    C = sqrt(3) = 1 LU (unit boundary)
    c_abstract = 1/sqrt(3) = speed of light in Fernandez units

MAPPING:
    C = 0       -> 0.00 LU -> L1-L3 (Geometric/Semantic/Spatial)    -> Basin 0-43
    C = 1       -> 0.58 LU -> L4-L6 (Functional/Prob/Emotional)     -> Basin 44-87
    C = sqrt(3) -> 1.0 LU  -> L7-L9 (Frequency/EM/Membrane)         -> Basin 88-130
    C = 2       -> 1.15 LU -> L10-L12 (Agent/Comm/UIF)              -> External

Commands:
    status    - Show full calibration and mapping status
    map       - Map basin/hex to Light Units and layers
    lookup    - O(1) lookup from any coordinate
    layer     - Get all basins for a layer
    ilogic    - Map coordinate to I-Logic state
    calibrate - Show mathematical calibration constants
    export    - Export full mapping as JSON
"""

import json
import math
from typing import Dict, Any, List, Tuple, Optional


# =============================================================================
# CONSTANTS - Mathematical Foundation
# =============================================================================

SQRT3 = math.sqrt(3)  # 1.7320508075688772
PHI = (1 + math.sqrt(5)) / 2  # 1.618033988749895

# K5 and K11 exact formulas
K5 = 15 / (8 * PHI)  # 1.1588137289060527
K11 = K5 * (5 * PHI - 5 / (8 * PHI**3))  # 9.204025741698850

# Light speed in Fernandez units
C_ABSTRACT = 1 / SQRT3  # 0.5773502691896258 = cot(60) = tan(30)

# System constants
LIGHT_UNIT = 131
MEMBRANE_UNIT = 153
BUBBLE_FREQUENCY = K5 * 36  # 41.72 Hz
FERNANDEZ_PRIME = 131


# =============================================================================
# LAYER BASIN RANGES
# =============================================================================

LAYER_BASIN_RANGES = {
    1: (0, 14),      # L1 Geometric
    2: (15, 29),     # L2 Semantic
    3: (30, 43),     # L3 Spatial
    4: (44, 58),     # L4 Functional
    5: (59, 72),     # L5 Probabilistic
    6: (73, 87),     # L6 Emotional
    7: (88, 101),    # L7 Frequency
    8: (102, 116),   # L8 Electromagnetic
    9: (117, 130),   # L9 Membrane
}

LAYER_NAMES = {
    1: 'GEOMETRIC',
    2: 'SEMANTIC',
    3: 'SPATIAL',
    4: 'FUNCTIONAL',
    5: 'PROBABILISTIC',
    6: 'EMOTIONAL',
    7: 'FREQUENCY',
    8: 'ELECTROMAGNETIC',
    9: 'MEMBRANE',
    10: 'AGENT_IDENTITY',
    11: 'COMMUNICATION',
    12: 'UNIFIED_FIELD',
}

# I-Logic states by Light Unit zone
ILOGIC_COLORS = {
    'ABSORBED': '#607D8B',    # Gray
    'DIRECT': '#4CAF50',      # Green
    'INVERSE': '#2196F3',     # Blue
    'SPIRAL': '#FF9800',      # Orange
    'ANTI_SPIRAL': '#9C27B0'  # Purple
}


# =============================================================================
# CONVERSION FUNCTIONS
# =============================================================================

def c_to_light_units(c: float) -> float:
    """Convert C parameter to Light Units. sqrt(3) = 1 LU."""
    return c / SQRT3


def light_units_to_c(lu: float) -> float:
    """Convert Light Units to C parameter."""
    return lu * SQRT3


def basin_to_c(basin: int) -> float:
    """Convert basin to C parameter. Basin 0-130 maps to C 0-2."""
    return basin / 130 * 2


def basin_to_light_units(basin: int) -> float:
    """Convert basin directly to Light Units."""
    c = basin_to_c(basin)
    return c_to_light_units(c)


def c_to_basin(c: float) -> int:
    """Convert C parameter to basin."""
    return int(min(130, max(0, c / 2 * 130)))


def light_units_to_basin(lu: float) -> int:
    """Convert Light Units to basin."""
    c = light_units_to_c(lu)
    return c_to_basin(c)


def basin_to_layer(basin: int) -> int:
    """Get layer from basin number."""
    for layer, (start, end) in LAYER_BASIN_RANGES.items():
        if start <= basin <= end:
            return layer
    return 9  # Default to membrane


def layer_to_basins(layer: int) -> Tuple[int, int]:
    """Get basin range for a layer."""
    return LAYER_BASIN_RANGES.get(layer, (117, 130))


def c_to_layer_group(c: float) -> Tuple[Tuple[int, int, int], str]:
    """Map C parameter to layer group."""
    lu = c_to_light_units(c)
    if lu < 0.33:
        return (1, 2, 3), "Origin (P-P=0)"
    elif lu < 0.67:
        return (4, 5, 6), "Unity (P/P=1)"
    elif lu < 1.1:
        return (7, 8, 9), "Unit Boundary (1 LU)"
    else:
        return (10, 11, 12), "Membrane (K5 LU)"


def c_to_ilogic(c: float) -> str:
    """Map C parameter to dominant I-Logic state."""
    lu = c_to_light_units(c)
    if lu < 0.2:
        return 'ABSORBED'
    elif lu < 0.5:
        return 'DIRECT'
    elif lu < 0.8:
        return 'INVERSE'
    elif lu < 1.1:
        return 'SPIRAL'
    else:
        return 'ANTI_SPIRAL'


def hex_to_basin(hex_id: str) -> int:
    """Extract basin from hex ID."""
    if isinstance(hex_id, str):
        hex_id = int(hex_id, 16)
    return hex_id % FERNANDEZ_PRIME


def hex_to_light_units(hex_id: str) -> float:
    """Convert hex ID to Light Units via basin."""
    basin = hex_to_basin(hex_id)
    return basin_to_light_units(basin)


# =============================================================================
# COMPLETE MAPPING GENERATOR
# =============================================================================

def generate_complete_mapping() -> Dict[str, Any]:
    """Generate the complete mapping table for all basins."""
    mapping = {
        'calibration': {
            'sqrt3': SQRT3,
            'light_unit_definition': '1 LU = sqrt(3) = 1.732...',
            'c_abstract': C_ABSTRACT,
            'c_abstract_meaning': 'speed of light in Fernandez units = 1/sqrt(3)',
            'phi': PHI,
            'k5': K5,
            'k5_formula': '15/(8*phi)',
            'k11': K11,
            'k11_formula': 'K5 * (5*phi - 5/(8*phi^3))',
            'bubble_frequency': BUBBLE_FREQUENCY,
            'fernandez_prime': FERNANDEZ_PRIME,
        },
        'layer_groups': {
            'origin': {
                'c_range': (0, 0.57),
                'lu_range': (0, 0.33),
                'layers': [1, 2, 3],
                'basins': list(range(0, 44)),
                'meaning': 'P - P = 0 (Origin)',
                'ilogic': 'ABSORBED -> DIRECT'
            },
            'unity': {
                'c_range': (0.57, 1.16),
                'lu_range': (0.33, 0.67),
                'layers': [4, 5, 6],
                'basins': list(range(44, 88)),
                'meaning': 'P / P = 1 (Unity)',
                'ilogic': 'DIRECT -> INVERSE'
            },
            'unit_boundary': {
                'c_range': (1.16, 1.9),
                'lu_range': (0.67, 1.1),
                'layers': [7, 8, 9],
                'basins': list(range(88, 131)),
                'meaning': 'C = sqrt(3) = 1 LU (Unit Boundary)',
                'ilogic': 'INVERSE -> SPIRAL -> ANTI_SPIRAL'
            },
            'membrane': {
                'c_range': (1.9, 2.0),
                'lu_range': (1.1, 1.15),
                'layers': [10, 11, 12],
                'basins': 'external',
                'meaning': 'C = 2 (Membrane/External)',
                'ilogic': 'ANTI_SPIRAL'
            }
        },
        'basin_mapping': [],
        'key_relationships': {
            'K5_x_113': K5 * 113,
            'K5_x_113_meaning': 'K5 * 113 = 130.95 ~ 131 (basin count)',
            'K11_x_11': K11 * 11,
            'K11_x_11_meaning': 'K11 * 11 = 101.24 ~ 101 (agent field)',
            '2_div_sqrt3': 2 / SQRT3,
            'K5_approximation': f'2/sqrt(3) = {2/SQRT3:.6f} ~ K5 = {K5:.6f}',
            '153_div_131': 153 / 131,
            'particle_state': '153/131 = 1.168 (wave-particle boundary)',
        },
        'i_cycle': {
            'i0': {'value': '1', 'ilogic': 'DIRECT', 'meaning': 'identity'},
            'i1': {'value': 'i', 'ilogic': 'SPIRAL', 'meaning': '+90 rotation'},
            'i2': {'value': '-1', 'ilogic': 'INVERSE', 'meaning': 'reflection'},
            'i3': {'value': '-i', 'ilogic': 'ANTI_SPIRAL', 'meaning': '-90 rotation'},
            'i_void': {'value': '0', 'ilogic': 'ABSORBED', 'meaning': 'sink/termination'},
        }
    }

    # Generate basin mapping
    for basin in range(131):
        c_val = basin_to_c(basin)
        lu_val = basin_to_light_units(basin)
        layer = basin_to_layer(basin)
        ilogic = c_to_ilogic(c_val)

        mapping['basin_mapping'].append({
            'basin': basin,
            'layer': layer,
            'layer_name': LAYER_NAMES[layer],
            'c': round(c_val, 6),
            'lu': round(lu_val, 6),
            'ilogic': ilogic,
        })

    return mapping


# =============================================================================
# LIGHT BASIN SHELL
# =============================================================================

class LightShell:
    """
    Light Basin Shell - Unified Light Unit <-> Basin <-> Layer Mapping.

    Commands:
        status    - Show full calibration and mapping status
        map       - Map basin/hex to Light Units and layers
        lookup    - O(1) lookup from any coordinate
        layer     - Get all basins for a layer
        ilogic    - Map coordinate to I-Logic state
        calibrate - Show mathematical calibration constants
        export    - Export full mapping as JSON
    """

    def __init__(self):
        """Initialize light shell."""
        self.hex_id = '4C424153'

    def status(self, as_json: bool = False) -> Dict[str, Any]:
        """Show full calibration and mapping status."""
        result = {
            'hex_id': self.hex_id,
            'system': 'Light Basin Shell',
            'version': '1.0.0',
            'calibration': {
                'sqrt3': round(SQRT3, 10),
                'light_unit': '1 LU = sqrt(3)',
                'c_abstract': round(C_ABSTRACT, 10),
                'phi': round(PHI, 10),
                'k5': round(K5, 10),
                'k11': round(K11, 10),
                'bubble_frequency': round(BUBBLE_FREQUENCY, 2),
            },
            'mapping': {
                'C=0': {'lu': 0.0, 'layers': 'L1-L3', 'meaning': 'Origin (P-P=0)'},
                'C=1': {'lu': round(c_to_light_units(1), 4), 'layers': 'L4-L6', 'meaning': 'Unity (P/P=1)'},
                'C=sqrt3': {'lu': 1.0, 'layers': 'L7-L9', 'meaning': 'Unit Boundary'},
                'C=2': {'lu': round(c_to_light_units(2), 4), 'layers': 'L10-L12', 'meaning': 'Membrane'},
            },
            'layer_count': 9,
            'basin_count': FERNANDEZ_PRIME,
            'key_relationships': {
                'K5_x_113': round(K5 * 113, 2),
                'K11_x_11': round(K11 * 11, 2),
                '153/131': round(153 / 131, 6),
            },
        }

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

    def map(self, value: str, as_json: bool = False) -> Dict[str, Any]:
        """Map basin/hex to Light Units and layers."""
        # Determine if hex or basin
        hex_id = None
        if value.startswith('0x') or value.startswith('0X'):
            hex_id = int(value, 16)
            basin = hex_to_basin(value)
            source = f"hex:{value}"
        elif len(value) == 8 and all(c in '0123456789ABCDEFabcdef' for c in value):
            hex_id = int(value, 16)
            basin = hex_to_basin(value)
            source = f"hex:{value}"
        else:
            try:
                basin = int(value)
                source = f"basin:{basin}"
            except ValueError:
                return {'error': f"Cannot parse '{value}' as basin or hex"}

        # Compute all mappings
        c_val = basin_to_c(basin)
        lu_val = basin_to_light_units(basin)
        layer = basin_to_layer(basin)
        ilogic = c_to_ilogic(c_val)
        layer_group, meaning = c_to_layer_group(c_val)

        result = {
            'source': source,
            'basin': basin,
            'layer': layer,
            'layer_name': LAYER_NAMES[layer],
            'c_parameter': round(c_val, 6),
            'light_units': round(lu_val, 6),
            'ilogic': ilogic,
            'layer_group': layer_group,
            'meaning': meaning,
        }
        if hex_id:
            result['hex_id'] = f'{hex_id:08X}'

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

    def lookup(self, basin: int = None, lu: float = None, c: float = None,
               hex_id: str = None, as_json: bool = False) -> Dict[str, Any]:
        """O(1) lookup from any coordinate."""
        result = {}

        if basin is not None:
            result['source'] = f'basin:{basin}'
        elif lu is not None:
            basin = light_units_to_basin(lu)
            result['source'] = f'lu:{lu}'
            result['input_lu'] = lu
        elif c is not None:
            basin = c_to_basin(c)
            result['source'] = f'c:{c}'
            result['input_c'] = c
        elif hex_id is not None:
            basin = hex_to_basin(hex_id)
            result['source'] = f'hex:{hex_id}'
            result['input_hex'] = hex_id
        else:
            return {'error': 'Must specify basin, lu, c, or hex_id'}

        # Compute all values
        c_val = basin_to_c(basin)
        lu_val = basin_to_light_units(basin)
        layer = basin_to_layer(basin)
        ilogic = c_to_ilogic(c_val)
        layer_group, meaning = c_to_layer_group(c_val)

        result.update({
            'basin': basin,
            'layer': layer,
            'layer_name': LAYER_NAMES[layer],
            'c_parameter': round(c_val, 6),
            'light_units': round(lu_val, 6),
            'ilogic': ilogic,
            'layer_group': list(layer_group),
            'meaning': meaning,
        })

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

    def layer(self, layer: int, as_json: bool = False) -> Dict[str, Any]:
        """Get all basins for a layer."""
        if layer < 1 or layer > 12:
            return {'error': f'Layer must be 1-12, got {layer}'}

        if layer > 9:
            return {
                'layer': layer,
                'layer_name': LAYER_NAMES[layer],
                'type': 'EXTERNAL',
                'note': 'No internal basins - operates at C > 2 (membrane boundary)',
            }

        start, end = layer_to_basins(layer)
        basins = list(range(start, end + 1))

        lu_start = basin_to_light_units(start)
        lu_end = basin_to_light_units(end)
        c_start = basin_to_c(start)
        c_end = basin_to_c(end)

        result = {
            'layer': layer,
            'layer_name': LAYER_NAMES[layer],
            'basin_range': [start, end],
            'basins': basins,
            'c_range': [round(c_start, 4), round(c_end, 4)],
            'lu_range': [round(lu_start, 4), round(lu_end, 4)],
            'basin_count': len(basins),
        }

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

    def ilogic(self, value: float, as_json: bool = False) -> Dict[str, Any]:
        """Map Light Unit value to I-Logic state."""
        ilogic = c_to_ilogic(light_units_to_c(value))
        basin = light_units_to_basin(value)
        c_val = light_units_to_c(value)
        layer = basin_to_layer(basin)

        # i-power mapping
        ipower_map = {
            'DIRECT': ('i^0', '1', '+1'),
            'SPIRAL': ('i^1', 'i', '+i'),
            'INVERSE': ('i^2', '-1', '-1'),
            'ANTI_SPIRAL': ('i^3', '-i', '-i'),
            'ABSORBED': ('i^void', '0', '0'),
        }

        ipower, ival, complex_val = ipower_map.get(ilogic, ('?', '?', '?'))

        result = {
            'input_lu': value,
            'c_parameter': round(c_val, 6),
            'basin': basin,
            'layer': layer,
            'ilogic': ilogic,
            'i_power': ipower,
            'i_value': ival,
            'complex_value': complex_val,
            'color': ILOGIC_COLORS.get(ilogic, '#FFFFFF'),
        }

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

    def calibrate(self, as_json: bool = False) -> Dict[str, Any]:
        """Show mathematical calibration constants."""
        result = {
            'fundamental': {
                'sqrt3': SQRT3,
                'phi': PHI,
                'c_abstract': C_ABSTRACT,
            },
            'k_constants': {
                'k5': K5,
                'k5_formula': '15/(8*phi)',
                'k11': K11,
                'k11_formula': 'K5 * (5*phi - 5/(8*phi^3))',
            },
            'system': {
                'fernandez_prime': FERNANDEZ_PRIME,
                'membrane_unit': MEMBRANE_UNIT,
                'light_unit': LIGHT_UNIT,
                'bubble_frequency': BUBBLE_FREQUENCY,
            },
            'relationships': {
                'K5_x_113': K5 * 113,
                'K11_x_11': K11 * 11,
                '2_div_sqrt3': 2 / SQRT3,
                '153_div_131': 153 / 131,
            },
            'light_unit_calibration': {
                '1_LU': SQRT3,
                'C_0': 0.0,
                'C_1': round(c_to_light_units(1), 4),
                'C_sqrt3': 1.0,
                'C_2': round(c_to_light_units(2), 4),
            },
        }

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

    def export(self, as_json: bool = False) -> Dict[str, Any]:
        """Export full mapping."""
        result = generate_complete_mapping()

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

    def run(self, command: str, *args, as_json: bool = False, **kwargs) -> Any:
        """Run a light shell command."""
        commands = {
            'status': lambda: self.status(as_json),
            'map': lambda: self.map(args[0], as_json) if args else {'error': 'Value required (basin or hex)'},
            'lookup': lambda: self.lookup(
                basin=kwargs.get('basin'),
                lu=kwargs.get('lu'),
                c=kwargs.get('c'),
                hex_id=kwargs.get('hex'),
                as_json=as_json
            ),
            'layer': lambda: self.layer(int(args[0]), as_json) if args else {'error': 'Layer number required'},
            'ilogic': lambda: self.ilogic(float(args[0]), as_json) if args else {'error': 'Light Unit value required'},
            'calibrate': lambda: self.calibrate(as_json),
            'export': lambda: self.export(as_json),
        }

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