"""Premium (custom) emoji helper for HTML messages.

Telegram supports premium / custom emojis in message text via the
``<tg-emoji emoji-id="...">X</tg-emoji>`` HTML tag (with parse_mode=HTML).

This module:

1. Defines a map of bare unicode emojis → custom_emoji_id, so any
   message containing those emojis is automatically converted into the
   premium version. This is what makes admin-typed product / category
   names render with premium emojis without any extra wiring.

2. Exposes `pe(emoji, custom_emoji_id)` to write a specific premium
   emoji inline (used for fixed strings where one regular emoji must
   map to a SPECIFIC custom id, e.g. the wallet emoji in the main
   menu card).

3. `premiumize(html)` walks an HTML string and wraps any bare emojis
   from AUTO_MAP. It skips text inside HTML tags and inside existing
   ``<tg-emoji>...</tg-emoji>`` blocks.

NOTE: Telegram Bot API does NOT support custom emojis inside inline
button labels. Only message body text and captions can carry them.
Buttons will keep their regular unicode emoji.
"""
from __future__ import annotations
import re

# ---------------------------------------------------------------------------
# Premium emoji IDs (provided by the bot owner)
# ---------------------------------------------------------------------------
PE_NAME             = "5197288608620360453"   # 👤 name (main menu)
PE_USER_ID          = "5841276284155467413"   # 🆔 user id (main menu)
PE_WALLET           = "5276137490846075469"   # 💰 wallet balance
PE_REF_EARNINGS     = "6087023283356568182"   # 🎁 referral earnings (card)
PE_SHOP_BAG         = "4958714256343171878"   # 🛍️ products button
PE_CART             = "5312361253610475399"   # 🛒 cart
PE_DEPOSIT          = "5931593777033517695"   # 💎 deposit button
PE_BINANCE          = "5397746448995464471"   # 🟡 binance
PE_USDT             = "5931677159528603317"   # 🟢/🟡 USDT TRC20 + BEP20
PE_BTC              = "5931740110864260787"   # ₿ bitcoin
PE_STARS            = "5933913338546231946"   # ⭐ telegram stars
PE_REFERRALS        = "5933808631538523391"   # 🎁 referrals button
PE_SUPPORT          = "5931489022781168927"   # 💬 support
PE_JOIN_CHANNEL     = "6053275839821257175"   # 📢 join channel
PE_ORDERS           = "5406944744230101045"   # 📦 my orders
PE_ABOUT            = "5933953307511888926"   # ℹ️/🌟 about
PE_DOLLAR           = "6053104294532487625"   # $
PE_TICK             = "6053202116707622090"   # ✅
PE_CROSS            = "5931527578702584794"   # ❌
PE_PLUS             = "5931287958182174532"   # ➕
PE_MINUS            = "5301240299085906131"   # ➖
PE_BACK             = "6053225373955530616"   # 🔙
PE_FIRE             = "6053163053980063912"   # 🔥
PE_CROWN            = "6338946058982267602"   # 👑
PE_NEW              = "6339306810465327721"   # 🆕


def pe(emoji: str, custom_emoji_id: str) -> str:
    """Return an HTML snippet that renders as a premium custom emoji."""
    return f'<tg-emoji emoji-id="{custom_emoji_id}">{emoji}</tg-emoji>'


# ---------------------------------------------------------------------------
# Auto map — bare unicode emoji → premium id.
# Anywhere a user / admin types one of these emojis (e.g. inside a product
# name) the bot will swap it for the premium version when sending.
#
# Keep keys with potential variation selectors (FE0F) BOTH with and without
# them so substring matching works regardless of how the source typed it.
# Order matters: longer matches first.
# ---------------------------------------------------------------------------
#
# IMPORTANT: Telegram requires the inner text of <tg-emoji> to be a real
# emoji unicode character. Plain ASCII like "$" is NOT allowed and causes
# the API to reject the whole message with "Entity_text_invalid".
# So we ONLY map real emojis here.
AUTO_MAP: dict[str, str] = {
    "🛍️": PE_SHOP_BAG,
    "🛍":  PE_SHOP_BAG,
    "🛒": PE_CART,
    "💎": PE_DEPOSIT,
    "⭐": PE_STARS,
    "🌟": PE_ABOUT,
    "💬": PE_SUPPORT,
    "📢": PE_JOIN_CHANNEL,
    "📦": PE_ORDERS,
    "✅": PE_TICK,
    "❌": PE_CROSS,
    "➕": PE_PLUS,
    "➖": PE_MINUS,
    "🔙": PE_BACK,
    "🔥": PE_FIRE,
    "👑": PE_CROWN,
    "🆕": PE_NEW,
    "🟢": PE_USDT,        # USDT TRC20
    "🟡": PE_BINANCE,     # Binance (single mapping; BEP20 uses pe() inline)
}

