"use client";
import { nanoid } from "nanoid";
import { useCallback, useEffect, useReducer } from "react";
import { FileAccessError, getFileParser as defaultGetFileParser, isValidFileName as defaultIsValidFileName, validateFiles as defaultValidateFiles } from "./fileUtils.js";
const noop = ()=>{
// do nothing
};
/** @internal */ const EMPTY_LIST = [];
/** @internal */ const EMPTY_OBJECT = {};
/**
 * This hook is generally used to upload files **to the browser** in different
 * formats to be previewed `<img>`, `<video>`, `<embed>`, etc tags. However, it
 * can also be used to upload the files as an `ArrayBuffer` and then uploaded to
 * a server.
 *
 * Note: If using the `aws-sdk` to upload files directly to S3, **do not use
 * this hook** since it uses its own upload process.
 *
 * @typeParam E - An optional HTMLElement type that is used for the
 * {@link FileUploadHandlers}.
 * @typeParam CustomError - An optional error type that gets returned from the
 * {@link FilesValidator}.
 * @param options - All the {@link FileUploadOptions}
 * @returns the {@link FileUploadHookReturnValue}
 * @since 2.9.0
 */ export function useFileUpload({ maxFiles = -1, extensions = EMPTY_LIST, minFileSize = -1, maxFileSize = -1, totalFileSize = -1, concurrency = -1, onDrop: propOnDrop = noop, onChange: propOnChange = noop, validateFiles = defaultValidateFiles, getFileParser = defaultGetFileParser, isValidFileName = defaultIsValidFileName } = {}) {
    const [state, dispatch] = useReducer(function reducer(state, action) {
        switch(action.type){
            case "reset":
                // need to reuse constants so that calling reset doesn't cause an
                // infinite loop in an effect
                return {
                    stats: EMPTY_OBJECT,
                    errors: EMPTY_LIST,
                    readers: EMPTY_OBJECT
                };
            case "remove":
                {
                    const stats = {};
                    for(const key in state.stats){
                        if (!action.files.includes(key)) {
                            stats[key] = state.stats[key];
                        }
                    }
                    return {
                        ...state,
                        stats
                    };
                }
            case "queue":
                return {
                    ...state,
                    stats: {
                        ...state.stats,
                        ...action.files.reduce((files, file)=>{
                            const key = nanoid();
                            files[key] = {
                                key,
                                file,
                                progress: 0,
                                status: "pending"
                            };
                            return files;
                        }, {})
                    },
                    errors: [
                        ...state.errors,
                        ...action.errors
                    ]
                };
            case "start":
                {
                    const { key, reader } = action;
                    const fileStats = {
                        key,
                        file: state.stats[key].file,
                        progress: 0,
                        status: "uploading"
                    };
                    return {
                        ...state,
                        readers: {
                            ...state.readers,
                            [key]: reader
                        },
                        stats: {
                            ...state.stats,
                            [key]: fileStats
                        }
                    };
                }
            case "progress":
                {
                    const { key, progress } = action;
                    return {
                        ...state,
                        stats: {
                            ...state.stats,
                            [key]: {
                                ...state.stats[key],
                                progress
                            }
                        }
                    };
                }
            case "complete":
                {
                    const { key, result } = action;
                    const file = {
                        key,
                        file: state.stats[key].file,
                        status: "complete",
                        result,
                        progress: 100
                    };
                    const { [key]: _reader, ...readers } = state.readers;
                    return {
                        ...state,
                        readers,
                        stats: {
                            ...state.stats,
                            [key]: file
                        }
                    };
                }
            case "clearErrors":
                return {
                    ...state,
                    errors: []
                };
        }
    }, {
        stats: EMPTY_OBJECT,
        errors: EMPTY_LIST,
        readers: EMPTY_OBJECT
    });
    const { stats, errors, readers } = state;
    const statsList = Object.values(stats);
    const totalFiles = statsList.length;
    const totalBytes = statsList.reduce((result, { file: { size } })=>result + size, 0);
    const queueFiles = useCallback((files)=>{
        const { pending, errors } = validateFiles(files, {
            maxFiles,
            extensions,
            minFileSize,
            maxFileSize,
            totalBytes,
            totalFiles,
            totalFileSize,
            isValidFileName
        });
        dispatch({
            type: "queue",
            errors,
            files: pending
        });
    }, [
        validateFiles,
        maxFiles,
        extensions,
        minFileSize,
        maxFileSize,
        totalBytes,
        totalFiles,
        totalFileSize,
        isValidFileName
    ]);
    const onDrop = useCallback((event)=>{
        propOnDrop(event);
        event.preventDefault();
        event.stopPropagation();
        try {
            const files = event.dataTransfer.files;
            if (files) {
                queueFiles(Array.from(files));
            }
        } catch (e) {
            dispatch({
                type: "queue",
                files: [],
                errors: [
                    new FileAccessError(e instanceof Error ? e.message : undefined)
                ]
            });
        }
    }, [
        queueFiles,
        propOnDrop
    ]);
    const onChange = useCallback((event)=>{
        propOnChange(event);
        try {
            const files = event.currentTarget.files;
            if (files) {
                queueFiles(Array.from(files));
            } else {
                throw new Error();
            }
        } catch (e) {
            dispatch({
                type: "queue",
                files: [],
                errors: [
                    new FileAccessError(e instanceof Error ? e.message : undefined)
                ]
            });
        }
    }, [
        queueFiles,
        propOnChange
    ]);
    const remove = useCallback((keyOrKeys)=>{
        const files = typeof keyOrKeys === "string" ? [
            keyOrKeys
        ] : keyOrKeys;
        files.forEach((fileKey)=>{
            readers[fileKey]?.abort();
        });
        dispatch({
            type: "remove",
            files
        });
    }, [
        readers
    ]);
    const reset = useCallback(()=>{
        Object.values(readers).forEach((reader)=>{
            reader.abort();
        });
        dispatch({
            type: "reset"
        });
    }, [
        readers
    ]);
    const clearErrors = useCallback(()=>{
        dispatch({
            type: "clearErrors"
        });
    }, []);
    const start = useCallback((key, reader)=>{
        dispatch({
            type: "start",
            key,
            reader
        });
    }, []);
    const complete = useCallback((key, result = null)=>{
        dispatch({
            type: "complete",
            key,
            result
        });
    }, []);
    const createProgressEventHandler = useCallback((key)=>(event)=>{
            if (event.lengthComputable) {
                const percentage = Math.round(event.loaded * 100 / event.total);
                dispatch({
                    type: "progress",
                    key,
                    progress: percentage
                });
            }
        }, []);
    useEffect(()=>{
        const pending = [];
        const uploading = [];
        Object.values(stats).forEach((file)=>{
            if (file.status === "pending") {
                pending.push(file);
            } else if (file.status === "uploading") {
                uploading.push(file);
            }
        });
        const lastIndex = concurrency === -1 ? pending.length : Math.max(0, concurrency - uploading.length);
        const queue = pending.slice(0, lastIndex);
        if (!queue.length) {
            return;
        }
        queue.forEach((stats)=>{
            const { key, file } = stats;
            const reader = new FileReader();
            // using `addEventListener` instead of directly setting to
            // `reader.progress`/`reader.load` so it's easier to test
            reader.addEventListener("progress", createProgressEventHandler(key));
            reader.addEventListener("load", ()=>{
                complete(key, reader.result);
            });
            start(key, reader);
            const parser = getFileParser(file);
            /* istanbul ignore next */ if (process.env.NODE_ENV !== "production" && ![
                "readAsText",
                "readAsDataURL",
                "readAsArrayBuffer",
                "readAsBinaryString"
            ].includes(parser)) {
                throw new Error("Invalid file reader parser");
            }
            reader[parser](file);
        });
    }, [
        concurrency,
        stats,
        getFileParser,
        createProgressEventHandler,
        start,
        complete
    ]);
    let accept = "";
    if (extensions.length) {
        accept = extensions.reduce((s, ext)=>`${s ? `${s},` : ""}.${ext}`, "");
    }
    return {
        stats: statsList,
        errors,
        accept,
        totalBytes,
        totalFiles,
        onDrop,
        onChange,
        reset,
        remove,
        clearErrors
    };
}

//# sourceMappingURL=useFileUpload.js.map