import React, { useEffect, useMemo, useState } from 'react';
import GreyRowBox from '@/components/elements/GreyRowBox';
import Spinner from '@/components/elements/Spinner';
import { ArchiveEntry, extractArchiveEntries, listArchive } from './api/server/files/betterFilesApi';
import { Button } from '@/components/elements/button/index';
import Input from '@/components/elements/Input';
import FileIcon from './FileIcon';
import { useFlashKey } from './useFlash';
import { themeColors } from './colorExtractor';
import { FaChevronLeft, FaFileArchive } from 'react-icons/fa';

interface ArchiveViewerProps {
    uuid: string;
    filePath: string;
    readOnly?: boolean;
}

const formatBytes = (bytes: number): string => {
    if (!Number.isFinite(bytes) || bytes <= 0) return bytes === 0 ? '0 B' : '-';
    const units = ['B', 'KB', 'MB', 'GB'];
    let value = bytes;
    let unit = 0;
    while (value >= 1024 && unit < units.length - 1) {
        value /= 1024;
        unit += 1;
    }
    return `${value >= 10 || unit === 0 ? value.toFixed(0) : value.toFixed(1)} ${units[unit]}`;
};

const formatDate = (value: string): string => {
    if (!value) return '-';
    const date = new Date(value);
    return Number.isNaN(date.getTime()) ? '-' : date.toLocaleString();
};

const viewerSurfaceStyle: React.CSSProperties = {
    backgroundColor: themeColors.page.background,
    borderColor: themeColors.page.secondaryHover,
};

const viewerHeaderStyle: React.CSSProperties = {
    backgroundColor: themeColors.page.secondary,
    borderColor: themeColors.page.secondaryHover,
};

const viewerBodyStyle: React.CSSProperties = {
    backgroundColor: themeColors.page.background,
};

const normalizeArchivePath = (value: string): string => {
    const cleaned = value.replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
    return cleaned ? `/${cleaned}` : '/';
};

const getParentPath = (value: string): string => {
    const normalized = normalizeArchivePath(value);
    if (normalized === '/') return '/';
    const parent = normalized.slice(0, normalized.lastIndexOf('/'));
    return parent || '/';
};

const getBasename = (value: string): string => {
    const normalized = normalizeArchivePath(value);
    if (normalized === '/') return '/';
    return normalized.slice(normalized.lastIndexOf('/') + 1);
};

