# flightmonitor/map/map_drawing.py

import logging
import math
from typing import Optional, Tuple, List, Dict, Any
from collections import deque  # Aggiunto per il type hinting di track_deque

try:
    from PIL import Image, ImageDraw, ImageFont, ImageColor

    PIL_LIB_AVAILABLE_DRAWING = True
except ImportError:
    Image = None  # type: ignore
    ImageDraw = None  # type: ignore
    ImageFont = None  # type: ignore
    ImageColor = None  # type: ignore
    PIL_LIB_AVAILABLE_DRAWING = False
    import logging

    logging.error("MapDrawing: Pillow not found. Drawing disabled.")

try:
    import cv2
    import numpy as np

    CV2_NUMPY_LIBS_AVAILABLE_DRAWING = True
except ImportError:
    cv2 = None  # type: ignore
    np = None  # type: ignore
    CV2_NUMPY_LIBS_AVAILABLE_DRAWING = False
    import logging

    logging.warning("MapDrawing: OpenCV or NumPy not found. Some drawing ops disabled.")

try:
    import mercantile

    MERCANTILE_LIB_AVAILABLE_DRAWING = True
except ImportError:
    mercantile = None  # type: ignore
    MERCANTILE_LIB_AVAILABLE_DRAWING = False
    import logging

    logging.error("MapDrawing: 'mercantile' not found. Conversions fail.")

from .map_utils import get_hgt_tile_geographic_bounds  # type: ignore
from .map_utils import MapCalculationError  # type: ignore
from .map_utils import MERCANTILE_MODULE_LOCALLY_AVAILABLE  # type: ignore

try:
    from ..data.common_models import CanonicalFlightState

    CANONICAL_FLIGHT_STATE_AVAILABLE_DRAWING = True
except ImportError:
    CANONICAL_FLIGHT_STATE_AVAILABLE_DRAWING = False

    class CanonicalFlightState:  # type: ignore
        pass

    import logging

    logging.warning("MapDrawing: CanonicalFlightState not found.")

try:
    from ..utils.logger import get_logger

    logger = get_logger(__name__)
except ImportError:
    logger = logging.getLogger(__name__)
    logger.warning("MapDrawing: App logger not available.")

try:
    from . import map_constants
    from ..data import config as app_config  # Importa app_config

    MAP_CONSTANTS_AVAILABLE_DRAWING = True
    # Leggi i valori da app_config, con fallback a map_constants o valori hardcoded
    TRACK_HISTORY_POINTS = getattr(
        app_config,
        "DEFAULT_TRACK_HISTORY_POINTS",
        getattr(map_constants, "DEFAULT_TRACK_HISTORY_POINTS", 20),
    )
    TRACK_LINE_WIDTH = getattr(
        app_config,
        "DEFAULT_TRACK_LINE_WIDTH",
        getattr(map_constants, "DEFAULT_TRACK_LINE_WIDTH", 2),
    )
except ImportError:
    MAP_CONSTANTS_AVAILABLE_DRAWING = False
    logger = logging.getLogger(__name__)
    logger.warning(
        "MapDrawing: map_constants or app_config not available. Using hardcoded drawing defaults."
    )

    class MockMapConstantsDrawing:  # Definizione di fallback per map_constants
        DEM_BOUNDARY_COLOR = "red"
        DEM_BOUNDARY_THICKNESS_PX = 3
        AREA_BOUNDARY_COLOR = "blue"
        AREA_BOUNDARY_THICKNESS_PX = 2
        TILE_TEXT_COLOR = "white"
        TILE_TEXT_BG_COLOR = "rgba(0, 0, 0, 150)"
        DEM_TILE_LABEL_BASE_FONT_SIZE = 12
        DEM_TILE_LABEL_BASE_ZOOM = 10
        DEFAULT_LABEL_FONT_PATH = None
        COORDINATE_DECIMAL_PLACES = 4
        TRACK_COLOR_PALETTE = [
            "#FF0000",
            "#00FF00",
            "#0000FF",
            "#FFFF00",
            "#FF00FF",
            "#00FFFF",
            "#800000",
            "#008000",
            "#000080",
            "#808000",
            "#800080",
            "#008080",
            "#C0C0C0",
            "#400000",
            "#004000",
            "#000040",
            "#404000",
            "#400040",
            "#004040",
            "#202020",
            "#FF8000",
            "#80FF00",
            "#00FF80",
            "#8000FF",
            "#FF0080",
            "#0080FF",
            "#808080",
            "#A0522D",
            "#D2691E",
            "#DAA520",
            "#BDB76B",
            "#8FBC8F",
            "#FA8072",
            "#E9967A",
            "#F08080",
            "#CD5C5C",
            "#DC143C",
            "#B22222",
            "#FF69B4",
            "#FF1493",
            "#C71585",
            "#DB7093",
            "#E6E6FA",
            "#D8BFD8",
            "#DDA0DD",
            "#EE82EE",
            "#9932CC",
            "#8A2BE2",
            "#BA55D3",
            "#9370DB",
            "#7B68EE",
            "#6A5ACD",
            "#483D8B",
            "#708090",
            "#778899",
            "#B0C4DE",
            "#ADD8E6",
            "#87CEEB",
            "#87CEFA",
            "#00BFFF",
            "#1E90FF",
            "#6495ED",
            "#4682B4",
            "#0000CD",
            "#00008B",
        ]
        DEFAULT_TRACK_HISTORY_POINTS = 20
        DEFAULT_TRACK_LINE_WIDTH = 2

    map_constants = MockMapConstantsDrawing()
    TRACK_HISTORY_POINTS = map_constants.DEFAULT_TRACK_HISTORY_POINTS
    TRACK_LINE_WIDTH = map_constants.DEFAULT_TRACK_LINE_WIDTH


