"""Spatial Utilities and layer querying Module.

Distance calculations, azimuth, and basic spatial operations.
"""

from __future__ import annotations

import math
from typing import Any

from qgis.core import (
    QgsCoordinateReferenceSystem,
    QgsDistanceArea,
    QgsGeometry,
    QgsPointXY,
    QgsProject,
    QgsWkbTypes,
)


def calculate_line_azimuth(line_geom: QgsGeometry) -> float:
    """Calculate the azimuth (compass bearing) of a line geometry.

    Calculates the azimuth based on the first two vertices of the line.
    Returns 0 for points or single-vertex lines.

    Args:
        line_geom: The QGIS line geometry.

    Returns:
        Azimuth in degrees (0-360).

    """
    if line_geom.wkbType() == QgsWkbTypes.Point:
        return 0  # Points have no azimuth

    line = (
        line_geom.asMultiPolyline()[0]
        if line_geom.isMultipart()
        else line_geom.asPolyline()
    )

    MIN_REQUIRED_POINTS = 2
    if len(line) < MIN_REQUIRED_POINTS:
        return 0
    # Calculate azimuth of first segment (from first to second point)
    p1 = line[0]
    p2 = line[1]
    azimuth = math.degrees(math.atan2(p2.x() - p1.x(), p2.y() - p1.y()))
    # Convert to compass bearing (0-360)
    if azimuth < 0:
        azimuth += 360
    return azimuth

    # For other geometry types, return a default value
    return 0


def calculate_step_size(geom: QgsGeometry, raster_lyr: Any) -> float:
    """Calculate step size based on slope and raster resolution.

    .. deprecated::
        Use densify_line_by_interval() instead for better precision and simpler code.
        This function is kept for backward compatibility but may be removed in future versions.

    Ensures that sampling occurs at approximately one pixel intervals,
    accounting for the slope of the line relative to the raster grid.

    Args:
        geom: The geometry to sample along.
        raster_lyr: The raster layer being sampled.

    Returns:
        Calculated step size in map units.

    """
    # Get raster resolution
    res = raster_lyr.rasterUnitsPerPixelX()

    # Calculate step size based on slope to ensure 1 pixel sampling
    dist_step = res
    try:
        if geom.isMultipart():
            parts = geom.asMultiPolyline()
            line_pts = parts[0] if parts else []
        else:
            line_pts = geom.asPolyline()

        MIN_REQUIRED_POINTS = 2
        if line_pts and len(line_pts) >= MIN_REQUIRED_POINTS:
            p1 = line_pts[0]
            p2 = line_pts[-1]
            dx = abs(p2.x() - p1.x())
            dy = abs(p2.y() - p1.y())
            if max(dx, dy) > 0:
                dist_step = geom.length() * res / max(dx, dy)
    except (ValueError, TypeError):
        # Fallback to simple resolution if geometry parsing fails
        pass
    return dist_step  # type: ignore[no-any-return]


def get_line_start_point(geometry: QgsGeometry) -> QgsPointXY:
    """Get the start point of a line geometry.

    Handles both singlepart and multipart line geometries.

    Args:
        geometry: The line geometry.

    Returns:
        The first vertex of the first part of the geometry.

    """
    if geometry.isMultipart():
        return geometry.asMultiPolyline()[0][0]

    polyline = geometry.asPolyline()
    if not polyline:
        from sec_interp.core.exceptions import GeometryError

        raise GeometryError("Polyline has no vertices")
    return polyline[0]


def create_distance_area(crs: QgsCoordinateReferenceSystem) -> QgsDistanceArea:
    """Create and configure a QgsDistanceArea object.

    Configures the distance area with the provided CRS, project transform context,
    and associated ellipsoid for geodesic calculations.

    Args:
        crs: The Coordinate Reference System to use.

    Returns:
        The configured distance calculation object.

    """
    da = QgsDistanceArea()
    da.setSourceCrs(crs, QgsProject.instance().transformContext())
    da.setEllipsoid(crs.ellipsoidAcronym())
    return da