const ArchiveViewer: React.FC<ArchiveViewerProps> = ({ uuid, filePath, readOnly = false }) => {
    const { addFlash, clearAndAddHttpError } = useFlashKey('better-files');
    const [entries, setEntries] = useState<ArchiveEntry[]>([]);
    const [currentPath, setCurrentPath] = useState('/');
    const [selectedEntriesByPath, setSelectedEntriesByPath] = useState<Record<string, ArchiveEntry>>({});
    const [query, setQuery] = useState('');
    const [truncated, setTruncated] = useState(false);
    const [totalEntries, setTotalEntries] = useState(0);
    const [loading, setLoading] = useState(true);
    const [extracting, setExtracting] = useState(false);
    const [error, setError] = useState<string | null>(null);

    useEffect(() => {
        let mounted = true;

        setLoading(true);
        setError(null);
        setCurrentPath('/');
        setSelectedEntriesByPath({});
        listArchive(uuid, filePath)
            .then((response) => {
                if (!mounted) return;
                setEntries(response.entries);
                setTruncated(response.truncated);
                setTotalEntries(response.total_entries);
            })
            .catch((error) => {
                if (!mounted) return;
                setError(error?.response?.data?.error || error?.message || 'Could not read archive.');
            })
            .finally(() => {
                if (mounted) setLoading(false);
            });

        return () => {
            mounted = false;
        };
    }, [uuid, filePath]);

    const visibleEntries = useMemo(() => {
        const needle = query.trim().toLowerCase();
        if (needle) {
            return entries
                .filter((entry) => entry.path.toLowerCase().includes(needle))
                .map((entry) => ({
                    ...entry,
                    path: normalizeArchivePath(entry.path),
                    name: normalizeArchivePath(entry.path),
                }))
                .sort((a, b) => a.path.localeCompare(b.path));
        }

        const normalizedCurrent = normalizeArchivePath(currentPath);
        const currentPrefix = normalizedCurrent === '/' ? '' : normalizedCurrent.slice(1);
        const folders = new Map<string, ArchiveEntry>();
        const files = new Map<string, ArchiveEntry>();

        entries.forEach((entry) => {
            const normalizedPath = normalizeArchivePath(entry.path);
            const withoutSlash = normalizedPath.slice(1);

            if (currentPrefix && withoutSlash !== currentPrefix && !withoutSlash.startsWith(`${currentPrefix}/`)) {
                return;
            }

            const relative = currentPrefix ? withoutSlash.slice(currentPrefix.length).replace(/^\/+/, '') : withoutSlash;
            if (!relative) return;

            const [childName, ...rest] = relative.split('/');
            const childPath = normalizeArchivePath(currentPrefix ? `${currentPrefix}/${childName}` : childName);

            if (rest.length > 0) {
                if (!folders.has(childPath)) {
                    folders.set(childPath, {
                        ...entry,
                        name: childName,
                        path: childPath,
                        directory: normalizedCurrent,
                        is_directory: true,
                        compressed_size: 0,
                        uncompressed_size: 0,
                        modified_at: '',
                    });
                }
                return;
            }

            if (entry.is_directory) {
                folders.set(childPath, {
                    ...entry,
                    name: childName,
                    path: childPath,
                    directory: normalizedCurrent,
                    is_directory: true,
                });
                return;
            }

            files.set(childPath, {
                ...entry,
                name: childName,
                path: childPath,
                directory: normalizedCurrent,
            });
        });

        return [...Array.from(folders.values()), ...Array.from(files.values())].sort((a, b) => {
            if (a.is_directory !== b.is_directory) return a.is_directory ? -1 : 1;
            return a.name.localeCompare(b.name);
        });
    }, [currentPath, entries, query]);

    const archiveName = filePath.substring(filePath.lastIndexOf('/') + 1);
    const archiveDirectory = filePath.substring(0, filePath.lastIndexOf('/')) || '/';
    const browsingArchive = query.trim().length === 0;
    const selectableEntries = visibleEntries;
    const selectedEntries = useMemo(() => Object.values(selectedEntriesByPath), [selectedEntriesByPath]);
    const allVisibleSelected =
        selectableEntries.length > 0 && selectableEntries.every((entry) => !!selectedEntriesByPath[entry.path]);

    const toggleSelected = (entry: ArchiveEntry) => {
        setSelectedEntriesByPath((previous) => {
            const next = { ...previous };
            if (next[entry.path]) {
                delete next[entry.path];
            } else {
                next[entry.path] = entry;
            }
            return next;
        });
    };

    const toggleAllVisible = () => {
        setSelectedEntriesByPath((previous) => {
            const next = { ...previous };
            if (allVisibleSelected) {
                selectableEntries.forEach((entry) => {
                    delete next[entry.path];
                });
            } else {
                selectableEntries.forEach((entry) => {
                    next[entry.path] = entry;
                });
            }
            return next;
        });
    };

    const extractEntries = async (targets: ArchiveEntry[]) => {
        if (readOnly) return;
        if (targets.length === 0 || extracting) return;

        setExtracting(true);
        setError(null);
        try {
            const response = await extractArchiveEntries(
                uuid,
                filePath,
                archiveDirectory,
                targets.map((entry) => ({
                    path: entry.path.replace(/^\/+/, ''),
                    is_directory: entry.is_directory,
                }))
            );
            setSelectedEntriesByPath({});
            addFlash({
                type: 'success',
                message: `Extracted ${response.extracted} file${response.extracted === 1 ? '' : 's'} to ${response.destination}.`,
            });
        } catch (error) {
            clearAndAddHttpError(error);
            setError(error?.response?.data?.error || error?.message || 'Could not extract archive entries.');
        } finally {
            setExtracting(false);
        }
    };

    return (
        <GreyRowBox
            className={'flex-1 flex flex-col !items-stretch !p-0 min-h-0 overflow-hidden w-full max-w-full'}
            $hoverable={false}
            style={{
                alignItems: 'stretch',
                alignSelf: 'stretch',
                backgroundColor: themeColors.page.background,
                borderColor: themeColors.page.secondaryHover,
                display: 'flex',
                flex: '1 1 0%',
                flexDirection: 'column',
                height: '100%',
                maxWidth: '100%',
                minHeight: 0,
                minWidth: 0,
                width: '100%',
            }}
        >
            <div className={'flex flex-col gap-3 border-b px-4 py-3 lg:flex-row lg:items-center lg:justify-between'} style={viewerHeaderStyle}>
                <div className={'flex min-w-0 items-center'}>
                    <FaFileArchive className={'w-4 h-4 text-neutral-400 mr-3 flex-shrink-0'} />
                    <div className={'min-w-0'}>
                        <p className={'text-sm font-medium text-neutral-100 truncate'}>{archiveName}</p>
                        <p className={'text-xs text-neutral-500'}>
                            {browsingArchive ? currentPath : 'Search results'} &middot; {totalEntries || entries.length} archive entries
                        </p>
                    </div>
                </div>
                <div className={'flex w-full min-w-0 flex-col gap-2 sm:flex-row sm:items-center lg:ml-4 lg:w-auto lg:flex-shrink-0'}>
                    {browsingArchive && currentPath !== '/' && (
                        <Button.Text
                            className={'w-full justify-center sm:w-auto'}
                            size={Button.Sizes.Small}
                            disabled={readOnly || extracting}
                            onClick={() => extractEntries([{
                                name: getBasename(currentPath),
                                path: currentPath,
                                directory: getParentPath(currentPath),
                                is_directory: true,
                                compressed_size: 0,
                                uncompressed_size: 0,
                                modified_at: '',
                            }])}
                        >
                            Extract Folder
                        </Button.Text>
                    )}
                    <Button
                        className={'w-full justify-center sm:w-auto'}
                        size={Button.Sizes.Small}
                        disabled={readOnly || extracting || selectedEntries.length === 0}
                        onClick={() => extractEntries(selectedEntries)}
                    >
                        {extracting ? 'Extracting...' : selectedEntries.length > 0 ? `Extract ${selectedEntries.length}` : 'Extract Selected'}
                    </Button>
                    <div className={'min-w-0 sm:min-w-[12rem] sm:flex-1 lg:w-64 lg:flex-none'}>
                        <Input
                            className={'!w-full !px-3 !py-2 !text-xs'}
                            value={query}
                            onChange={(event) => setQuery(event.currentTarget.value)}
                            placeholder={'Filter archive'}
                        />
                    </div>
                </div>
            </div>
            {truncated && (
                <div className={'border-b border-yellow-500/30 bg-yellow-500/10 px-4 py-2 text-xs text-yellow-300'}>
                    Showing the first {entries.length} entries. This archive is very large.
                </div>
            )}
            <div className={'flex-1 min-h-0 overflow-auto'} style={viewerBodyStyle}>
                {loading ? (
                    <div className={'flex h-full items-center justify-center'}>
                        <Spinner size={'large'} />
                    </div>
                ) : error ? (
                    <div className={'p-4 text-sm text-red-300'}>{error}</div>
                ) : visibleEntries.length === 0 && currentPath === '/' ? (
                    <div className={'flex h-full items-center justify-center text-sm text-neutral-500'}>
                        No archive entries found.
                    </div>
                ) : (
                    <div className={'min-w-[720px] lg:min-w-full'} style={viewerSurfaceStyle}>
                        <div className={'grid grid-cols-[28px_minmax(260px,1fr)_110px_130px_170px] gap-3 border-b px-4 py-2 text-[10px] uppercase tracking-wider text-neutral-500'} style={viewerHeaderStyle}>
                            <label
                                className={'flex h-5 w-5 cursor-pointer items-center justify-center'}
                                onClick={(event) => event.stopPropagation()}
                            >
                                <Input
                                    type={'checkbox'}
                                    className={'!h-4 !w-4 !shrink-0'}
                                    aria-label={'Select visible archive entries'}
                                    checked={allVisibleSelected}
                                    onChange={() => toggleAllVisible()}
                                />
                            </label>
                            <span>Name</span>
                            <span>Packed</span>
                            <span>Size</span>
                            <span>Modified</span>
                        </div>
                        {browsingArchive && currentPath !== '/' && (
                            <div
                                className={'grid cursor-pointer grid-cols-[28px_minmax(260px,1fr)_110px_130px_170px] gap-3 border-b border-neutral-700/70 px-4 py-2 text-xs text-neutral-300 hover:bg-neutral-700/60'}
                                onClick={() => setCurrentPath(getParentPath(currentPath))}
                            >
                                <span />
                                <div className={'flex min-w-0 items-center'}>
                                    <FaChevronLeft className={'mr-2 h-3 w-3 flex-shrink-0 text-neutral-500'} />
                                    <span className={'truncate'}>..</span>
                                </div>
                                <span className={'font-mono text-neutral-500'}>-</span>
                                <span className={'font-mono text-neutral-500'}>-</span>
                                <span className={'font-mono text-neutral-500'}>-</span>
                            </div>
                        )}
                        {visibleEntries.length === 0 ? (
                            <div className={'px-4 py-8 text-center text-sm text-neutral-500'}>
                                This archive folder is empty.
                            </div>
                        ) : visibleEntries.map((entry) => (
                            <div
                                key={`${entry.is_directory ? 'dir' : 'file'}:${entry.path}`}
                                className={[
                                    'grid grid-cols-[28px_minmax(260px,1fr)_110px_130px_170px] gap-3 border-b border-neutral-700/70 px-4 py-2 text-xs text-neutral-300 hover:bg-neutral-700/60',
                                    entry.is_directory && browsingArchive ? 'cursor-pointer' : '',
                                ].join(' ')}
                                onClick={() => {
                                    if (entry.is_directory && browsingArchive) {
                                        setCurrentPath(entry.path);
                                    }
                                }}
                            >
                                <label
                                    className={'flex h-5 w-5 cursor-pointer items-center justify-center'}
                                    onClick={(event) => event.stopPropagation()}
                                >
                                    <Input
                                        type={'checkbox'}
                                        className={'!h-4 !w-4 !shrink-0'}
                                        aria-label={`Select ${entry.name}`}
                                        checked={!!selectedEntriesByPath[entry.path]}
                                        onChange={() => toggleSelected(entry)}
                                    />
                                </label>
                                <div className={'flex min-w-0 items-center'}>
                                    <span className={'mr-2 flex h-4 w-4 flex-shrink-0 items-center justify-center'}>
                                        <FileIcon filename={getBasename(entry.path)} isFolder={entry.is_directory} isOpen={entry.is_directory && currentPath === entry.path} />
                                    </span>
                                    <span className={'truncate'} title={entry.path}>
                                        {browsingArchive ? entry.name : entry.path}
                                    </span>
                                </div>
                                <span className={'font-mono text-neutral-500'}>{entry.is_directory || entry.compressed_size <= 0 ? '-' : formatBytes(entry.compressed_size)}</span>
                                <span className={'font-mono text-neutral-500'}>{entry.is_directory ? '-' : formatBytes(entry.uncompressed_size)}</span>
                                <span className={'font-mono text-neutral-500'}>{formatDate(entry.modified_at)}</span>
                            </div>
                        ))}
                    </div>
                )}
            </div>
        </GreyRowBox>
    );
};

export default ArchiveViewer;
