"""Julian Date conversions.

Julian Date (JD) is a continuous count of days since the beginning of the Julian
Period (January 1, 4713 BC in the proleptic Julian calendar). It is widely used
in astronomy and is essential for coordinate system transformations involving
Earth's rotation (e.g., ECEF to ECI).

Key constants:
    - J2000.0 epoch: JD 2451545.0 (January 1, 2000, 12:00:00 TT)
    - Unix epoch to JD offset: 2440587.5 (January 1, 1970, 00:00:00 UTC)

References:
    - Astronomical Algorithms, Jean Meeus (2nd ed.)
    - USNO Circular 179
"""

from __future__ import annotations

from datetime import UTC, datetime
from typing import TYPE_CHECKING

import numpy as np

if TYPE_CHECKING:
    from collections.abc import Sequence

    from numpy.typing import NDArray

# Julian Date at Unix epoch (1970-01-01 00:00:00 UTC)
JD_UNIX_EPOCH = 2440587.5

# Julian Date at J2000.0 epoch (2000-01-01 12:00:00 TT)
JD_J2000 = 2451545.0

# Seconds per day
SECONDS_PER_DAY = 86400.0


def unix_to_jd(
    unix_s: float | NDArray[np.floating] | Sequence[float],
) -> NDArray[np.floating]:
    """Convert Unix timestamp (seconds since 1970-01-01) to Julian Date.

    Args:
        unix_s: Unix timestamp in seconds. Can be a scalar, sequence, or array.

    Returns:
        Julian Date as numpy array.

    Examples:
        >>> unix_to_jd(0.0)  # Unix epoch
        array(2440587.5)
        >>> unix_to_jd(946728000.0)  # 2000-01-01 12:00:00 UTC
        array(2451545.0)
    """
    unix_arr = np.asarray(unix_s, dtype=np.float64)
    return unix_arr / SECONDS_PER_DAY + JD_UNIX_EPOCH


def jd_to_unix(
    jd: float | NDArray[np.floating] | Sequence[float],
) -> NDArray[np.floating]:
    """Convert Julian Date to Unix timestamp (seconds since 1970-01-01).

    Args:
        jd: Julian Date. Can be a scalar, sequence, or array.

    Returns:
        Unix timestamp in seconds as numpy array.

    Examples:
        >>> jd_to_unix(2440587.5)  # Unix epoch
        array(0.)
        >>> jd_to_unix(2451545.0)  # J2000.0
        array(946728000.)
    """
    jd_arr = np.asarray(jd, dtype=np.float64)
    return (jd_arr - JD_UNIX_EPOCH) * SECONDS_PER_DAY


def datetime_to_jd(dt: datetime) -> float:
    """Convert a datetime object to Julian Date.

    The datetime is converted to UTC if timezone-aware, or assumed to be UTC
    if naive.

    Args:
        dt: A datetime object. If timezone-naive, assumed to be UTC.

    Returns:
        Julian Date as a float.

    Examples:
        >>> from datetime import datetime, UTC
        >>> datetime_to_jd(datetime(1970, 1, 1, tzinfo=UTC))
        2440587.5
        >>> datetime_to_jd(datetime(2000, 1, 1, 12, 0, 0, tzinfo=UTC))
        2451545.0
    """
    if dt.tzinfo is None:
        dt = dt.replace(tzinfo=UTC)
    unix_s = dt.timestamp()
    return float(unix_to_jd(unix_s))


def jd_to_datetime(jd: float) -> datetime:
    """Convert Julian Date to a timezone-aware datetime object (UTC).

    Args:
        jd: Julian Date.

    Returns:
        Timezone-aware datetime object in UTC.

    Examples:
        >>> jd_to_datetime(2440587.5)
        datetime.datetime(1970, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)
        >>> jd_to_datetime(2451545.0)
        datetime.datetime(2000, 1, 1, 12, 0, tzinfo=datetime.timezone.utc)
    """
    unix_s = float(jd_to_unix(jd))
    return datetime.fromtimestamp(unix_s, tz=UTC)
