import React, { useCallback, useMemo, useEffect, useRef, useState } from 'react';
import { type FileObject } from '../api/server/files';
import VirtualList from './VirtualList';

interface Props {
    tree: Record<string, FileObject[]>;
    rootPath: string;
    expandedFolders: Set<string>;
    sortBy: 'name' | 'size' | 'date' | 'type';
    sortDirection: 'asc' | 'desc';
    containerRef: React.RefObject<HTMLElement>;
    renderFolderRow: (
        file: FileObject,
        fullPath: string,
        depth: number,
        isLastChild: boolean,
        isExpanded: boolean
    ) => React.ReactNode;
    renderFileRow: (file: FileObject, fullPath: string, depth: number, isLastChild: boolean) => React.ReactNode;
    selectedFile?: string | null;
    selectedFiles?: string[];
    draggedPaths?: Set<string>;
    isDragging?: boolean;
    showFileCheckboxes?: boolean;
}

// Item height in pixels - must match actual rendered height (including gap)
const ITEM_HEIGHT = 28;

interface FlatItem {
    type: 'folder' | 'file';
    file: FileObject;
    fullPath: string;
    depth: number;
    isLastChild: boolean;
    isExpanded?: boolean;
}

const TreeList: React.FC<Props> = ({
    tree,
    rootPath,
    expandedFolders,
    sortBy,
    sortDirection,
    containerRef,
    renderFolderRow,
    renderFileRow,
    selectedFile,
    selectedFiles,
    draggedPaths,
    isDragging,
    showFileCheckboxes,
}) => {
    // Flatten the tree into a virtual list, respecting expanded state
    const flatItems = useMemo(() => {
        const items: FlatItem[] = [];

        const sortFiles = (files: FileObject[]) => {
            return [...files].sort((a, b) => {
                if (a.isFile !== b.isFile) {
                    return a.isFile ? 1 : -1;
                }
                let comparison = 0;
                switch (sortBy) {
                    case 'name':
                        comparison = a.name.localeCompare(b.name);
                        break;
                    case 'size':
                        comparison = (a.size || 0) - (b.size || 0);
                        break;
                    case 'date':
                        const aDate = a.modifiedAt ? new Date(a.modifiedAt).getTime() : 0;
                        const bDate = b.modifiedAt ? new Date(b.modifiedAt).getTime() : 0;
                        comparison = aDate - bDate;
                        break;
                    case 'type':
                        const aExt = a.name.split('.').pop() || '';
                        const bExt = b.name.split('.').pop() || '';
                        comparison = aExt.localeCompare(bExt);
                        break;
                }
                return sortDirection === 'asc' ? comparison : -comparison;
            });
        };

        const flatten = (path: string, depth: number = 0) => {
            const files = tree[path] || [];
            const filteredFiles = files.filter(file => file.name !== '.trash-bin');
            const sorted = sortFiles(filteredFiles);

            sorted.forEach((file, index) => {
                const fullPath = path === '/' ? `/${file.name}` : `${path}/${file.name}`;
                const isLastChild = index === sorted.length - 1;

                if (!file.isFile) {
                    const isExpanded = expandedFolders.has(fullPath);
                    items.push({
                        type: 'folder',
                        file,
                        fullPath,
                        depth,
                        isLastChild,
                        isExpanded,
                    });
                    if (isExpanded) {
                        flatten(fullPath, depth + 1);
                    }
                } else {
                    items.push({
                        type: 'file',
                        file,
                        fullPath,
                        depth,
                        isLastChild,
                    });
                }
            });
        };

        flatten(rootPath);
        return items;
    }, [tree, rootPath, expandedFolders, sortBy, sortDirection]);

    const [animatedItems, setAnimatedItems] = useState<FlatItem[]>(flatItems);
    const [enteringKeys, setEnteringKeys] = useState<Set<string>>(new Set());
    const [activeEnteringKeys, setActiveEnteringKeys] = useState<Set<string>>(new Set());
    const [exitingKeys, setExitingKeys] = useState<Set<string>>(new Set());
    const [enterOrder, setEnterOrder] = useState<Map<string, number>>(new Map());
    const [exitOrder, setExitOrder] = useState<Map<string, number>>(new Map());
    const prevItemsRef = useRef<FlatItem[]>(flatItems);
    const enterRafRef = useRef<number | null>(null);
    const exitTimerRef = useRef<number | null>(null);
    const ENTER_DURATION = 220;
    const EXIT_DURATION = 220;
    const STAGGER_STEP = 20;
    const MAX_STAGGER = 120;

    useEffect(() => {
        const prevItems = prevItemsRef.current;
        const prevKeys = new Set(prevItems.map(item => item.fullPath));
        const nextKeys = new Set(flatItems.map(item => item.fullPath));

        const entering = new Set<string>();
        const exiting = new Set<string>();

        nextKeys.forEach(key => {
            if (!prevKeys.has(key)) entering.add(key);
        });
        prevKeys.forEach(key => {
            if (!nextKeys.has(key)) exiting.add(key);
        });

        const enteringItems = flatItems.filter(item => entering.has(item.fullPath));
        const exitingItems = prevItems.filter(item => exiting.has(item.fullPath));

        const nextEnterOrder = new Map<string, number>();
        enteringItems.forEach((item, idx) => nextEnterOrder.set(item.fullPath, idx));

        const nextExitOrder = new Map<string, number>();
        exitingItems.forEach((item, idx) => {
            const reverseIndex = Math.max(0, exitingItems.length - 1 - idx);
            nextExitOrder.set(item.fullPath, reverseIndex);
        });

        let merged = flatItems.slice();
        if (exiting.size) {
            const exitingIndexItems = prevItems
                .map((item, index) => ({ item, index }))
                .filter(({ item }) => exiting.has(item.fullPath));

            exitingIndexItems.forEach(({ item, index }) => {
                const insertAt = Math.min(index, merged.length);
                merged.splice(insertAt, 0, item);
            });
        }

        setAnimatedItems(merged);
        setEnteringKeys(entering);
        setExitingKeys(exiting);
        setEnterOrder(nextEnterOrder);
        setExitOrder(nextExitOrder);

        if (enterRafRef.current !== null) {
            cancelAnimationFrame(enterRafRef.current);
        }
        if (entering.size) {
            enterRafRef.current = requestAnimationFrame(() => {
                setActiveEnteringKeys(new Set(entering));
                enterRafRef.current = null;
            });
        } else {
            setActiveEnteringKeys(new Set());
        }

        if (exitTimerRef.current !== null) {
            window.clearTimeout(exitTimerRef.current);
        }
        if (exiting.size) {
            const maxExitDelay = Math.min(MAX_STAGGER, (exitingItems.length - 1) * STAGGER_STEP);
            const timeout = EXIT_DURATION + maxExitDelay + 40;
            exitTimerRef.current = window.setTimeout(() => {
                setExitingKeys(new Set());
                setAnimatedItems(flatItems);
                exitTimerRef.current = null;
            }, timeout);
            return () => {
                if (exitTimerRef.current !== null) {
                    window.clearTimeout(exitTimerRef.current);
                    exitTimerRef.current = null;
                }
            };
        }

        prevItemsRef.current = flatItems;
        return undefined;
    }, [flatItems]);

    useEffect(() => {
        prevItemsRef.current = flatItems;
    }, [flatItems]);

    const selectedFilesSet = useMemo(() => new Set(selectedFiles ?? []), [selectedFiles]);

    // Render individual items
    const renderItem = useCallback((item: FlatItem) => {
        if (draggedPaths?.has(item.fullPath)) {
            return null;
        }
        const isEntering = enteringKeys.has(item.fullPath) && !activeEnteringKeys.has(item.fullPath);
        const isExiting = exitingKeys.has(item.fullPath);
        const enterIndex = enterOrder.get(item.fullPath) ?? 0;
        const exitIndex = exitOrder.get(item.fullPath) ?? 0;
        const enterDelay = enteringKeys.has(item.fullPath) ? Math.min(MAX_STAGGER, enterIndex * STAGGER_STEP) : 0;
        const exitDelay = exitingKeys.has(item.fullPath) ? Math.min(MAX_STAGGER, exitIndex * STAGGER_STEP) : 0;

        return (
            <div
                style={{
                    transition: `opacity ${ENTER_DURATION}ms ease, transform ${ENTER_DURATION}ms ease`,
                    transitionDelay: `${isExiting ? exitDelay : enterDelay}ms`,
                    opacity: isEntering || isExiting ? 0 : 1,
                    transform: isEntering ? 'translateY(-8px)' : isExiting ? 'translateY(8px)' : 'translateY(0)',
                }}
            >
                {item.type === 'folder'
                    ? renderFolderRow(item.file, item.fullPath, item.depth, item.isLastChild, item.isExpanded ?? false)
                    : renderFileRow(item.file, item.fullPath, item.depth, item.isLastChild)}
            </div>
        );
    }, [
        activeEnteringKeys,
        draggedPaths,
        enterOrder,
        enteringKeys,
        exitOrder,
        exitingKeys,
        renderFileRow,
        renderFolderRow,
    ]);

    if (animatedItems.length === 0) {
        return null;
    }

    return (
        <VirtualList
            items={animatedItems}
            itemHeight={ITEM_HEIGHT}
            containerRef={containerRef}
            renderItem={renderItem}
            getItemKey={(item) => item.fullPath}
            getItemState={(item) => {
                const rowState = [
                    selectedFile === item.fullPath ? 'selected' : 'idle',
                    selectedFilesSet.has(item.fullPath) ? 'checked' : 'unchecked',
                    draggedPaths?.has(item.fullPath) ? 'dragged' : 'rest',
                    showFileCheckboxes ? 'checkboxes' : 'plain',
                ].join(':');
                if (exitingKeys.has(item.fullPath)) return `exiting-${exitOrder.get(item.fullPath) ?? 0}-${rowState}`;
                if (enteringKeys.has(item.fullPath)) {
                    return `entering-${enterOrder.get(item.fullPath) ?? 0}-${activeEnteringKeys.has(item.fullPath) ? 'on' : 'off'}-${rowState}`;
                }
                return rowState;
            }}
            animatePosition
            positionAnimationDuration={220}
            overscan={10}
            debugName="TreeList"
            draggedPaths={draggedPaths}
            isDragging={isDragging}
            selectedFile={selectedFile}
            selectedFiles={selectedFiles}
        />
    );
};

export default TreeList;
