#!/usr/bin/env python3
"""
Auto-Δ selector for multi-state basin control.
Selects optimal DELTA_BASE per cardinality from validated ranges.
"""

from typing import Tuple, Optional


# Optimal DELTA_BASE per cardinality (from validation experiments)
OPTIMAL_DELTA = {
    2: 60,   # Binary: Wide range, 60° is safe middle
    3: 90,   # Trit: Wide continuous range, 90° validated
    4: 60,   # Quad: Narrower range (30-75°), 60° is center
    8: 25,   # Octal: Tightest range (20-35°), 25° is safe
}

# Safe DELTA_BASE ranges (min, max) per cardinality
SAFE_DELTA_RANGES = {
    2: (15, 90),   # Binary: Exclude 45° ± 2° (π/4 singularity)
    3: (15, 120),  # Trit: Continuous, no null points
    4: (30, 75),   # Quad: Narrow range, avoid wraparound
    8: (20, 35),   # Octal: Tightest, precision required
}

# State letter to value mapping (Helix64 alphabet)
STATE_LETTER_TO_VALUE = {
    # Binary (2-state)
    'a': 0,
    'b': 1,

    # Trit (3-state) - uses retreat/hold/advance semantics
    # 'a': -1,  # Retreat (mapped below)
    # 'b': 0,   # Hold
    # 'c': +1,  # Advance

    # Quad (4-state)
    # 'a': -2,
    # 'b': -1,
    # 'c': +1,
    # 'd': +2,

    # Octal (8-state)
    # a-h: 0-7 (standard sequential)
}


def get_optimal_delta(cardinality: int) -> float:
    """
    Get optimal DELTA_BASE for given cardinality.

    Args:
        cardinality: Basin count (2, 3, 4, or 8)

    Returns:
        Optimal DELTA_BASE in degrees

    Raises:
        ValueError: If cardinality not supported
    """
    if cardinality not in OPTIMAL_DELTA:
        raise ValueError(f"Unsupported cardinality: {cardinality}. Supported: {list(OPTIMAL_DELTA.keys())}")

    return OPTIMAL_DELTA[cardinality]


def get_safe_range(cardinality: int) -> Tuple[float, float]:
    """
    Get safe DELTA_BASE range for given cardinality.

    Args:
        cardinality: Basin count (2, 3, 4, or 8)

    Returns:
        (min_delta, max_delta) tuple in degrees

    Raises:
        ValueError: If cardinality not supported
    """
    if cardinality not in SAFE_DELTA_RANGES:
        raise ValueError(f"Unsupported cardinality: {cardinality}. Supported: {list(SAFE_DELTA_RANGES.keys())}")

    return SAFE_DELTA_RANGES[cardinality]


def validate_delta(cardinality: int, delta: float) -> bool:
    """
    Check if DELTA_BASE is in safe range for cardinality.

    Args:
        cardinality: Basin count
        delta: DELTA_BASE to validate

    Returns:
        True if delta is in safe range, False otherwise
    """
    min_delta, max_delta = get_safe_range(cardinality)

    # Special case: Binary excludes 45° ± 2° (π/4 singularity)
    if cardinality == 2:
        if 43 <= delta <= 47:
            return False

    return min_delta <= delta <= max_delta


def state_letter_to_value(letter: str, cardinality: int) -> int:
    """
    Convert Helix64 state letter to numeric value for PHI0 calculation.

    Args:
        letter: State letter ('a'-'h')
        cardinality: Basin count (determines mapping)

    Returns:
        Numeric state value

    Raises:
        ValueError: If letter invalid for cardinality
    """
    if cardinality == 2:
        # Binary: a=0, b=1
        mapping = {'a': 0, 'b': 1}
    elif cardinality == 3:
        # Trit: a=-1 (retreat), b=0 (hold), c=+1 (advance)
        mapping = {'a': -1, 'b': 0, 'c': +1}
    elif cardinality == 4:
        # Quad: a=-2, b=-1, c=+1, d=+2
        mapping = {'a': -2, 'b': -1, 'c': +1, 'd': +2}
    elif cardinality == 8:
        # Octal: a-h = 0-7
        mapping = {chr(ord('a') + i): i for i in range(8)}
    else:
        raise ValueError(f"Unsupported cardinality: {cardinality}")

    if letter not in mapping:
        raise ValueError(f"Letter '{letter}' invalid for cardinality={cardinality}. Valid: {list(mapping.keys())}")

    return mapping[letter]


