<script lang="ts" setup>
import type { HTMLAttributes } from "vue";
import { Motion } from "motion-v";
import { ref } from "vue";
import { X } from "lucide-vue-next";
import Button from "../button/Button.vue";

interface FileUploadProps {
    class?: HTMLAttributes["class"];
    accept?: string;
    modelValue?: File[];
}

const props = defineProps<FileUploadProps>();

const emit = defineEmits<{
    (e: "onChange", files: File[]): void;
    (e: "update:modelValue", files: File[]): void;
}>();

const fileInputRef = ref<HTMLInputElement | null>(null);
const files = ref<File[]>(props.modelValue || []);
const isActive = ref<boolean>(false);

function handleFileChange(newFiles: File[]) {
    files.value = [...files.value, ...newFiles];
    emit("onChange", files.value);
    emit("update:modelValue", files.value);
}

function onFileChange(e: Event) {
    const input = e.target as HTMLInputElement;
    if (!input.files) return;
    handleFileChange(Array.from(input.files));
}

function handleClick() {
    fileInputRef.value?.click();
}

function handleEnter() {
    isActive.value = true;
}
function handleLeave() {
    isActive.value = false;
}
function handleDrop(e: DragEvent) {
    isActive.value = false;
    const droppedFiles = e.dataTransfer?.files ? Array.from(e.dataTransfer.files) : [];
    if (droppedFiles.length) handleFileChange(droppedFiles);
}
function removeFile(event: MouseEvent, index: number) {
    files.value.splice(index, 1);
    event.stopPropagation();
    emit("update:modelValue", files.value);
}

watch(() => props.modelValue, (newVal) => {
    if (newVal) {
        files.value = newVal;
    }
}, { deep: true });
</script>

<template>
    <ClientOnly>
        <div class="w-full" :class="[$props.class]" @dragover.prevent="handleEnter" @dragleave="handleLeave"
            @drop.prevent="handleDrop" @mouseover="handleEnter" @mouseleave="handleLeave">
            <div class="group/file relative block w-full cursor-pointer overflow-hidden rounded-lg p-10 bg-muted space-y-4"
                @click="handleClick">
                <input ref="fileInputRef" type="file" class="hidden" :accept="accept" @change="onFileChange" />

                <!-- Grid pattern -->
                <div
                    class="pointer-events-none absolute inset-0 [mask-image:radial-gradient(ellipse_at_center,white,transparent)]">
                    <slot />
                </div>

                <div class="relative mx-auto mt-10 w-full max-w-xl space-y-4">
                    <Motion v-for="(file, idx) in files" :key="`file-${idx}`" :initial="{ opacity: 0, scaleX: 0 }"
                        :animate="{ opacity: 1, scaleX: 1 }"
                        class="relative z-40 mx-auto flex w-full flex-col items-start justify-start overflow-hidden rounded-md p-4 shadow-sm md:h-24 text-muted-foreground bg-background">
                        <div class="flex w-full items-center justify-between gap-4">
                            <Motion as="p" :initial="{ opacity: 0 }" :animate="{ opacity: 1 }"
                                class="max-w-xs truncate text-base">
                                {{ file.name }}
                            </Motion>
                            <Motion as="p" :initial="{ opacity: 0 }" :animate="{ opacity: 1 }"
                                class="shadow-input w-fit shrink-0 rounded-lg px-2 py-1 text-sm">
                                {{ (file.size / (1024 * 1024)).toFixed(2) }} MB
                            </Motion>
                        </div>

                        <div
                            class="mt-2 flex w-full flex-col items-start justify-between text-sm text-muted-foreground md:flex-row md:items-center">
                            <Motion as="p" :initial="{ opacity: 0 }" :animate="{ opacity: 1 }"
                                class="rounded-md bg-muted px-1.5 py-1 text-sm">
                                {{ file.type || "unknown type" }}
                            </Motion>
                            <Motion as="div" :initial="{ opacity: 0 }" :animate="{ opacity: 1 }">
                                <Button variant="ghost" @click="(e: MouseEvent) => removeFile(e, idx)">
                                    <X />
                                </Button>
                            </Motion>
                        </div>
                    </Motion>

                    <template v-if="!files.length">
                        <Motion as="div"
                            class="relative z-40 mx-auto mt-4 flex h-32 w-full max-w-32 items-center justify-center rounded-md shadow-[0px_10px_50px_rgba(0,0,0,0.1)] bg-background group-hover/file:shadow-2xl"
                            :initial="{
                                x: 0,
                                y: 0,
                                opacity: 1,
                            }" :transition="{
                                type: 'spring',
                                stiffness: 300,
                                damping: 20,
                            }" :animate="isActive
                                ? {
                                    x: 20,
                                    y: -20,
                                    opacity: 0.9,
                                }
                                : {}
                                ">
                            <Icon name="heroicons:arrow-up-tray-20-solid" size="20" />
                        </Motion>

                        <div class="absolute inset-0 z-30 mx-auto mt-4 flex h-32 w-full max-w-32 items-center justify-center rounded-md border border-dashed border-border bg-transparent transition-opacity"
                            :class="{ 'opacity-100': isActive, 'opacity-0': !isActive }" />
                    </template>
                </div>

                <!-- Content -->
                <div class="flex flex-col items-center justify-center gap-2">
                    <slot name="content" />
                </div>
            </div>
        </div>
    </ClientOnly>
</template>

<style scoped>
.group-hover\/file\:shadow-2xl:hover {
    box-shadow: 0px 10px 20px rgba(0, 0, 0, 0.25);
}

.transition-opacity {
    transition: opacity 0.3s ease;
}
</style>
