"""Extra emotion frames for the LED mask, in the same 46x58 display space + RGB
style as :mod:`colorface` (black bg, cyan eyes, red mouth). These are the
expression frames Gemini can trigger via ``set_expression`` that the base
``colorface.default_frames`` does not draw (heart, laugh, love-eyes, cool,
sleepy, confused, kiss, star-struck).

``emotion_frames(...)`` returns ``{name: raw_bytes}`` ready for the mask's DIY
image upload, exactly like ``colorface.default_frames``. Positions mirror
``colorface.build_face`` so the eyes/mouth line up with the rest of the set.
"""

from __future__ import annotations

import math

import colorface as _cf
from colorface import DISPLAY_W as W, DISPLAY_H as H, encode
from PIL import Image, ImageDraw

# eye/mouth geometry copied from colorface.build_face so frames are consistent
_EYE_L = W // 2 - 10        # 13
_EYE_R = W // 2 + 10        # 33
_EYE_T, _EYE_B = 15, 29     # normal eye top/bottom
_EYE_W = 6
_MOUTH_CY = 44
_MOUTH_CX = W // 2          # 23


def _canvas():
    img = Image.new("RGB", (W, H), (0, 0, 0))
    return img, ImageDraw.Draw(img)


def _round_eye(g, cx, eye_color, sclera_color, *, t=_EYE_T, b=_EYE_B, w=_EYE_W):
    g.ellipse([cx - w, t, cx + w, b], fill=sclera_color)
    g.ellipse([cx - w + 3, t + 4, cx + w - 3, b - 2], fill=eye_color)
    m = (t + b) // 2
    g.ellipse([cx - 1, m - 1, cx + 1, m + 2], fill=(0, 0, 0))


def _happy_eye(g, cx, color):
    # upward "^"-ish squint (a smiling eye)
    g.arc([cx - 7, _EYE_T - 1, cx + 7, _EYE_B + 5], start=200, end=340,
          fill=color, width=3)


def _heart(g, cx, cy, half, color):
    """A filled heart centred at (cx, cy), ``half`` = half-width."""
    r = half / 2.0
    g.pieslice([cx - half, cy - r, cx, cy + r], 0, 360, fill=color)      # left lobe
    g.pieslice([cx, cy - r, cx + half, cy + r], 0, 360, fill=color)      # right lobe
    g.polygon([(cx - half, cy + r * 0.2), (cx + half, cy + r * 0.2),
               (cx, cy + half)], fill=color)


def _star(g, cx, cy, r, color):
    pts = []
    for i in range(10):
        ang = -math.pi / 2 + i * math.pi / 5
        rad = r if i % 2 == 0 else r * 0.45
        pts.append((cx + rad * math.cos(ang), cy + rad * math.sin(ang)))
    g.polygon(pts, fill=color)


def _smile(g, color, *, big=False):
    if big:  # open grin
        g.chord([_MOUTH_CX - 13, _MOUTH_CY - 6, _MOUTH_CX + 13, _MOUTH_CY + 12],
                start=0, end=180, fill=color)
    else:
        g.arc([_MOUTH_CX - 12, _MOUTH_CY - 8, _MOUTH_CX + 12, _MOUTH_CY + 8],
              start=20, end=160, fill=color, width=4)


# Fixed emoji colors — these frames are icons, not part of the face's colour
# scheme, so a heart is always red and a thumb always yellow regardless of the
# user's chosen eye/mouth colours.
_RED = (255, 45, 75)
_PINK = (255, 95, 155)
_YELLOW = (255, 200, 40)


# -- individual emotion drawings ---------------------------------------------