# --- Helper for Geo to Pixel Conversion on Unscaled Stitched Image ---


def _geo_to_pixel_on_unscaled_map(
    latitude_deg: float,
    longitude_deg: float,
    current_map_geo_bounds: Optional[Tuple[float, float, float, float]],
    current_stitched_map_pixel_shape: Optional[Tuple[int, int]],  # (width, height)
) -> Optional[Tuple[int, int]]:
    """Convert geographic to pixel on unscaled stitched image. Requires mercantile."""
    logger.debug(
        f"Attempting to convert geo ({latitude_deg:.5f}, {longitude_deg:.5f}) to pixel."
    )

    if not MERCANTILE_LIB_AVAILABLE_DRAWING or mercantile is None:
        logger.error(
            "_geo_to_pixel_on_unscaled_map: Mercantile missing or not available."
        )
        return None
    if current_map_geo_bounds is None:
        logger.warning("_geo_to_pixel_on_unscaled_map: Map geo bounds missing.")
        return None
    if current_stitched_map_pixel_shape is None:
        logger.warning("_geo_to_pixel_on_unscaled_map: Map pixel shape missing.")
        return None

    if not (-90.0 <= latitude_deg <= 90.0 and -180.0 <= longitude_deg <= 180.0):
        logger.debug(
            f"Input geo ({latitude_deg:.4f}, {longitude_deg:.4f}) out of range. Clamping."
        )
        latitude_deg = max(-85.05112878, min(85.05112878, latitude_deg))
        longitude_deg = (longitude_deg + 180.0) % 360.0 - 180.0
        logger.debug(f"Clamped geo: ({latitude_deg:.4f}, {longitude_deg:.4f})")

    img_w, img_h = current_stitched_map_pixel_shape
    map_west_lon, map_south_lat, map_east_lon, map_north_lat = current_map_geo_bounds

    logger.debug(
        f"Map geo bounds used: W={map_west_lon}, S={map_south_lat}, E={map_east_lon}, N={map_north_lat}"
    )
    logger.debug(f"Stitched image pixel shape: {img_w}x{img_h}")

    if img_w <= 0 or img_h <= 0:
        logger.warning(f"Map pixel dims invalid ({img_w}x{img_h}). Cannot convert.")
        return None

    try:
        map_ul_merc_x, map_ul_merc_y = mercantile.xy(map_west_lon, map_north_lat)
        map_lr_merc_x, map_lr_merc_y = mercantile.xy(map_east_lon, map_south_lat)

        total_map_width_merc = abs(map_lr_merc_x - map_ul_merc_x)
        total_map_height_merc = abs(
            map_ul_merc_y - map_lr_merc_y
        )  # Mercator Y decreases with increasing latitude

        logger.debug(
            f"Map Mercator extent: UL=({map_ul_merc_x:.2f}, {map_ul_merc_y:.2f}), LR=({map_lr_merc_x:.2f}, {map_lr_merc_y:.2f})"
        )
        logger.debug(
            f"Total Mercator size: W={total_map_width_merc:.2f}, H={total_map_height_merc:.2f}"
        )

        if total_map_width_merc <= 1e-9 or total_map_height_merc <= 1e-9:
            logger.warning(
                f"Map mercator dims zero/near-zero ({total_map_width_merc:.2e}x{total_map_height_merc:.2e}). Cannot convert."
            )
            return None

        target_merc_x, target_merc_y = mercantile.xy(longitude_deg, latitude_deg)
        logger.debug(
            f"Target Geo ({longitude_deg:.5f}, {latitude_deg:.5f}) in Mercator: ({target_merc_x:.2f}, {target_merc_y:.2f})"
        )

        relative_x_on_mercator = (
            (target_merc_x - map_ul_merc_x) / total_map_width_merc
            if total_map_width_merc > 0
            else 0.0
        )
        relative_y_on_mercator = (
            (map_ul_merc_y - target_merc_y) / total_map_height_merc
            if total_map_height_merc > 0
            else 0.0
        )

        pixel_x_on_unscaled_raw = relative_x_on_mercator * img_w
        pixel_y_on_unscaled_raw = relative_y_on_mercator * img_h

        logger.debug(
            f"Relative Mercator ({relative_x_on_mercator:.4f}, {relative_y_on_mercator:.4f}) -> Raw Pixel ({pixel_x_on_unscaled_raw:.2f}, {pixel_y_on_unscaled_raw:.2f})"
        )

        rounded_pixel_x = int(round(pixel_x_on_unscaled_raw))
        rounded_pixel_y = int(round(pixel_y_on_unscaled_raw))

        clamp_margin = 10
        px_clamped = max(-clamp_margin, min(rounded_pixel_x, img_w + clamp_margin))
        py_clamped = max(-clamp_margin, min(rounded_pixel_y, img_h + clamp_margin))

        logger.debug(
            f"Rounded Pixel ({rounded_pixel_x}, {rounded_pixel_y}) -> Clamped Pixel ({px_clamped}, {py_clamped})"
        )
        logger.debug(
            f"Geo ({latitude_deg:.5f}, {longitude_deg:.5f}) -> Pixel ({px_clamped}, {py_clamped})"
        )

        return (px_clamped, py_clamped)

    except Exception as e_geo_to_px_unscaled:
        logger.error(
            f"Error during geo-to-pixel conversion for geo ({latitude_deg:.5f}, {longitude_deg:.5f}): {e_geo_to_px_unscaled}",
            exc_info=True,
        )
        return None


