import { FILE_URL } from "../../Config";
import API from "../../Utils/API/API";
import ModelAPI from "../../Utils/ModelAPI/ModelAPI";
import Icon from "../Icon/Icon";
import CommonURL from "../../../Types/API/URL";
import MimeType from "../../../Types/File/MimeType";
import IconProp from "../../../Types/Icon/IconProp";
import FileModel from "../../../Models/DatabaseModels/File";
import React, { useEffect, useState, } from "react";
import { useDropzone } from "react-dropzone";
const MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024; // 10MB limit
const FilePicker = (props) => {
    var _a, _b;
    const [isLoading, setIsLoading] = useState(false);
    const [error, setError] = useState("");
    const [filesModel, setFilesModel] = useState([]);
    const [acceptTypes, setAcceptTypes] = useState({});
    const [uploadStatuses, setUploadStatuses] = useState([]);
    const addUploadStatus = (status) => {
        setUploadStatuses((current) => {
            return [...current, status];
        });
    };
    const updateUploadStatus = (id, updates) => {
        setUploadStatuses((current) => {
            return current.map((upload) => {
                return upload.id === id
                    ? Object.assign(Object.assign({}, upload), updates) : upload;
            });
        });
    };
    const updateUploadProgress = (id, total, loaded) => {
        setUploadStatuses((current) => {
            return current.map((upload) => {
                if (upload.id !== id || upload.status === "error") {
                    return upload;
                }
                const hasTotal = Boolean(total && total > 0);
                const progressFromEvent = hasTotal
                    ? Math.min(100, Math.round(((loaded || 0) / total) * 100))
                    : null;
                const fallbackProgress = Math.min(upload.progress + 5, 95);
                return Object.assign(Object.assign({}, upload), { progress: progressFromEvent !== null ? progressFromEvent : fallbackProgress });
            });
        });
    };
    const removeUploadStatus = (id) => {
        setUploadStatuses((current) => {
            return current.filter((upload) => {
                return upload.id !== id;
            });
        });
    };
    useEffect(() => {
        const _acceptTypes = {};
        if (props.mimeTypes) {
            for (const key of props.mimeTypes) {
                _acceptTypes[key] = [];
            }
        }
        setAcceptTypes(_acceptTypes);
    }, [props.mimeTypes]);
    useEffect(() => {
        setInitialValue();
    }, [props.initialValue]);
    const setInitialValue = () => {
        if (Array.isArray(props.initialValue) &&
            props.initialValue &&
            props.initialValue.length > 0) {
            setFilesModel(props.initialValue);
        }
        else if (props.initialValue instanceof FileModel) {
            setFilesModel([props.initialValue]);
        }
    };
    useEffect(() => {
        if (props.value && props.value.length > 0) {
            setFilesModel(props.value && props.value.length > 0 ? props.value : []);
        }
        else {
            setInitialValue();
        }
    }, [props.value]);
    const buildFileSizeError = (fileNames) => {
        if (fileNames.length === 0) {
            return "";
        }
        if (fileNames.length === 1) {
            return `"${fileNames[0]}" exceeds the 10MB limit.`;
        }
        return `These files exceed the 10MB limit: ${fileNames.join(", ")}.`;
    };
    const { getRootProps, getInputProps, isDragActive } = useDropzone({
        accept: acceptTypes,
        multiple: props.isMultiFilePicker,
        noClick: true,
        disabled: props.readOnly || isLoading,
        maxSize: MAX_FILE_SIZE_BYTES,
        onDropRejected: (fileRejections) => {
            const oversizedFiles = fileRejections
                .filter((rejection) => {
                return rejection.file.size > MAX_FILE_SIZE_BYTES;
            })
                .map((rejection) => {
                return rejection.file.name;
            });
            if (oversizedFiles.length > 0) {
                setError(buildFileSizeError(oversizedFiles));
            }
        },
        onDrop: async (acceptedFiles) => {
            var _a, _b;
            if (props.readOnly) {
                return;
            }
            setIsLoading(true);
            setError("");
            try {
                // Upload these files.
                const filesResult = [];
                const resolveMimeType = (file) => {
                    var _a;
                    const direct = file.type || undefined;
                    if (direct && Object.values(MimeType).includes(direct)) {
                        return direct;
                    }
                    // fallback based on extension
                    const ext = (_a = file.name
                        .split(".")
                        .pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase();
                    if (!ext) {
                        return undefined;
                    }
                    const map = {
                        png: MimeType.png,
                        jpg: MimeType.jpg,
                        jpeg: MimeType.jpeg,
                        svg: MimeType.svg,
                        gif: MimeType.gif,
                        webp: MimeType.webp,
                        pdf: MimeType.pdf,
                        doc: MimeType.doc,
                        docx: MimeType.docx,
                        txt: MimeType.txt,
                        log: MimeType.txt,
                        md: MimeType.md,
                        markdown: MimeType.md,
                        csv: MimeType.csv,
                        json: MimeType.json,
                        zip: MimeType.zip,
                        rtf: MimeType.rtf,
                        odt: MimeType.odt,
                        xls: MimeType.xls,
                        xlsx: MimeType.xlsx,
                        ods: MimeType.ods,
                        ppt: MimeType.ppt,
                        pptx: MimeType.pptx,
                        odp: MimeType.odp,
                    };
                    return map[ext];
                };
                const oversizedFiles = [];
                for (const acceptedFile of acceptedFiles) {
                    if (acceptedFile.size > MAX_FILE_SIZE_BYTES) {
                        oversizedFiles.push(acceptedFile.name);
                        continue;
                    }
                    const uploadId = `${acceptedFile.name}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
                    addUploadStatus({
                        id: uploadId,
                        name: acceptedFile.name,
                        progress: 0,
                        status: "uploading",
                    });
                    try {
                        const fileModel = new FileModel();
                        fileModel.name = acceptedFile.name;
                        const arrayBuffer = await acceptedFile.arrayBuffer();
                        const fileBuffer = new Uint8Array(arrayBuffer);
                        fileModel.file = Buffer.from(fileBuffer);
                        fileModel.isPublic = false;
                        fileModel.fileType = resolveMimeType(acceptedFile) || MimeType.txt; // default to text/plain to satisfy required field
                        const result = (await ModelAPI.create({
                            model: fileModel,
                            modelType: FileModel,
                            requestOptions: {
                                overrideRequestUrl: CommonURL.fromURL(FILE_URL),
                                apiRequestOptions: {
                                    onUploadProgress: (progressEvent) => {
                                        updateUploadProgress(uploadId, progressEvent.total, progressEvent.loaded);
                                    },
                                },
                            },
                        }));
                        filesResult.push(result.data);
                        removeUploadStatus(uploadId);
                    }
                    catch (uploadErr) {
                        const friendlyMessage = API.getFriendlyMessage(uploadErr);
                        updateUploadStatus(uploadId, {
                            status: "error",
                            errorMessage: friendlyMessage,
                            progress: 100,
                        });
                        setError(friendlyMessage);
                    }
                }
                if (oversizedFiles.length > 0) {
                    setError(buildFileSizeError(oversizedFiles));
                }
                if (filesResult.length > 0) {
                    const updatedFiles = props.isMultiFilePicker
                        ? [...filesModel, ...filesResult]
                        : filesResult;
                    setFilesModel(updatedFiles);
                    (_a = props.onBlur) === null || _a === void 0 ? void 0 : _a.call(props);
                    (_b = props.onChange) === null || _b === void 0 ? void 0 : _b.call(props, updatedFiles);
                }
            }
            catch (err) {
                setError(API.getFriendlyMessage(err));
            }
            finally {
                setIsLoading(false);
            }
        },
    });
    const formatFileSize = (file) => {
        const buffer = file.file;
        if (!buffer) {
            return null;
        }
        const sizeInKB = buffer.byteLength / 1024;
        if (sizeInKB < 1024) {
            return `${sizeInKB.toFixed(1)} KB`;
        }
        return `${(sizeInKB / 1024).toFixed(2)} MB`;
    };
    const getThumbs = () => {
        return filesModel.map((file, i) => {
            var _a;
            const key = ((_a = file._id) === null || _a === void 0 ? void 0 : _a.toString()) || `${file.name || "file"}-${i}`;
            const removeFile = () => {
                var _a;
                const tempFileModel = [...filesModel];
                tempFileModel.splice(i, 1);
                setFilesModel(tempFileModel);
                (_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, tempFileModel);
            };
            const metadata = [];
            if (file.fileType) {
                metadata.push(file.fileType);
            }
            const readableSize = formatFileSize(file);
            if (readableSize) {
                metadata.push(readableSize);
            }
            return (React.createElement("div", { key: key, className: "flex w-full items-center justify-between gap-4 rounded-lg border border-gray-200 bg-white px-3 py-2 shadow-sm" },
                React.createElement("div", { className: "flex items-start gap-3 text-left" },
                    React.createElement("div", { className: "flex h-10 w-10 items-center justify-center rounded border border-gray-200 bg-gray-50" },
                        React.createElement(Icon, { icon: IconProp.File, className: "text-gray-500" })),
                    React.createElement("div", { className: "flex flex-col" },
                        React.createElement("p", { className: "text-sm font-medium text-gray-900" }, file.name || `File ${i + 1}`),
                        metadata.length > 0 && (React.createElement("p", { className: "text-xs text-gray-500" }, metadata.join(" • "))))),
                React.createElement("button", { type: "button", className: "rounded-md border border-gray-200 px-3 py-1 text-xs font-medium text-gray-700 transition hover:bg-gray-50", onClick: removeFile }, "Remove")));
        });
    };
    const hasActiveUploads = uploadStatuses.some((upload) => {
        return upload.status === "uploading";
    });
    return (React.createElement("div", { className: "space-y-4 w-full" },
        React.createElement("div", { onClick: () => {
                var _a, _b;
                (_a = props.onClick) === null || _a === void 0 ? void 0 : _a.call(props);
                (_b = props.onFocus) === null || _b === void 0 ? void 0 : _b.call(props);
            }, "data-testid": props.dataTestId, className: `flex w-full justify-center rounded-md border-2 border-dashed px-6 py-8 transition ${props.readOnly ? "cursor-not-allowed bg-gray-50 border-gray-200" : "bg-white border-gray-300"} ${hasActiveUploads ? "ring-1 ring-indigo-200" : ""} ${isDragActive ? "border-indigo-400" : ""}` },
            React.createElement("div", Object.assign({}, getRootProps({
                className: "w-full flex flex-col items-center justify-center space-y-3 text-center",
                "aria-busy": hasActiveUploads || isLoading,
            })), (filesModel.length === 0 || props.isMultiFilePicker) && (React.createElement(React.Fragment, null,
                React.createElement("div", { className: "flex flex-col items-center space-y-2" },
                    React.createElement("svg", { className: "mx-auto h-12 w-12 text-gray-400", stroke: "currentColor", fill: "none", viewBox: "0 0 48 48", "aria-hidden": "true" },
                        React.createElement("path", { d: "M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" })),
                    React.createElement("div", { className: "flex flex-col items-center text-sm text-gray-600 space-y-1" },
                        React.createElement("label", { className: "relative cursor-pointer rounded-md bg-white px-4 py-2 font-medium text-indigo-600 hover:text-indigo-500" },
                            React.createElement("span", null, props.placeholder
                                ? props.placeholder
                                : filesModel.length > 0
                                    ? "Add more files"
                                    : "Upload files"),
                            React.createElement("input", Object.assign({ tabIndex: props.tabIndex }, getInputProps(), { id: "file-upload", name: "file-upload", type: "file", className: "sr-only" }))),
                        React.createElement("p", { className: "text-gray-500" },
                            isDragActive
                                ? "Release to start uploading"
                                : filesModel.length === 0
                                    ? "Click to choose files"
                                    : "Click to add more",
                            " ",
                            "or drag & drop."),
                        React.createElement("p", { className: "text-xs text-gray-500" },
                            props.mimeTypes && ((_a = props.mimeTypes) === null || _a === void 0 ? void 0 : _a.length) > 0 && (React.createElement("span", null, "Types: ")),
                            props.mimeTypes &&
                                props.mimeTypes
                                    .map((type) => {
                                    const enumKey = Object.keys(MimeType)[Object.values(MimeType).indexOf(type)];
                                    return (enumKey === null || enumKey === void 0 ? void 0 : enumKey.toUpperCase()) || "";
                                })
                                    .filter((item, pos, array) => {
                                    return array.indexOf(item) === pos;
                                })
                                    .join(", "),
                            props.mimeTypes && ((_b = props.mimeTypes) === null || _b === void 0 ? void 0 : _b.length) > 0 && (React.createElement("span", null, ".")),
                            " ",
                            "Max 10MB each."),
                        error && (React.createElement("p", { className: "text-xs text-red-500 font-medium" }, error)))))))),
        uploadStatuses.length > 0 && (React.createElement("div", { className: "space-y-2 w-full" },
            React.createElement("p", { className: "text-sm font-medium text-gray-700 text-left" }, hasActiveUploads ? "Uploading files" : "Upload status"),
            React.createElement("div", { className: "space-y-2" }, uploadStatuses.map((upload) => {
                return (React.createElement("div", { key: upload.id, className: `rounded border px-3 py-2 ${upload.status === "error" ? "border-red-200 bg-red-50" : "border-gray-200 bg-white"}` },
                    React.createElement("div", { className: "flex items-center justify-between text-sm" },
                        React.createElement("p", { className: "font-medium text-gray-800 truncate" }, upload.name),
                        React.createElement("span", { className: `text-xs ${upload.status === "error" ? "text-red-600" : "text-gray-500"}` }, upload.status === "error"
                            ? "Failed"
                            : `${upload.progress}%`)),
                    React.createElement("div", { className: "mt-2 h-2 rounded bg-gray-200 overflow-hidden" },
                        React.createElement("div", { className: `h-full transition-all duration-300 ${upload.status === "error" ? "bg-red-400" : "bg-indigo-500"}`, style: { width: `${Math.min(upload.progress, 100)}%` } })),
                    upload.status === "error" && upload.errorMessage && (React.createElement("p", { className: "mt-2 text-xs text-red-600 text-left" }, upload.errorMessage)),
                    upload.status === "error" && (React.createElement("div", { className: "mt-2 text-right" },
                        React.createElement("button", { type: "button", className: "text-xs font-medium text-gray-600 hover:text-gray-800", onClick: () => {
                                removeUploadStatus(upload.id);
                            } }, "Dismiss")))));
            })))),
        filesModel.length > 0 && (React.createElement("div", { className: "space-y-2 w-full" },
            React.createElement("p", { className: "text-sm font-medium text-gray-700 text-left" }, "Uploaded files"),
            React.createElement("div", { className: "flex flex-wrap gap-4" }, getThumbs()))),
        props.error && (React.createElement("p", { "data-testid": "error-message", className: "text-sm text-red-400" }, props.error))));
};
export default FilePicker;
//# sourceMappingURL=FilePicker.js.map