import {
    useEffect,
    useState,
    useMemo,
    useRef
} from "react";
import {
    Text as NativeText,
    TouchableOpacity,
    ScrollView,
    Platform,
    View
} from "react-native";
import type ICodeViewerProps from "./type";
import type {
    TokenType
} from "./type";
import stylesheet, {
    useStyles
} from "./stylesheet";
import {
    splitTokensIntoLines,
    getLineIndentation,
    parseCode
} from "./parser";
import {
    NCoreUIKitLocalize,
    NCoreUIKitTheme,
    NCoreUIKitToast
} from "../../core/hooks";
import {
    ChevronRightIcon,
    ChevronDownIcon,
    CheckIcon,
    CopyIcon
} from "lucide-react-native";
import Clipboard from "@react-native-clipboard/clipboard";
import {
    webStyle
} from "../../utils";
import Button from "../button";

const CodeViewer = ({
    isLineNumberVisible = true,
    autoFoldMaxLines = 60,
    isCollapsible = true,
    autoFold = false,
    language,
    code
}: ICodeViewerProps) => {
    const {
        typography,
        colors,
        spaces
    } = NCoreUIKitTheme.useContext();

    const {
        localize
    } = NCoreUIKitLocalize.useContext();

    const [
        isCopied,
        setIsCopied
    ] = useState<boolean>(false);

    const timeoutRef = useRef<NodeJS.Timeout | null>(null);

    useEffect(() => {
        return () => {
            if (timeoutRef.current) {
                clearTimeout(timeoutRef.current);
            }
        };
    }, []);

    const tokens = useMemo(() => parseCode(code, language), [
        language,
        code
    ]);

    const lines = useMemo(() => {
        return splitTokensIntoLines(tokens);
    }, [tokens]);

    const lineIndentations = useMemo(() => {
        return lines.map((line) => getLineIndentation(line));
    }, [lines]);

    const isLineFoldable = useMemo(() => {
        return lines.map((_, i) => {
            const currentIndent = lineIndentations[i] as number;
            for (let j = i + 1; j < lines.length; j++) {
                const nextIndent = lineIndentations[j] as number;
                const nextLineStr = (lines[j] as import("./type").Token[]).map((t) => t.value).join("").trim();

                if (nextLineStr.length === 0) continue;

                if (nextIndent > currentIndent) {
                    return true;
                } else {
                    return false;
                }
            }
            return false;
        });
    }, [
        lines,
        lineIndentations
    ]);

    const hasAnyFoldableLine = useMemo(() => {
        return isLineFoldable.some((foldable) => foldable);
    }, [isLineFoldable]);

    const actualIsCollapsible = isCollapsible && hasAnyFoldableLine;

    const {
        languageText: languageTextDynamicStyle,
        container: containerDynamicStyle,
        lineRow: lineRowDynamicStyle,
        gutter: gutterDynamicStyle,
        text: textDynamicStyle
    } = useStyles({
        isCollapsible: actualIsCollapsible,
        isLineNumberVisible,
        typography,
        colors,
        spaces
    });

    const initialCollapsedLines = useMemo(() => {
        const collapsed = new Set<number>();
        if (!autoFold || !actualIsCollapsible) return collapsed;

        let minIndent = Infinity;
        for (let i = 0; i < lines.length; i++) {
            if (isLineFoldable[i]) {
                const indent = lineIndentations[i] as number;
                if (indent < minIndent) {
                    minIndent = indent;
                }
            }
        }

        for (let i = 0; i < lines.length; i++) {
            if (isLineFoldable[i]) {
                const startIndent = lineIndentations[i] as number;

                if (startIndent <= minIndent) {
                    continue;
                }

                let blockLength = 0;

                for (let j = i + 1; j < lines.length; j++) {
                    const currentIndent = lineIndentations[j] as number;
                    const lineStr = (lines[j] as import("./type").Token[]).map((t) => t.value).join("").trim();

                    if (lineStr.length !== 0 && currentIndent <= startIndent) {
                        break;
                    }

                    blockLength++;
                }

                if (blockLength > autoFoldMaxLines) {
                    collapsed.add(i);
                }
            }
        }
        return collapsed;
    }, [
        autoFoldMaxLines,
        lineIndentations,
        isLineFoldable,
        autoFold,
        lines
    ]);

    const [
        collapsedLines,
        setCollapsedLines
    ] = useState<Set<number>>(initialCollapsedLines);

    useEffect(() => {
        setCollapsedLines(initialCollapsedLines);
    }, [
        initialCollapsedLines
    ]);

    const visibleLines = useMemo(() => {
        const visible = new Set<number>();
        const activeFolds: number[] = [];

        for (let i = 0; i < lines.length; i++) {
            const indent = lineIndentations[i] as number;
            const lineStr = (lines[i] as import("./type").Token[]).map((t) => t.value).join("").trim();
            const isEmpty = lineStr.length === 0;

            if (!isEmpty) {
                while (activeFolds.length > 0 && indent <= (activeFolds[activeFolds.length - 1] as number)) {
                    activeFolds.pop();
                }
            }

            if (activeFolds.length > 0) {
                continue;
            }

            visible.add(i);

            if (actualIsCollapsible && collapsedLines.has(i) && isLineFoldable[i]) {
                activeFolds.push(indent);
            }
        }
        return visible;
    }, [
        lineIndentations,
        isLineFoldable,
        collapsedLines,
        actualIsCollapsible,
        lines
    ]);

    const toggleFold = (lineIndex: number) => {
        setCollapsedLines((prev) => {
            const next = new Set(prev);
            if (next.has(lineIndex)) {
                next.delete(lineIndex);
            } else {
                next.add(lineIndex);
            }
            return next;
        });
    };

    const getTokenColor = (type: TokenType) => {
        return colors.code[type as keyof typeof colors.code] || colors.code.default;
    };

    const handleCopyCode = async () => {
        if (isCopied) return;

        const replacedCode = tokens
            .filter(token => token.type !== "comment")
            .map(token => token.value)
            .join("");

        let isSuccess = false;

        if (Platform.OS === "web") {
            try {
                await navigator.clipboard.writeText(replacedCode);

                isSuccess = true;
            } catch {
                NCoreUIKitToast.open({
                    title: localize("code-copy-error-to-clipboard")
                });
            }
        } else {
            try {
                Clipboard.setString(replacedCode);

                isSuccess = true;
            } catch {
                NCoreUIKitToast.open({
                    title: localize("code-copy-error-to-clipboard")
                });
            }
        }

        if (isSuccess) {
            NCoreUIKitToast.open({
                title: localize("code-copy-success-to-clipboard")
            });

            setIsCopied(true);

            timeoutRef.current = setTimeout(() => {
                setIsCopied(false);
            }, 3000);
        }
    };

    return <View
        style={[
            stylesheet.container,
            containerDynamicStyle
        ]}
    >
        <ScrollView
            contentContainerStyle={stylesheet.scrollContent}
            style={[
                stylesheet.scrollView,
                {
                    paddingVertical: spaces.spacingSm
                }
            ]}
            showsHorizontalScrollIndicator={true}
            horizontal={true}
        >
            <View>
                {
                    language ?
                        <NativeText
                            style={[
                                stylesheet.content,
                                textDynamicStyle,
                                languageTextDynamicStyle
                            ]}
                            selectable={false}
                        >
                            // {language}
                        </NativeText>
                    :
                        null
                }
                {lines.map((lineTokens, index) => {
                    if (!visibleLines.has(index)) return null;

                    const isFoldable = isLineFoldable[index];
                    const isCollapsed = collapsedLines.has(index);
                    const isGutterVisible = isLineNumberVisible || actualIsCollapsible;

                    return <View
                        style={[
                            stylesheet.lineRow,
                            lineRowDynamicStyle
                        ]}
                        key={`line-${index}`}
                    >
                        {
                            isGutterVisible ?
                                <View
                                    style={[
                                        stylesheet.gutter,
                                        gutterDynamicStyle,
                                        {
                                            borderRightColor: colors.content.border.subtle
                                        }
                                    ]}
                                >
                                    {
                                        isLineNumberVisible ?
                                            <NativeText
                                                style={[
                                                    stylesheet.lineNumber,
                                                    {
                                                        fontFamily: Platform.OS === "web" ? "monospace" : "System",
                                                        color: colors.content.text.low
                                                    }
                                                ]}
                                                selectable={false}
                                            >
                                                {index + 1}
                                            </NativeText>
                                        :
                                            null
                                    }
                                    {
                                        actualIsCollapsible ?
                                            isFoldable ?
                                                <TouchableOpacity
                                                    style={stylesheet.foldIcon}
                                                    onPress={() => {
                                                        toggleFold(index);
                                                    }}
                                                >
                                                    {
                                                        isCollapsed ?
                                                            <ChevronRightIcon
                                                                color={colors.content.text.low}
                                                                size={14}
                                                            />
                                                        :
                                                            <ChevronDownIcon
                                                                color={colors.content.text.low}
                                                                size={14}
                                                            />
                                                    }
                                                </TouchableOpacity>
                                                :
                                                <View
                                                    style={stylesheet.foldIconPlaceholder}
                                                />
                                            :
                                            null
                                    }
                                </View>
                            :
                                null
                        }
                        <NativeText
                            style={[
                                stylesheet.content,
                                textDynamicStyle,
                                {
                                    color: colors.content.text.high
                                }
                            ]}
                        >
                            {lineTokens.map((token, tIndex) => {
                                return <NativeText
                                    style={{
                                        color: getTokenColor(token.type)
                                    }}
                                    key={`token-${index}-${tIndex}`}
                                >
                                    {token.value}
                                </NativeText>;
                            })}
                            {isCollapsed && (
                                <NativeText
                                    style={{
                                        backgroundColor: colors.content.container.subtle,
                                        color: colors.content.text.mid
                                    }}
                                >
                                    {" ... "}
                                </NativeText>)}
                        </NativeText>
                    </View>;
                })}
            </View>
        </ScrollView>
        <View
            style={[
                stylesheet.stickyActionContainer,
                {
                    marginLeft: spaces.spacingMd,
                    ...webStyle({
                        top: spaces.spacingMd,
                        position: "sticky"
                    })
                }
            ]}
        >
            <Button
                icon={({
                    color,
                    size
                }) => {
                    if (isCopied) {
                        return <CheckIcon
                            color={colors.content.icon.success}
                            size={size + 5}
                        />;
                    }

                    return <CopyIcon
                        color={colors.content.icon[color]}
                        size={size + 5}
                    />;
                }}
                onPress={handleCopyCode}
                spreadBehaviour="free"
                variant="ghost"
                type="neutral"
                size="small"
            />
        </View>
    </View>;
};
export default CodeViewer;
