"""Transform XYZ (ECEF) vectors <=> CCR (Clock, Cone, Range) vectors.

Clock/Cone/Range is an alternative to Azimuth/Elevation/Range used in some
direction-finding applications (see US Patent 20110309983A1).

Relationship to AER:
- Clock = Azimuth (identical)
- Cone = -Elevation
- Range = Range (identical)

XYZ is in meters and CCR is in degrees, degrees, meters. Takes and returns numpy arrays.

# Get the CCR vector between the two positions
get_ccr(xyz_pos1,xyz_pos2) -> ccr

# Get the new XYZ location shifted by this CCR vector
translate_ccr(xyz_pos, ccr) -> xyz_pos

# Convert the XYZ vector to a CCR vector
xyz_to_ccr(xyz_pos, xyz_vec) -> ccr

# Convert the CCR vector to an XYZ vector
ccr_to_xyz(xyz_pos, ccr) -> xyz_vec
"""

from __future__ import annotations

from typing import TYPE_CHECKING

import numpy as np

from .xyz_aer import aer_to_xyz, get_aer, translate_aer, xyz_to_aer

if TYPE_CHECKING:
    from collections.abc import Sequence

    from numpy.typing import NDArray


def get_ccr(
    xyz_pos1_m: NDArray[np.floating] | Sequence,
    xyz_pos2_m: NDArray[np.floating] | Sequence,
) -> NDArray[np.floating]:
    """Return the CCR vector from pos1 to pos2 in the plane of pos1.

    Args:
        xyz_pos1_m(np.ndarray|Sequence): XYZ reference location in meters. Shape (3,).
        xyz_pos2_m(np.ndarray|Sequence): XYZ target location(s) in meters. Shape (3,)
            or (N, 3).

    Returns:
        np.ndarray: CCR vector(s) in the plane of pos1, pointed to pos2. In degrees
            (0,360), degrees (-90=zenith, 0=horizon, 90=nadir), meters. Shape (3,) or
            (N, 3) matching xyz_pos2_m.
    """
    aer = get_aer(xyz_pos1_m, xyz_pos2_m)
    return np.stack((aer[..., 0], -aer[..., 1], aer[..., 2]), axis=-1)


def translate_ccr(
    xyz_pos_m: NDArray[np.floating] | Sequence,
    ccr_ddm: NDArray[np.floating] | Sequence,
) -> NDArray[np.floating]:
    """Return the XYZ location of pos shifted by this CCR.

    Args:
        xyz_pos_m(np.ndarray|Sequence): XYZ reference location in meters. Shape (3,).
        ccr_ddm(np.ndarray|Sequence): CCR vector(s) from pos in degrees, degrees,
            meters.  Shape (3,) or (N, 3).

    Returns:
        np.ndarray: XYZ location(s) pointed at by ccr in meters. Shape (3,) or (N, 3)
            matching ccr_ddm.
    """
    ccr_ddm = np.asarray(ccr_ddm)
    aer_ddm = np.stack((ccr_ddm[..., 0], -ccr_ddm[..., 1], ccr_ddm[..., 2]), axis=-1)
    return translate_aer(xyz_pos_m, aer_ddm)


def xyz_to_ccr(
    xyz_pos_m: NDArray[np.floating] | Sequence,
    xyz_vec_m: NDArray[np.floating] | Sequence,
) -> NDArray[np.floating]:
    """Convert the XYZ vector to CCR in the plane of the XYZ position.

    Args:
        xyz_pos_m(np.ndarray|Sequence): XYZ reference location in meters. Shape (3,).
        xyz_vec_m(np.ndarray|Sequence): XYZ vector(s) from that location in meters.
            Shape (3,) or (N, 3).

    Returns:
        np.ndarray: CCR vector(s) in the plane of pos in degrees(0,360),
            degrees(-90=zenith, 0=horizon, 90=nadir), meters. Shape (3,) or (N, 3)
            matching xyz_vec_m.
    """
    aer = xyz_to_aer(xyz_pos_m, xyz_vec_m)
    return np.stack((aer[..., 0], -aer[..., 1], aer[..., 2]), axis=-1)


def ccr_to_xyz(
    xyz_pos_m: NDArray[np.floating] | Sequence,
    ccr_ddm: NDArray[np.floating] | Sequence,
) -> NDArray[np.floating]:
    """Convert the CCR vector to an XYZ vector.

    Args:
        xyz_pos_m(np.ndarray|Sequence): XYZ reference location in meters. Shape (3,).
        ccr_ddm(np.ndarray|Sequence): CCR vector(s) from pos in degrees, degrees,
            meters.  Shape (3,) or (N, 3).

    Returns:
        np.ndarray: XYZ vector(s) from pos in meters. Shape (3,) or (N, 3) matching
            ccr_ddm.
    """
    ccr_ddm = np.asarray(ccr_ddm)
    aer_ddm = np.stack((ccr_ddm[..., 0], -ccr_ddm[..., 1], ccr_ddm[..., 2]), axis=-1)
    return aer_to_xyz(xyz_pos_m, aer_ddm)