# --- Drawing Functions ---


def draw_point_marker(
    pil_image_to_draw_on: Image.Image,
    latitude_deg: float,
    longitude_deg: float,
    current_map_geo_bounds: Optional[Tuple[float, float, float, float]],
    current_stitched_map_pixel_shape: Optional[Tuple[int, int]],
) -> Image.Image:
    """Draw point marker on PIL Image. Requires OpenCV/NumPy."""
    if (
        not PIL_LIB_AVAILABLE_DRAWING
        or not CV2_NUMPY_LIBS_AVAILABLE_DRAWING
        or cv2 is None
        or np is None
        or pil_image_to_draw_on is None
        or current_map_geo_bounds is None
        or current_stitched_map_pixel_shape is None
    ):
        logger.warning("Cannot draw marker: Libs/context missing.")
        return pil_image_to_draw_on

    pixel_coords_on_unscaled = _geo_to_pixel_on_unscaled_map(
        latitude_deg,
        longitude_deg,
        current_map_geo_bounds,
        current_stitched_map_pixel_shape,
    )

    if pixel_coords_on_unscaled:
        px_clamped, py_clamped = pixel_coords_on_unscaled

        img_w, img_h = current_stitched_map_pixel_shape
        px_strict = max(0, min(px_clamped, img_w - 1))
        py_strict = max(0, min(py_clamped, img_h - 1))

        if cv2 and np:
            try:
                if pil_image_to_draw_on.mode != "RGBA":
                    if Image:
                        pil_image_to_draw_on = pil_image_to_draw_on.convert("RGBA")
                    else:
                        return pil_image_to_draw_on

                map_cv_rgba = np.array(pil_image_to_draw_on)
                map_cv_bgr = cv2.cvtColor(map_cv_rgba, cv2.COLOR_RGBA2BGR)

                cv2.drawMarker(
                    map_cv_bgr,
                    (px_strict, py_strict),
                    (0, 0, 255),
                    cv2.MARKER_CROSS,
                    markerSize=15,
                    thickness=2,
                )

                if Image:
                    return Image.fromarray(cv2.cvtColor(map_cv_bgr, cv2.COLOR_BGR2RGB))
                else:
                    return pil_image_to_draw_on

            except Exception as e:
                logger.exception(f"Error drawing marker with OpenCV: {e}")
                return pil_image_to_draw_on
        else:
            logger.warning("OpenCV or NumPy not available for drawing point marker.")
            return pil_image_to_draw_on
    else:
        logger.debug(
            f"Geo-to-pixel failed for ({latitude_deg:.5f},{longitude_deg:.5f}), cannot draw marker."
        )
        return pil_image_to_draw_on


