"""
Safe HTML <-> Telegram entity parser
Production ready and surrogate safe

📦 Delimiters advanced add-ons for Telethon
👤 Created by Ankit Chaubey | https://github.com/ankit-chaubey/delimiters
© 2026 Ankit Chaubey. All rights reserved.
📬 Contact: m.ankitchaubey@gmail.com | Telegram: @ankify @AnkitChaubey @ChaubeyBot
⚠️ Warning: Do not edit this file unless you are absolutely sure what you are doing.
💥 Changing this file may break the package.
🚀 Initially built for personal use, now released publicly.
"""

from collections import deque
from html import escape
from html.parser import HTMLParser
from typing import Iterable, Tuple, List

from telethon.helpers import (
    add_surrogate,
    del_surrogate,
    within_surrogate,
    strip_text,
)
from telethon.tl import TLObject
from telethon.tl.types import (
    MessageEntityBold,
    MessageEntityItalic,
    MessageEntityCode,
    MessageEntityPre,
    MessageEntityEmail,
    MessageEntityUrl,
    MessageEntityTextUrl,
    MessageEntityMentionName,
    MessageEntityUnderline,
    MessageEntityStrike,
    MessageEntityBlockquote,
    MessageEntityCustomEmoji,
    MessageEntitySpoiler,
    TypeMessageEntity,
)


# ===============================
# HTML -> Telegram entities
# ===============================

class HTMLToTelegramParser(HTMLParser):
    def __init__(self):
        super().__init__()
        self.text = ""
        self.entities: List[TypeMessageEntity] = []

        self._building_entities = {}
        self._open_tags = deque()

    def handle_starttag(self, tag, attrs):
        attrs = dict(attrs)

        # Line break
        if tag == "br":
            self.handle_data("\n")
            return

        self._open_tags.appendleft(tag)

        EntityType = None
        args = {}

        match tag:
            case "b" | "strong":
                EntityType = MessageEntityBold
            case "i" | "em":
                EntityType = MessageEntityItalic
            case "u":
                EntityType = MessageEntityUnderline
            case "s" | "del":
                EntityType = MessageEntityStrike

            case "blockquote":
                EntityType = MessageEntityBlockquote
                args["collapsed"] = "collapsed" in attrs

            case "span":
                if attrs.get("class") == "tg-spoiler":
                    EntityType = MessageEntitySpoiler

            case "tg-spoiler":
                EntityType = MessageEntitySpoiler

            case "pre":
                EntityType = MessageEntityPre
                args["language"] = ""

            case "code":
                pre = self._building_entities.get("pre")
                if pre:
                    lang = attrs.get("class", "")
                    if lang.startswith("language-"):
                        pre.language = lang[9:]
                else:
                    EntityType = MessageEntityCode

            case "a":
                href = attrs.get("href")
                if not href:
                    return

                if href.startswith("mailto:"):
                    EntityType = MessageEntityEmail
                    args["email"] = href[7:]
                else:
                    EntityType = MessageEntityTextUrl
                    args["url"] = del_surrogate(href)

            case "tg-emoji":
                try:
                    emoji_id = int(attrs.get("emoji-id", ""))
                except ValueError:
                    return
                EntityType = MessageEntityCustomEmoji
                args["document_id"] = emoji_id

        if EntityType and tag not in self._building_entities:
            self._building_entities[tag] = EntityType(
                offset=len(self.text),
                length=0,
                **args,
            )

    def handle_data(self, data):
        for tag in self._open_tags:
            entity = self._building_entities.get(tag)
            if entity:
                entity.length += len(data)

        self.text += data

    def handle_endtag(self, tag):
        try:
            self._open_tags.popleft()
        except IndexError:
            pass

        entity = self._building_entities.pop(tag, None)
        if entity:
            self.entities.append(entity)


# ===============================
# Telegram entities -> HTML
# ===============================

ENTITY_TO_HTML = {
    MessageEntityBold: ("<strong>", "</strong>"),
    MessageEntityItalic: ("<em>", "</em>"),
    MessageEntityUnderline: ("<u>", "</u>"),
    MessageEntityStrike: ("<del>", "</del>"),
    MessageEntityCode: ("<code>", "</code>"),
    MessageEntityBlockquote: (lambda e, _: (f"<blockquote{' collapsed' if getattr(e, 'collapsed', False) else ''}>", "</blockquote>")),
    MessageEntitySpoiler: ("<tg-spoiler>", "</tg-spoiler>"),

    MessageEntityPre: lambda e, _: (
        f"<pre><code class='language-{e.language}'>",
        "</code></pre>",
    ),

    MessageEntityEmail: lambda _, t: (
        f'<a href="mailto:{t}">',
        "</a>",
    ),

    MessageEntityUrl: lambda _, t: (
        f'<a href="{t}">',
        "</a>",
    ),

    MessageEntityTextUrl: lambda e, _: (
        f'<a href="{escape(e.url)}">',
        "</a>",
    ),

    MessageEntityMentionName: lambda e, _: (
        f'<a href="tg://user?id={e.user_id}">',
        "</a>",
    ),

    MessageEntityCustomEmoji: lambda e, _: (
        f'<tg-emoji emoji-id="{e.document_id}">',
        "</tg-emoji>",
    ),
}


# ===============================
# Public API
# ===============================

class CustomHtmlParser:
    @staticmethod
    def parse(html: str) -> Tuple[str, List[TypeMessageEntity]]:
        if not html:
            return html, []

        parser = HTMLToTelegramParser()
        parser.feed(add_surrogate(html))

        text = strip_text(parser.text, parser.entities)
        parser.entities.sort(key=lambda e: e.offset)

        return del_surrogate(text), parser.entities

    @staticmethod
    def unparse(
        text: str,
        entities: Iterable[TypeMessageEntity],
    ) -> str:
        if not text:
            return text

        if not entities:
            return escape(text)

        if isinstance(entities, TLObject):
            entities = (entities,)

        text = add_surrogate(text)
        insert_at = []

        for i, e in enumerate(entities):
            start = e.offset
            end = e.offset + e.length

            formatter = ENTITY_TO_HTML.get(type(e))
            if not formatter:
                continue

            if callable(formatter):
                open_tag, close_tag = formatter(e, text[start:end])
            else:
                open_tag, close_tag = formatter

            insert_at.append((start, i, open_tag))
            insert_at.append((end, -i, close_tag))

        insert_at.sort(key=lambda x: (x[0], x[1]))

        next_escape = len(text)
        while insert_at:
            pos, _, value = insert_at.pop()

            while within_surrogate(text, pos):
                pos += 1

            text = (
                text[:pos]
                + value
                + escape(text[pos:next_escape])
                + text[next_escape:]
            )
            next_escape = pos

        text = escape(text[:next_escape]) + text[next_escape:]
        return del_surrogate(text)
