<script setup lang="ts">
import MediaPreviewDialog from '@/components/media/MediaPreviewDialog.vue';
import {
    Field,
    FieldDescription,
    FieldError,
    FieldLabel,
} from '@/components/ui/field';
import type { MediaUploadState } from '@/types/media';
import { createMediaUploadState } from '@/types/media';
import DropZone from 'dropzone-vue';
import 'dropzone-vue/dist/dropzone-vue.common.css';
import { computed, ref, watch } from 'vue';

const props = withDefaults(
    defineProps<{
        id: string;
        label: string;
        description?: string;
        maxFiles?: number;
        maxFileSize?: number;
        required?: boolean;
        errors?: string[];
        acceptedFiles?: string[];
    }>(),
    {
        maxFiles: 5,
        maxFileSize: 5_000_000,
        required: false,
        errors: () => [],
        acceptedFiles: () => ['image'],
    },
);

const state = defineModel<MediaUploadState>({
    default: () => createMediaUploadState(),
});

// ─── Dialog ──────────────────────────────────────────────────────────────────
const previewOpen = ref(false);
const previewUrl = ref<string | null>(null);

function openPreview(url: string) {
    previewUrl.value = url;
    previewOpen.value = true;
}

// ─── New file tracking (dropzone) ────────────────────────────────────────────
type FilePreview = { id: string; file: File; objectUrl: string };

const filePreviews = ref<FilePreview[]>([]);

function formatBytes(bytes: number): string {
    if (bytes < 1024) {
        return `${bytes} B`;
    }

    if (bytes < 1024 * 1024) {
        return `${(bytes / 1024).toFixed(1)} KB`;
    }

    return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}

function onAddedFile(item: { id: string; file: File }) {
    if (props.maxFiles === 1) {
        state.value.existing.forEach((existingItem) => {
            if (!state.value.removeIds.includes(existingItem.id)) {
                state.value.removeIds.push(existingItem.id);
            }
        });
        state.value.existing = [];

        const previewsCopy = [...filePreviews.value];
        previewsCopy.forEach((p) => {
            dropzoneRef.value?.removeFile(p.id);
        });
        filePreviews.value.forEach((p) => URL.revokeObjectURL(p.objectUrl));
        filePreviews.value = [];
        state.value.newFiles = [];
    }

    state.value.newFiles.push(item.file);
    filePreviews.value.push({
        id: item.id,
        file: item.file,
        objectUrl: URL.createObjectURL(item.file),
    });
}

function onRemovedFile(item: { id: string; file: File }) {
    const idx = state.value.newFiles.indexOf(item.file);

    if (idx !== -1) {
        state.value.newFiles.splice(idx, 1);
    }

    const pi = filePreviews.value.findIndex((p) => p.id === item.id);

    if (pi !== -1) {
        URL.revokeObjectURL(filePreviews.value[pi].objectUrl);
        filePreviews.value.splice(pi, 1);
    }
}

// ─── Unified normalized entries ───────────────────────────────────────────────
type NormalizedEntry = {
    key: string;
    thumbSrc: string; // src for thumbnail img
    isImage: boolean; // false → show file icon
    previewSrc: string; // src for fullscreen preview
    name: string;
    sizeLabel: string;
    onRemove: () => void;
};

const dropzoneRef = ref<InstanceType<typeof DropZone> | null>(null);

const allPreviews = computed<NormalizedEntry[]>(() => {
    const existing: NormalizedEntry[] = state.value.existing.map((item) => ({
        key: `existing-${item.id}`,
        thumbSrc: item.url, // thumb_url terlalu kecil → pixelated; pakai url full
        isImage: true,
        previewSrc: item.url,
        name: item.url.split('/').pop() ?? `image-${item.id}`,
        sizeLabel: 'Tersimpan',
        onRemove: () => {
            const idx = state.value.existing.findIndex((e) => e.id === item.id);

            if (idx !== -1) {
                state.value.existing.splice(idx, 1);

                if (!state.value.removeIds.includes(item.id)) {
                    state.value.removeIds.push(item.id);
                }
            }
        },
    }));

    const newFiles: NormalizedEntry[] = filePreviews.value.map((p) => ({
        key: `new-${p.id}`,
        thumbSrc: p.file.type.startsWith('image/') ? p.objectUrl : '',
        isImage: p.file.type.startsWith('image/'),
        previewSrc: p.objectUrl,
        name: p.file.name,
        sizeLabel: formatBytes(p.file.size),
        onRemove: () => dropzoneRef.value?.removeFile(p.id),
    }));

    return [...existing, ...newFiles];
});

