"""
Universal URL-based Custom Markdown for Telethon

Supports ALL Telegram entities via URL syntax
with full round-trip safety.

Special support:
- Mentions via [Name](tg://user?id=USER_ID)

📦 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 telethon.extensions import markdown
from telethon import types


class CustomMarkdown:
    # ===============================
    # Markdown -> Telegram entities
    # ===============================

    @staticmethod
    def _reorder_entities(entities):
        """Re-order entities so that nested (contained) entities appear before their containers.
        This avoids overlapping issues where parent entities (e.g., blockquote) may come before
        inner inline entities and cause clients to ignore the inner formatting.
        """
        if not entities:
            return []
        # Sort by starting offset (stable)
        ents = sorted(entities, key=lambda e: e.offset)
        stack = []
        result = []
        for e in ents:
            # Pop entities that do not contain the current one
            while stack:
                top = stack[-1]
                top_end = top.offset + top.length
                e_end = e.offset + e.length
                if top.offset <= e.offset and top_end >= e_end:
                    break
                result.append(stack.pop())
            stack.append(e)
        while stack:
            result.append(stack.pop())
        return result


    @staticmethod
    def parse(text: str):
        if not text:
            return text, []

        text, raw_entities = markdown.parse(text)
        new_entities = []

        for e in raw_entities:
            # Only transform TextUrl entities
            if not isinstance(e, types.MessageEntityTextUrl):
                new_entities.append(e)
                continue

            url = e.url.lower()
            offset, length = e.offset, e.length

            # -------- Text styles --------
            if url == "bold":
                new_entities.append(types.MessageEntityBold(offset, length))

            elif url == "italic":
                new_entities.append(types.MessageEntityItalic(offset, length))

            elif url == "underline":
                new_entities.append(types.MessageEntityUnderline(offset, length))

            elif url == "strike":
                new_entities.append(types.MessageEntityStrike(offset, length))

            elif url == "spoiler":
                new_entities.append(types.MessageEntitySpoiler(offset, length))

            elif url == "code":
                new_entities.append(types.MessageEntityCode(offset, length))

            elif url == "pre":
                new_entities.append(types.MessageEntityPre(offset, length, language=""))

            # -------- Blockquote --------
            elif url.startswith("quote"):
                new_entities.append(
                    types.MessageEntityBlockquote(
                        offset,
                        length,
                        collapsed=url.endswith("collapse"),
                    )
                )

            # -------- Mention (tg://user?id=...) --------
            elif url.startswith("tg://user?id="):
                # Use a text URL for mentions so Telegram preserves the clickable
                # tg:// link in all contexts (Saved Messages often strip raw
                # MessageEntityMentionName entities).
                try:
                    uid = int(url.split("=", 1)[1])
                    new_entities.append(
                        types.MessageEntityTextUrl(offset, length, f"tg://user?id={uid}")
                    )
                except ValueError:
                    new_entities.append(e)

            # -------- Legacy mention --------
            elif url.startswith("user:"):
                try:
                    uid = int(url[5:])
                    new_entities.append(
                        types.MessageEntityTextUrl(offset, length, f"tg://user?id={uid}")
                    )
                except ValueError:
                    new_entities.append(e)

            # -------- Custom Emoji --------
            elif url.startswith("emoji/"):
                try:
                    doc_id = int(url.split("/", 1)[1])
                    new_entities.append(
                        types.MessageEntityCustomEmoji(offset, length, doc_id)
                    )
                except ValueError:
                    new_entities.append(e)

            # -------- Normal URL --------
            else:
                new_entities.append(e)

        # Sort entities by offset, and for same offset put longer entities first (container before inner)
        new_entities = sorted(new_entities, key=lambda e: (e.offset, -e.length))
        return text, new_entities

    # ===============================
    # Telegram entities -> Markdown
    # ===============================

    @staticmethod
    def unparse(text: str, entities):
        if not text:
            return text

        new_entities = []

        for e in entities or []:
            offset, length = e.offset, e.length

            # Leave native formatting entity types as-is so markdown.unparse
            # can emit the appropriate delimiters (e.g. **bold**, __italic__, !!underline!!).
            if isinstance(e, (
                types.MessageEntityBold,
                types.MessageEntityItalic,
                types.MessageEntityUnderline,
                types.MessageEntityStrike,
                types.MessageEntitySpoiler,
                types.MessageEntityCode,
                types.MessageEntityPre,
            )):
                new_entities.append(e)
                continue

            # Keep blockquotes as actual blockquote entities so custom handling
            # below can emit %%/^^ per-entity.
            if isinstance(e, types.MessageEntityBlockquote):
                new_entities.append(e)
                continue

            # Mentions and text URLs can be emitted directly by markdown.unparse
            if isinstance(e, (types.MessageEntityMentionName, types.MessageEntityTextUrl)):
                new_entities.append(e)
                continue

            # Custom emoji keep as-is; handled below to emit emoji/URL form
            if isinstance(e, types.MessageEntityCustomEmoji):
                new_entities.append(e)
                continue

            # Fallback: keep the entity
            new_entities.append(e)

        # Sort entities by offset, and for same offset put longer entities first (container before inner)
        new_entities = sorted(new_entities, key=lambda e: (e.offset, -e.length))

        # Re-implement markdown.unparse logic here so blockquotes can use
        # custom delimiters (%% for collapsed, ^^ for expanded) on a per-entity basis.
        if not text or not new_entities:
            return text

        # Build reverse delimiters mapping for types that are classes
        delimiters = markdown.DEFAULT_DELIMITERS
        reverse = {}
        for k, v in delimiters.items():
            # only map entries where the value is a class/type
            if isinstance(v, type):
                reverse[v] = k

        # Prepare insert_at list similar to telethon.markdown.unparse
        insert_at = []
        for i, entity in enumerate(new_entities):
            s = entity.offset
            e = entity.offset + entity.length

            # Native formatting that has a single static delimiter
            delim = reverse.get(type(entity), None)
            if delim:
                insert_at.append((s, i, delim))
                insert_at.append((e, -i, delim))
                continue

            # Blockquote: choose delimiter per-entity
            if isinstance(entity, types.MessageEntityBlockquote):
                delim = '%%' if entity.collapsed else '^^'
                insert_at.append((s, i, delim))
                insert_at.append((e, -i, delim))
                continue

            # URLs and mentions
            url = None
            if isinstance(entity, types.MessageEntityTextUrl):
                url = entity.url
            elif isinstance(entity, types.MessageEntityMentionName):
                url = f"tg://user?id={entity.user_id}"

            if url:
                insert_at.append((s, i, '['))
                insert_at.append((e, -i, ']({})'.format(url)))
                continue

            # Custom emoji -> emit as markdown image-style text so it round-trips as ![EMOJI](emoji/ID)
            if isinstance(entity, types.MessageEntityCustomEmoji):
                # Insert opening '![' before the emoji text and the URL after.
                # Avoid duplicate '!' if the character immediately before the emoji
                # is already a '!'. Use the original text to inspect.
                try:
                    prev_char = text[s-1] if s > 0 else ''
                except Exception:
                    prev_char = ''
                opening = '[' if prev_char == '!' else '!['
                insert_at.append((s, i, opening))
                insert_at.append((e, -i, ']({})'.format(f"emoji/{entity.document_id}")))
                continue

            # Fallback: ignore

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

        text = markdown.add_surrogate(text)
        while insert_at:
            at, _, what = insert_at.pop()
            while markdown.within_surrogate(text, at):
                at += 1
            text = text[:at] + what + text[at:]

        return markdown.del_surrogate(text)
