"""Central Angle of a Sphere using lat/lon.

https://en.wikipedia.org/wiki/Great-circle_distance

https://en.wikipedia.org/wiki/Haversine_formula

d = theta * r:
    d is distance
    r is the radius of the sphere
    theta is the central angle

d is also computed via haversine, so we can use the haversine formula directly minus
the radius component to find the central angle.

The haversine formula can be better conditioned for small distances.

Supports vectorized operations: pass arrays of shape (N, 2+) for pairwise angles.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

import numpy as np

if TYPE_CHECKING:
    from collections.abc import Sequence

    from numpy.typing import NDArray


def central_angle(
    lla1: NDArray[np.floating] | Sequence,
    lla2: NDArray[np.floating] | Sequence,
) -> NDArray[np.floating] | float:
    """Central Angle between two points assuming a sphere.

    Ignores altitude.

    Supports vectorized operations for pairwise angles between corresponding points.

    Args:
        lla1: LLA coordinate(s). Shape (2+,) for single point or (N, 2+) for N points.
            Only lat/lon (first two elements) are used.
        lla2: LLA coordinate(s). Must match shape of lla1.

    Returns:
        Great circle angle(s) in radians. Returns a float for single point pairs,
        or ndarray of shape (N,) for vectorized input.
    """
    arr1 = np.asarray(lla1)
    arr2 = np.asarray(lla2)

    lat1_rad = np.radians(arr1[..., 0])
    lat2_rad = np.radians(arr2[..., 0])
    lon1_rad = np.radians(arr1[..., 1])
    lon2_rad = np.radians(arr2[..., 1])

    dlat = lat2_rad - lat1_rad
    dlon = lon2_rad - lon1_rad

    hav_arg = (
        1 - np.cos(dlat) + np.cos(lat1_rad) * np.cos(lat2_rad) * (1 - np.cos(dlon))
    ) / 2
    result = 2 * np.arcsin(np.sqrt(hav_arg))

    if result.ndim == 0:
        return float(result)
    return result
