from __future__ import annotations

import html
from collections import defaultdict
from html.parser import HTMLParser


_SUPPORTED_SIMPLE_TAGS = {
    "b": "b",
    "strong": "b",
    "i": "i",
    "em": "i",
    "u": "u",
    "ins": "u",
    "s": "s",
    "strike": "s",
    "del": "s",
    "code": "code",
    "pre": "pre",
    "a": "a",
    "span": "span",
    "blockquote": "blockquote",
    "tg-emoji": "tg-emoji",
}


def _utf16_index_to_python(text: str, utf16_index: int) -> int:
    """Ubah offset UTF-16 Telegram menjadi indeks string Python."""
    if utf16_index <= 0:
        return 0

    used = 0
    for index, char in enumerate(text):
        width = 2 if ord(char) > 0xFFFF else 1
        if used + width > utf16_index:
            return index
        used += width
        if used == utf16_index:
            return index + 1
    return len(text)


def _entity_tags(entity) -> tuple[str, str] | None:
    entity_type = getattr(entity, "type", None)

    simple_tags = {
        "bold": ("<b>", "</b>"),
        "italic": ("<i>", "</i>"),
        "underline": ("<u>", "</u>"),
        "strikethrough": ("<s>", "</s>"),
        "spoiler": ('<span class="tg-spoiler">', "</span>"),
        "code": ("<code>", "</code>"),
        "blockquote": ("<blockquote>", "</blockquote>"),
        "expandable_blockquote": ("<blockquote expandable>", "</blockquote>"),
    }
    if entity_type in simple_tags:
        return simple_tags[entity_type]

    if entity_type == "pre":
        language = getattr(entity, "language", None)
        if language:
            safe_language = html.escape(str(language), quote=True)
            return (f'<pre><code class="language-{safe_language}">', "</code></pre>")
        return ("<pre>", "</pre>")

    if entity_type == "text_link":
        url = getattr(entity, "url", None)
        if not url:
            return None
        return (f'<a href="{html.escape(str(url), quote=True)}">', "</a>")

    if entity_type == "text_mention":
        user = getattr(entity, "user", None)
        user_id = getattr(user, "id", None)
        if not user_id:
            return None
        return (f'<a href="tg://user?id={int(user_id)}">', "</a>")

    if entity_type == "custom_emoji":
        emoji_id = getattr(entity, "custom_emoji_id", None)
        if not emoji_id:
            return None
        safe_id = html.escape(str(emoji_id), quote=True)
        return (f'<tg-emoji emoji-id="{safe_id}">', "</tg-emoji>")

    return None


def text_with_custom_emoji(message) -> str:
    """Ubah teks/caption Telegram beserta entities menjadi HTML Bot API.

    Format yang dipertahankan: bold, italic, underline, coret, spoiler,
    code, pre, link, mention, blockquote, expandable blockquote, dan custom
    emoji Telegram Premium.
    """
    text = message.text if message.text is not None else (message.caption or "")
    entities = (message.entities if message.text is not None else message.caption_entities) or []

    openings: dict[int, list[tuple[int, int, str]]] = defaultdict(list)
    closings: dict[int, list[tuple[int, int, str]]] = defaultdict(list)

    for order, entity in enumerate(entities):
        tags = _entity_tags(entity)
        if not tags:
            continue

        start = _utf16_index_to_python(text, int(entity.offset))
        end = _utf16_index_to_python(text, int(entity.offset + entity.length))
        if start < 0 or end > len(text) or start >= end:
            continue

        opening_tag, closing_tag = tags
        span_length = end - start
        openings[start].append((-span_length, order, opening_tag))
        closings[end].append((span_length, -order, closing_tag))

    if not openings and not closings:
        return text

    output: list[str] = []
    for index in range(len(text) + 1):
        if index in closings:
            for _, _, tag in sorted(closings[index]):
                output.append(tag)
        if index in openings:
            for _, _, tag in sorted(openings[index]):
                output.append(tag)
        if index < len(text):
            output.append(text[index])
    return "".join(output)


