import { AdminPageHeader, adminLayout } from '@/Layouts/AdminLayout';
import { Button, Card, Checkbox, Input, Label, Modal, Pill, Popover } from '@/Components/ui';
import { cn } from '@/lib/cn';
import type { SharedProps } from '@/types/inertia';
import { Head, Link, router, usePage } from '@inertiajs/react';
import {
    AlertTriangle,
    CheckCircle2,
    FileUp,
    Loader2,
    MoreHorizontal,
    Package,
    Sparkles,
    Star,
    Upload,
    X,
} from 'lucide-react';
import {
    useEffect,
    useMemo,
    useRef,
    useState,
    type ChangeEvent,
    type DragEvent,
    type MouseEvent as ReactMouseEvent,
} from 'react';
import { DeleteModal } from './DeleteModal';
import { EditModal } from './EditModal';
import {
    formatBytes,
    jsonHeaders,
    multipartHeaders,
    relativeTime,
    type KitRow,
} from './shared';

type Summary = {
    total: number;
    active: number;
    featured: number;
    premium: number;
};

type PageProps = SharedProps & {
    kits: KitRow[];
    summary: Summary;
    categories: string[];
};

type BulkAction = 'delete' | 'activate' | 'deactivate';

type LintIssue = {
    severity?: string;
    path?: string;
    message?: string;
};

type UploadErrorPayload = {
    error?: string;
    message?: string;
    kit_key?: string;
    issues?: LintIssue[];
};

