"""Composites the manage-button overlay -- "scan to manage" QR, battery,
location/date-taken, share-QR, named face labels -- server-side, onto an
already-composed image (any mode: a photo, or a calendar view), before
quantization. Replaces what used to be firmware/main/manage_qr_overlay.c
generating and positioning all of this on-device.

Corner/spacing constants below are plain Python now, not a protocol
contract with firmware -- adjustable here without touching anything else.
Uses the same toolkit image_pipeline.render_placeholder already does
(PIL ImageDraw/ImageFont, the qrcode library), just doing more with it.
"""

from __future__ import annotations

from PIL import Image, ImageDraw, ImageFont

from .image_pipeline import DEFAULT_PALETTE_RGB, draw_text

PADDING = 16
QR_TEXT_GAP = 8
LINE_GAP = 4
PANEL_MARGIN = 20
QR_TARGET_PX = 180

TITLE_FONT_SIZE = 22
BODY_FONT_SIZE = 20

BATTERY_ICON_W = 40
BATTERY_ICON_H = 22
BATTERY_ICON_STROKE = 2
BATTERY_NUB_W = 5
BATTERY_NUB_H = 10
BATTERY_ICON_TEXT_GAP = 8
BATTERY_REGION_GAP = 8  # vertical gap below the manage QR box

FACE_LABEL_PADDING = 8
FACE_LABEL_GAP = 4  # distance from the face's anchor point to the label box


def _font(size: int) -> ImageFont.ImageFont:
    return ImageFont.load_default(size=size)