def draw_area_bounding_box(
    pil_image_to_draw_on: Image.Image,
    area_geo_bbox: Tuple[float, float, float, float],
    current_map_geo_bounds: Optional[Tuple[float, float, float, float]],
    current_stitched_map_pixel_shape: Optional[Tuple[int, int]],  # (width, height)
    color: str = map_constants.AREA_BOUNDARY_COLOR,
    thickness: int = map_constants.AREA_BOUNDARY_THICKNESS_PX,
) -> Image.Image:
    """Draw BBox on PIL Image. Requires PIL/ImageDraw/Mercantile."""
    logger.debug(f"Attempting to draw area bounding box {area_geo_bbox}.")

    if (
        not PIL_LIB_AVAILABLE_DRAWING
        or ImageDraw is None
        or pil_image_to_draw_on is None
        or current_map_geo_bounds is None
        or current_stitched_map_pixel_shape is None
    ):
        logger.warning("Cannot draw BBox: Libs/context missing.")
        return pil_image_to_draw_on
    if not MERCANTILE_LIB_AVAILABLE_DRAWING or mercantile is None:
        logger.error("Mercantile missing or not available for BBox drawing.")
        return pil_image_to_draw_on

    west, south, east, north = area_geo_bbox
    corners_geo = [(west, north), (east, north), (east, south), (west, south)]
    pixel_corners: List[Tuple[int, int]] = []

    try:
        img_w, img_h = current_stitched_map_pixel_shape

        for lon, lat in corners_geo:
            pixel_coords = _geo_to_pixel_on_unscaled_map(
                lat, lon, current_map_geo_bounds, current_stitched_map_pixel_shape
            )
            logger.debug(
                f"Converted BBox corner geo ({lat:.5f}, {lon:.5f}) to pixel {pixel_coords}."
            )

            if pixel_coords:
                margin = max(img_w, img_h)
                px, py = pixel_coords
                if not (
                    -margin <= px < img_w + margin and -margin <= py < img_h + margin
                ):
                    logger.debug(
                        f"BBox corner pixel ({px},{py}) outside generous drawing margin [{ -margin }:{ img_w + margin }, { -margin }:{ img_h + margin }]. Skipping BBox draw."
                    )
                    return pil_image_to_draw_on

                pixel_corners.append(pixel_coords)
            else:
                logger.warning(
                    f"Failed to convert corner geo ({lat:.4f}, {lon:.4f}). Aborting BBox draw."
                )
                return pil_image_to_draw_on

    except Exception as e:
        logger.exception(f"Error geo-to-pixel for BBox corners: {e}")
        return pil_image_to_draw_on

    if len(pixel_corners) == 4 and ImageDraw is not None:
        if pil_image_to_draw_on.mode not in ("RGB", "RGBA"):
            if Image:
                pil_image_to_draw_on = pil_image_to_draw_on.convert("RGBA")
            else:
                logger.error("Pillow Image missing for RGBA conversion for BBox.")
                return pil_image_to_draw_on

        draw = ImageDraw.Draw(pil_image_to_draw_on)
        try:
            draw.line([pixel_corners[0], pixel_corners[1]], fill=color, width=thickness)
            draw.line([pixel_corners[1], pixel_corners[2]], fill=color, width=thickness)
            draw.line([pixel_corners[2], pixel_corners[3]], fill=color, width=thickness)
            draw.line([pixel_corners[3], pixel_corners[0]], fill=color, width=thickness)
            logger.debug(f"Drew lines for BBox corners: {pixel_corners}")
        except Exception as e:
            logger.exception(f"Error drawing BBox lines: {e}")

        return pil_image_to_draw_on
    else:
        logger.warning(
            f"Not 4 pixel corners ({len(pixel_corners)}) or ImageDraw missing. Cannot draw BBox."
        )
        return pil_image_to_draw_on