# Longest keys first so multi-codepoint emojis win over single chars.
_AUTO_KEYS = sorted(AUTO_MAP.keys(), key=len, reverse=True)

# Match existing <tg-emoji>...</tg-emoji> blocks so we don't double-wrap.
_TG_EMOJI_RE = re.compile(r"<tg-emoji\b[^>]*>.*?</tg-emoji>", re.DOTALL | re.IGNORECASE)

# Inline syntax admin can type ANYWHERE (product/category name, description,
# announcement, etc.) to attach a specific premium emoji id to the next
# emoji character(s):  "{pe:5197288608620360453}🔥 ChatGPT".
# Emoji payload = run of non-ASCII / symbol chars right after the marker
# (covers FE0F variation selectors, ZWJ sequences, skin tone modifiers).
_PE_INLINE_RE = re.compile(r"\{pe:(\d+)\}(\s*)([^\sA-Za-z0-9<>{}]+)")


def _wrap_outside_tags(s: str) -> str:
    """Walk a chunk that contains no existing tg-emoji blocks. Skip HTML tags."""
    if not s:
        return s
    out: list[str] = []
    i, n = 0, len(s)
    while i < n:
        ch = s[i]
        # Skip whole HTML tag
        if ch == "<":
            j = s.find(">", i)
            if j == -1:
                out.append(s[i:])
                break
            out.append(s[i:j + 1])
            i = j + 1
            continue
        # Explicit {pe:ID}emoji marker — wins over AUTO_MAP
        if ch == "{":
            m = _PE_INLINE_RE.match(s, i)
            if m:
                eid, ws, emo = m.group(1), m.group(2), m.group(3)
                out.append(ws)  # preserve any whitespace between marker and emoji
                out.append(f'<tg-emoji emoji-id="{eid}">{emo}</tg-emoji>')
                i = m.end()
                continue
            # Orphan marker (no emoji follows) — just strip it so the raw
            # "{pe:12345}" never shows up in the rendered message.
            m2 = re.match(r"\{pe:\d+\}", s[i:])
            if m2:
                i += m2.end()
                continue
        # Try longest matching emoji key from AUTO_MAP
        matched_key = None
        for key in _AUTO_KEYS:
            if s.startswith(key, i):
                matched_key = key
                break
        if matched_key:
            eid = AUTO_MAP[matched_key]
            out.append(f'<tg-emoji emoji-id="{eid}">{matched_key}</tg-emoji>')
            i += len(matched_key)
        else:
            out.append(ch)
            i += 1
    return "".join(out)


def strip_pe_markers(s: str | None) -> str:
    """Remove {pe:ID} markers, keep the emoji char — for plain-text contexts
    like button labels where premium emojis can't render in the body."""
    if not s:
        return s or ""
    return re.sub(r"\{pe:\d+\}", "", s)


def premiumize(html: str | None) -> str | None:
    """Auto-wrap known emojis in an HTML string with <tg-emoji> tags.

    Safe to call on text that already contains <tg-emoji> blocks — those
    are preserved untouched.
    """
    if not html:
        return html
    pieces: list[str] = []
    last = 0
    for m in _TG_EMOJI_RE.finditer(html):
        pieces.append(_wrap_outside_tags(html[last:m.start()]))
        pieces.append(m.group(0))
        last = m.end()
    pieces.append(_wrap_outside_tags(html[last:]))
    return "".join(pieces)


def install_bot_patch():
    """Monkey-patch python-telegram-bot so every outgoing HTML message /
    caption is auto-premiumized. Call once at app startup."""
    from telegram import Bot
    import functools

    def _patch(name: str, text_keys: tuple[str, ...]):
        original = getattr(Bot, name, None)
        if original is None or getattr(original, "_premium_patched", False):
            return

        @functools.wraps(original)
        async def wrapper(self, *args, **kwargs):
            pm = kwargs.get("parse_mode")
            # Only touch when caller explicitly uses HTML
            if pm == "HTML":
                for k in text_keys:
                    if k in kwargs and isinstance(kwargs[k], str):
                        kwargs[k] = premiumize(kwargs[k])
            return await original(self, *args, **kwargs)

        wrapper._premium_patched = True  # type: ignore[attr-defined]
        setattr(Bot, name, wrapper)

    _patch("send_message",         ("text",))
    _patch("edit_message_text",    ("text",))
    _patch("send_photo",           ("caption",))
    _patch("send_document",        ("caption",))
    _patch("send_video",           ("caption",))
    _patch("send_animation",       ("caption",))
    _patch("send_audio",           ("caption",))
    _patch("send_voice",           ("caption",))
    _patch("edit_message_caption", ("caption",))
    _patch("copy_message",         ("caption",))