def _qr_image(url: str, target_px: int = QR_TARGET_PX) -> Image.Image:
    import qrcode

    qr = qrcode.QRCode(border=1, box_size=1)
    qr.add_data(url)
    qr.make(fit=True)
    raw = qr.make_image().get_image().convert("RGB")
    scale = max(1, target_px // raw.width)
    return raw.resize((raw.width * scale, raw.height * scale), Image.NEAREST)


def _text_box(draw: ImageDraw.ImageDraw, lines: list[str], font: ImageFont.ImageFont) -> tuple[int, int]:
    """(width, height) of `lines` stacked with LINE_GAP between them, at
    `font` -- the box _draw_text_box below will need."""
    w = 0
    h = 0
    for i, line in enumerate(lines):
        bbox = draw.textbbox((0, 0), line, font=font)
        w = max(w, bbox[2] - bbox[0])
        h += (bbox[3] - bbox[1]) + (LINE_GAP if i else 0)
    return w, h


def _draw_centered_lines(img: Image.Image, draw: ImageDraw.ImageDraw, lines: list[str], font: ImageFont.ImageFont,
                         center_x: int, top: int) -> None:
    y = top
    for line in lines:
        bbox = draw.textbbox((0, 0), line, font=font)
        w = bbox[2] - bbox[0]
        draw_text(img, (center_x - w // 2, y), line, font)
        y += (bbox[3] - bbox[1]) + LINE_GAP


def _draw_qr_box(img: Image.Image, draw: ImageDraw.ImageDraw, url: str, caption: list[str],
                 corner: str) -> tuple[int, int, int, int]:
    """White-padded box with a QR code and centered caption lines below
    it, placed in one of the panel's four corners. Returns (x0, y0, w, h)
    -- callers that need to anchor something else relative to this box
    (the battery, below the manage QR) use it instead of recomputing the
    same geometry a second time."""
    qr_img = _qr_image(url)
    text_w, text_h = _text_box(draw, caption, _font(TITLE_FONT_SIZE)) if caption else (0, 0)
    content_w = max(qr_img.width, text_w)
    content_h = qr_img.height + (QR_TEXT_GAP + text_h if caption else 0)

    w = content_w + PADDING * 2
    h = content_h + PADDING * 2
    x0, y0 = _corner_origin(img.size, (w, h), corner)

    draw.rectangle([x0, y0, x0 + w, y0 + h], fill=(255, 255, 255), outline=(0, 0, 0))
    center_x = x0 + w // 2
    img.paste(qr_img, (center_x - qr_img.width // 2, y0 + PADDING))
    if caption:
        _draw_centered_lines(img, draw, caption, _font(TITLE_FONT_SIZE), center_x, y0 + PADDING + qr_img.height + QR_TEXT_GAP)
    return x0, y0, w, h


def _draw_text_box(img: Image.Image, draw: ImageDraw.ImageDraw, lines: list[str], corner: str) -> None:
    """White-padded box with centered text lines, placed in one of the
    panel's four corners."""
    font = _font(BODY_FONT_SIZE)
    text_w, text_h = _text_box(draw, lines, font)
    w = text_w + PADDING * 2
    h = text_h + PADDING * 2
    x0, y0 = _corner_origin(img.size, (w, h), corner)

    draw.rectangle([x0, y0, x0 + w, y0 + h], fill=(255, 255, 255), outline=(0, 0, 0))
    _draw_centered_lines(img, draw, lines, font, x0 + w // 2, y0 + PADDING)


def _corner_origin(img_size: tuple[int, int], box_size: tuple[int, int], corner: str) -> tuple[int, int]:
    img_w, img_h = img_size
    box_w, box_h = box_size
    if corner == "top-left":
        return PANEL_MARGIN, PANEL_MARGIN
    if corner == "top-right":
        return img_w - PANEL_MARGIN - box_w, PANEL_MARGIN
    if corner == "bottom-left":
        return PANEL_MARGIN, img_h - PANEL_MARGIN - box_h
    return img_w - PANEL_MARGIN - box_w, img_h - PANEL_MARGIN - box_h  # bottom-right


# DEFAULT_PALETTE_RGB order is [BLACK, WHITE, YELLOW, RED, BLUE, GREEN]
# (see image_pipeline.PANEL_CODES) -- picked by level so the fill itself
# carries the "how worried should I be" signal, not just the number next
# to it. Thresholds match the low-battery-alert spirit elsewhere in this
# project (not tied to a frame's own configured alert threshold, since
# this glyph has to make sense with no configuration at all).
_BATTERY_LOW = DEFAULT_PALETTE_RGB[3]  # red
_BATTERY_MEDIUM = DEFAULT_PALETTE_RGB[2]  # yellow
_BATTERY_HIGH = DEFAULT_PALETTE_RGB[5]  # green


def _battery_fill_color(percent: int) -> tuple[int, int, int]:
    if percent <= 15:
        return _BATTERY_LOW
    if percent <= 40:
        return _BATTERY_MEDIUM
    return _BATTERY_HIGH


def _draw_battery(img: Image.Image, draw: ImageDraw.ImageDraw, percent: int, anchor_x0: int, anchor_y0: int,
                  anchor_w: int, anchor_h: int) -> None:
    """Battery glyph (now actually filled to `percent`, not just a static
    outline -- easy now that this renders server-side instead of being a
    fixed bitmap firmware drew) + "NN%" text, right-aligned under the
    given anchor box (the manage QR box) -- a sensible default position,
    not a constraint anything else has to route around; move this call
    site's arguments to place it anywhere else instead."""
    font = _font(BODY_FONT_SIZE)
    text = f"{percent}%"
    icon_total_w = BATTERY_ICON_W + BATTERY_NUB_W
    text_w = draw.textlength(text, font=font)
    content_w = icon_total_w + BATTERY_ICON_TEXT_GAP + text_w
    content_h = max(font.size, BATTERY_ICON_H)

    w = int(content_w + PADDING * 2)
    h = int(content_h + PADDING * 2)
    x0 = anchor_x0 + anchor_w - w
    y0 = anchor_y0 + anchor_h + BATTERY_REGION_GAP

    draw.rectangle([x0, y0, x0 + w, y0 + h], fill=(255, 255, 255), outline=(0, 0, 0))

    icon_x = x0 + PADDING
    icon_y = y0 + PADDING + (content_h - BATTERY_ICON_H) // 2
    inner_x0, inner_y0 = icon_x + BATTERY_ICON_STROKE, icon_y + BATTERY_ICON_STROKE
    inner_x1, inner_y1 = icon_x + BATTERY_ICON_W - BATTERY_ICON_STROKE, icon_y + BATTERY_ICON_H - BATTERY_ICON_STROKE
    fill_x1 = inner_x0 + round((inner_x1 - inner_x0) * (percent / 100))
    if fill_x1 > inner_x0:
        draw.rectangle([inner_x0, inner_y0, fill_x1, inner_y1], fill=_battery_fill_color(percent))
    draw.rectangle([icon_x, icon_y, icon_x + BATTERY_ICON_W, icon_y + BATTERY_ICON_H], outline=(0, 0, 0),
                   width=BATTERY_ICON_STROKE)
    nub_y = icon_y + (BATTERY_ICON_H - BATTERY_NUB_H) // 2
    draw.rectangle([icon_x + BATTERY_ICON_W, nub_y, icon_x + BATTERY_ICON_W + BATTERY_NUB_W, nub_y + BATTERY_NUB_H],
                   fill=(0, 0, 0))
    draw_text(img, (icon_x + icon_total_w + BATTERY_ICON_TEXT_GAP, y0 + PADDING + (content_h - font.size) // 2),
             text, font)


def _draw_face_label(img: Image.Image, draw: ImageDraw.ImageDraw, name: str, anchor_x: int, anchor_y: int) -> None:
    """White-padded name label centered under an arbitrary (anchor_x,
    anchor_y) point, flipped above if there's no room below, clamped to
    stay fully on-panel -- unlike the four corner boxes (always in-bounds
    by construction), a face can be anywhere, including near an edge."""
    font = _font(BODY_FONT_SIZE)
    text_w = draw.textlength(name, font=font)
    bbox = draw.textbbox((0, 0), name, font=font)
    text_h = bbox[3] - bbox[1]

    w = int(text_w + FACE_LABEL_PADDING * 2)
    h = int(text_h + FACE_LABEL_PADDING * 2)
    img_w, img_h = img.size

    x0 = anchor_x - w // 2
    y0 = anchor_y + FACE_LABEL_GAP
    if y0 + h > img_h:
        y0 = anchor_y - FACE_LABEL_GAP - h  # no room below -- place above instead
    x0 = max(0, min(x0, img_w - w))
    y0 = max(0, min(y0, img_h - h))

    draw.rectangle([x0, y0, x0 + w, y0 + h], fill=(255, 255, 255), outline=(0, 0, 0))
    draw_text(img, (x0 + FACE_LABEL_PADDING, y0 + FACE_LABEL_PADDING - bbox[1]), name, font)


def compose(image: Image.Image, management_url: str, battery_percent: int | None = None,
           location_lines: tuple[str, str] | None = None, taken_at: str | None = None,
           share_url: str | None = None, face_labels: list[dict] | None = None) -> Image.Image:
    """Draws the manage overlay onto a copy of `image` (RGB, any mode's
    already-composed/enhanced logical-space canvas) and returns it.
    management_url's "scan to manage" box always shows; everything else
    is optional and simply omitted when not given -- battery_percent
    None or out of 0-100 skips the battery box, location_lines/taken_at/
    share_url empty/None skip their own box, face_labels empty skips
    those."""
    img = image.copy()
    draw = ImageDraw.Draw(img)

    qr_x0, qr_y0, qr_w, qr_h = _draw_qr_box(img, draw, management_url, ["SCAN TO", "MANAGE"], "top-right")

    if battery_percent is not None and 0 <= battery_percent <= 100:
        _draw_battery(img, draw, battery_percent, qr_x0, qr_y0, qr_w, qr_h)

    if location_lines and location_lines[0]:
        lines = [line for line in location_lines if line]
        _draw_text_box(img, draw, lines, "top-left")

    if taken_at:
        _draw_text_box(img, draw, [taken_at], "bottom-right")

    if share_url:
        _draw_qr_box(img, draw, share_url, ["SCAN TO", "DOWNLOAD"], "bottom-left")

    for label in face_labels or []:
        if label.get("name"):
            _draw_face_label(img, draw, label["name"], label["x"], label["y"])

    return img