// ─── Dropzone picker trigger ──────────────────────────────────────────────────
function triggerDropzonePicker(event?: MouseEvent) {
    event?.stopPropagation();

    const container = document.getElementById(`dz-container-${props.id}`);
    const input =
        container?.querySelector<HTMLInputElement>('input[type="file"]');

    input?.click();
}

watch(
    () => state.value.newFiles,
    (newFiles) => {
        if (newFiles.length === 0 && filePreviews.value.length > 0) {
            const previewsCopy = [...filePreviews.value];
            previewsCopy.forEach((p) => {
                dropzoneRef.value?.removeFile(p.id);
            });
            filePreviews.value.forEach((p) => URL.revokeObjectURL(p.objectUrl));
            filePreviews.value = [];
        }
    },
    { deep: true },
);
</script>

<template>
    <div class="media-dropzone-root">
        <Field>
            <FieldLabel :for="id" :required="required">
                {{ label }}
            </FieldLabel>
            <FieldDescription v-if="description">
                {{ description }}
            </FieldDescription>
            <FieldDescription v-else-if="maxFiles > 1">
                Maks. {{ maxFiles }} gambar. Gambar pertama akan dijadikan
                thumbnail.
            </FieldDescription>

            <div class="dropzone-wrapper">
                <!-- Dropzone form: hidden when any preview exists to prevent layout corruption -->
                <div
                    :id="`dz-container-${id}`"
                    :class="[
                        'dz-form-host',
                        { 'dz-form-hidden': allPreviews.length > 0 },
                    ]"
                >
                    <DropZone
                        :id="id"
                        ref="dropzoneRef"
                        :max-files="maxFiles"
                        :max-file-size="maxFileSize"
                        :accepted-files="acceptedFiles"
                        :upload-on-drop="false"
                        :clickable="true"
                        :hidden-input-container="`#dz-container-${id}`"
                        dropzone-class-name="dz-box"
                        :dropzone-message-class-name="`dz-message-${id}`"
                        @added-file="onAddedFile"
                        @removed-file="onRemovedFile"
                    >
                        <template #message>
                            <div class="dz-placeholder">
                                <svg
                                    xmlns="http://www.w3.org/2000/svg"
                                    width="22"
                                    height="22"
                                    viewBox="0 0 24 24"
                                    fill="none"
                                    stroke="currentColor"
                                    stroke-width="1.75"
                                    stroke-linecap="round"
                                    stroke-linejoin="round"
                                >
                                    <path
                                        d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"
                                    />
                                    <polyline points="17 8 12 3 7 8" />
                                    <line x1="12" y1="3" x2="12" y2="15" />
                                </svg>
                                <span class="dz-placeholder-primary"
                                    >Klik atau seret file ke sini</span
                                >
                                <span class="dz-placeholder-secondary">
                                    {{ acceptedFiles.join(', ') }} &mdash; Maks.
                                    {{ maxFiles }} file,
                                    {{ formatBytes(maxFileSize) }}/file
                                </span>
                            </div>
                        </template>
                    </DropZone>
                </div>

                <!-- Overlay: shown when any preview exists, triggers file picker on click -->
                <div
                    v-if="allPreviews.length > 0"
                    class="dz-overlay"
                    @click.stop="triggerDropzonePicker($event)"
                >
                    <svg
                        xmlns="http://www.w3.org/2000/svg"
                        width="22"
                        height="22"
                        viewBox="0 0 24 24"
                        fill="none"
                        stroke="currentColor"
                        stroke-width="1.75"
                        stroke-linecap="round"
                        stroke-linejoin="round"
                    >
                        <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
                        <polyline points="17 8 12 3 7 8" />
                        <line x1="12" y1="3" x2="12" y2="15" />
                    </svg>
                    <span class="dz-placeholder-primary"
                        >Klik atau seret file ke sini</span
                    >
                    <span class="dz-placeholder-secondary">
                        {{ acceptedFiles.join(', ') }} &mdash; Maks.
                        {{ maxFiles }} file, {{ formatBytes(maxFileSize) }}/file
                    </span>
                </div>
            </div>

            <!-- Preview grid (existing + new) -->
            <div
                v-if="allPreviews.length > 0"
                :class="[
                    'preview-grid',
                    { 'preview-grid--single': maxFiles === 1 },
                ]"
            >
                <div
                    v-for="entry in allPreviews"
                    :key="entry.key"
                    class="preview-card"
                >
                    <!-- Thumbnail -->
                    <div
                        class="preview-thumb"
                        @click="openPreview(entry.previewSrc)"
                    >
                        <img
                            v-if="entry.isImage"
                            :src="entry.thumbSrc"
                            :alt="entry.name"
                        />
                        <div v-else class="preview-thumb-icon">
                            <svg
                                xmlns="http://www.w3.org/2000/svg"
                                width="32"
                                height="32"
                                viewBox="0 0 24 24"
                                fill="none"
                                stroke="currentColor"
                                stroke-width="1.5"
                                stroke-linecap="round"
                                stroke-linejoin="round"
                            >
                                <path
                                    d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"
                                />
                                <polyline points="14 2 14 8 20 8" />
                            </svg>
                        </div>
                    </div>

                    <!-- Info + Remove -->
                    <div class="preview-footer">
                        <div class="preview-info">
                            <p class="preview-name" :title="entry.name">
                                {{ entry.name }}
                            </p>
                            <p class="preview-size">{{ entry.sizeLabel }}</p>
                        </div>
                        <button
                            type="button"
                            class="preview-remove"
                            title="Hapus"
                            @click="entry.onRemove()"
                        >
                            <svg
                                xmlns="http://www.w3.org/2000/svg"
                                width="14"
                                height="14"
                                viewBox="0 0 24 24"
                                fill="none"
                                stroke="currentColor"
                                stroke-width="2.5"
                                stroke-linecap="round"
                                stroke-linejoin="round"
                            >
                                <line x1="18" y1="6" x2="6" y2="18" />
                                <line x1="6" y1="6" x2="18" y2="18" />
                            </svg>
                        </button>
                    </div>
                </div>
            </div>

            <FieldError :errors="errors" />

            <MediaPreviewDialog
                v-model:open="previewOpen"
                :url="previewUrl"
                :title="label"
            />
        </Field>
    </div>
