import { Anchor, Text, useComputedColorScheme, useMantineTheme } from "@mantine/core";
import React from "react";
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import dark from 'react-syntax-highlighter/dist/esm/styles/prism/one-dark';
import light from 'react-syntax-highlighter/dist/esm/styles/prism/one-light';
import { UserMention } from "../UserMention/UserMention";
import { Emoji } from "../Emoji/Emoji";
import { GroupInviteMessage } from "../GroupInviteMessage/GroupInviteMessage";
import { ALLOWED_DOMAINS_ZONES } from "@/app/constants";

interface TextParserProps {
    text: string;
    /**
     * If true, the parsed entities will be rendered without hydration (static rendering).
     */
    noHydrate?: boolean;
    /**
     * If the text (excluding emojis) is smaller than this value, render emojis in oversize (40px).
     */
    oversizeIfTextSmallerThan?: number;
    /**
     * Limits the number of parsed entities (like links, mentions, emojis, etc.) in the text.
     * If the limit is reached, the remaining text will be rendered as plain text.
     */
    performanceEntityLimit?: number;
    /**
     * Flags to enable other effects
     */
    __reserved_1?: boolean;
    __reserved_2?: boolean;
    __reserved_3?: boolean;
}

interface FormatRule {
    pattern: RegExp[];
    render: (match: string) => React.ReactNode;
    flush: (match: string) => React.ReactNode;
}