def draw_dem_tile_boundary(
    pil_image_to_draw_on: Image.Image,
    dem_tile_geo_bbox: Tuple[float, float, float, float],
    current_map_geo_bounds: Optional[Tuple[float, float, float, float]],
    current_map_render_zoom: Optional[int],
    current_stitched_map_pixel_shape: Optional[Tuple[int, int]],  # (width, height)
) -> Image.Image:
    """Draw DEM tile boundary on PIL Image. Uses map_constants."""
    if (
        not PIL_LIB_AVAILABLE_DRAWING
        or pil_image_to_draw_on is None
        or current_map_geo_bounds is None
        or current_map_render_zoom is None
        or current_stitched_map_pixel_shape is None
    ):
        logger.warning("Cannot draw DEM boundary: PIL/context missing.")
        return pil_image_to_draw_on

    return draw_area_bounding_box(
        pil_image_to_draw_on,
        dem_tile_geo_bbox,
        current_map_geo_bounds,
        current_stitched_map_pixel_shape,
        color=map_constants.DEM_BOUNDARY_COLOR,
        thickness=map_constants.DEM_BOUNDARY_THICKNESS_PX,
    )


def _load_label_font(
    scaled_font_size: int,
) -> Optional[ImageFont.FreeTypeFont | ImageFont.ImageFont]:
    """Load TrueType font or default PIL font."""
    if not PIL_LIB_AVAILABLE_DRAWING or ImageFont is None:
        logger.warning("_load_label_font: Pillow/ImageFont missing.")
        return None

    font_to_use: Optional[ImageFont.FreeTypeFont | ImageFont.ImageFont] = None
    scaled_font_size = max(1, scaled_font_size)

    if (
        hasattr(map_constants, "DEFAULT_LABEL_FONT_PATH")
        and map_constants.DEFAULT_LABEL_FONT_PATH
    ):
        try:
            if hasattr(ImageFont, "truetype"):
                font_to_use = ImageFont.truetype(
                    map_constants.DEFAULT_LABEL_FONT_PATH, scaled_font_size
                )
                return font_to_use
            else:
                logger.warning("ImageFont.truetype not available.")
        except FileNotFoundError:
            logger.warning(
                f"Label font file not found: {map_constants.DEFAULT_LABEL_FONT_PATH}. Falling back."
            )
        except Exception as e:
            logger.warning(
                f"Error loading TrueType font: {e}. Falling back.", exc_info=False
            )

    try:
        if hasattr(ImageFont, "load_default"):
            font_to_use = ImageFont.load_default()
        else:
            logger.warning("ImageFont.load_default not available.")
            return None
    except Exception as e:
        logger.warning(f"Error loading default PIL font: {e}.")
        font_to_use = None

    if font_to_use is None:
        logger.error("Failed to load any font for labels.")
    return font_to_use