function AdminKitsIndex({ kits, summary, categories }: PageProps) {
    const { flash } = usePage<PageProps>().props;

    const [uploadOpen, setUploadOpen] = useState(false);
    const [editing, setEditing] = useState<KitRow | null>(null);
    const [deleting, setDeleting] = useState<KitRow | null>(null);
    const [bulkDeleteOpen, setBulkDeleteOpen] = useState(false);
    const [selected, setSelected] = useState<string[]>([]);
    const [pendingBulk, setPendingBulk] = useState<BulkAction | null>(null);
    const [inlineNotice, setInlineNotice] = useState<{
        kind: 'success' | 'error';
        message: string;
    } | null>(null);

    // Reset selection when the visible set of kits changes.
    const visibleKey = kits.map((k) => k.key).join(',');
    useEffect(() => {
        setSelected((prev) => prev.filter((key) => kits.some((k) => k.key === key)));
    }, [visibleKey]); // eslint-disable-line react-hooks/exhaustive-deps

    const allSelected = kits.length > 0 && selected.length === kits.length;
    const someSelected = selected.length > 0 && selected.length < kits.length;

    const toggleOne = (key: string, on: boolean) => {
        setSelected((prev) => {
            if (on) return prev.includes(key) ? prev : [...prev, key];
            return prev.filter((k) => k !== key);
        });
    };
    const toggleAll = () => {
        if (allSelected) setSelected([]);
        else setSelected(kits.map((k) => k.key));
    };

    const refresh = () => {
        router.reload({ only: ['kits', 'summary'] });
    };

    const flashSuccess = (message: string) => {
        setInlineNotice({ kind: 'success', message });
        window.setTimeout(() => {
            setInlineNotice((cur) =>
                cur?.kind === 'success' && cur.message === message ? null : cur,
            );
        }, 4000);
    };
    const flashError = (message: string) => {
        setInlineNotice({ kind: 'error', message });
    };

    const toggleActive = async (kit: KitRow) => {
        try {
            const res = await fetch(`/admin/kits/${encodeURIComponent(kit.key)}`, {
                method: 'PATCH',
                credentials: 'same-origin',
                headers: jsonHeaders(),
                body: JSON.stringify({ is_active: !kit.is_active }),
            });
            if (!res.ok) {
                const body = (await res.json().catch(() => null)) as {
                    message?: string;
                } | null;
                flashError(body?.message ?? `Failed to update (${res.status}).`);
                return;
            }
            flashSuccess(
                kit.is_active
                    ? `Deactivated "${kit.name}"`
                    : `Activated "${kit.name}"`,
            );
            refresh();
        } catch {
            flashError('Network error. Try again.');
        }
    };

    const applyKit = (kit: KitRow) => {
        // POST follows the server-side redirect to /editor/{id}.
        router.post(
            `/admin/kits/${encodeURIComponent(kit.key)}/apply`,
            {},
            {
                onError: () => flashError('Failed to apply kit.'),
            },
        );
    };

    const downloadKit = (kit: KitRow) => {
        window.location.href = `/admin/kits/${encodeURIComponent(kit.key)}/download`;
    };

    const runBulk = async (action: BulkAction) => {
        if (selected.length === 0) return;
        setPendingBulk(action);
        try {
            const res = await fetch('/admin/kits/bulk', {
                method: 'POST',
                credentials: 'same-origin',
                headers: jsonHeaders(),
                body: JSON.stringify({ action, keys: selected }),
            });
            const body = (await res.json().catch(() => null)) as {
                results?: Array<{ key: string; ok: boolean; error?: string }>;
                message?: string;
            } | null;
            if (!res.ok) {
                flashError(body?.message ?? `Bulk action failed (${res.status}).`);
                return;
            }
            const results = body?.results ?? [];
            const okCount = results.filter((r) => r.ok).length;
            const errCount = results.length - okCount;
            const verb =
                action === 'delete'
                    ? 'Deleted'
                    : action === 'activate'
                      ? 'Activated'
                      : 'Deactivated';
            if (errCount === 0) {
                flashSuccess(
                    `${verb} ${okCount} ${okCount === 1 ? 'kit' : 'kits'}`,
                );
            } else {
                flashError(
                    `${verb} ${okCount}, but ${errCount} failed. Check individual kits.`,
                );
            }
            setSelected([]);
            setBulkDeleteOpen(false);
            refresh();
        } catch {
            flashError('Network error. Try again.');
        } finally {
            setPendingBulk(null);
        }
    };

    const tiles = useMemo(
        () => [
            { label: 'Total kits', value: summary.total, icon: Package },
            { label: 'Active', value: summary.active, icon: CheckCircle2 },
            { label: 'Featured', value: summary.featured, icon: Star },
            { label: 'Premium', value: summary.premium, icon: Sparkles },
        ],
        [summary],
    );

    // Featured count for the EditModal warning. Prefer the summary value
    // (authoritative + cheap) and fall back to a client-side count when an
    // older backend version doesn't include featured on the summary tile.
    const featuredCount = useMemo(() => {
        if (typeof summary.featured === 'number') return summary.featured;
        return kits.filter((k) => k.is_featured).length;
    }, [summary.featured, kits]);

    return (
        <>
            <Head title="Admin · Kits" />
            <AdminPageHeader
                title="Kits"
                description="Manage installed kits — upload new kits, edit marketing metadata, delete unused ones."
                actions={
                    <Button
                        type="button"
                        variant="primary"
                        size="sm"
                        onClick={() => setUploadOpen(true)}
                        data-testid="admin-kits-upload-button"
                    >
                        <Upload className="mr-1.5 h-3.5 w-3.5" strokeWidth={2.5} />
                        Upload kit
                    </Button>
                }
            />
                {flash.success && (
                    <div
                        className="mb-4 rounded-lg border border-success/30 bg-success/5 px-4 py-2 text-sm text-success"
                        data-testid="admin-kits-flash-success"
                    >
                        {flash.success}
                    </div>
                )}
                {flash.error && (
                    <div
                        className="mb-4 rounded-lg border border-destructive/30 bg-destructive/5 px-4 py-2 text-sm text-destructive"
                        data-testid="admin-kits-flash-error"
                    >
                        {flash.error}
                    </div>
                )}
                {inlineNotice && (
                    <div
                        className={cn(
                            'mb-4 flex items-start justify-between gap-3 rounded-lg border px-4 py-2 text-sm',
                            inlineNotice.kind === 'success'
                                ? 'border-accent/30 bg-accent/5 text-accent'
                                : 'border-destructive/30 bg-destructive/5 text-destructive',
                        )}
                        role={inlineNotice.kind === 'error' ? 'alert' : 'status'}
                        data-testid={`admin-kits-inline-${inlineNotice.kind}`}
                    >
                        <span>{inlineNotice.message}</span>
                        <button
                            type="button"
                            onClick={() => setInlineNotice(null)}
                            aria-label="Dismiss"
                            className="text-current/70 hover:text-current"
                        >
                            <X className="h-3.5 w-3.5" strokeWidth={2} />
                        </button>
                    </div>
                )}

                <div
                    className="mb-5 grid grid-cols-2 gap-3 sm:grid-cols-4"
                    data-testid="admin-kits-summary-tiles"
                >
                    {tiles.map(({ label, value, icon: Icon }) => (
                        <Card
                            key={label}
                            padding="md"
                            className="flex items-center gap-3"
                        >
                            <span className="flex h-9 w-9 items-center justify-center rounded-md bg-accent/10 text-accent">
                                <Icon className="h-4 w-4" strokeWidth={2} />
                            </span>
                            <div>
                                <div className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
                                    {label}
                                </div>
                                <div className="text-xl font-semibold tabular-nums text-foreground">
                                    {value.toLocaleString()}
                                </div>
                            </div>
                        </Card>
                    ))}
                </div>

                {selected.length > 0 && (
                    <BulkActionBar
                        count={selected.length}
                        pending={pendingBulk}
                        onAction={(a) => {
                            if (a === 'delete') {
                                setBulkDeleteOpen(true);
                                return;
                            }
                            runBulk(a);
                        }}
                        onClear={() => setSelected([])}
                    />
                )}

                {kits.length === 0 ? (
                    <Card
                        padding="lg"
                        className="flex flex-col items-center justify-center py-16 text-center"
                        data-testid="admin-kits-empty"
                    >
                        <span className="mb-3 flex h-12 w-12 items-center justify-center rounded-full bg-muted text-muted-foreground">
                            <Package className="h-5 w-5" strokeWidth={2} />
                        </span>
                        <h3 className="text-base font-semibold text-foreground">
                            No kits installed yet
                        </h3>
                        <p className="mt-1 max-w-sm text-sm text-muted-foreground">
                            Upload a kit ZIP to get started. New kits appear on{' '}
                            <span className="font-mono text-[12px]">/templates</span>{' '}
                            once they're active.
                        </p>
                        <Button
                            type="button"
                            variant="primary"
                            size="sm"
                            onClick={() => setUploadOpen(true)}
                            className="mt-5"
                            data-testid="admin-kits-empty-upload"
                        >
                            <Upload className="mr-1.5 h-3.5 w-3.5" strokeWidth={2.5} />
                            Upload kit
                        </Button>
                    </Card>
                ) : (
                    <Card padding="none" className="overflow-hidden">
                        <table className="stagger-rows w-full text-sm">
                            <thead className="border-b border-border bg-muted/30">
                                <tr className="text-left text-[11px] font-medium text-muted-foreground">
                                    <th className="w-10 px-4 py-2.5">
                                        <Checkbox
                                            wrapperClassName="align-middle"
                                            aria-label="Select all kits"
                                            checked={allSelected}
                                            ref={(el) => {
                                                if (el)
                                                    el.indeterminate = someSelected;
                                            }}
                                            onChange={toggleAll}
                                            data-testid="admin-kits-select-all"
                                        />
                                    </th>
                                    <th className="px-4 py-2.5">Name</th>
                                    <th className="px-4 py-2.5">Category</th>
                                    <th className="px-4 py-2.5 text-right">Sort</th>
                                    <th className="px-4 py-2.5 text-right">
                                        Screens
                                    </th>
                                    <th className="px-4 py-2.5">Status</th>
                                    <th className="px-4 py-2.5">Updated</th>
                                    <th className="w-12 px-4 py-2.5" />
                                </tr>
                            </thead>
                            <tbody>
                                {kits.map((kit) => (
                                    <tr
                                        key={kit.key}
                                        className={cn(
                                            'border-b border-border/60 last:border-0',
                                            selected.includes(kit.key) &&
                                                'bg-accent/5',
                                            !kit.is_active && 'opacity-70',
                                        )}
                                        data-testid={`admin-kit-row-${kit.key}`}
                                    >
                                        <td className="px-4 py-3">
                                            <Checkbox
                                                wrapperClassName="align-middle"
                                                aria-label={`Select ${kit.name}`}
                                                checked={selected.includes(kit.key)}
                                                onChange={(e) =>
                                                    toggleOne(
                                                        kit.key,
                                                        e.target.checked,
                                                    )
                                                }
                                                data-testid={`admin-kit-select-${kit.key}`}
                                            />
                                        </td>
                                        <td className="px-4 py-3">
                                            <div className="flex flex-col gap-0.5">
                                                <Link
                                                    href={`/admin/kits/${encodeURIComponent(kit.key)}`}
                                                    className="font-medium text-foreground hover:text-accent"
                                                    data-testid={`admin-kit-name-link-${kit.key}`}
                                                >
                                                    {kit.name}
                                                </Link>
                                                <span className="font-mono text-[11px] text-muted-foreground">
                                                    {kit.key}
                                                </span>
                                                {kit.tagline && (
                                                    <span className="mt-0.5 max-w-md truncate text-[12px] text-muted-foreground">
                                                        {kit.tagline}
                                                    </span>
                                                )}
                                                {!kit.marketing_present && (
                                                    <button
                                                        type="button"
                                                        onClick={() =>
                                                            setEditing(kit)
                                                        }
                                                        className="mt-1 inline-flex w-fit items-center gap-1 rounded-full border border-warning/40 bg-warning/10 px-2 py-0.5 text-[10px] font-medium text-warning hover:bg-warning/15"
                                                        data-testid={`admin-kit-add-metadata-${kit.key}`}
                                                    >
                                                        <AlertTriangle
                                                            className="h-3 w-3"
                                                            strokeWidth={2.5}
                                                        />
                                                        Add metadata
                                                    </button>
                                                )}
                                            </div>
                                        </td>
                                        <td className="px-4 py-3 text-foreground/80">
                                            {kit.category ? (
                                                <span className="rounded bg-muted px-1.5 py-0.5 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
                                                    {kit.category}
                                                </span>
                                            ) : (
                                                <span className="text-muted-foreground">
                                                    —
                                                </span>
                                            )}
                                        </td>
                                        <td
                                            className="px-4 py-3 text-right tabular-nums text-foreground/80"
                                            data-testid={`admin-kit-sort-${kit.key}`}
                                        >
                                            {kit.sort_order ?? 100}
                                        </td>
                                        <td className="px-4 py-3 text-right tabular-nums text-foreground/80">
                                            {kit.screen_count}
                                        </td>
                                        <td className="px-4 py-3">
                                            <div className="flex flex-wrap items-center gap-1">
                                                {kit.is_active ? (
                                                    <Pill tone="success" size="sm">
                                                        Active
                                                    </Pill>
                                                ) : (
                                                    <Pill tone="neutral" size="sm">
                                                        Inactive
                                                    </Pill>
                                                )}
                                                {kit.is_featured && (
                                                    <span
                                                        className="inline-flex items-center gap-1 rounded-full border border-accent/30 bg-accent/5 px-2 py-0.5 text-[10px] font-medium text-accent"
                                                        title="Featured kit"
                                                    >
                                                        <Star
                                                            className="h-2.5 w-2.5"
                                                            strokeWidth={2.5}
                                                        />
                                                        Featured
                                                    </span>
                                                )}
                                                {kit.is_premium && (
                                                    <span
                                                        className="inline-flex items-center gap-1 rounded-full border border-border bg-muted px-2 py-0.5 text-[10px] font-medium text-foreground/70"
                                                        title="Premium kit"
                                                    >
                                                        <Sparkles
                                                            className="h-2.5 w-2.5"
                                                            strokeWidth={2.5}
                                                        />
                                                        Premium
                                                    </span>
                                                )}
                                            </div>
                                        </td>
                                        <td className="px-4 py-3 text-[12px] text-muted-foreground">
                                            {relativeTime(kit.updated_at)}
                                        </td>
                                        <td className="px-4 py-3">
                                            <KitRowMenu
                                                kit={kit}
                                                onView={() =>
                                                    router.visit(
                                                        `/admin/kits/${encodeURIComponent(kit.key)}`,
                                                    )
                                                }
                                                onApply={() => applyKit(kit)}
                                                onDownload={() => downloadKit(kit)}
                                                onEdit={() => setEditing(kit)}
                                                onToggleActive={() =>
                                                    toggleActive(kit)
                                                }
                                                onDelete={() => setDeleting(kit)}
                                            />
                                        </td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    </Card>
                )}

            {uploadOpen && (
                <UploadModal
                    onClose={() => setUploadOpen(false)}
                    onUploaded={(kit) => {
                        setUploadOpen(false);
                        flashSuccess(`Installed "${kit.name}"`);
                        refresh();
                    }}
                />
            )}

            {editing && (
                <EditModal
                    kit={editing}
                    featuredCount={featuredCount}
                    categories={categories}
                    onClose={() => setEditing(null)}
                    onSaved={() => {
                        const name = editing.name;
                        setEditing(null);
                        flashSuccess(`Saved "${name}"`);
                        refresh();
                    }}
                />
            )}

            {deleting && (
                <DeleteModal
                    kit={deleting}
                    onClose={() => setDeleting(null)}
                    onDeleted={() => {
                        const name = deleting.name;
                        setDeleting(null);
                        flashSuccess(`Deleted "${name}"`);
                        refresh();
                    }}
                />
            )}

            {bulkDeleteOpen && (
                <BulkDeleteModal
                    kits={kits.filter((k) => selected.includes(k.key))}
                    pending={pendingBulk === 'delete'}
                    onCancel={() => setBulkDeleteOpen(false)}
                    onConfirm={() => runBulk('delete')}
                />
            )}
        </>
    );
}

AdminKitsIndex.layout = adminLayout;

export default AdminKitsIndex;

function BulkActionBar({
    count,
    pending,
    onAction,
    onClear,
}: {
    count: number;
    pending: BulkAction | null;
    onAction: (a: BulkAction) => void;
    onClear: () => void;
}) {
    const disabled = pending !== null;
    return (
        <div
            className="mb-3 flex flex-wrap items-center gap-3 rounded-lg border border-accent/30 bg-accent/5 px-4 py-2 text-sm"
            data-testid="admin-kits-bulk-bar"
        >
            <span className="font-medium text-foreground">{count} selected</span>
            <span className="ml-auto flex flex-wrap items-center gap-1">
                <BulkBtn
                    label={pending === 'activate' ? 'Activating…' : 'Activate'}
                    onClick={() => onAction('activate')}
                    disabled={disabled}
                    testId="admin-kits-bulk-activate"
                />
                <BulkBtn
                    label={pending === 'deactivate' ? 'Deactivating…' : 'Deactivate'}
                    onClick={() => onAction('deactivate')}
                    disabled={disabled}
                    testId="admin-kits-bulk-deactivate"
                />
                <BulkBtn
                    label={pending === 'delete' ? 'Deleting…' : 'Delete'}
                    onClick={() => onAction('delete')}
                    disabled={disabled}
                    tone="danger"
                    testId="admin-kits-bulk-delete"
                />
                <button
                    type="button"
                    onClick={onClear}
                    className="ml-1 rounded px-2 py-1 text-[11px] font-medium text-muted-foreground hover:text-foreground"
                    data-testid="admin-kits-bulk-clear"
                >
                    Clear
                </button>
            </span>
        </div>
    );
}

function BulkBtn({
    label,
    onClick,
    disabled,
    tone = 'default',
    testId,
}: {
    label: string;
    onClick: () => void;
    disabled?: boolean;
    tone?: 'default' | 'danger';
    testId?: string;
}) {
    return (
        <button
            type="button"
            onClick={onClick}
            disabled={disabled}
            data-testid={testId}
            className={cn(
                'rounded-md border px-2.5 py-1 text-[11px] font-medium transition-colors disabled:opacity-40',
                tone === 'danger'
                    ? 'border-destructive/40 text-destructive hover:bg-destructive/10'
                    : 'border-border text-foreground hover:bg-muted',
            )}
        >
            {label}
        </button>
    );
}

function KitRowMenu({
    kit,
    onView,
    onApply,
    onDownload,
    onEdit,
    onToggleActive,
    onDelete,
}: {
    kit: KitRow;
    onView: () => void;
    onApply: () => void;
    onDownload: () => void;
    onEdit: () => void;
    onToggleActive: () => void;
    onDelete: () => void;
}) {
    const stop = (e: ReactMouseEvent) => {
        e.preventDefault();
        e.stopPropagation();
    };
    return (
        <div onClick={stop} onMouseDown={stop} className="flex justify-end">
            <Popover
                align="end"
                trigger={({ open, toggle }) => (
                    <button
                        type="button"
                        onClick={toggle}
                        aria-label="Kit actions"
                        aria-expanded={open}
                        data-testid={`admin-kit-menu-${kit.key}`}
                        className={cn(
                            'inline-flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground',
                            open && 'bg-muted text-foreground',
                        )}
                    >
                        <MoreHorizontal className="h-4 w-4" strokeWidth={2} />
                    </button>
                )}
            >
                <div className="flex min-w-[180px] flex-col">
                    <button
                        type="button"
                        onClick={onView}
                        data-testid={`admin-kit-view-${kit.key}`}
                        className="rounded-md px-2 py-1.5 text-left text-sm text-foreground hover:bg-muted"
                    >
                        View details
                    </button>
                    <button
                        type="button"
                        onClick={onApply}
                        data-testid={`admin-kit-apply-${kit.key}`}
                        className="rounded-md px-2 py-1.5 text-left text-sm text-foreground hover:bg-muted"
                    >
                        Apply (test kit)
                    </button>
                    <button
                        type="button"
                        onClick={onDownload}
                        data-testid={`admin-kit-download-${kit.key}`}
                        className="rounded-md px-2 py-1.5 text-left text-sm text-foreground hover:bg-muted"
                    >
                        Download ZIP
                    </button>
                    <div className="my-1 h-px bg-border/60" />
                    <button
                        type="button"
                        onClick={onEdit}
                        data-testid={`admin-kit-edit-${kit.key}`}
                        className="rounded-md px-2 py-1.5 text-left text-sm text-foreground hover:bg-muted"
                    >
                        Edit
                    </button>
                    <button
                        type="button"
                        onClick={onToggleActive}
                        data-testid={`admin-kit-toggle-active-${kit.key}`}
                        className="rounded-md px-2 py-1.5 text-left text-sm text-foreground hover:bg-muted"
                    >
                        {kit.is_active ? 'Deactivate' : 'Activate'}
                    </button>
                    <button
                        type="button"
                        onClick={onDelete}
                        data-testid={`admin-kit-delete-${kit.key}`}
                        className="rounded-md px-2 py-1.5 text-left text-sm text-destructive hover:bg-destructive/10"
                    >
                        Delete
                    </button>
                </div>
            </Popover>
        </div>
    );
}

/* ---------------------------------------------------------------------------
 * Upload modal.
 * ------------------------------------------------------------------------ */

function UploadModal({
    onClose,
    onUploaded,
}: {
    onClose: () => void;
    onUploaded: (kit: KitRow) => void;
}) {
    const [file, setFile] = useState<File | null>(null);
    const [dragging, setDragging] = useState(false);
    const [submitting, setSubmitting] = useState(false);
    const [error, setError] = useState<string | null>(null);
    const [lintIssues, setLintIssues] = useState<LintIssue[] | null>(null);
    const [conflict, setConflict] = useState<{ kitKey: string } | null>(null);
    const [replace, setReplace] = useState(false);
    const inputRef = useRef<HTMLInputElement>(null);

    const acceptFile = (f: File | null) => {
        if (!f) {
            setFile(null);
            return;
        }
        const isZip =
            f.type === 'application/zip' ||
            f.type === 'application/x-zip-compressed' ||
            f.name.toLowerCase().endsWith('.zip');
        if (!isZip) {
            setError('Only .zip files are accepted.');
            return;
        }
        setError(null);
        setLintIssues(null);
        setConflict(null);
        setReplace(false);
        setFile(f);
    };

    const onDrop = (e: DragEvent<HTMLDivElement>) => {
        e.preventDefault();
        setDragging(false);
        const f = e.dataTransfer?.files?.[0];
        if (f) acceptFile(f);
    };

    const onPick = (e: ChangeEvent<HTMLInputElement>) => {
        const f = e.target.files?.[0];
        if (f) acceptFile(f);
    };

    const submit = async () => {
        if (!file || submitting) return;
        setSubmitting(true);
        setError(null);
        setLintIssues(null);
        try {
            const fd = new FormData();
            fd.append('file', file);
            if (replace) fd.append('replace', '1');

            const res = await fetch('/admin/kits', {
                method: 'POST',
                credentials: 'same-origin',
                headers: multipartHeaders(),
                body: fd,
            });
            const body = (await res.json().catch(() => null)) as
                | UploadErrorPayload
                | { kit: KitRow }
                | null;

            if (!res.ok) {
                const payload = (body ?? {}) as UploadErrorPayload;
                if (payload.error === 'KIT_ALREADY_EXISTS') {
                    setConflict({ kitKey: payload.kit_key ?? '' });
                    return;
                }
                if (payload.error === 'LINT_FAILED') {
                    setLintIssues(payload.issues ?? []);
                    setError(payload.message ?? 'Kit validation failed.');
                    return;
                }
                setError(
                    payload.message ??
                        payload.error ??
                        `Upload failed (${res.status}).`,
                );
                return;
            }

            const kit = body as KitRow | null;
            if (kit && typeof kit === 'object' && typeof kit.key === 'string') {
                onUploaded(kit);
            } else {
                onClose();
            }
        } catch {
            setError('Network error. Try again.');
        } finally {
            setSubmitting(false);
        }
    };

    return (
        <Modal
            open
            onClose={onClose}
            size="md"
            closeOnBackdrop={false}
            closeOnEsc={!submitting}
            data-testid="admin-kits-upload-modal"
            aria-labelledby="admin-kits-upload-title"
        >
            <Modal.Header
                eyebrow="Upload"
                title="Install a kit"
                titleId="admin-kits-upload-title"
                subtitle="Drop a kit ZIP — the lint pass runs server-side before install."
                actions={
                    <button
                        type="button"
                        onClick={onClose}
                        disabled={submitting}
                        aria-label="Close"
                        className="rounded-md p-1.5 text-muted-foreground hover:bg-muted hover:text-foreground"
                    >
                        <X className="h-4 w-4" strokeWidth={2} />
                    </button>
                }
            />
            <Modal.Body fade={false}>
                <div
                    onDragOver={(e) => {
                        e.preventDefault();
                        setDragging(true);
                    }}
                    onDragLeave={() => setDragging(false)}
                    onDrop={onDrop}
                    className={cn(
                        'mt-4 flex flex-col items-center justify-center rounded-lg border-2 border-dashed px-4 py-8 text-center transition-colors',
                        dragging
                            ? 'border-accent bg-accent/5'
                            : 'border-border bg-muted/20',
                    )}
                    data-testid="admin-kits-upload-dropzone"
                >
                    <FileUp
                        className="mb-2 h-6 w-6 text-muted-foreground"
                        strokeWidth={2}
                    />
                    {file ? (
                        <div className="text-sm">
                            <div
                                className="font-medium text-foreground"
                                data-testid="admin-kits-upload-filename"
                            >
                                {file.name}
                            </div>
                            <div className="mt-0.5 text-[12px] text-muted-foreground">
                                {formatBytes(file.size)}
                            </div>
                            <button
                                type="button"
                                onClick={() => setFile(null)}
                                disabled={submitting}
                                className="mt-2 text-[11px] font-medium text-muted-foreground underline hover:text-foreground"
                            >
                                Pick a different file
                            </button>
                        </div>
                    ) : (
                        <>
                            <p className="text-sm text-foreground">
                                Drop a kit ZIP here, or
                            </p>
                            <button
                                type="button"
                                onClick={() => inputRef.current?.click()}
                                className="mt-1 text-sm font-medium text-accent hover:underline"
                                data-testid="admin-kits-upload-browse"
                            >
                                browse to upload
                            </button>
                            <p className="mt-2 text-[11px] text-muted-foreground">
                                .zip only
                            </p>
                        </>
                    )}
                    <input
                        ref={inputRef}
                        type="file"
                        accept=".zip,application/zip,application/x-zip-compressed"
                        onChange={onPick}
                        className="hidden"
                        data-testid="admin-kits-upload-input"
                    />
                </div>

                {conflict && (
                    <div
                        className="mt-3 rounded-md border border-warning/30 bg-warning/10 px-3 py-2 text-[12px] text-foreground"
                        data-testid="admin-kits-upload-conflict"
                    >
                        <p className="font-medium">
                            A kit with key{' '}
                            <span className="font-mono text-[11px]">
                                {conflict.kitKey || 'unknown'}
                            </span>{' '}
                            already exists.
                        </p>
                        <label className="mt-2 flex items-start gap-2 text-[12px] text-muted-foreground">
                            <Checkbox
                                checked={replace}
                                onChange={(e) => setReplace(e.target.checked)}
                                wrapperClassName="mt-0.5"
                                data-testid="admin-kits-upload-replace"
                            />
                            <span>
                                Replace existing kit — overwrites files +
                                marketing previews. Existing projects using this
                                kit aren't affected.
                            </span>
                        </label>
                    </div>
                )}

                {lintIssues && lintIssues.length > 0 && (
                    <div
                        className="mt-3 rounded-md border border-destructive/30 bg-destructive/5 p-3 text-[12px]"
                        data-testid="admin-kits-upload-lint-issues"
                    >
                        <p className="font-medium text-destructive">
                            Lint failed — {lintIssues.length}{' '}
                            {lintIssues.length === 1 ? 'issue' : 'issues'}:
                        </p>
                        <ul className="mt-2 max-h-44 space-y-1 overflow-y-auto">
                            {lintIssues.map((issue, i) => (
                                <li
                                    key={i}
                                    className="rounded bg-card px-2 py-1 font-mono text-[11px] text-foreground/80"
                                >
                                    <span
                                        className={cn(
                                            'mr-1.5 font-semibold uppercase',
                                            issue.severity === 'error'
                                                ? 'text-destructive'
                                                : 'text-warning',
                                        )}
                                    >
                                        {issue.severity ?? 'error'}
                                    </span>
                                    {issue.path && (
                                        <span className="text-muted-foreground">
                                            {issue.path}:{' '}
                                        </span>
                                    )}
                                    <span>{issue.message ?? '—'}</span>
                                </li>
                            ))}
                        </ul>
                    </div>
                )}

                {error && !lintIssues && (
                    <p
                        className="mt-3 text-xs text-destructive"
                        data-testid="admin-kits-upload-error"
                        role="alert"
                    >
                        {error}
                    </p>
                )}

            </Modal.Body>
            <Modal.Footer>
                <Button
                    type="button"
                    variant="ghost"
                    onClick={onClose}
                    disabled={submitting}
                >
                    Cancel
                </Button>
                <Button
                    type="button"
                    variant="primary"
                    onClick={submit}
                    disabled={!file || submitting || (conflict !== null && !replace)}
                    data-testid="admin-kits-upload-submit"
                >
                    {submitting ? (
                        <span className="flex items-center gap-1.5">
                            <Loader2 className="h-3.5 w-3.5 animate-spin" />
                            Validating + installing…
                        </span>
                    ) : conflict ? (
                        'Upload anyway'
                    ) : (
                        'Upload'
                    )}
                </Button>
            </Modal.Footer>
        </Modal>
    );
}

/* ---------------------------------------------------------------------------
 * Bulk delete modal.
 * ------------------------------------------------------------------------ */

function BulkDeleteModal({
    kits,
    pending,
    onCancel,
    onConfirm,
}: {
    kits: KitRow[];
    pending: boolean;
    onCancel: () => void;
    onConfirm: () => void;
}) {
    const [confirmWord, setConfirmWord] = useState('');
    const matches = confirmWord === 'DELETE';

    return (
        <Modal
            open
            onClose={onCancel}
            size="md"
            onSubmit={(e) => {
                e.preventDefault();
                if (matches && !pending) onConfirm();
            }}
            closeOnBackdrop={false}
            closeOnEsc={!pending}
            data-testid="admin-kits-bulk-delete-modal"
            aria-labelledby="admin-kits-bulk-delete-title"
        >
            <Modal.Header
                eyebrow="Bulk delete"
                eyebrowClassName="text-destructive"
                titleId="admin-kits-bulk-delete-title"
                title={
                    <>
                        Delete {kits.length}{' '}
                        {kits.length === 1 ? 'kit' : 'kits'}?
                    </>
                }
            />
            <Modal.Body fade={false}>
                <p className="text-sm text-muted-foreground">
                    Removes the files + marketing previews for each. This
                    cannot be undone. Existing projects scaffolded from
                    these kits keep working.
                </p>

                {kits.length > 0 && (
                    <ul className="max-h-40 overflow-y-auto rounded-md border border-border bg-muted/30 px-3 py-2 text-[12px] text-foreground/80">
                        {kits.slice(0, 8).map((k) => (
                            <li key={k.key} className="truncate">
                                <span className="font-medium">{k.name}</span>{' '}
                                <span className="font-mono text-[11px] text-muted-foreground">
                                    ({k.key})
                                </span>
                            </li>
                        ))}
                        {kits.length > 8 && (
                            <li className="text-muted-foreground">
                                + {kits.length - 8} more
                            </li>
                        )}
                    </ul>
                )}

                <div>
                    <Label htmlFor="admin-kits-bulk-delete-confirm-input">
                        Type{' '}
                        <span className="font-mono text-[11px] text-foreground/80">
                            DELETE
                        </span>{' '}
                        to confirm
                    </Label>
                    <Input
                        id="admin-kits-bulk-delete-confirm-input"
                        value={confirmWord}
                        onChange={(e) => setConfirmWord(e.target.value)}
                        placeholder="DELETE"
                        className="mt-1 font-mono"
                        disabled={pending}
                        autoFocus
                        data-testid="admin-kits-bulk-delete-confirm-input"
                    />
                </div>
            </Modal.Body>
            <Modal.Footer>
                <Button
                    type="button"
                    variant="ghost"
                    onClick={onCancel}
                    disabled={pending}
                >
                    Cancel
                </Button>
                <Button
                    type="button"
                    variant="danger"
                    onClick={() => {
                        if (matches) onConfirm();
                    }}
                    disabled={!matches || pending}
                    data-testid="admin-kits-bulk-delete-confirm"
                >
                    {pending ? 'Deleting…' : `Delete ${kits.length}`}
                </Button>
            </Modal.Footer>
        </Modal>
    );
}
