"""Shadow boundary extraction for terrain visibility analysis.

Find contours that separate visible terrain from shadowed regions when
viewed from an observer position. These boundaries represent the edges
of terrain shadows cast by elevated features.

Example::

    from gri_utils import terrain
    import numpy as np

    # Create terrain sheet
    sheet = terrain.lla_to_xyz_sheet(lats, lons, alts)

    # Observer on a hilltop
    observer = terrain.lla_to_xyz_sheet(
        np.array([39.05]), np.array([-105.0]), np.array([[2500]])
    )[0, 0]

    # Get shadow boundary contours
    contours = terrain.get_shadow_boundary(sheet, observer)

    # Each contour is an array of XYZ points tracing the shadow edge
    for contour in contours:
        print(f"Contour with {len(contour)} points")
"""

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 get_shadow_boundary(
    sheet_xyz: NDArray[np.floating] | Sequence,
    observer_xyz: NDArray[np.floating] | Sequence,
    *,
    order: int = 3,
) -> list[NDArray[np.floating]]:
    """Find shadow boundaries on terrain from observer viewpoint.

    Returns contours separating visible from shadowed regions by finding
    zero crossings of the shadow depth field.

    Args:
        sheet_xyz: Terrain sheet in ECEF meters. Shape (M, N, 3).
        observer_xyz: Observer position in ECEF meters. Shape (3,).
        order: Interpolation order for contour XYZ conversion.
            0=nearest, 1=bilinear, 3=bicubic (default).

    Returns:
        List of contour arrays, each (K, 3) in ECEF meters, representing
        the boundaries between visible and shadowed terrain.
    """
    from .marching_squares import contours_to_xyz, find_contours  # noqa: PLC0415
    from .shadow_depth import get_shadow_depth  # noqa: PLC0415

    sheet_xyz = np.asarray(sheet_xyz, dtype=np.float64)
    observer_xyz = np.asarray(observer_xyz, dtype=np.float64)

    depth = get_shadow_depth(sheet_xyz, observer_xyz)
    contours = find_contours(depth, level=0.0)
    return contours_to_xyz(contours, sheet_xyz, order=order)
