import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
/*
 * Copyright (c) 2023-2025 Datalayer, Inc.
 * Distributed under the terms of the Modified BSD License.
 */
import { useCallback, useEffect, useState, } from 'react';
import { PathExt } from '@jupyterlab/coreutils';
import { PromiseDelegate } from '@lumino/coreutils';
import { ActionList, ActionMenu, Heading, IconButton, Spinner, TreeView, } from '@primer/react';
import { Box } from '@datalayer/primer-addons';
import { Blankslate, Dialog } from '@primer/react/experimental';
import { CounterClockWiseIcon } from '@datalayer/icons-react';
import { useIsMounted } from 'usehooks-ts';
import { useToast } from '../../hooks/index.js';
import { UploadIconButton } from '../buttons/index.js';
import { DirectoryItem, TreeItem, modelToView, } from './ContentsItems.js';
/**
 * The maximum upload size (in bytes) for notebook version < 5.1.0
 */
export const LARGE_FILE_SIZE = 15 * 1024 * 1024;
/**
 * The size (in bytes) of the biggest chunk we should upload at once.
 */
export const CHUNK_SIZE = 1024 * 1024;
/**
 * Storage browser component.
 */
export function ContentsBrowser(props) {
    const { contents, localContents, documentRegistry, title = (_jsx(Heading, { as: "h4", sx: {
            fontSize: 'var(--text-title-size-small)',
            lineHeight: 'var(--text-title-lineHeight-medium)',
            fontWeight: 'var(--text-title-weight-medium)',
        }, children: "Contents Browser" })), } = props;
    const isMounted = useIsMounted();
    const { trackAsyncTask } = useToast();
    const [children, setChildren] = useState(null);
    const [isLoading, setIsLoading] = useState(true);
    const [selectedItem, setSelectedItem] = useState(null);
    const [contextMenuAnchor, setContextMenuAnchor] = useState(null);
    const [openDeleteConfirmation, setOpenDeleteConfirmation] = useState(false);
    const [copyToLocalConfirmation, setCopyToLocalConfirmation] = useState(false);
    const refresh = useCallback(() => {
        contents
            .get('')
            .then(model => {
            setIsLoading(false);
            setChildren(modelToView(model.content, documentRegistry));
        })
            .catch(reason => {
            setIsLoading(false);
            console.error(`Failed to fetch folder '' content for manager ${contents.serverSettings.appUrl}.`, reason);
        });
    }, [contents]);
    useEffect(() => {
        refresh();
    }, [refresh]);
    const upload = useCallback(
    /**
     * @param file File to upload
     */
    async (file) => {
        const checkIsMounted = () => {
            if (!isMounted()) {
                return Promise.reject(`Failed to upload ${file.name}; StorageBrowser component is unmounted.`);
            }
        };
        checkIsMounted();
        const chunked = file.size > CHUNK_SIZE;
        const currentDirectory = selectedItem
            ? selectedItem.type === 'directory'
                ? selectedItem.path
                : PathExt.dirname(selectedItem.path)
            : '';
        const path = currentDirectory
            ? PathExt.join(currentDirectory, file.name)
            : file.name;
        const name = file.name;
        const type = 'file';
        const format = 'base64';
        const uploadChunk = async (blob, chunk) => {
            checkIsMounted();
            const reader = new FileReader();
            reader.readAsDataURL(blob);
            await new Promise((resolve, reject) => {
                reader.onload = resolve;
                reader.onerror = event => reject(`Failed to upload "${file.name}":` + event);
            });
            checkIsMounted();
            // remove header https://stackoverflow.com/a/24289420/907060
            const content = reader.result.split(',')[1];
            const model = {
                type,
                format,
                name,
                chunk,
                content,
            };
            return await contents.save(path, model);
        };
        const toastOptions = {
            error: {
                message: reason => {
                    const msg = `Failed to upload ${file.name}.`;
                    console.error(msg, reason);
                    return msg;
                },
            },
            pending: { message: `Uploading ${file.name}…` },
            success: { message: () => `${file.name} uploaded.` },
        };
        if (chunked) {
            const task = new PromiseDelegate();
            trackAsyncTask(task.promise, toastOptions);
            try {
                let finalModel;
                for (let start = 0; !finalModel; start += CHUNK_SIZE) {
                    const end = start + CHUNK_SIZE;
                    const lastChunk = end >= file.size;
                    const chunk = lastChunk ? -1 : end / CHUNK_SIZE;
                    const currentModel = await uploadChunk(file.slice(start, end), chunk);
                    if (lastChunk) {
                        finalModel = currentModel;
                        task.resolve(finalModel);
                    }
                }
                if (selectedItem) {
                    selectedItem.refresh();
                }
                else {
                    refresh();
                }
                return finalModel;
            }
            catch (error) {
                task.reject(error);
                throw error;
            }
        }
        else {
            const task = uploadChunk(file);
            trackAsyncTask(task, toastOptions);
            task.then(() => {
                if (selectedItem) {
                    selectedItem.refresh();
                }
                else {
                    refresh();
                }
            });
            return task;
        }
    }, [contents, selectedItem, refresh]);
    const onContextMenu = useCallback((ref) => {
        if (contextMenuAnchor === ref) {
            setContextMenuAnchor(null);
        }
        else {
            setContextMenuAnchor(ref);
        }
    }, [contextMenuAnchor]);
    const onSelectDelete = useCallback(() => {
        setOpenDeleteConfirmation(true);
    }, []);
    const deleteItem = useCallback(() => {
        if (selectedItem) {
            const task = contents.delete(selectedItem.path);
            trackAsyncTask(task, {
                success: { message: () => `${selectedItem.path} deleted.` },
                pending: { message: `Deleting ${selectedItem.path}…` },
                error: {
                    message: reason => {
                        const msg = `Failed to delete ${selectedItem.path}.`;
                        console.error(msg, reason);
                        return msg;
                    },
                },
            });
            task.finally(() => {
                selectedItem.refresh();
            });
        }
        setOpenDeleteConfirmation(false);
    }, [contents, selectedItem]);
    const onSelectCopyToLocal = useCallback(() => {
        setCopyToLocalConfirmation(true);
    }, []);
    const copyToLocal = useCallback(() => {
        if (selectedItem && localContents) {
            contents.get(selectedItem.path).then(model => {
                const copyTask = localContents?.save(model.path, model);
                trackAsyncTask(copyTask, {
                    success: { message: () => `${selectedItem.path} copied to local.` },
                    pending: { message: `Copying to local ${selectedItem.path}…` },
                    error: {
                        message: reason => {
                            const msg = `Failed to copy to local ${selectedItem.path}.`;
                            console.error(msg, reason);
                            return msg;
                        },
                    },
                });
                copyTask.finally(() => {
                    selectedItem.refresh();
                });
                setCopyToLocalConfirmation(false);
            });
        }
    }, [localContents, selectedItem]);
    const onSelect = useCallback((item, refresh) => {
        setSelectedItem(item.path === selectedItem?.path ? null : { ...item, refresh });
    }, [selectedItem]);
    return (_jsxs(Box, { sx: {
            display: 'grid',
            gridTemplateAreas: `"header" "content"`,
            overflow: 'hidden',
        }, children: [_jsxs(Box, { sx: {
                    gridArea: 'header',
                    display: 'flex',
                    alignItems: 'center',
                    minWidth: 0,
                    overflow: 'hidden',
                }, children: [_jsx(Box, { sx: { flex: '1 1 auto', minWidth: 0, overflow: 'hidden' }, children: title }), _jsxs(Box, { sx: {
                            display: 'flex',
                            flexDirection: 'row',
                            alignItems: 'center',
                            flexWrap: 'nowrap',
                            flexShrink: 0,
                        }, children: [_jsx(IconButton, { variant: "invisible", "aria-label": 'Refresh contents browser.', title: 'Refresh contents browser.', icon: CounterClockWiseIcon, onClick: refresh }), _jsx(UploadIconButton, { label: 'Upload a file', multiple: true, upload: upload })] })] }), isLoading ? (_jsx(Box, { sx: {
                    gridArea: 'content',
                    display: 'flex',
                    alignItems: 'center',
                    justifyContent: 'center',
                    minHeight: '40px',
                    height: '100vh',
                }, children: _jsx(Spinner, {}) })) : (_jsx(Box, { sx: { gridArea: 'content' }, children: children ? (_jsxs(_Fragment, { children: [_jsx(TreeView, { children: children?.map(child => {
                                return child.type === 'directory' ? (_jsx(DirectoryItem, { item: child, contents: contents, current: selectedItem, documentRegistry: documentRegistry, onContextMenu: onContextMenu, onSelect: onSelect }, child.name)) : (_jsx(TreeItem, { item: child, current: selectedItem?.path === child.path, onSelect: item => {
                                        onSelect(item, refresh);
                                    }, onContextMenu: onContextMenu }, child.name));
                            }) }), contextMenuAnchor !== null && (_jsx(ActionMenu, { anchorRef: contextMenuAnchor ?? undefined, open: contextMenuAnchor?.current !== null, onOpenChange: () => {
                                setContextMenuAnchor(null);
                            }, children: _jsx(ActionMenu.Overlay, { children: _jsxs(ActionList, { children: [_jsx(ActionList.Item, { title: "Delete the active item.", onSelect: onSelectDelete, children: "Delete\u2026" }), localContents && (_jsx(ActionList.Item, { title: "Copy the active item to the local drive.", onSelect: onSelectCopyToLocal, children: "Copy to local drive\u2026" }))] }) }) })), openDeleteConfirmation && (_jsx(Dialog, { title: _jsx("span", { style: { color: 'var(--fgColor-default)' }, children: "Confirm deletion" }), onClose: () => {
                                setOpenDeleteConfirmation(false);
                            }, footerButtons: [
                                {
                                    buttonType: 'default',
                                    content: 'Cancel',
                                    onClick: () => {
                                        setOpenDeleteConfirmation(false);
                                    },
                                },
                                {
                                    buttonType: 'danger',
                                    content: 'Delete',
                                    onClick: () => {
                                        deleteItem();
                                    },
                                },
                            ], children: `Are you sure you want to delete ${selectedItem?.path}?` })), copyToLocalConfirmation && (_jsx(Dialog, { title: _jsx("span", { style: { color: 'var(--fgColor-default)' }, children: "Confirm copy to local" }), onClose: () => {
                                setCopyToLocalConfirmation(false);
                            }, footerButtons: [
                                {
                                    buttonType: 'default',
                                    content: 'Cancel',
                                    onClick: () => {
                                        setCopyToLocalConfirmation(false);
                                    },
                                },
                                {
                                    buttonType: 'danger',
                                    content: 'Copy to local',
                                    onClick: () => {
                                        copyToLocal();
                                    },
                                },
                            ], children: `Are you sure you want to copy to local ${selectedItem?.path}?` }))] })) : (_jsx(Blankslate, { children: _jsx(Blankslate.Heading, { children: "No contents" }) })) }))] }));
}
export default ContentsBrowser;
