import Button from "../layout/Button";
import Stack from "../layout/Stack";
import { FileArchive, FilePlus2, X } from "lucide-react";
import React, { ComponentProps, DetailedHTMLProps, ReactNode } from "react";
import Card from "../elements/Card";
import Span from "../layout/Span";
import Center from "../layout/Center";
import { FileInputToBase64FunctionReturn } from "../utils/form/fileInputToBase64";
import { twMerge } from "tailwind-merge";
import fileInputToBase64 from "../utils/form/fileInputToBase64";
import Row from "../layout/Row";
import Input from "./Input";
import Loading from "../elements/Loading";

type ImageUploadProps = DetailedHTMLProps<
    React.HTMLAttributes<HTMLDivElement>,
    HTMLDivElement
> & {
    onChangeHandler?: (
        fileData: FileInputToBase64FunctionReturn | undefined
    ) => any;
    onClear?: () => void;
    fileInputProps?: DetailedHTMLProps<
        React.InputHTMLAttributes<HTMLInputElement>,
        HTMLInputElement
    >;
    placeHolderWrapper?: DetailedHTMLProps<
        React.HTMLAttributes<HTMLDivElement>,
        HTMLDivElement
    >;
    previewImageWrapperProps?: DetailedHTMLProps<
        React.HTMLAttributes<HTMLDivElement>,
        HTMLDivElement
    >;
    previewImageProps?: DetailedHTMLProps<
        React.ImgHTMLAttributes<HTMLImageElement>,
        HTMLImageElement
    >;
    label?: string | ReactNode;
    disablePreview?: boolean;
    allowedRegex?: RegExp;
    externalSetFile?: React.Dispatch<
        React.SetStateAction<FileInputToBase64FunctionReturn | undefined>
    >;
    externalSetFiles?: React.Dispatch<
        React.SetStateAction<FileInputToBase64FunctionReturn[] | undefined>
    >;
    existingFile?: FileInputToBase64FunctionReturn;
    existingFileUrl?: string;
    icon?: ReactNode;
    labelSpanProps?: ComponentProps<typeof Span>;
    loading?: boolean;
    multiple?: boolean;
};

/**
 * @note use the `onChangeHandler` prop to grab the parsed base64 image object
 */