</template>

<style scoped>
/* === Root containment: prevents MediaDropzone from expanding parent grid/flex === */
.media-dropzone-root {
    width: 100%;
    min-width: 0;
    overflow: hidden;
}

/* === Wrapper === */
.dropzone-wrapper {
    position: relative;
    width: 100%;
    min-width: 0;
    max-width: 100%;
    box-sizing: border-box;
    overflow: hidden;
}

/* Host div that wraps the dropzone <form> */
.dz-form-host {
    display: block;
    width: 100%;
    min-width: 0;
}

/* When previews exist: collapse form out of view (keep in DOM so dropzone JS stays alive) */
.dz-form-hidden {
    position: absolute;
    visibility: hidden;
    pointer-events: none;
    width: 100%;
    height: 0;
    overflow: hidden;
}

/* === Dropzone box (the <form> element) === */
/* Target both our custom class and dropzone-vue's own .dropzone class */
.dropzone-wrapper :deep(.dz-box),
.dropzone-wrapper :deep(.dropzone) {
    width: 100% !important;
    max-width: 100% !important;
    min-width: 0 !important;
    box-sizing: border-box !important;
}

.dropzone-wrapper :deep(.dz-box) {
    border: 2px dashed var(--border);
    border-radius: var(--radius-lg);
    background: color-mix(in oklch, var(--muted) 50%, transparent);
    cursor: pointer;
    transition:
        border-color 0.2s,
        background-color 0.2s;
    display: flex !important;
    flex-direction: column !important;
    min-height: 0 !important;
}

.dropzone-wrapper :deep(.dz-box:hover) {
    border-color: var(--primary);
    background: color-mix(in oklch, var(--muted) 80%, transparent);
}

/* Hide built-in item previews */
.dropzone-wrapper :deep(.dropzone__item--style),
.dropzone-wrapper :deep(.dropzone__item),
.dropzone-wrapper :deep(.dropzone__container),
.dropzone-wrapper :deep(.dropzone__items) {
    display: none !important;
}

