<script setup lang="ts">
import {
    Dialog,
    DialogTrigger,
    DialogContent,
    DialogHeader,
    DialogTitle,
    DialogDescription,
    DialogFooter,
} from '@/components/ui/dialog';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Button } from '@/components/ui/button';
import { Progress } from '@/components/ui/progress';
import byteSize from 'byte-size';
import { uploadToWarren } from '~/lib/api/warrens';
import { toast } from 'vue-sonner';
import UploadListEntry from './UploadListEntry.vue';
import { useDropZone } from '@vueuse/core';

const warrenStore = useWarrenStore();
const uploadStore = useUploadStore();

const currentAndUploadRouteMatch = computed(
    () =>
        uploadStore.destination != null &&
        warrenStore.current != null &&
        uploadStore.destination.warrenId == warrenStore.current.warrenId &&
        uploadStore.destination.path == warrenStore.current.path
);

const fileInputElement = ref<HTMLInputElement>(
    null as unknown as HTMLInputElement
);

const uploading = ref(false);
const dropZoneRef = ref<HTMLElement>();

const dropZone = useDropZone(dropZoneRef, {
    onDrop,
    multiple: true,
});

function onDrop(files: File[] | null, event: DragEvent) {
    event.preventDefault();

    if (warrenStore.current == null || files == null || files.length < 1) {
        return;
    }

    uploadStore.addFiles(
        warrenStore.current.warrenId,
        warrenStore.current.path,
        files
    );
}

function onFilesChanged(event: Event) {
    if (warrenStore.current == null) {
        toast.warning('Upload', {
            description: 'Enter a warren before attempting to upload files',
        });
        return;
    }

    if (event.target == null) {
        return;
    }

    const target = event.target as HTMLInputElement;

    if (target.files == null) {
        return;
    }

    uploadStore.addFiles(
        warrenStore.current.warrenId,
        warrenStore.current.path,
        [...target.files]
    );

    fileInputElement.value.value = '';
}

function removeFile(index: number) {
    uploadStore.removeFile(index);
}

function clearCompletedFiles() {
    uploadStore.clearCompletedFiles();
}

function updateFileListStatus(loadedBytes: number, ended: boolean = false) {
    let acc = 0;

    for (let i = uploadStore.startIndex; i < uploadStore.files.length; i++) {
        if (uploadStore.progress == null) {
            uploadStore.files[i].status = 'not_uploaded';
            continue;
        }

        acc += uploadStore.files[i].data.size;

        if (acc > loadedBytes) {
            if (ended) {
                for (let j = i; j < uploadStore.files.length; j++) {
                    uploadStore.files[j].status = 'failed';
                }
            } else {
                uploadStore.files[i].status = 'uploading';
            }
            break;
        }

        uploadStore.files[i].status = 'completed';
    }
}

async function submit() {
    if (uploadStore.destination == null) {
        return;
    }

    const dt = new DataTransfer();

    uploadStore.startIndex = uploadStore.files.findIndex(
        (f) => f.status !== 'completed'
    );
    let calculatedTotal = 0;

    for (let i = uploadStore.startIndex; i < uploadStore.files.length; i++) {
        const file = uploadStore.files[i];

        if (file.status !== 'completed') {
            dt.items.add(file.data);
            calculatedTotal += file.data.size;
        }
    }

    uploading.value = true;

    uploadStore.progress = {
        loadedBytes: 0,
        totalBytes: calculatedTotal,
    };

    const { success } = await uploadToWarren(
        uploadStore.destination.warrenId,
        uploadStore.destination.path,
        dt.files,
        (loaded, total) => {
            if (uploadStore.progress != null) {
                uploadStore.progress.loadedBytes = loaded;
                uploadStore.progress.totalBytes = total;

                updateFileListStatus(loaded, false);
            }
        }
    );

    uploading.value = false;
    if (success) {
        if (uploadStore.progress != null) {
            uploadStore.progress.loadedBytes = uploadStore.progress.totalBytes;
        }

        for (const file of uploadStore.files) {
            file.status = 'completed';
        }
    } else {
        updateFileListStatus(uploadStore.progress.loadedBytes, true);
    }
}
</script>

<template>
    <Dialog v-model:open="uploadStore.dialogOpen">
        <DialogTrigger as-child>
            <slot />
        </DialogTrigger>
        <DialogContent class="!min-w-[min(50%,98vw)] sm:!min-h-[unset]">
            <DialogHeader>
                <DialogTitle>Upload files</DialogTitle>
                <DialogDescription
                    >Upload files to
                    <span class="text-foreground">{{
                        currentAndUploadRouteMatch ||
                        uploadStore.destination == null
                            ? 'the current directory'
                            : routeWithWarrenName(
                                  uploadStore.destination!.warrenId,
                                  uploadStore.destination!.path
                              )
                    }}</span></DialogDescription
                >
            </DialogHeader>

            <input
                ref="fileInputElement"
                class="hidden"
                type="file"
                multiple="true"
                @change="onFilesChanged"
            />

            <div
                ref="dropZoneRef"
                class="flex min-h-[280px] w-full items-center justify-center overflow-hidden rounded-xl border sm:aspect-video sm:min-h-[unset]"
            >
                <ScrollArea
                    v-if="uploadStore.files.length > 0"
                    class="h-full w-full"
                >
                    <div
                        class="flex h-full w-full flex-col items-stretch gap-2 p-4 text-left"
                    >
                        <UploadListEntry
                            v-for="(file, i) in uploadStore.files"
                            :key="file.data.name"
                            :file="file"
                            :uploading="uploading"
                            @remove-file="() => removeFile(i)"
                        />
                    </div>
                </ScrollArea>
                <div
                    v-else
                    class="flex h-full w-full items-center justify-center"
                    :disabled="fileInputElement == null"
                    @click="
                        () =>
                            uploadStore.files.length < 1 &&
                            fileInputElement?.click()
                    "
                >
                    <Icon
                        class="h-[25%] w-[25%] opacity-25"
                        name="lucide:upload"
                    />
                </div>
            </div>

            <div
                v-if="uploadStore.progress != null"
                class="flex flex-col gap-1"
            >
                <Progress
                    :model-value="
                        (uploadStore.progress.loadedBytes /
                            uploadStore.progress.totalBytes) *
                        100.0
                    "
                    class="h-4 [&_*]:!transition-none"
                />
                <span class="text-right">
                    {{ byteSize(uploadStore.progress.loadedBytes) }} /
                    {{ byteSize(uploadStore.progress.totalBytes) }}
                </span>
            </div>

            <DialogFooter>
                <Button
                    class="sm:mr-auto"
                    variant="outline"
                    :disabled="
                        uploadStore.files.every((f) => f.status !== 'completed')
                    "
                    @click="clearCompletedFiles"
                >
                    Clear completed
                </Button>
                <Button
                    variant="outline"
                    :disabled="fileInputElement == null || uploading"
                    @click="() => fileInputElement?.click()"
                >
                    Select files
                </Button>
                <Button
                    :disabled="
                        uploading ||
                        !uploadStore.files.some((f) => f.status !== 'completed')
                    "
                    @click="submit"
                    >Upload</Button
                >
            </DialogFooter>
        </DialogContent>
    </Dialog>
</template>