export default function FileUpload({
    onChangeHandler,
    fileInputProps,
    placeHolderWrapper,
    previewImageWrapperProps,
    previewImageProps,
    label,
    disablePreview,
    allowedRegex,
    externalSetFile,
    externalSetFiles,
    existingFile,
    existingFileUrl,
    icon,
    labelSpanProps,
    loading,
    multiple,
    onClear,
    ...props
}: ImageUploadProps) {
    const [file, setFile] = React.useState<
        FileInputToBase64FunctionReturn | undefined
    >(existingFile);
    const [fileUrl, setFileUrl] = React.useState<string | undefined>(
        existingFileUrl
    );

    const [fileDraggedOver, setFileDraggedOver] = React.useState(false);
    const inputRef = React.useRef<HTMLInputElement>(null);

    React.useEffect(() => {
        if (existingFileUrl) {
            setFileUrl(existingFileUrl);
        }
    }, [existingFileUrl]);

    React.useEffect(() => {
        if (existingFile) {
            setFile(existingFile);
        }
    }, [existingFile]);

    return (
        <Stack
            {...props}
            className={twMerge("w-full h-[300px]", props?.className)}
        >
            <input
                type="file"
                multiple={multiple}
                className={twMerge("hidden", fileInputProps?.className)}
                {...fileInputProps}
                onChange={(e) => {
                    if (multiple) {
                        (async () => {
                            const files = e.target.files;
                            if (!files?.[0]) return;

                            let filesArr: FileInputToBase64FunctionReturn[] =
                                [];

                            for (let i = 0; i < files.length; i++) {
                                const file = files[i];
                                const fileObj = await fileInputToBase64({
                                    inputFile: file,
                                });
                                filesArr.push(fileObj);
                            }

                            externalSetFiles?.(filesArr);
                        })();
                    } else {
                        const inputFile = e.target.files?.[0];

                        if (!inputFile) return;

                        fileInputToBase64({ inputFile, allowedRegex }).then(
                            (res) => {
                                setFile(res);
                                externalSetFile?.(res);
                                onChangeHandler?.(res);
                                fileInputProps?.onChange?.(e);
                            }
                        );
                    }
                }}
                ref={inputRef as any}
            />

            {loading ? (
                <Card className={twMerge("w-full h-full ")}>
                    <Center>
                        <Loading />
                    </Center>
                </Card>
            ) : file ? (
                <Card
                    {...previewImageWrapperProps}
                    className={twMerge(
                        "w-full relative h-full items-center justify-center overflow-hidden",
                        "pb-10",
                        previewImageWrapperProps?.className
                    )}
                >
                    <Stack>
                        {disablePreview ? (
                            <Span className="opacity-50" size="small">
                                Image Uploaded!
                            </Span>
                        ) : file.fileType?.match(/image/i) ? (
                            <img
                                src={file.fileBase64Full}
                                className="w-full object-contain overflow-hidden"
                                {...previewImageProps}
                            />
                        ) : (
                            <Stack>
                                <FileArchive size={36} strokeWidth={1} />
                                <Stack className="gap-0">
                                    <Span>
                                        {file.file?.name || file.fileName}
                                    </Span>
                                    <Span size="smaller" className="opacity-70">
                                        {file.fileType}
                                    </Span>
                                </Stack>
                            </Stack>
                        )}
                        <Button
                            variant="ghost"
                            className={twMerge(
                                "absolute p-2 top-2 right-2 z-20 bg-background-light dark:bg-background-dark",
                                "hover:bg-white dark:hover:bg-black"
                            )}
                            onClick={(e) => {
                                setFile(undefined);
                                externalSetFile?.(undefined);
                                onChangeHandler?.(undefined);
                                if (inputRef.current) {
                                    inputRef.current.value = "";
                                }
                                onClear?.();
                            }}
                            title="Cancel File Upload Button"
                        >
                            <X className="text-slate-950 dark:text-white" />
                        </Button>
                        <Input
                            value={file.fileName}
                            onChange={(e) => {
                                setFile({ ...file, fileName: e.target.value });
                                externalSetFile?.({
                                    ...file,
                                    fileName: e.target.value,
                                });
                            }}
                        />
                    </Stack>
                </Card>
            ) : fileUrl ? (
                <Card
                    className="w-full relative h-full items-center justify-center overflow-hidden"
                    {...previewImageWrapperProps}
                >
                    {disablePreview ? (
                        <Span className="opacity-50" size="small">
                            Image Uploaded!
                        </Span>
                    ) : fileUrl.match(/\.pdf$|\.txt$/) ? (
                        <Row>
                            <FileArchive size={36} strokeWidth={1} />
                            <Stack className="gap-0">
                                <Span size="smaller" className="opacity-70">
                                    {fileUrl}
                                </Span>
                            </Stack>
                        </Row>
                    ) : (
                        <img
                            src={fileUrl}
                            className="w-full object-contain overflow-hidden"
                            {...previewImageProps}
                        />
                    )}
                    <Button
                        variant="ghost"
                        className={twMerge(
                            "absolute p-2 top-2 right-2 z-20 bg-white dark:bg-black",
                            "hover:bg-white dark:hover:bg-black"
                        )}
                        onClick={(e) => {
                            setFile(undefined);
                            externalSetFile?.(undefined);
                            onChangeHandler?.(undefined);
                            setFileUrl(undefined);
                        }}
                        title="Cancel File Button"
                    >
                        <X className="text-slate-950 dark:text-white" />
                    </Button>
                </Card>
            ) : (
                <Card
                    className={twMerge(
                        "w-full h-full cursor-pointer hover:bg-slate-100/50 dark:hover:bg-white/5",
                        "border-dashed border-2",
                        fileDraggedOver ? "bg-slate-100 dark:bg-white/10" : "",
                        placeHolderWrapper?.className
                    )}
                    onClick={(e) => {
                        inputRef.current?.click();
                        placeHolderWrapper?.onClick?.(e);
                    }}
                    onDragOver={(e) => {
                        e.preventDefault();
                        setFileDraggedOver(true);
                    }}
                    onDragLeave={(e) => {
                        setFileDraggedOver(false);
                    }}
                    onDrop={(e) => {
                        e.preventDefault();
                        setFileDraggedOver(false);
                        let inputFile: File | null = null;

                        if (e.dataTransfer.items) {
                            [...e.dataTransfer.items].forEach((item, i) => {
                                if (inputFile) return;
                                if (item.kind === "file") {
                                    const file = item.getAsFile();
                                    inputFile = file;
                                }
                            });
                        } else {
                            inputFile = e.dataTransfer.files?.[0];
                        }

                        if (!inputFile) return;

                        fileInputToBase64({ inputFile, allowedRegex }).then(
                            (res) => {
                                setFile(res);
                                externalSetFile?.(res);
                                onChangeHandler?.(res);
                            }
                        );
                    }}
                    {...placeHolderWrapper}
                >
                    <Center
                        className={twMerge(
                            fileDraggedOver ? "pointer-events-none" : ""
                        )}
                    >
                        <Stack className="items-center gap-2">
                            {icon || <FilePlus2 className="text-slate-400" />}
                            <Span
                                size="smaller"
                                variant="faded"
                                {...labelSpanProps}
                            >
                                {label || "Click to Upload File"}
                            </Span>
                        </Stack>
                    </Center>
                </Card>
            )}
        </Stack>
    );
}