def _draw_text_on_placeholder(
    draw: ImageDraw.ImageDraw, image_size: Tuple[int, int], text_to_draw: str
):
    """Draw text on placeholder image."""
    img_width, img_height = image_size
    if not PIL_LIB_AVAILABLE_DRAWING or ImageDraw is None or ImageFont is None:
        logger.warning(
            "Pillow/ImageDraw/ImageFont missing, cannot draw text on placeholder."
        )
        return

    font_to_use: Optional[ImageFont.FreeTypeFont | ImageFont.ImageFont] = None
    try:
        if hasattr(ImageFont, "load_default"):
            font_to_use = ImageFont.load_default()
        else:
            logger.warning("ImageFont.load_default not available for placeholder.")
    except Exception as e:
        logger.warning(f"Error loading default PIL font for placeholder: {e}.")

    if font_to_use is None:
        logger.warning("No font available for drawing placeholder text. Drawing basic.")
        try:
            draw.text((10, 10), text_to_draw, fill="black")
        except Exception as e:
            logger.error(f"Error drawing basic placeholder text: {e}", exc_info=False)
        return

    try:
        text_width, text_height = 0, 0
        if hasattr(draw, "textbbox"):
            try:
                text_bbox = draw.textbbox(
                    (0, 0), text_to_draw, font=font_to_use, spacing=2
                )
                text_width, text_height = (
                    text_bbox[2] - text_bbox[0],
                    text_bbox[3] - text_bbox[1],
                )
            except Exception as e:
                logger.warning(
                    f"Error textbbox placeholder: {e}. Fallback textsize.",
                    exc_info=False,
                )
        if (text_width == 0 or text_height == 0) and hasattr(draw, "textsize"):
            try:
                text_width, text_height = draw.textsize(text_to_draw, font=font_to_use)
            except Exception as e:
                logger.warning(f"Error textsize placeholder: {e}.", exc_info=False)
                text_width, text_height = 0, 0

        if text_width > 0 and text_height > 0:
            text_x = (img_width - text_width) // 2
            text_y = (img_height - text_height) // 2

            bg_padding = 1
            bg_coords = [
                text_x - bg_padding,
                text_y - bg_padding,
                text_x + text_width + bg_padding,
                text_y + text_height + bg_padding,
            ]
            draw.rectangle(bg_coords, fill=map_constants.TILE_TEXT_BG_COLOR)

            draw.text(
                (text_x, text_y),
                text_to_draw,
                fill=map_constants.TILE_TEXT_COLOR,
                font=font_to_use,
                anchor="lt",
            )
            logger.debug(f"Drew text on placeholder at pixel ({text_x}, {text_y}).")

        else:
            logger.warning(
                "Zero text size determined for placeholder text, skipping text drawing."
            )

    except Exception as e_draw_text:
        logger.error(
            f"Error drawing text on placeholder image: {e_draw_text}", exc_info=True
        )
        try:
            draw.text((10, 10), text_to_draw, fill="black")
            logger.debug("Drew basic text on placeholder (error during font draw).")
        except Exception as e_fallback_draw:
            logger.error(
                f"Error drawing basic text on placeholder after font error: {e_fallback_draw}",
                exc_info=False,
            )


def _calculate_faded_color(
    base_color_hex: str,
    index_from_end: int,
    total_visible_points: int,
    min_intensity: float = 0.1,
) -> Optional[Tuple[int, int, int, int]]:
    """
    Calculates a faded RGBA color based on index from the end of the track.
    index_from_end = 0 is the point closest to the aircraft (most recent).
    total_visible_points is the number of points actually being drawn for the track.
    """
    if not (0 <= index_from_end < total_visible_points):
        # This can happen if total_visible_points is 1, and index_from_end is 0, then (total_visible_points - 1 - index_from_end) is 0.
        # The ratio should be based on how far back from the *current* point it is.
        # If total_visible_points is 1, it's the current point, full intensity.
        # If total_visible_points is 2, current is index 0 (ratio 1.0), previous is index 1 (ratio min_intensity)
        pass  # Allow index_from_end == total_visible_points -1

    try:
        if not ImageColor:
            logger.warning("_calculate_faded_color: ImageColor not available.")
            return None

        r, g, b = ImageColor.getrgb(base_color_hex)  # Get RGB tuple

        if total_visible_points <= 1:  # Single point or no history
            intensity = 1.0
        else:
            # Intensity decreases as we go further back in history (index_from_end increases)
            # Point closest to aircraft (index_from_end = 0) has max_intensity
            # Point furthest from aircraft (index_from_end = total_visible_points - 1) has min_intensity
            ratio = (
                (total_visible_points - 1 - index_from_end) / (total_visible_points - 1)
                if total_visible_points > 1
                else 1.0
            )
            intensity = min_intensity + (1.0 - min_intensity) * ratio
            intensity = max(min_intensity, min(1.0, intensity))  # Clamp

        final_alpha = int(intensity * 255)
        faded_color_tuple = (r, g, b, final_alpha)
        logger.debug(
            f"Faded color for index {index_from_end}/{total_visible_points-1}: {faded_color_tuple} from base {base_color_hex} with intensity {intensity:.2f}"
        )
        return faded_color_tuple
    except Exception as e:
        logger.error(
            f"Error interpolating color for '{base_color_hex}': {e}", exc_info=False
        )
        # Fallback to a default non-transparent color if fading fails
        try:
            if ImageColor:
                r_fb, g_fb, b_fb = ImageColor.getrgb(base_color_hex)
                return (r_fb, g_fb, b_fb, 255)
        except:
            return (128, 128, 128, 255)  # Grey if everything fails