def calculate_phi0(state_letter: str, cardinality: int, delta_base: Optional[float] = None) -> float:
    """
    Calculate PHI0 from state letter, cardinality, and DELTA_BASE.

    Args:
        state_letter: Helix64 state letter ('a'-'h')
        cardinality: Basin count (2, 3, 4, or 8)
        delta_base: Custom DELTA_BASE (if None, uses optimal)

    Returns:
        PHI0 in degrees (0-360)

    Examples:
        >>> calculate_phi0('a', 2)  # Binary state 'a'
        0.0
        >>> calculate_phi0('b', 2)  # Binary state 'b'
        60.0
        >>> calculate_phi0('c', 3)  # Trit state 'c' (advance)
        90.0
    """
    if delta_base is None:
        delta_base = get_optimal_delta(cardinality)

    # Validate delta
    if not validate_delta(cardinality, delta_base):
        raise ValueError(f"DELTA_BASE={delta_base}° outside safe range for cardinality={cardinality}")

    # Convert letter to value
    state_val = state_letter_to_value(state_letter, cardinality)

    # Calculate PHI0 (formula depends on cardinality)
    if cardinality == 2:
        # Binary: PHI0 = state_val × Δ
        phi0 = state_val * delta_base
    elif cardinality == 3:
        # Trit: PHI0 = (state_val × Δ + 360) % 360
        phi0 = (state_val * delta_base + 360) % 360
    elif cardinality == 4:
        # Quad: PHI0 = (state_val × Δ + 720) % 360
        phi0 = (state_val * delta_base + 720) % 360
    elif cardinality == 8:
        # Octal: PHI0 = (state_val × Δ) % 360
        phi0 = (state_val * delta_base) % 360
    else:
        raise ValueError(f"Unsupported cardinality: {cardinality}")

    return phi0


def auto_select_phi0(state_letter: str, cardinality: int) -> Tuple[float, float]:
    """
    Automatically select optimal PHI0 for given state and cardinality.

    Args:
        state_letter: Helix64 state letter
        cardinality: Basin count

    Returns:
        (phi0, delta_base) tuple

    Examples:
        >>> auto_select_phi0('a', 8)  # Octal state 'a'
        (0.0, 25.0)
        >>> auto_select_phi0('h', 8)  # Octal state 'h'
        (175.0, 25.0)
    """
    delta_base = get_optimal_delta(cardinality)
    phi0 = calculate_phi0(state_letter, cardinality, delta_base)

    return phi0, delta_base


if __name__ == "__main__":
    # Self-test
    print("Auto-Δ Selector Self-Test")
    print("=" * 50)

    for card in [2, 3, 4, 8]:
        delta = get_optimal_delta(card)
        range_min, range_max = get_safe_range(card)
        print(f"\nCardinality {card}:")
        print(f"  Optimal Δ: {delta}°")
        print(f"  Safe range: [{range_min}°, {range_max}°]")

        # Test all valid letters
        if card == 2:
            letters = ['a', 'b']
        elif card == 3:
            letters = ['a', 'b', 'c']
        elif card == 4:
            letters = ['a', 'b', 'c', 'd']
        elif card == 8:
            letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']

        print(f"  PHI0 mapping:")
        for letter in letters:
            phi0, _ = auto_select_phi0(letter, card)
            print(f"    {letter} → {phi0:.1f}°")
