"""Maps named faces (from Immich's own face recognition/People feature)
onto their position in the final rendered frame, for the manage-button
overlay's named-face labels (see manage_overlay.py, which draws them).

No face detection or recognition happens here or anywhere else in this
project -- Immich's GET /api/faces?id={assetId} already returns each
detected face's bounding box plus a nullable `person` object (with a
`name`, if the user has identified them in Immich); this module only
does the coordinate math to place a label next to a *named* one.
"""

from __future__ import annotations

import io

from PIL import Image, ImageOps

from .image_pipeline import _has_bounding_box, _placement_transform, logical_render_size

# Not a memory constraint anymore (the overlay renders server-side now,
# not malloc'd per-label on the device) -- purely a legibility cap. A
# photo with a dozen named people would just be visual clutter regardless
# of what's rendering it.
MAX_LABELED_FACES = 6


def compute_face_labels(preview_bytes: bytes, faces: list[dict], display_mode: str,
                        orientation: str = "landscape", region: tuple[int, int, int, int] | None = None) -> list[dict]:
    """Returns up to MAX_LABELED_FACES [{"name", "x", "y"}], x/y in
    logical (pre-rotation) frame space at each named face's bottom-center
    point -- manage_overlay.compose() draws these directly onto the
    logical-space image before it's rotated into native panel space, so
    no rotation happens here (contrast with the old firmware-side
    version, which drew post-rotation and needed logical_to_native).
    Faces without an Immich-identified person name are skipped entirely.
    preview_bytes must be the same preview image render_frame() used for
    the currently-displayed frame, and display_mode/orientation must
    match the settings that were active then -- otherwise the placement
    computed here won't match what's actually on screen.

    `region` is (x0, y0, w, h): where in the logical canvas the photo
    actually landed, if not the whole thing -- e.g. a photo widget placed
    in one corner of the panel rather than full-screen (see
    routers/common.py's build_manage_content, which passes each photo
    widget's own placement rect) -- without this a label would be placed
    as if the photo filled the entire canvas, landing well off where the
    widget actually is. None (the default) means the photo fills the
    whole logical canvas.

    The placement math matches render_frame()'s own composition step
    exactly (see image_pipeline._placement_transform, shared so the two
    can't drift apart).
    """
    named = [face for face in faces if (face.get("person") or {}).get("name")]
    if not named:
        return []

    if region is None:
        logical_w, logical_h = logical_render_size(orientation)
        region_x0, region_y0, target_w, target_h = 0, 0, logical_w, logical_h
    else:
        region_x0, region_y0, target_w, target_h = region

    fitted = ImageOps.exif_transpose(Image.open(io.BytesIO(preview_bytes)).convert("RGB"))

    scale_x, scale_y, offset_x, offset_y = _placement_transform(
        fitted.width, fitted.height, target_w, target_h, display_mode, faces
    )

    labels = []
    for face in named[:MAX_LABELED_FACES]:
        if not _has_bounding_box(face):
            continue
        face_w = face.get("imageWidth") or fitted.width
        face_h = face.get("imageHeight") or fitted.height
        img_scale_x = fitted.width / face_w
        img_scale_y = fitted.height / face_h

        center_x = (face["boundingBoxX1"] + face["boundingBoxX2"]) / 2 * img_scale_x
        bottom_y = face["boundingBoxY2"] * img_scale_y

        # Relative to the region's own origin first (matches
        # _placement_transform's target_w/target_h space), then shifted
        # into full-canvas coordinates.
        region_x = center_x * scale_x + offset_x
        region_y = bottom_y * scale_y + offset_y

        if not (0 <= region_x <= target_w and 0 <= region_y <= target_h):
            continue  # this face got cropped out of the region entirely

        labels.append({"name": face["person"]["name"],
                       "x": int(region_x + region_x0), "y": int(region_y + region_y0)})

    return labels
