"""
helix_cdc/scheduler/fibpi_nd.py
===============================

FibPi N-Dimensional Low-Discrepancy Points Generator

Generates low-discrepancy points on S^(N-1) spheres using:
- Kronecker/Weyl sequences with golden ratio conjugate
- Cranley-Patterson rotation for seed-based variation
- Equal-area mapping for S² special case
"""

import math
import random
import hashlib
from typing import List, Tuple

# Fixed irrational slopes (Kronecker), derived from golden ratio powers
_GOLD = (1 + 5 ** 0.5) / 2
_G = (math.sqrt(5) - 1) / 2  # golden ratio conjugate

_ALPHAS = [
    1/_GOLD,                    # ~0.618...
    1/_GOLD**2,                 # ~0.381...
    (2 ** 0.5 - 1),             # ~0.414...
    (3 ** 0.5 - 1),             # ~0.732...
    (5 ** 0.5 - 2),             # ~0.236...
    (2 ** 0.5) / _GOLD - 1/2,   # irrational mix
    (3 ** 0.5) / _GOLD - 1/3,
    (5 ** 0.5) / _GOLD - 1/5,
]


def _frac(x: float) -> float:
    return x - math.floor(x)


def _weyl_hash(k: int) -> int:
    """Weyl hash to break k ~ square resonances."""
    return (k * 0x9E3779B97F4A7C15) & ((1 << 64) - 1)


def _phi_inv_cdf(u: float) -> float:
    """Inverse CDF of standard normal via Acklam's approximation."""
    a1,a2,a3 = -39.69683028665376, 220.9460984245205, -275.9285104469687
    a4,a5,a6 = 138.3577518672690, -30.66479806614716, 2.506628277459239
    b1,b2,b3 = -54.47609879822406, 161.5858368580409, -155.6989798598866
    b4,b5    = 66.80131188771972, -13.28068155288572
    c1,c2,c3 = -0.007784894002430293, -0.3223964580411365, -2.400758277161838
    c4,c5,c6 = -2.549732539343734, 4.374664141464968, 2.938163982698783
    d1,d2,d3 = 0.007784695709041462, 0.3224671290700398, 2.445134137142996
    d4       = 3.754408661907416
    pl, ph = 0.02425, 1-0.02425
    if u < pl:
        q = math.sqrt(-2*math.log(u))
        return (((((c1*q+c2)*q+c3)*q+c4)*q+c5)*q+c6)/((((d1*q+d2)*q+d3)*q+d4))
    if u > ph:
        q = math.sqrt(-2*math.log(1-u))
        return -(((((c1*q+c2)*q+c3)*q+c4)*q+c5)*q+c6)/((((d1*q+d2)*q+d3)*q+d4))
    q = u - 0.5
    r = q*q
    return (((((a1*r+a2)*r+a3)*r+a4)*r+a5)*r+a6)*q / (((((b1*r+b2)*r+b3)*r+b4)*r+b5)*r+1)


def fibpi_s2_points(M: int, seed: str = "default") -> List[Tuple[float, float, float]]:
    """
    Generate M points on S² using equal-area mapping.

    Uses Weyl-hashed Kronecker sequence + Cranley-Patterson rotation
    to avoid near-period duplicates.
    """
    rng = random.Random(hashlib.sha256(("fibpi_s2:" + seed).encode()).digest())
    s0, s1 = rng.random(), rng.random()

    pts = []
    for k in range(M):
        kk = _weyl_hash(k)
        kk_norm = kk / (1 << 64)

        u = (kk_norm + s0) % 1.0
        v = (kk_norm * _G + s1) % 1.0

        z = 1.0 - 2.0 * u
        phi = 2.0 * math.pi * v
        r = math.sqrt(max(0.0, 1.0 - z * z))

        pts.append((r * math.cos(phi), r * math.sin(phi), z))

    return pts


def fibpi_nd_points(N: int, M: int, seed: str = "default") -> List[List[float]]:
    """
    Generate M points on S^(N-1) via Kronecker torus -> Gaussian -> normalize.

    For N=3 (S²), use fibpi_s2_points() instead for better distribution.
    """
    if N == 3:
        pts_s2 = fibpi_s2_points(M, seed)
        return [[x, y, z] for x, y, z in pts_s2]

    assert 2 <= N <= len(_ALPHAS), f"N up to {len(_ALPHAS)} supported"

    h = hashlib.sha256(("fibpi_nd:" + seed).encode()).digest()
    offs = [(int.from_bytes(h[d*4:(d+1)*4], "big") / 2**32) for d in range(N)]
    alphas = _ALPHAS[:N]

    rng = random.Random(h)
    cp_shift = [rng.random() for _ in range(N)]

    pts = []
    for i in range(M):
        kk = _weyl_hash(i)
        u = [_frac(offs[d] + cp_shift[d] + (kk / (1 << 64)) * alphas[d]) for d in range(N)]
        z = [_phi_inv_cdf(min(max(uu, 1e-12), 1 - 1e-12)) for uu in u]
        norm = math.sqrt(sum(v*v for v in z)) or 1.0
        pts.append([v / norm for v in z])

    return pts
