"""Geodetic coordinate conversions for GNSS applications.

Provides ECEF (Earth-Centered, Earth-Fixed) to WGS84 geodetic coordinate
conversion using the standard iterative algorithm. Zero external dependencies.
"""

import math

# WGS84 ellipsoid parameters
WGS84_A = 6378137.0  # Semi-major axis (equatorial radius) in meters
WGS84_F = 1.0 / 298.257223563  # Flattening
WGS84_B = WGS84_A * (1.0 - WGS84_F)  # Semi-minor axis (polar radius)
WGS84_E2 = 2.0 * WGS84_F - WGS84_F ** 2  # First eccentricity squared
WGS84_EP2 = (WGS84_A ** 2 - WGS84_B ** 2) / WGS84_B ** 2  # Second eccentricity squared


def _prime_vertical_radius(sin_lat: float) -> float:
    """Compute the radius of curvature in the prime vertical (N).

    N = a / sqrt(1 - e^2 * sin^2(lat))
    """
    return WGS84_A / math.sqrt(1.0 - WGS84_E2 * sin_lat ** 2)


def ecef_to_wgs84(x: float, y: float, z: float) -> tuple[float, float, float]:
    """Convert ECEF coordinates to WGS84 geodetic coordinates.

    Uses the standard iterative (Bowring-type) method. Converges in
    3-5 iterations for any point on or near Earth's surface.

    Args:
        x: ECEF X coordinate in meters.
        y: ECEF Y coordinate in meters.
        z: ECEF Z coordinate in meters.

    Returns:
        Tuple of (latitude_deg, longitude_deg, altitude_m) where:
        - latitude is in decimal degrees, positive north
        - longitude is in decimal degrees, positive east
        - altitude is height above the WGS84 ellipsoid in meters
    """
    # Longitude is straightforward
    lon = math.atan2(y, x)

    # Distance from Z axis
    p = math.sqrt(x ** 2 + y ** 2)

    # Handle degenerate case at poles (p ~ 0)
    if p < 1e-10:
        lat = math.copysign(math.pi / 2.0, z)
        alt = abs(z) - WGS84_B
        return (math.degrees(lat), math.degrees(lon), alt)

    # Initial estimate: use parametric latitude (Bowring's starting point)
    theta = math.atan2(z * WGS84_A, p * WGS84_B)
    lat = math.atan2(
        z + WGS84_EP2 * WGS84_B * math.sin(theta) ** 3,
        p - WGS84_E2 * WGS84_A * math.cos(theta) ** 3,
    )

    # Iterate to convergence
    for _ in range(10):
        sin_lat = math.sin(lat)
        cos_lat = math.cos(lat)
        n = _prime_vertical_radius(sin_lat)
        alt = p / cos_lat - n

        lat_new = math.atan2(z, p * (1.0 - WGS84_E2 * n / (n + alt)))
        if abs(lat_new - lat) < 1e-12:
            break
        lat = lat_new

    return (math.degrees(lat), math.degrees(lon), alt)