def _heart_face(eye, mouth, sclera):
    img, g = _canvas()
    _heart(g, W // 2, 26, 18, _RED)          # one big RED heart fills the face
    return img


def _laugh(eye, mouth, sclera):
    img, g = _canvas()
    _happy_eye(g, _EYE_L, eye)
    _happy_eye(g, _EYE_R, eye)
    _smile(g, mouth, big=True)               # wide open grin
    # a joy tear under each eye
    for cx in (_EYE_L, _EYE_R):
        g.ellipse([cx - 2, _EYE_B + 3, cx + 2, _EYE_B + 9], fill=(0, 180, 255))
    return img


def _love(eye, mouth, sclera):
    img, g = _canvas()
    _heart(g, _EYE_L, 22, 8, _PINK)          # pink heart-shaped eyes
    _heart(g, _EYE_R, 22, 8, _PINK)
    _smile(g, _PINK)
    return img


def _cool(eye, mouth, sclera):
    img, g = _canvas()
    frame = (40, 40, 55)
    lens = (10, 10, 20)
    # two lenses + bridge (sunglasses)
    for cx in (_EYE_L, _EYE_R):
        g.rounded_rectangle([cx - 8, _EYE_T, cx + 8, _EYE_B + 1], radius=4,
                            fill=lens, outline=frame, width=2)
        g.line([cx - 5, _EYE_T + 3, cx + 2, _EYE_T + 3], fill=eye, width=2)  # glint
    g.line([_EYE_L + 8, _EYE_T + 3, _EYE_R - 8, _EYE_T + 3], fill=frame, width=3)
    # a cool little smirk (raised on one side)
    g.arc([_MOUTH_CX - 11, _MOUTH_CY - 5, _MOUTH_CX + 12, _MOUTH_CY + 8],
          start=15, end=120, fill=mouth, width=4)
    return img


def _sleepy(eye, mouth, sclera):
    img, g = _canvas()
    # droopy half-closed eyes: lid arc over a thin slit
    for cx in (_EYE_L, _EYE_R):
        g.arc([cx - 7, _EYE_T + 2, cx + 7, _EYE_B + 4], start=160, end=20,
              fill=eye, width=3)
    # small tired mouth
    g.ellipse([_MOUTH_CX - 4, _MOUTH_CY - 2, _MOUTH_CX + 4, _MOUTH_CY + 4], fill=mouth)
    # zZ drawn as cheap line-glyphs (no font dependency)
    for (x, y, s) in ((36, 8, 5), (41, 3, 3)):
        g.line([x, y, x + s, y], fill=eye, width=1)
        g.line([x + s, y, x, y + s], fill=eye, width=1)
        g.line([x, y + s, x + s, y + s], fill=eye, width=1)
    return img


def _confused(eye, mouth, sclera):
    img, g = _canvas()
    _round_eye(g, _EYE_L, eye, sclera)                       # normal eye
    _round_eye(g, _EYE_R, eye, sclera, t=_EYE_T - 3, b=_EYE_B - 3, w=5)  # raised/small
    # raised brow over the small eye
    g.line([_EYE_R - 6, _EYE_T - 6, _EYE_R + 6, _EYE_T - 9], fill=eye, width=2)
    # wavy/squiggle mouth
    pts = [(_MOUTH_CX - 12 + i * 4, _MOUTH_CY + (3 if i % 2 else -3)) for i in range(7)]
    g.line(pts, fill=mouth, width=3, joint="curve")
    return img


def _kiss(eye, mouth, sclera):
    img, g = _canvas()
    _round_eye(g, _EYE_L, eye, sclera)
    g.line([_EYE_R - 6, (_EYE_T + _EYE_B) // 2, _EYE_R + 6, (_EYE_T + _EYE_B) // 2],
           fill=eye, width=3)                                # winking eye
    # puckered red lips
    g.ellipse([_MOUTH_CX - 4, _MOUTH_CY - 4, _MOUTH_CX + 4, _MOUTH_CY + 5], fill=_RED)
    g.ellipse([_MOUTH_CX - 2, _MOUTH_CY - 2, _MOUTH_CX + 2, _MOUTH_CY + 3], fill=(0, 0, 0))
    _heart(g, 37, 13, 6, _RED)                               # little floating red heart
    return img


def _star_struck(eye, mouth, sclera):
    img, g = _canvas()
    _star(g, _EYE_L, 22, 7, (255, 220, 0))
    _star(g, _EYE_R, 22, 7, (255, 220, 0))
    _smile(g, mouth, big=True)
    return img


def _thumbs_up(eye, mouth, sclera):
    # a 👍: one bold vertical thumb + a bold fist block, kept simple so it reads
    # on the low-res LED grid (fine detail just blurs into a blob).
    img, g = _canvas()
    g.rounded_rectangle([11, 30, 37, 52], radius=8, fill=_YELLOW)    # fist block
    g.rounded_rectangle([13, 6, 29, 34], radius=8, fill=_YELLOW)     # big thumb up
    g.line([30, 34, 36, 34], fill=(0, 0, 0), width=3)               # thumb/finger split
    return img


_BUILDERS = {
    "heart": _heart_face,
    "laugh": _laugh,
    "love": _love,
    "cool": _cool,
    "sleepy": _sleepy,
    "confused": _confused,
    "kiss": _kiss,
    "star_struck": _star_struck,
    "thumbs_up": _thumbs_up,
}


def emotion_frames(*, eye_color=_cf.DEFAULT_EYE, mouth_color=_cf.DEFAULT_MOUTH,
                   sclera_color=_cf.WHITE, include=None) -> dict:
    """Return ``{name: raw_bytes}`` for the extra emotion frames.

    ``include`` optionally restricts to a subset (a set/list of names) so the
    caller can honour the mask's slot budget.
    """
    names = list(_BUILDERS) if include is None else [n for n in _BUILDERS if n in include]
    out = {}
    for name in names:
        img = _BUILDERS[name](eye_color, mouth_color, sclera_color)
        out[name] = encode(img)
    return out
