export class ContentRenderer {
    constructor() {
        this.emojiMap = this.buildEmojiMap();
        this.imageExtRe = /\.(jpg|jpeg|png|gif|webp|svg|bmp|webp)(\?.*)?$/i;
        this.youtubeRe = /(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/(?:watch\?v=|embed\/|v\/|shorts\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/;
    }

    buildEmojiMap() {
        return {
            "grinning": "\u{1F600}", "smiley": "\u{1F603}", "smile": "\u{1F604}",
            "grin": "\u{1F601}", "laughing": "\u{1F606}", "sweat_smile": "\u{1F605}",
            "joy": "\u{1F602}", "blush": "\u{1F60A}", "innocent": "\u{1F607}",
            "wink": "\u{1F609}", "heart_eyes": "\u{1F60D}", "kissing_heart": "\u{1F618}",
            "stuck_out_tongue": "\u{1F61B}", "stuck_out_tongue_winking_eye": "\u{1F61C}",
            "sunglasses": "\u{1F60E}", "unamused": "\u{1F612}", "sweat": "\u{1F613}",
            "sob": "\u{1F62D}", "scream": "\u{1F631}", "sleeping": "\u{1F634}",
            "relieved": "\u{1F60C}", "heart": "\u{2764}\u{FE0F}", "broken_heart": "\u{1F494}",
            "two_hearts": "\u{1F495}", "sparkling_heart": "\u{1F496}",
            "star": "\u{2B50}", "sparkles": "\u{2728}", "zap": "\u{26A1}",
            "fire": "\u{1F525}", "rocket": "\u{1F680}", "100": "\u{1F4AF}",
            "clap": "\u{1F44F}", "ok_hand": "\u{1F44C}", "wave": "\u{1F44B}",
            "thumbsup": "\u{1F44D}", "+1": "\u{1F44D}", "thumbsdown": "\u{1F44E}",
            "-1": "\u{1F44E}", "pray": "\u{1F64F}", "muscle": "\u{1F4AA}",
            "tada": "\u{1F389}", "confetti_ball": "\u{1F38A}", "party": "\u{1F973}",
            "beers": "\u{1F37B}", "coffee": "\u{2615}", "pizza": "\u{1F355}",
            "computer": "\u{1F4BB}", "bug": "\u{1F41B}", "gear": "\u{2699}\u{FE0F}",
            "warning": "\u{26A0}\u{FE0F}", "question": "\u{2753}", "exclamation": "\u{2757}",
            "checkered_flag": "\u{1F3C1}", "rocket": "\u{1F680}",
            "eyes": "\u{1F440}", "brain": "\u{1F9E0}", "robot": "\u{1F916}",
            "skull": "\u{1F480}", "point_up": "\u{261D}\u{FE0F}", "point_down": "\u{1F447}",
            "point_left": "\u{1F448}", "point_right": "\u{1F449}",
            "pencil2": "\u{270F}\u{FE0F}", "memo": "\u{1F4DD}", "book": "\u{1F4D6}",
            "tools": "\u{1F6E0}\u{FE0F}", "hammer_and_wrench": "\u{1F6E0}\u{FE0F}",
        };
    }

    replaceShortcodes(text) {
        return text.replace(/:([a-zA-Z0-9_+\-]+):/g, (match, name) => {
            return this.emojiMap[name.toLowerCase()] || match;
        });
    }

    render(text) {
        if (!text) return "";
        text = this.replaceShortcodes(text);

        let html;
        if (typeof marked !== "undefined") {
            html = marked.parse(text, { breaks: true, gfm: true });
        } else {
            html = "<p>" + text.replace(/\n/g, "<br>") + "</p>";
        }

        html = this.processMedia(html);

        return html;
    }

    processMedia(html) {
        const temp = document.createElement("div");
        temp.innerHTML = html;

        this.walkNodes(temp, (node) => {
            if (node.nodeType !== 3) return;
            const text = node.textContent;
            if (!text || text.trim().length === 0) return;

            const parts = [];
            let lastIndex = 0;
            const urlRe = /(https?:\/\/[^\s<]+)/g;
            const mentionRe = /(?:^|\s|\x28)@([a-zA-Z0-9_-]+)/g;
            let match;

            while ((match = urlRe.exec(text)) !== null) {
                if (match.index > lastIndex) {
                    const chunk = text.substring(lastIndex, match.index);
                    this.splitMentions(chunk, parts);
                }
                parts.push({ type: "url", value: match[0], start: match.index, end: match.index + match[0].length });
                lastIndex = match.index + match[0].length;
            }
            if (lastIndex < text.length) {
                const chunk = text.substring(lastIndex);
                this.splitMentions(chunk, parts);
            }

            if (parts.length <= 1 && parts[0]?.type === "text") return;

            const fragment = document.createDocumentFragment();
            for (const part of parts) {
                if (part.type === "text") {
                    fragment.appendChild(document.createTextNode(part.value));
                } else if (part.type === "mention") {
                    const a = document.createElement("a");
                    a.href = "/profile/" + part.username;
                    a.className = "mention-link";
                    a.textContent = "@" + part.username;
                    fragment.appendChild(a);
                } else {
                    const el = this.urlToEmbed(part.value);
                    if (el) {
                        fragment.appendChild(el);
                    } else {
                        const a = document.createElement("a");
                        a.href = part.value;
                        a.target = "_blank";
                        a.rel = "noopener noreferrer";
                        a.textContent = part.value;
                        fragment.appendChild(a);
                    }
                }
            }

            node.parentNode.replaceChild(fragment, node);
        });

        return temp.innerHTML;
    }

    splitMentions(text, parts) {
        const mentionRe = /(?:^|\s|\x28)@([a-zA-Z0-9_-]+)/g;
        let m;
        let last = 0;
        while ((m = mentionRe.exec(text)) !== null) {
            if (m.index > last) {
                parts.push({ type: "text", value: text.substring(last, m.index) });
            }
            const atIdx = m[0].lastIndexOf('@');
            const prefix = m[0].slice(0, atIdx);
            if (prefix) {
                parts.push({ type: "text", value: prefix });
            }
            parts.push({ type: "mention", username: m[1] });
            last = m.index + m[0].length;
        }
        if (last < text.length) {
            parts.push({ type: "text", value: text.substring(last) });
        }
    }

    urlToEmbed(url) {
        const ytMatch = url.match(this.youtubeRe);
        if (ytMatch) {
            const videoId = ytMatch[1];
            const wrap = document.createElement("div");
            wrap.className = "embed-youtube";
            wrap.innerHTML = (
                '<iframe src="https://www.youtube.com/embed/' + videoId + '" ' +
                'frameborder="0" allowfullscreen ' +
                'allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture">' +
                '</iframe>'
            );
            return wrap;
        }

        if (this.imageExtRe.test(url)) {
            const img = document.createElement("img");
            img.src = url;
            img.alt = "";
            img.loading = "lazy";
            return img;
        }

        return null;
    }

    walkNodes(root, callback) {
        const skipTags = new Set(["CODE", "PRE", "A", "IFRAME", "IMG", "SCRIPT", "STYLE"]);
        const iter = document.createNodeIterator(root, NodeFilter.SHOW_TEXT, null, false);
        let node;
        while ((node = iter.nextNode())) {
            let parent = node.parentNode;
            let skip = false;
            while (parent) {
                if (skipTags.has(parent.tagName)) { skip = true; break; }
                parent = parent.parentNode;
            }
            if (!skip) callback(node);
        }
    }

    applyTo(element) {
        if (!element) return;
        const text = element.textContent || element.innerText || "";
        const rendered = this.render(text);
        element.innerHTML = rendered;
    }

    highlightAll() {
        if (typeof hljs !== "undefined") {
            document.querySelectorAll("pre code").forEach((el) => {
                hljs.highlightElement(el);
            });
        }
    }
}

export const contentRenderer = new ContentRenderer();
window.contentRenderer = contentRenderer;
