<script lang="ts" module>
    /**
     * Working type for a staged page. Owned by the parent so it can
     * read/write `pages` via `bind:pages`. The component is responsible
     * for `previewUrl` lifecycle (created on add, revoked on remove /
     * unmount).
     */
    export type PendingPage = {
        id: string;
        file: File;
        error: string | null;
        previewUrl: string;
    };
</script>

<script lang="ts">
    import { onDestroy } from 'svelte';
    import { formatBytes, validateImageFile } from '$lib/upload-validation';
    import Modal from './Modal.svelte';
    import IconButton from '$lib/components/IconButton.svelte';
    import ArrowUp from '@lucide/svelte/icons/arrow-up';
    import ArrowDown from '@lucide/svelte/icons/arrow-down';
    import Trash2 from '@lucide/svelte/icons/trash-2';
    import UploadCloud from '@lucide/svelte/icons/upload-cloud';

    let {
        pages = $bindable<PendingPage[]>([]),
        testidPrefix = 'pages'
    }: {
        pages?: PendingPage[];
        testidPrefix?: string;
    } = $props();

    let isDragOver = $state(false);
    let previewIndex = $state<number | null>(null);
    const previewPage = $derived(
        previewIndex != null ? pages[previewIndex] ?? null : null
    );

    function addFiles(files: File[] | FileList) {
        const arr = Array.from(files);
        const additions: PendingPage[] = arr.map((file) => ({
            id: crypto.randomUUID(),
            file,
            error: validateImageFile(file),
            previewUrl: URL.createObjectURL(file)
        }));
        pages = [...pages, ...additions];
    }

    function removePage(id: string) {
        const idx = pages.findIndex((p) => p.id === id);
        if (idx < 0) return;
        URL.revokeObjectURL(pages[idx].previewUrl);
        pages = pages.filter((p) => p.id !== id);
    }

    function movePage(id: string, dir: -1 | 1) {
        const i = pages.findIndex((p) => p.id === id);
        const j = i + dir;
        if (i < 0 || j < 0 || j >= pages.length) return;
        const copy = pages.slice();
        [copy[i], copy[j]] = [copy[j], copy[i]];
        pages = copy;
    }

    function onPagesInputChange(e: Event) {
        const input = e.target as HTMLInputElement;
        if (input.files) addFiles(input.files);
        input.value = '';
    }

    function onDrop(e: DragEvent) {
        e.preventDefault();
        isDragOver = false;
        if (e.dataTransfer?.files) addFiles(e.dataTransfer.files);
    }

    function onDragOver(e: DragEvent) {
        e.preventDefault();
        isDragOver = true;
    }

    function onDragLeave() {
        isDragOver = false;
    }

    function pageLabel(i: number): string {
        // Mirror the server's `{nnnn}` storage convention so the visible
        // label matches what the file ends up named on disk.
        return `Page ${String(i + 1).padStart(3, '0')}`;
    }

    onDestroy(() => {
        // Revoke any outstanding object URLs so the browser can free the
        // backing image data. Closing the page would do this eventually
        // anyway, but components inside long-lived single-page apps
        // benefit from explicit cleanup.
        for (const p of pages) URL.revokeObjectURL(p.previewUrl);
    });
</script>

<div
    class="drop-zone"
    class:drag-over={isDragOver}
    ondrop={onDrop}
    ondragover={onDragOver}
    ondragleave={onDragLeave}
    role="region"
    aria-label="page upload"
    data-testid="{testidPrefix}-drop-zone"
>
    <UploadCloud size={32} aria-hidden="true" class="drop-icon" />
    <p>
        Drop pages here, or
        <label class="file-link">
            browse
            <input
                type="file"
                accept="image/*"
                multiple
                onchange={onPagesInputChange}
                data-testid="{testidPrefix}-input"
            />
        </label>
    </p>
</div>

