#!/usr/bin/env python3
"""
fc_perform_router.py — map Malin's reply TEXT to an FC expression. (Cal's lane: the
performance router for the live loop.)

v1 = dumb version first: cue-based sentiment -> a canonical emotion -> SNAPPED to
whatever the live FC library actually has available right now. It reads the available
list at call time, so it grows automatically as overnight renders add emotions, and
never asks for an expression that isn't built yet.

Also honours an explicit [PERFORM:xxx] tag if Malin's brain emits one (the designed
path) — so the moment her prompt starts tagging emotions, the router uses HER choice
and the heuristic just becomes the fallback. The tag is stripped from the text that
goes to TTS.

Usage:
  from fc_perform_router import route
  emotion, clean_text = route(reply_text, available=['neutral','happy','amused_flirty','surprised','sad'])
"""
import re

# canonical emotion -> ordered fallbacks; snap to the first one that's AVAILABLE.
# extra canon names map onto the bigger 27-emotion set as those get rendered.
FALLBACK = {
    "amused_flirty": ["amused_flirty", "happy", "neutral"],
    "happy":         ["happy", "amused_flirty", "satisfaction", "neutral"],
    "excited":       ["excitement", "happy", "surprised", "neutral"],
    "surprised":     ["surprised", "awe", "horror", "neutral"],
    "sad":           ["sad", "neutral"],
    "concerned":     ["anxiety", "calmness", "neutral"],
    "angry":         ["anger", "neutral"],
    "calm":          ["calmness", "neutral"],
    "adoring":       ["adoration", "romance", "happy", "neutral"],
    "neutral":       ["neutral"],
}

# cue regex -> canonical (first match wins; ORDER MATTERS).
# care/negative cues come BEFORE generic positives so a stray "love" can't make an
# apology read as happy.
CUES = [
    (r"(haha|hehe|lol|lmao|\bteas|\bwink|flirt|naughty|trouble|;\)|😏|😈)", "amused_flirty"),
    (r"(i'?m sorry|so sad|unfortunately|that hurts|heartbreak|hard day|rough day)", "sad"),
    (r"(worried|concern|be careful|are you ok|you okay|take care of yourself|you went quiet)", "concerned"),
    (r"(breathe|take it easy|it'?s okay|i'?m here|slow down|you'?re safe)", "calm"),
    (r"(adore|my love|gorgeous|i want you|come here)", "adoring"),
    (r"(love|sweet|glad|happy|wonderful|yay|great job|perfect|delight|proud of you)", "happy"),
    (r"(!{2,}|\bwow\b|\bwhoa\b|\bomg\b|can'?t believe|no way|holy)", "surprised"),
]


def classify(text):
    t = (text or "").lower()
    for pat, emo in CUES:
        if re.search(pat, t):
            return emo
    if t.count("!") >= 1:
        return "happy"
    if t.count("?") >= 2:
        return "surprised"
    return "neutral"


def _snap(canon, available):
    for cand in FALLBACK.get(canon, ["neutral"]):
        if cand in available:
            return cand
    if "neutral" in available:
        return "neutral"
    return available[0] if available else "neutral"


def route(text, available):
    """Return (emotion, clean_text). emotion is guaranteed to be in `available`."""
    raw = text or ""
    m = re.search(r"\[PERFORM:\s*([a-z_]+)\s*\]", raw, re.I)
    if m:
        canon = m.group(1).lower()
        clean = (raw[:m.start()] + raw[m.end():])
    else:
        canon = classify(raw)
        clean = raw
    return _snap(canon, available), clean.strip()


if __name__ == "__main__":
    avail = ["neutral", "happy", "amused_flirty", "surprised", "sad"]
    samples = [
        "Haha okay you got me, that was smooth.",
        "I'm so glad you're home, I missed you.",
        "Wait WHAT?? No way that actually worked!",
        "I'm sorry love, that sounds like a hard day.",
        "Are you okay? You went quiet on me.",
        "Breathe. I'm here. Slow down.",
        "The render finished and the manifest looks clean.",
        "[PERFORM: amused_flirty] anything you want, architect.",
    ]
    for s in samples:
        emo, clean = route(s, avail)
        print(f"  {emo:14} <- {s!r}")
        if clean != s:
            print(f"                 clean: {clean!r}")