export function TextParser(props: TextParserProps) {
    const computedTheme = useComputedColorScheme();
    const theme = useMantineTheme();
    let entityCount = 0;

    const UNICODE_EMOJI_SEQUENCE_REGEX =
        /(?:\p{Regional_Indicator}{2}|[0-9#*]\uFE0F?\u20E3|\p{Extended_Pictographic}(?:\uFE0F|\uFE0E)?(?:\p{Emoji_Modifier})?(?:\u200D\p{Extended_Pictographic}(?:\uFE0F|\uFE0E)?(?:\p{Emoji_Modifier})?)*)/u;

    const UNICODE_EMOJI_SEQUENCE_REGEX_GLOBAL = new RegExp(
        UNICODE_EMOJI_SEQUENCE_REGEX.source,
        "gu"
    );

    const toUnified = (value: string): string =>
        Array.from(value)
            .map((ch) => ch.codePointAt(0)?.toString(16))
            .filter(Boolean)
            .join("-");

    const formatRules : FormatRule[] = [
        {  
            pattern: [
                /(https?:\/\/[^\s]+)/g,
                /\b(?:https?:\/\/)?(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?:\/[\w\-\.\/\?&#=%]*)?/g
            ],
            render: (match: string) => {
                let domain = match.replace(/https?:\/\//, '').split('/')[0];
                let domainZone = domain.split('.').pop() || '';
                domainZone = domainZone.split('/')[0];
                if(!ALLOWED_DOMAINS_ZONES.includes(domainZone)) {
                    return <>{match}</>;
                }
                return <Anchor style={{
                    userSelect: 'auto',
                    color: props.__reserved_2 ? theme.colors.blue[2] : undefined
                }} fz={14} href={match.startsWith('http') ? match : 'https://' + match} target="_blank" rel="noopener noreferrer">{match}</Anchor>;
            },
            flush: (match: string) => {
                return <>{match}</>;
            }
        },
        {
            pattern: [/\*\*(.+?)\*\*/],
            render: (match: string) => {
                const boldText = match.replace(/^\*\*(.+)\*\*$/, '$1');
                return <b style={{
                    userSelect: 'auto'
                }}>{boldText}</b>;
            },
            flush: (match: string) => {
                const boldText = match.replace(/^\*\*(.+)\*\*$/, '$1');
                return <>{boldText}</>;
            }
        },
        {
            // language```code```
            pattern: [/([a-zA-Z0-9]+)```([\s\S]*?)```/],
            render: (match: string) => {
                const langMatch = match.match(/^([a-zA-Z0-9]+)```([\s\S]*?)```$/);
                const language = langMatch ? langMatch[1] : "plaintext";
                const codeContent = langMatch ? langMatch[2] : match;
                return (
                    <SyntaxHighlighter customStyle={{
                        borderRadius: '8px',
                        padding: '12px',
                        fontSize: '14px',
                        margin: '8px 0',
                        overflowX: 'scroll',
                        maxWidth: '40vw'
                    }} showLineNumbers wrapLongLines lineProps={{
                        style: {
                            //flexWrap: 'wrap'
                        }
                    }} wrapLines language={language} style={
                        computedTheme === 'dark' ? dark : light
                    }>
                        {codeContent.trim()}
                    </SyntaxHighlighter>
                );
            },
            flush: (match: string) => {
                const langMatch = match.match(/^([a-zA-Z0-9]+)```([\s\S]*?)```$/);
                const codeContent = langMatch ? langMatch[2] : match;
                return <>{codeContent}</>;
            }
        },
        {
            // @username
            pattern: [/@([a-zA-Z0-9_]+)/],
            render: (match: string) => {
                return <UserMention color={props.__reserved_2 ? theme.colors.blue[2] : undefined}  key={match} username={match}></UserMention>;
            },
            flush: (match: string) => {
                return <>{match}</>;
            }
        },
        {
            // unicode emojis (including composite sequences)
            pattern: [UNICODE_EMOJI_SEQUENCE_REGEX],
            render: (match: string) => {
                const textWithoutEmojis = props.text.replace(UNICODE_EMOJI_SEQUENCE_REGEX_GLOBAL, "");
                const unified = toUnified(match);

                if (textWithoutEmojis.length <= (props.oversizeIfTextSmallerThan ?? 0)) {
                    return <Emoji size={40} unified={unified}></Emoji>;
                }

                return <Emoji unified={unified}></Emoji>;
            },
            flush: (match: string) => {
                return <Emoji unified={toUnified(match)}></Emoji>;
            }
        },
        {
            // :emoji_code:
            pattern: [/:emoji_([a-zA-Z0-9_-]+):/],
            render: (match: string) => {
                const emojiCode = match.slice(1, -1);
                let textWithoutEmojis = props.text.replace(/:emoji_[a-zA-Z0-9_-]+:/g, '');
                if(textWithoutEmojis.length <= (props.oversizeIfTextSmallerThan ?? 0)) {
                    return <Emoji size={40} unified={emojiCode.replace("emoji_", "")}></Emoji>;
                }
                return <Emoji unified={emojiCode.replace("emoji_", "")}></Emoji>;
            },
            flush: (match: string) => {
                const emojiCode = match.slice(1, -1);
                return <Emoji unified={emojiCode.replace("emoji_", "")}></Emoji>;
            }
        },
        {
            // $a=Attachment text
            pattern: [/^\$a=(.+)$/],
            render: (match: string) => {
                const attachmentText = match.replace(/^\$a=(.+)$/, '$1');
                return <>{attachmentText}</>;
            },
            flush: (match: string) => {
                const attachmentText = match.replace(/^\$a=(.+)$/, '$1');
                return <Text display={'inline-block'} c={props.__reserved_1 ? '#FFF' : 'blue'}>{attachmentText}</Text>;
            }
        },
        {
            //#group:stringbase64
            pattern: [/^#group:([A-Za-z0-9+/=:]+)$/],
            render: (match: string) => {
                const groupString = match.replace(/^#group:([A-Za-z0-9+/=]+)$/, '$1');
                return <GroupInviteMessage groupInviteCode={groupString}></GroupInviteMessage>;
            },
            flush: () => {
                return <Text c={props.__reserved_1 ? '#FFF' : 'blue'}>Group Invite Code</Text>;
            }
        }
    ];

    function parseText(text: string): React.ReactNode[] {
        let result: React.ReactNode[] = [];
        let remainingText = text;
        let index = 0;

        while (remainingText.length > 0) {
            let earliestMatch: {start: number, end: number, rule: FormatRule, match: string} | null = null;

            for (const rule of formatRules) {
                for (const pattern of rule.pattern) {
                    pattern.lastIndex = 0; // Reset regex state for global patterns
                    const match = pattern.exec(remainingText);
                    if (match && (earliestMatch === null || match.index < earliestMatch.start)) {
                        earliestMatch = {
                            start: match.index,
                            end: match.index + match[0].length,
                            rule,
                            match: match[0]
                        };
                    }
                }
            }
            if (earliestMatch) {
                // Performance limit check
                if (props.performanceEntityLimit !== undefined && entityCount >= props.performanceEntityLimit) {
                    result.push(
                        <React.Fragment key={index++}>
                            {remainingText}
                        </React.Fragment>
                    );
                    break;
                }
                entityCount += 1;

                if (earliestMatch.start > 0) {
                    result.push(
                        <React.Fragment key={index++}>
                            {remainingText.slice(0, earliestMatch.start)}
                        </React.Fragment>
                    );
                }
                result.push(
                    <React.Fragment key={index++}>
                        {props.noHydrate ? earliestMatch.rule.flush(earliestMatch.match) : earliestMatch.rule.render(earliestMatch.match)}
                    </React.Fragment>
                );
                remainingText = remainingText.slice(earliestMatch.end);
            } else {
                result.push(
                    <React.Fragment key={index++}>
                        {remainingText}
                    </React.Fragment>
                );
                break;
            }
        }
        return result;
    }

    return <>{parseText(props.text)}</>;
}