{#if pages.length > 0}
    <ol class="pages" data-testid="{testidPrefix}-list">
        {#each pages as p, i (p.id)}
            <li class:invalid={p.error} data-testid="{testidPrefix}-row">
                <button
                    type="button"
                    class="thumb-btn"
                    onclick={() => (previewIndex = i)}
                    aria-label="Preview {pageLabel(i)}"
                    title="Preview"
                    data-testid="{testidPrefix}-thumb"
                >
                    <img src={p.previewUrl} alt="" class="thumb" loading="lazy" />
                </button>
                <div class="page-meta">
                    <span class="page-label">{pageLabel(i)}</span>
                    <span class="page-origin" title={p.file.name}>
                        from {p.file.name} · {formatBytes(p.file.size)}
                    </span>
                </div>
                <IconButton
                    onclick={() => movePage(p.id, -1)}
                    disabled={i === 0}
                    aria-label="Move {pageLabel(i)} up"
                    title="Move up"
                >
                    <ArrowUp size={16} aria-hidden="true" />
                </IconButton>
                <IconButton
                    onclick={() => movePage(p.id, 1)}
                    disabled={i === pages.length - 1}
                    aria-label="Move {pageLabel(i)} down"
                    title="Move down"
                >
                    <ArrowDown size={16} aria-hidden="true" />
                </IconButton>
                <IconButton
                    variant="danger"
                    onclick={() => removePage(p.id)}
                    aria-label="Remove {pageLabel(i)}"
                    title="Remove page"
                    data-testid="{testidPrefix}-remove"
                >
                    <Trash2 size={16} aria-hidden="true" />
                </IconButton>
                {#if p.error}
                    <span class="field-error" role="alert">{p.error}</span>
                {/if}
            </li>
        {/each}
    </ol>
{/if}

<Modal
    open={previewIndex != null}
    title={previewPage ? pageLabel(previewIndex ?? 0) : 'Preview'}
    onClose={() => (previewIndex = null)}
    size="lg"
    closeOnBackdrop={true}
    testid="page-preview-modal"
>
    {#if previewPage}
        <img
            src={previewPage.previewUrl}
            alt={pageLabel(previewIndex ?? 0)}
            class="preview-large"
        />
    {/if}
</Modal>

<style>
    .drop-zone {
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: var(--space-2);
        border: 2px dashed var(--border-strong);
        border-radius: var(--radius-md);
        padding: var(--space-6);
        text-align: center;
        background: var(--surface);
        color: var(--text-muted);
        transition:
            background var(--transition),
            border-color var(--transition);
    }

    .drop-zone :global(.drop-icon) {
        color: var(--text-muted);
    }

    .drop-zone.drag-over {
        background: var(--primary-soft-bg);
        border-color: var(--primary);
    }

    .file-link input[type='file'] {
        display: none;
    }

    .file-link {
        color: var(--primary);
        text-decoration: underline;
        cursor: pointer;
    }

    .pages {
        padding: 0;
        margin: var(--space-3) 0 0;
        list-style: none;
    }

    .pages li {
        display: grid;
        grid-template-columns: 56px 1fr auto auto auto;
        align-items: center;
        gap: var(--space-2);
        padding: var(--space-1) var(--space-2);
        border-bottom: 1px solid var(--border);
    }

    .pages li.invalid {
        background: var(--danger-soft-bg);
    }

    .thumb-btn {
        padding: 0;
        background: transparent;
        border: 1px solid var(--border);
        border-radius: var(--radius-sm);
        cursor: pointer;
        line-height: 0;
        overflow: hidden;
        width: 56px;
        height: 80px;
    }

    .thumb-btn:hover {
        border-color: var(--primary);
    }

    .thumb {
        width: 100%;
        height: 100%;
        object-fit: cover;
    }

    .page-meta {
        display: flex;
        flex-direction: column;
        min-width: 0;
    }

    .page-label {
        font-weight: var(--weight-semibold);
        color: var(--text);
        font-size: var(--font-sm);
    }

    .page-origin {
        color: var(--text-muted);
        font-size: var(--font-xs);
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .field-error {
        grid-column: 1 / -1;
        color: var(--danger);
        font-size: var(--font-sm);
    }

    .preview-large {
        display: block;
        max-width: 100%;
        max-height: 75vh;
        margin: 0 auto;
        object-fit: contain;
        background: var(--surface-elevated);
        border-radius: var(--radius-sm);
    }
</style>