def _draw_single_flight(
    draw: ImageDraw.ImageDraw,
    pixel_coords: Tuple[int, int],
    flight_state: CanonicalFlightState,
    label_font: Optional[ImageFont.FreeTypeFont | ImageFont.ImageFont],
    track_deque: Optional[deque],  # Pass the specific track deque for this flight
    current_map_geo_bounds: Optional[
        Tuple[float, float, float, float]
    ],  # For track point conversion
    current_stitched_map_pixel_shape: Optional[
        Tuple[int, int]
    ],  # For track point conversion
):
    """Draw single flight marker, its track, and label on PIL ImageDraw."""
    global TRACK_HISTORY_POINTS, TRACK_LINE_WIDTH  # Use global config values

    if not CANONICAL_FLIGHT_STATE_AVAILABLE_DRAWING:
        logger.warning("_draw_single_flight: CanonicalFlightState not available.")
        return

    if (
        not PIL_LIB_AVAILABLE_DRAWING
        or ImageDraw is None
        or ImageFont is None
        or ImageColor is None
    ):
        logger.warning("_draw_single_flight: Pillow components missing.")
        return

    if draw is None or pixel_coords is None or flight_state is None:
        logger.warning("_draw_single_flight called with missing essential arguments.")
        return

    px, py = pixel_coords
    base_length = 8
    side_length = base_length * 1.5

    img_w, img_h = draw.im.size
    label_text = (
        flight_state.callsign.strip()
        if flight_state.callsign and flight_state.callsign.strip()
        else flight_state.icao24
    )
    if not label_text.strip():
        label_text = "N/A"

    logger.debug(
        f"Drawing flight '{label_text}' (ICAO: {flight_state.icao24}) at pixel ({px},{py})."
    )

    margin = 100  # Margin for drawing aircraft symbol
    if not (-margin <= px < img_w + margin and -margin <= py < img_h + margin):
        logger.debug(
            f"Flight pixel coords ({px},{py}) outside drawing margin. Skipping draw for '{label_text}'."
        )
        return

    # Determine base color for this flight (triangle and its track)
    color_index = hash(flight_state.icao24) % len(map_constants.TRACK_COLOR_PALETTE)
    flight_and_track_base_color_hex = map_constants.TRACK_COLOR_PALETTE[color_index]

    logger.debug(
        f"MAP_DRAWING: _draw_single_flight for {flight_state.icao24} at px {pixel_coords}. Track deque length: {len(track_deque) if track_deque else 'None'}"
    )

    # --- Draw Track First (so aircraft is on top) ---
    if track_deque and len(track_deque) > 1:
        # Convert all relevant geo points in track_deque to pixel points
        # Limit to TRACK_HISTORY_POINTS
        num_points_to_draw = min(len(track_deque), TRACK_HISTORY_POINTS)
        pixel_track_points: List[Tuple[int, int]] = []

        # Iterate from most recent to oldest relevant point in the deque
        for i in range(num_points_to_draw):
            geo_lat, geo_lon, _timestamp = track_deque[
                len(track_deque) - 1 - i
            ]  # Last element is most recent
            track_px_coords = _geo_to_pixel_on_unscaled_map(
                geo_lat,
                geo_lon,
                current_map_geo_bounds,
                current_stitched_map_pixel_shape,
            )
            if track_px_coords:
                pixel_track_points.append(track_px_coords)
            else:
                logger.debug(
                    f"Could not convert track point {i} for {flight_state.icao24} to pixel."
                )

        # pixel_track_points are now ordered from most recent (closest to aircraft) to oldest
        if len(pixel_track_points) > 1:
            logger.debug(
                f"MAP_DRAWING ({flight_state.icao24}): Will draw {len(pixel_track_points)-1} line segments."
            )
            for i in range(len(pixel_track_points) - 1):
                start_point_px = pixel_track_points[i]
                end_point_px = pixel_track_points[i + 1]
                # index_from_end for color: i=0 is closest to aircraft, should be most intense
                faded_color = _calculate_faded_color(
                    flight_and_track_base_color_hex, i, len(pixel_track_points)
                )

                logger.debug(
                    f"MAP_DRAWING ({flight_state.icao24}): Segment {i}, Start: {start_point_px}, End: {end_point_px}, Color: {faded_color}"
                )  # LOG AGGIUNTO
                if faded_color:
                    try:
                        draw.line(
                            [start_point_px, end_point_px],
                            fill=faded_color,
                            width=TRACK_LINE_WIDTH,
                        )
                    except Exception as e_draw_line:
                        logger.error(
                            f"Error drawing track line for {flight_state.icao24}: {e_draw_line}",
                            exc_info=False,
                        )
                else:
                    logger.warning(
                        f"Could not calculate faded color for track segment for {flight_state.icao24}"
                    )
            logger.debug(
                f"Drew {len(pixel_track_points)-1} track segments for {flight_state.icao24}"
            )

    # --- Draw Aircraft Triangle ---
    try:
        angle_deg = (
            flight_state.true_track_deg
            if flight_state.true_track_deg is not None
            else 0.0
        )
        angle_rad = math.radians(
            90.0 - angle_deg
        )  # Adjust for PIL's coordinate system (0 deg is East, positive is CCW)

        # Tip of the triangle (points in direction of flight)
        tip_x = px + side_length * math.cos(angle_rad)
        tip_y = py - side_length * math.sin(angle_rad)  # PIL Y is inverted

        # Base corners (perpendicular to the direction vector)
        base_angle_offset = math.pi / 2
        base1_x = px + (base_length / 2) * math.cos(angle_rad + base_angle_offset)
        base1_y = py - (base_length / 2) * math.sin(angle_rad + base_angle_offset)
        base2_x = px + (base_length / 2) * math.cos(angle_rad - base_angle_offset)
        base2_y = py - (base_length / 2) * math.sin(angle_rad - base_angle_offset)

        triangle_coords = [(tip_x, tip_y), (base1_x, base1_y), (base2_x, base2_y)]
        draw.polygon(
            triangle_coords, fill=flight_and_track_base_color_hex, outline="black"
        )
        logger.debug(
            f"Drew triangle for flight '{label_text}' at ({px},{py}) with color {flight_and_track_base_color_hex}."
        )

    except Exception as e:
        logger.error(
            f"Error drawing triangle for flight '{label_text}': {e}", exc_info=False
        )

    # --- Draw Label ---
    if label_font is not None:
        try:
            text_width, text_height = 0, 0
            # Simplified text size calculation
            if hasattr(draw, "textlength"):  # Pillow 9.2.0+
                text_width = draw.textlength(label_text, font=label_font)
                # For height, approximation or use fixed value if textlength doesn't give it.
                # Or use textbbox for more accuracy if available and needed.
                # For simplicity, let's try to get ascent/descent if possible.
                if hasattr(label_font, "getmetrics"):
                    ascent, descent = label_font.getmetrics()
                    text_height = ascent + descent
                elif hasattr(
                    draw, "textbbox"
                ):  # Fallback to textbbox if getmetrics not available
                    bbox = draw.textbbox((0, 0), label_text, font=label_font)
                    text_height = bbox[3] - bbox[1]
                    if text_width == 0:
                        text_width = bbox[2] - bbox[0]  # If textlength wasn't available
                else:  # Basic fallback
                    logger.debug("Using approximate text height for label.")
                    text_height = (
                        scaled_font_size if "scaled_font_size" in locals() else 10
                    )

            if text_width == 0 and hasattr(
                draw, "textbbox"
            ):  # If textlength failed or not available
                bbox = draw.textbbox((0, 0), label_text, font=label_font)
                text_width = bbox[2] - bbox[0]
                if text_height == 0:
                    text_height = bbox[3] - bbox[1]

            if text_width > 0 and text_height > 0:
                text_anchor_x = px + side_length + 3  # Adjust based on triangle size
                text_anchor_y = py - (text_height // 2)

                clamp_margin_text = 5
                label_text_x = max(
                    -clamp_margin_text,
                    min(text_anchor_x, img_w - text_width - clamp_margin_text),
                )
                label_text_y = max(
                    -clamp_margin_text,
                    min(text_anchor_y, img_h - text_height - clamp_margin_text),
                )

                bg_padding = 1
                bg_coords = [
                    label_text_x - bg_padding,
                    label_text_y - bg_padding,
                    label_text_x + text_width + bg_padding,
                    label_text_y + text_height + bg_padding,
                ]
                draw.rectangle(bg_coords, fill=map_constants.TILE_TEXT_BG_COLOR)
                draw.text(
                    (label_text_x, label_text_y),
                    label_text,
                    fill=map_constants.TILE_TEXT_COLOR,
                    font=label_font,
                    anchor="lt",
                )
                logger.debug(f"Drew text label '{label_text}'.")
            else:
                logger.warning(
                    f"Zero text size for label '{label_text}', skipping text."
                )
        except Exception as e:
            logger.error(f"Error drawing label for '{label_text}': {e}", exc_info=False)
    else:
        logger.debug(f"No label font for flight '{label_text}', skipping label.")