/* Message slot wrapper */
.dropzone-wrapper :deep(.dropzone__message),
.dropzone-wrapper :deep([class*='dz-message-']) {
    flex: 1 !important;
    display: flex !important;
    padding: 0 !important;
    margin: 0 !important;
}

/* === Placeholder inside slot === */
.dz-placeholder {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 0.75rem;
    padding: 0.75rem 1rem;
    width: 100%;
    min-width: 0;
    cursor: pointer;
    overflow: hidden;
}

/* === Overlay (shown when previews exist) === */
.dz-overlay {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 0.75rem;
    padding: 0.75rem 1rem;
    width: 100%;
    cursor: pointer;
    border: 2px dashed var(--border);
    border-radius: var(--radius-lg);
    background: color-mix(in oklch, var(--muted) 50%, transparent);
    transition:
        border-color 0.2s,
        background-color 0.2s;
    box-sizing: border-box;
}

.dz-overlay:hover {
    border-color: var(--primary);
    background: color-mix(in oklch, var(--muted) 80%, transparent);
}

.dz-placeholder svg,
.dz-overlay svg {
    flex-shrink: 0;
    opacity: 0.7;
    color: var(--muted-foreground);
}

.dz-placeholder-primary {
    font-size: 0.875rem;
    font-weight: 500;
    color: var(--foreground);
    white-space: nowrap;
    flex-shrink: 0;
}

.dz-placeholder-secondary {
    font-size: 0.75rem;
    color: var(--muted-foreground);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
    flex: 1;
}

/* ---- Preview grid — multiple mode (default) ---- */
.preview-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0.5rem;
    margin-top: 0.75rem;
}

@media (min-width: 400px) {
    .preview-grid {
        grid-template-columns: repeat(4, 1fr);
    }
}

@media (min-width: 560px) {
    .preview-grid {
        grid-template-columns: repeat(5, 1fr);
    }
}

@media (min-width: 720px) {
    .preview-grid {
        grid-template-columns: repeat(6, 1fr);
    }
}

.preview-card {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    background: color-mix(in oklch, var(--card) 90%, transparent);
    overflow: hidden;
    transition: box-shadow 0.15s;
}

.preview-card:hover {
    box-shadow: 0 2px 8px color-mix(in oklch, var(--foreground) 8%, transparent);
}

/* ---- Preview grid — single mode (maxFiles === 1) ---- */
.preview-grid--single {
    grid-template-columns: 1fr !important;
}

/* Single: card is horizontal — thumbnail left, info right */
.preview-grid--single .preview-card {
    flex-direction: row;
    align-items: center;
}

.preview-grid--single .preview-thumb {
    width: 56px;
    height: 56px;
    aspect-ratio: unset;
    flex-shrink: 0;
    border-radius: 0;
}

.preview-grid--single .preview-footer {
    flex: 1;
    border-top: none;
    border-left: 1px solid var(--border);
    padding: 0.4rem 0.6rem;
    min-width: 0;
}

/* Thumbnail: square, full-width */
.preview-thumb {
    width: 100%;
    aspect-ratio: 1 / 1;
    overflow: hidden;
    background: var(--muted);
    cursor: zoom-in;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.preview-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: transform 0.2s;
}

.preview-thumb:hover img {
    transform: scale(1.04);
}

.preview-thumb-icon {
    color: var(--muted-foreground);
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0.5;
}

/* Footer row: info + remove button */
.preview-footer {
    display: flex;
    align-items: center;
    gap: 0.2rem;
    padding: 0.3rem 0.35rem 0.3rem 0.45rem;
    border-top: 1px solid var(--border);
}

/* Info */
.preview-info {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
}

.preview-name {
    font-size: 0.7rem;
    font-weight: 500;
    color: var(--foreground);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    margin: 0;
    line-height: 1.3;
}

.preview-size {
    font-size: 0.65rem;
    color: var(--muted-foreground);
    margin: 0;
    line-height: 1.2;
}

/* Remove button */
.preview-remove {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    border: none;
    background: transparent;
    color: var(--muted-foreground);
    cursor: pointer;
    transition:
        background 0.15s,
        color 0.15s;
    padding: 0;
}

.preview-remove:hover {
    background: color-mix(in oklch, var(--destructive) 15%, transparent);
    color: var(--destructive);
}
</style>