class _PreviewRenderer(HTMLParser):
    """Buat pratinjau aman dan tetap pertahankan custom emoji Premium."""

    def __init__(self) -> None:
        super().__init__(convert_charrefs=True)
        self.parts: list[str] = []
        self.open_tags: list[str] = []

    def handle_starttag(self, tag: str, attrs) -> None:
        tag = tag.lower()
        attrs_dict = dict(attrs)

        if tag == "tg-emoji":
            emoji_id = attrs_dict.get("emoji-id")
            if emoji_id and str(emoji_id).isdigit():
                self.parts.append(
                    f'<tg-emoji emoji-id="{html.escape(str(emoji_id), quote=True)}">'
                )
                self.open_tags.append("tg-emoji")
            return

        # Pratinjau mempertahankan format ringan. Blockquote tidak diteruskan
        # karena panel sendiri sudah membungkus pratinjau dalam blockquote.
        if tag in {"b", "strong"}:
            self.parts.append("<b>")
            self.open_tags.append("b")
        elif tag in {"i", "em"}:
            self.parts.append("<i>")
            self.open_tags.append("i")
        elif tag in {"u", "ins"}:
            self.parts.append("<u>")
            self.open_tags.append("u")
        elif tag in {"s", "strike", "del"}:
            self.parts.append("<s>")
            self.open_tags.append("s")
        elif tag == "code":
            self.parts.append("<code>")
            self.open_tags.append("code")
        elif tag == "span" and attrs_dict.get("class") == "tg-spoiler":
            self.parts.append('<span class="tg-spoiler">')
            self.open_tags.append("span")
        elif tag == "a":
            href = attrs_dict.get("href")
            if href:
                self.parts.append(f'<a href="{html.escape(str(href), quote=True)}">')
                self.open_tags.append("a")
        elif tag in {"blockquote", "pre", "br"}:
            if self.parts and not self.parts[-1].endswith("\n"):
                self.parts.append("\n")

    def handle_endtag(self, tag: str) -> None:
        tag = tag.lower()
        canonical = _SUPPORTED_SIMPLE_TAGS.get(tag)
        if canonical in {"blockquote", "pre"}:
            if self.parts and not self.parts[-1].endswith("\n"):
                self.parts.append("\n")
            return
        if canonical and canonical in self.open_tags:
            # Tutup hanya tag paling akhir yang cocok; ini mencegah HTML silang
            # dari data lama membuat panel pengaturan gagal dibuka.
            while self.open_tags:
                opened = self.open_tags.pop()
                self.parts.append(f"</{opened}>")
                if opened == canonical:
                    break

    def handle_data(self, data: str) -> None:
        self.parts.append(html.escape(data))

    def result(self) -> str:
        while self.open_tags:
            self.parts.append(f"</{self.open_tags.pop()}>")
        rendered = "".join(self.parts)
        lines = [line.rstrip() for line in rendered.splitlines()]
        while lines and not lines[0]:
            lines.pop(0)
        while lines and not lines[-1]:
            lines.pop()
        return "\n".join(lines) or "-"


def preview_html(value: str) -> str:
    """Render HTML aman untuk panel, termasuk custom emoji Premium."""
    parser = _PreviewRenderer()
    parser.feed(value or "-")
    parser.close()
    return parser.result()


class _PlainTextRenderer(HTMLParser):
    def __init__(self) -> None:
        super().__init__(convert_charrefs=True)
        self.parts: list[str] = []

    def handle_starttag(self, tag: str, attrs) -> None:
        if tag.lower() in {"br", "blockquote", "pre"}:
            self.parts.append("\n")

    def handle_endtag(self, tag: str) -> None:
        if tag.lower() in {"blockquote", "pre"}:
            self.parts.append("\n")

    def handle_data(self, data: str) -> None:
        self.parts.append(data)


def html_to_plain_text(value: str) -> str:
    parser = _PlainTextRenderer()
    parser.feed(value or "")
    parser.close()
    return html.unescape("".join(parser.parts)).strip()
