"use client";

import React, { useState, useEffect, useRef, useCallback } from "react";
import Image from "next/image";
import {
  UploadCloud,
  FileText,
  CheckCircle2,
  AlertTriangle,
  X,
  RefreshCw,
  Trash2,
  ChevronDown,
  ChevronUp,
  Loader2,
  Scan,
  Copy,
  Plus,
  ArrowUpRight,
  FolderKanban,
} from "lucide-react";
import { useI18n } from "@/lib/i18n/context";
import { ProcessedReceipt } from "@/lib/schema/receipt";
import { findDuplicates, DuplicateMatch } from "@/lib/storage/duplicates";
import { getSessionQuota } from "@/lib/auth/sessionClient";
import { PaywallModal } from "@/components/paywall/PaywallModal";
import { DuplicateWarningDialog, PendingDuplicate } from "./DuplicateWarningDialog";
import { formatMoney } from "./receiptFormat";
import { apiFetch } from "@/lib/csrf/client";
import { ServerProject, assignReceiptsToProject } from "@/lib/storage/server";
import { FREE_SCAN_LIMIT, MAX_UPLOAD_BYTES } from "@/lib/limits";
import { RECEIPT_FILE_ACCEPT } from "@/lib/ingest/acceptedTypes";
import { prepareUploadFile } from "@/lib/image/prepareUpload";

export type UploadStatus =
  | "queued"
  | "preprocessing"
  | "uploading"
  | "extracting"
  | "success"
  | "duplicate_suspected"
  | "error";

export interface BatchQueueItem {
  id: string;
  file: File;
  fileName: string;
  fileSizeBytes: number;
  mimeType: string;
  previewThumbnailUrl?: string;
  isPdf: boolean;
  status: UploadStatus;
  progressPercent: number; // 0 - 100
  stageMessage: string;
  error?: string;
  receiptResult?: ProcessedReceipt;
  duplicateMatches?: DuplicateMatch[];
  pageCount?: number;
  /** Pro-only: per-file target project, editable individually while still queued. */
  projectId?: string;
}

export interface BatchUploadDrawerProps {
  isOpen: boolean;
  onClose: () => void;
  files?: File[];
  onFilesAdded?: (files: File[]) => void;
  onComplete?: (receipts: ProcessedReceipt[]) => void;
  onReceiptProcessed?: (receipt: ProcessedReceipt) => void;
  /** Bereits geladene Belege (aus PostgreSQL) für den Duplikat-Check. */
  existingReceipts?: ProcessedReceipt[];
  /** Pro-only: lets the user file this batch straight into one of their projects. */
  projects?: ServerProject[];
}

const MAX_CONCURRENT_UPLOADS = 2;
const MAX_FILE_SIZE_BYTES = MAX_UPLOAD_BYTES;

function formatFileSize(bytes: number): string {
  if (bytes < 1024) return `${bytes} B`;
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
  return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
}

export function BatchUploadDrawer({
  isOpen,
  onClose,
  files = [],
  onFilesAdded,
  onComplete,
  onReceiptProcessed,
  existingReceipts,
  projects,
}: BatchUploadDrawerProps) {
  const { language } = useI18n();
  const isDe = language === "de";

  const [queue, setQueue] = useState<BatchQueueItem[]>([]);
  const [isMinimized, setIsMinimized] = useState(false);
  const [showPaywall, setShowPaywall] = useState(false);
  const [freeAllowance, setFreeAllowance] = useState<number>(FREE_SCAN_LIMIT);
  const [selectedProjectId, setSelectedProjectId] = useState<string>("");
  const [pendingDuplicate, setPendingDuplicate] = useState<PendingDuplicate | null>(null);
  const duplicateWaiterRef = useRef<((keep: boolean) => void) | null>(null);
  const duplicateQueueRef = useRef<
    Array<{ pending: PendingDuplicate; resolve: (keep: boolean) => void }>
  >([]);
  const activeWorkersRef = useRef<Set<string>>(new Set());
  const fileInputRef = useRef<HTMLInputElement>(null);
  const createdUrlsRef = useRef<Set<string>>(new Set());
  const queueRef = useRef<BatchQueueItem[]>(queue);
  queueRef.current = queue;
  // Read from a ref inside the processing pipeline so changing the target
  // project mid-batch doesn't force-recreate the (already-running) workers.
  const selectedProjectIdRef = useRef<string>("");
  selectedProjectIdRef.current = selectedProjectId;

  const pumpDuplicateQueue = useCallback(() => {
    if (duplicateWaiterRef.current) return;
    const next = duplicateQueueRef.current.shift();
    if (!next) {
      setPendingDuplicate(null);
      return;
    }
    duplicateWaiterRef.current = next.resolve;
    setPendingDuplicate(next.pending);
  }, []);

  const askDuplicateDecision = useCallback(
    (pending: PendingDuplicate): Promise<boolean> => {
      return new Promise((resolve) => {
        duplicateQueueRef.current.push({ pending, resolve });
        pumpDuplicateQueue();
      });
    },
    [pumpDuplicateQueue]
  );

  // Clean up object URLs on component unmount reliably via mutable ref
  useEffect(() => {
    return () => {
      // Primary cleanup: revoke all URLs tracked in the set
      createdUrlsRef.current.forEach((url) => {
        try {
          URL.revokeObjectURL(url);
        } catch {}
      });
      createdUrlsRef.current.clear();

      // Secondary cleanup: check queueRef items
      queueRef.current.forEach((item) => {
        if (item.previewThumbnailUrl && !item.previewThumbnailUrl.startsWith("data:")) {
          try {
            URL.revokeObjectURL(item.previewThumbnailUrl);
          } catch {}
        }
      });
    };
  }, []);

  useEffect(() => {
    getSessionQuota()
      .then((quota) => {
        setFreeAllowance(Number.isFinite(quota.scanLimit) ? quota.scanLimit : FREE_SCAN_LIMIT);
      })
      .catch(() => {});
  }, []);

  // Helper to construct new queue items from incoming files
  const createQueueItems = useCallback((incomingFiles: File[]): BatchQueueItem[] => {
    return incomingFiles.map((file) => {
      const isPdf =
        file.type === "application/pdf" || file.name.toLowerCase().endsWith(".pdf");
      let previewThumbnailUrl: string | undefined = undefined;

      if (!isPdf && typeof window !== "undefined" && typeof URL !== "undefined" && typeof URL.createObjectURL === "function") {
        try {
          previewThumbnailUrl = URL.createObjectURL(file);
          if (previewThumbnailUrl) {
            createdUrlsRef.current.add(previewThumbnailUrl);
          }
        } catch {}
      }

      return {
        id: `upload_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
        file,
        fileName: file.name || "unnamed_file",
        fileSizeBytes: file.size || 0,
        mimeType: file.type || (isPdf ? "application/pdf" : "image/jpeg"),
        previewThumbnailUrl,
        isPdf,
        status: "queued",
        progressPercent: 0,
        stageMessage: isDe ? "In Warteschlange..." : "Queued...",
        // New files default to whatever project is currently selected — each
        // one stays individually editable below until it starts processing.
        projectId: selectedProjectIdRef.current || undefined,
      };
    });
  }, [isDe]);

  // Sync incoming files prop with queue and verify guest quota
  useEffect(() => {
    if (files && files.length > 0) {
      // Re-fetch the allowance fresh instead of trusting the `freeAllowance`
      // state — it can go stale if Pro status changed server-side (e.g. a
      // Stripe checkout completed) after this drawer first mounted.
      getSessionQuota().then((quota) => {
        const limit = Number.isFinite(quota.scanLimit) ? quota.scanLimit : FREE_SCAN_LIMIT;
        setFreeAllowance(limit);
        if (!quota.isPro && quota.scanCount >= limit) {
          setShowPaywall(true);
        }
      });
      const newItems = createQueueItems(files);
      setQueue((prev) => {
        // Prevent duplicate file entries by filename & size if already queued
        const existingSignatures = new Set(
          prev.map((i) => `${i.fileName}_${i.fileSizeBytes}`)
        );
        const filteredNew = newItems.filter(
          (item) => !existingSignatures.has(`${item.fileName}_${item.fileSizeBytes}`)
        );
        return [...prev, ...filteredNew];
      });
    }
  }, [files, createQueueItems]);

  // Processing pipeline for a single queue item
  const processQueueItem = useCallback(
    async (itemId: string) => {
      const currentItem = queueRef.current.find((i) => i.id === itemId);

      if (!currentItem) {
        activeWorkersRef.current.delete(itemId);
        return;
      }

      const file = currentItem.file;

      // 1. Pre-flight Validation
      if (file.size > MAX_FILE_SIZE_BYTES) {
        setQueue((prev) =>
          prev.map((i) =>
            i.id === itemId
              ? {
                  ...i,
                  status: "error",
                  progressPercent: 100,
                  stageMessage: isDe ? "Datei zu groß" : "File too large",
                  error: isDe
                    ? `Die Datei überschreitet das Limit von ${formatFileSize(MAX_FILE_SIZE_BYTES)}.`
                    : `File exceeds the maximum limit of ${formatFileSize(MAX_FILE_SIZE_BYTES)}.`,
                }
              : i
          )
        );
        activeWorkersRef.current.delete(itemId);
        return;
      }

      // 1b. Quota verification — re-checked fresh (not from the possibly-stale
      // `freeAllowance` state) so a Pro upgrade takes effect without a reload.
      const quota = await getSessionQuota();
      const allowance = Number.isFinite(quota.scanLimit) ? quota.scanLimit : FREE_SCAN_LIMIT;
      setFreeAllowance(allowance);
      if (!quota.isPro && quota.scanCount >= allowance) {
        setShowPaywall(true);
        setQueue((prev) =>
          prev.map((i) =>
            i.id === itemId
              ? {
                  ...i,
                  status: "error",
                  progressPercent: 100,
                  stageMessage: isDe ? "Limit erreicht" : "Limit reached",
                  error: isDe
                    ? `Kostenloses Limit von ${allowance} Belegen erreicht (${quota.scanCount}/${allowance}). Upgrade auf Pro erforderlich.`
                    : `Free limit of ${allowance} receipts reached (${quota.scanCount}/${allowance}). Pro upgrade required.`,
                }
              : i
          )
        );
        activeWorkersRef.current.delete(itemId);
        return;
      }

      setQueue((prev) =>
        prev.map((i) =>
          i.id === itemId
            ? {
                ...i,
                status: "preprocessing",
                progressPercent: 20,
                stageMessage: isDe ? "Bild wird vorbereitet..." : "Preparing image...",
              }
            : i
        )
      );

      let uploadFile = file;
      try {
        uploadFile = await prepareUploadFile(file);
      } catch {
        uploadFile = file;
      }

      setQueue((prev) =>
        prev.map((i) =>
          i.id === itemId
            ? {
                ...i,
                status: "extracting",
                progressPercent: 50,
                stageMessage: isDe
                  ? "KI-Vision & Beleg-Analyse..."
                  : "AI Vision & Analysis...",
              }
            : i
        )
      );

      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), 60000);

      try {
        const formData = new FormData();
        formData.append("file", uploadFile);

        const response = await apiFetch("/api/scan", {
          method: "POST",
          body: formData,
          signal: controller.signal,
        });

        clearTimeout(timeoutId);

        const data = await response.json();

        if (!response.ok || !data.success) {
          throw new Error(
            data.error ||
              (isDe
                ? "Serverfehler bei der Belegverarbeitung"
                : "Server error during receipt extraction")
          );
        }

        // Extracted receipts (handling multi-page PDFs or single images)
        const extractedReceipts: ProcessedReceipt[] = data.receipts || (data.receipt ? [data.receipt] : []);

        if (extractedReceipts.length === 0) {
          throw new Error(
            isDe
              ? "Keine Belegdaten aus dieser Datei extrahiert"
              : "No receipt data extracted from this file"
          );
        }

        // Check for duplicates against the currently loaded receipts (PostgreSQL
        // when the drawer is used inside the dashboard) and hand every extracted
        // receipt to the parent, which persists it server-side. Re-read the
        // item's project from the live queue or fallback to current default project.
        const liveItem = queueRef.current.find((i) => i.id === itemId);
        const targetProjectId =
          liveItem?.projectId || selectedProjectIdRef.current || undefined;
        let detectedDuplicates: DuplicateMatch[] = [];
        let discardedDuplicate = false;
        for (let i = 0; i < extractedReceipts.length; i++) {
          const receipt = targetProjectId
            ? { ...extractedReceipts[i], projectId: targetProjectId }
            : extractedReceipts[i];
          extractedReceipts[i] = receipt;

          let matches: DuplicateMatch[] = [];
          try {
            matches = findDuplicates(receipt, existingReceipts ?? []);
            if (matches.length > 0) {
              detectedDuplicates = [...detectedDuplicates, ...matches];
            }
          } catch (dupErr) {
            console.warn("Duplicate check warning:", dupErr);
          }

          if (matches[0] && matches[0].score >= 0.7) {
            const keep = await askDuplicateDecision({ candidate: receipt, matches });
            if (!keep) {
              discardedDuplicate = true;
              continue;
            }
          }

          try {
            onReceiptProcessed?.(receipt);
          } catch (saveErr) {
            console.error("Receipt persist error:", saveErr);
          }
        }

        const isDuplicateSuspected =
          !discardedDuplicate &&
          detectedDuplicates.length > 0 &&
          detectedDuplicates[0].score >= 0.7;

        setQueue((prev) =>
          prev.map((i) =>
            i.id === itemId
              ? {
                  ...i,
                  status: discardedDuplicate
                    ? "error"
                    : isDuplicateSuspected
                    ? "duplicate_suspected"
                    : "success",
                  progressPercent: 100,
                  projectId: targetProjectId,
                  error: discardedDuplicate
                    ? isDe
                      ? "Als Duplikat übersprungen"
                      : "Skipped as duplicate"
                    : undefined,
                  stageMessage: discardedDuplicate
                    ? isDe
                      ? "Übersprungen"
                      : "Skipped"
                    : data.pageCount && data.pageCount > 1
                    ? isDe
                      ? `${data.pageCount} Seiten als ein Beleg erfasst`
                      : `${data.pageCount} pages as one receipt`
                    : isDe
                    ? "Erfolgreich erfasst"
                    : "Ready",
                  receiptResult: extractedReceipts[0],
                  duplicateMatches: detectedDuplicates,
                  pageCount: data.pageCount || 1,
                }
              : i
          )
        );

        getSessionQuota()
          .then((quota) => {
            setFreeAllowance(Number.isFinite(quota.scanLimit) ? quota.scanLimit : FREE_SCAN_LIMIT);
          })
          .catch(() => {});
      } catch (err: any) {
        console.error(`Batch item [${itemId}] processing error:`, err);
        setQueue((prev) =>
          prev.map((i) =>
            i.id === itemId
              ? {
                  ...i,
                  status: "error",
                  progressPercent: 100,
                  stageMessage: isDe ? "Fehlgeschlagen" : "Failed",
                  error:
                    err.message ||
                    (isDe ? "Unbekannter Verarbeitungsfehler" : "Processing failed"),
                }
              : i
          )
        );
      } finally {
        activeWorkersRef.current.delete(itemId);
      }
    },
    [isDe, onReceiptProcessed, existingReceipts, askDuplicateDecision]
  );

  // Concurrency scheduler loop
  useEffect(() => {
    const queuedItems = queue.filter(
      (item) => item.status === "queued" && !activeWorkersRef.current.has(item.id)
    );

    while (
      activeWorkersRef.current.size < MAX_CONCURRENT_UPLOADS &&
      queuedItems.length > 0
    ) {
      const nextItem = queuedItems.shift();
      if (!nextItem) break;

      activeWorkersRef.current.add(nextItem.id);
      processQueueItem(nextItem.id);
    }
  }, [queue, processQueueItem]);

  // Per-file Quick Actions
  const handleRetryItem = (itemId: string) => {
    setQueue((prev) =>
      prev.map((i) =>
        i.id === itemId
          ? {
              ...i,
              status: "queued",
              progressPercent: 0,
              error: undefined,
              stageMessage: isDe ? "In Warteschlange..." : "Queued...",
            }
          : i
      )
    );
  };

  const handleQueueItemProject = async (itemId: string, projectId: string) => {
    const targetVal = projectId || undefined;
    setQueue((prev) =>
      prev.map((i) => {
        if (i.id !== itemId) return i;
        const updatedResult = i.receiptResult
          ? { ...i.receiptResult, projectId: targetVal }
          : undefined;
        return { ...i, projectId: targetVal, receiptResult: updatedResult };
      })
    );

    const currentItem = queueRef.current.find((i) => i.id === itemId);
    if (currentItem?.receiptResult) {
      const updatedReceipt: ProcessedReceipt = {
        ...currentItem.receiptResult,
        projectId: targetVal,
      };
      onReceiptProcessed?.(updatedReceipt);
      await assignReceiptsToProject([updatedReceipt.id], targetVal || null);
    }
  };

  const handleDefaultProjectChange = async (projectId: string) => {
    const targetVal = projectId || undefined;
    setSelectedProjectId(projectId);
    selectedProjectIdRef.current = projectId;

    setQueue((prev) =>
      prev.map((i) => {
        const updatedResult = i.receiptResult
          ? { ...i.receiptResult, projectId: targetVal }
          : undefined;
        return {
          ...i,
          projectId: targetVal,
          receiptResult: updatedResult,
        };
      })
    );

    const finishedReceipts = queueRef.current
      .filter((i) => i.receiptResult?.id)
      .map((i) => ({ ...i.receiptResult!, projectId: targetVal }));

    if (finishedReceipts.length > 0) {
      finishedReceipts.forEach((r) => onReceiptProcessed?.(r));
      await assignReceiptsToProject(
        finishedReceipts.map((r) => r.id),
        targetVal || null
      );
    }
  };

  const handleRemoveItem = (itemId: string) => {
    activeWorkersRef.current.delete(itemId);
    setQueue((prev) => {
      const target = prev.find((i) => i.id === itemId);
      if (target?.previewThumbnailUrl && !target.previewThumbnailUrl.startsWith("data:")) {
        try {
          URL.revokeObjectURL(target.previewThumbnailUrl);
          createdUrlsRef.current.delete(target.previewThumbnailUrl);
        } catch {}
      }
      return prev.filter((i) => i.id !== itemId);
    });
  };

  const handleClearCompleted = () => {
    setQueue((prev) => {
      const remaining: BatchQueueItem[] = [];
      prev.forEach((item) => {
        if (item.status === "success") {
          if (item.previewThumbnailUrl && !item.previewThumbnailUrl.startsWith("data:")) {
            try {
              URL.revokeObjectURL(item.previewThumbnailUrl);
              createdUrlsRef.current.delete(item.previewThumbnailUrl);
            } catch {}
          }
        } else {
          remaining.push(item);
        }
      });
      return remaining;
    });
  };

  const handleAddMoreFiles = async (e: React.ChangeEvent<HTMLInputElement>) => {
    if (e.target.files && e.target.files.length > 0) {
      const quota = await getSessionQuota();
      const limit = Number.isFinite(quota.scanLimit) ? quota.scanLimit : FREE_SCAN_LIMIT;
      setFreeAllowance(limit);
      if (!quota.isPro && quota.scanCount >= limit) {
        setShowPaywall(true);
      }
      const filesArray = Array.from(e.target.files);
      const newItems = createQueueItems(filesArray);
      setQueue((prev) => [...prev, ...newItems]);
      onFilesAdded?.(filesArray);
    }
    if (fileInputRef.current) fileInputRef.current.value = "";
  };

  const handleCompleteAndClose = () => {
    const successReceipts = queue
      .filter((i) => i.receiptResult && (i.status === "success" || i.status === "duplicate_suspected"))
      .map((i) => i.receiptResult as ProcessedReceipt);

    if (successReceipts.length > 0) {
      onComplete?.(successReceipts);
    }

    // Clean up created blob preview URLs
    createdUrlsRef.current.forEach((url) => {
      try {
        URL.revokeObjectURL(url);
      } catch {}
    });
    createdUrlsRef.current.clear();
    setQueue([]);
    onClose();
  };

  // Real-time batch statistics
  const totalCount = queue.length;
  const inProgressCount = queue.filter((i) =>
    ["queued", "preprocessing", "uploading", "extracting"].includes(i.status)
  ).length;
  const successCount = queue.filter(
    (i) => i.status === "success" || i.status === "duplicate_suspected"
  ).length;
  const errorCount = queue.filter((i) => i.status === "error").length;

  const aggregatePercent =
    totalCount === 0
      ? 0
      : Math.round(
          queue.reduce((acc, curr) => acc + (curr.progressPercent || 0), 0) / totalCount
        );

  if (!isOpen) return null;

  return (
    <>
      <DuplicateWarningDialog
        pending={pendingDuplicate}
        onKeep={() => {
          duplicateWaiterRef.current?.(true);
          duplicateWaiterRef.current = null;
          pumpDuplicateQueue();
        }}
        onDiscard={() => {
          duplicateWaiterRef.current?.(false);
          duplicateWaiterRef.current = null;
          pumpDuplicateQueue();
        }}
      />

      {/* Hidden File Input for Add More */}
      <input
        type="file"
        ref={fileInputRef}
        onChange={handleAddMoreFiles}
        accept={RECEIPT_FILE_ACCEPT}
        multiple
        className="hidden"
      />

      {/* Minimized Floating Status Pill */}
      {isMinimized && (
        <div
          data-testid="batch-drawer-minimized"
          className="fixed bottom-6 right-6 z-[80] bg-black text-white p-3.5 shadow-2xl border border-white/20 flex items-center gap-4 animate-fade-in font-sans cursor-pointer"
          onClick={() => setIsMinimized(false)}
        >
          <div className="flex items-center gap-2">
            {inProgressCount > 0 ? (
              <Loader2 className="w-4 h-4 text-emerald-400 animate-spin" />
            ) : errorCount > 0 ? (
              <AlertTriangle className="w-4 h-4 text-amber-400" />
            ) : (
              <CheckCircle2 className="w-4 h-4 text-emerald-400" />
            )}
            <span className="font-mono text-xs uppercase tracking-caps font-semibold">
              {inProgressCount > 0
                ? isDe
                  ? `Verarbeite (${successCount}/${totalCount})`
                  : `Processing (${successCount}/${totalCount})`
                : isDe
                ? `Stapel abgeschlossen (${successCount}/${totalCount})`
                : `Batch Finished (${successCount}/${totalCount})`}
            </span>
          </div>

          <div className="flex items-center gap-2 border-l border-white/20 pl-3">
            <span className="font-mono text-xs text-slate-300">{aggregatePercent}%</span>
            <button
              type="button"
              onClick={(e) => {
                e.stopPropagation();
                setIsMinimized(false);
              }}
              className="p-1 hover:bg-white/10 text-white"
              title={isDe ? "Maximieren" : "Maximize"}
            >
              <ChevronUp className="w-4 h-4" />
            </button>
          </div>
        </div>
      )}

      {/* Expanded Floating Drawer Panel */}
      {!isMinimized && (
        <div
          data-testid="batch-upload-drawer"
          className="fixed bottom-0 right-0 sm:bottom-6 sm:right-6 z-[85] w-full sm:max-w-xl md:max-w-2xl bg-white border-2 border-black shadow-2xl flex flex-col max-h-[85vh] sm:max-h-[75vh] animate-fade-in font-sans"
        >
          {/* Sharp 0px Corner Accents */}
          <div className="absolute top-0 left-0 w-3 h-3 border-t-2 border-l-2 border-black pointer-events-none"></div>
          <div className="absolute top-0 right-0 w-3 h-3 border-t-2 border-r-2 border-black pointer-events-none"></div>

          {/* Header */}
          <div className="p-4 sm:p-5 border-b border-[#E2E8F0] bg-[#F8FAFC] flex items-center justify-between gap-3 shrink-0">
            <div className="flex items-center gap-2.5">
              <div className="w-7 h-7 bg-black text-white flex items-center justify-center">
                <UploadCloud className="w-4 h-4" />
              </div>
              <div>
                <h3 className="font-mono text-xs uppercase tracking-caps font-bold text-black flex items-center gap-2">
                  <span>{isDe ? "Upload-Warteschlange" : "Upload queue"}</span>
                  <span className="bg-black text-white text-[10px] px-1.5 py-0.2 font-mono">
                    {totalCount}
                  </span>
                </h3>
                <p className="font-sans text-[11px] text-[#64748B]">
                  {inProgressCount > 0
                    ? isDe
                      ? `${inProgressCount} Beleg(e) in Verarbeitung`
                      : `${inProgressCount} receipt(s) in progress`
                    : isDe
                    ? "Alle Belege der Warteschlange abgearbeitet"
                    : "All queued files processed"}
                </p>
              </div>
            </div>

            <div className="flex items-center gap-1.5">
              <button
                type="button"
                onClick={() => fileInputRef.current?.click()}
                className="px-2.5 py-1.5 bg-white border border-[#E2E8F0] hover:border-black text-black font-mono text-[11px] uppercase tracking-caps flex items-center gap-1.5 transition-colors"
                title={isDe ? "Weitere Belege hinzufügen" : "Add more receipts"}
              >
                <Plus className="w-3.5 h-3.5" />
                <span className="hidden sm:inline">{isDe ? "Hinzufügen" : "Add"}</span>
              </button>

              <button
                type="button"
                onClick={() => setIsMinimized(true)}
                className="p-1.5 hover:bg-[#EEF4FC] text-[#475569] hover:text-black transition-colors"
                title={isDe ? "Minimieren" : "Minimize"}
                data-testid="batch-minimize-button"
              >
                <ChevronDown className="w-4 h-4" />
              </button>

              <button
                type="button"
                onClick={handleCompleteAndClose}
                className="p-1.5 hover:bg-[#EEF4FC] text-[#475569] hover:text-black transition-colors"
                title={isDe ? "Schließen" : "Close"}
                data-testid="batch-close-button"
              >
                <X className="w-4 h-4" />
              </button>
            </div>
          </div>

          {/* Default Project for new files (Pro-only — hidden when the user has no projects).
              Applies to all files in this batch; each file below can also still be
              adjusted individually. */}
          {projects && projects.length > 0 && (
            <div className="px-5 py-2.5 bg-white border-b border-[#E2E8F0] shrink-0 flex items-center gap-2">
              <FolderKanban className="w-3.5 h-3.5 text-[#64748B] shrink-0" />
              <label className="font-mono text-[10px] uppercase tracking-caps text-[#64748B] shrink-0">
                {isDe ? "Standardprojekt:" : "Default project:"}
              </label>
              <select
                data-testid="batch-upload-project-select"
                value={selectedProjectId}
                onChange={(e) => void handleDefaultProjectChange(e.target.value)}
                className="flex-1 min-w-0 bg-[#F8FAFC] border border-[#E2E8F0] px-2 py-1 font-mono text-xs text-black focus:border-black focus:outline-none cursor-pointer"
              >
                <option value="">{isDe ? "Kein Projekt" : "No project"}</option>
                {projects.map((p) => (
                  <option key={p.id} value={p.id}>
                    {p.name}
                  </option>
                ))}
              </select>
            </div>
          )}

          {/* Aggregate Progress Strip & Metrics */}
          <div className="px-5 py-3 bg-white border-b border-[#E2E8F0] shrink-0 space-y-2">
            <div className="flex items-center justify-between text-xs font-mono">
              <div className="flex items-center gap-4 text-[11px]">
                <span className="text-black font-medium">
                  {isDe ? "Gesamt:" : "Total:"} <strong>{totalCount}</strong>
                </span>
                {inProgressCount > 0 && (
                  <span className="text-blue-700 flex items-center gap-1">
                    <Loader2 className="w-3 h-3 animate-spin" />
                    {isDe ? "In Arbeit:" : "Active:"} <strong>{inProgressCount}</strong>
                  </span>
                )}
                <span className="text-emerald-700">
                  {isDe ? "Erfolgreich:" : "Ready:"} <strong>{successCount}</strong>
                </span>
                {errorCount > 0 && (
                  <span className="text-red-600 font-bold">
                    {isDe ? "Fehler:" : "Failed:"} <strong>{errorCount}</strong>
                  </span>
                )}
              </div>
              <span className="font-bold text-black">{aggregatePercent}%</span>
            </div>

            {/* Progress Bar Track */}
            <div className="w-full h-1.5 bg-[#EEF4FC] overflow-hidden relative">
              <div
                className={`h-full transition-all duration-300 ${
                  errorCount > 0 && inProgressCount === 0 && successCount === 0
                    ? "bg-red-500"
                    : successCount === totalCount && totalCount > 0
                    ? "bg-emerald-500"
                    : "bg-black"
                }`}
                style={{ width: `${aggregatePercent}%` }}
              ></div>
            </div>
          </div>

          {/* Queue Item List (Scrollable) */}
          <div className="p-4 overflow-y-auto space-y-3 flex-1 min-h-[160px] max-h-[360px] divide-y divide-[#F1F5F9]">
            {queue.length === 0 ? (
              <div className="py-8 text-center text-[#64748B] font-mono text-xs">
                {isDe
                  ? "Keine Dateien in der Warteschlange. Ziehen Sie Belege hierher."
                  : "No files in the queue. Drop receipts here."}
              </div>
            ) : (
              queue.map((item) => {
                const isItemError = item.status === "error";
                const isItemSuccess =
                  item.status === "success" || item.status === "duplicate_suspected";
                const isItemActive = [
                  "queued",
                  "preprocessing",
                  "uploading",
                  "extracting",
                ].includes(item.status);

                return (
                  <div
                    key={item.id}
                    data-testid={`batch-item-${item.id}`}
                    className={`pt-3 first:pt-0 flex items-center gap-3 transition-colors ${
                      isItemError ? "bg-red-50/50 p-2 border border-red-200" : ""
                    }`}
                  >
                    {/* Instant Thumbnail Preview / Document Badge */}
                    <div className="relative w-12 h-14 shrink-0 bg-[#0F172A] border border-[#E2E8F0] overflow-hidden flex items-center justify-center">
                      {item.previewThumbnailUrl ? (
                        <Image
                          src={item.previewThumbnailUrl}
                          alt={item.fileName}
                          fill
                          className="object-cover"
                          unoptimized
                        />
                      ) : (
                        <div className="flex flex-col items-center justify-center text-white">
                          <FileText className="w-5 h-5 text-red-400" />
                          <span className="font-mono text-[8px] uppercase tracking-tight text-slate-300 mt-0.5">
                            {item.isPdf ? "PDF" : "DOC"}
                          </span>
                        </div>
                      )}

                      {/* Active item scanning indicator */}
                      {isItemActive && item.status !== "queued" && (
                        <div className="absolute inset-0 bg-black/40 flex items-center justify-center">
                          <Loader2 className="w-4 h-4 text-emerald-400 animate-spin" />
                        </div>
                      )}
                    </div>

                    {/* Main Item Meta & Progress Bar */}
                    <div className="flex-1 min-w-0 space-y-1">
                      <div className="flex items-center justify-between gap-2">
                        <p
                          className="text-xs font-bold text-black truncate max-w-[220px] sm:max-w-[280px]"
                          title={item.fileName}
                        >
                          {item.fileName}
                        </p>
                        <span className="font-mono text-[10px] text-[#64748B] shrink-0">
                          {formatFileSize(item.fileSizeBytes)}
                        </span>
                      </div>

                      {/* Status & Stage Text */}
                      <div className="flex items-center gap-2 text-[11px] font-mono">
                        {isItemSuccess && (
                          <span className="text-emerald-700 flex items-center gap-1">
                            <CheckCircle2 className="w-3.5 h-3.5 text-emerald-600 shrink-0" />
                            <span>
                              {item.receiptResult?.merchant?.name
                                ? `${item.receiptResult.merchant.name} (${formatMoney(
                                    item.receiptResult.totalAmount?.value,
                                    item.receiptResult.currency || "EUR"
                                  )})`
                                : item.stageMessage}
                            </span>
                          </span>
                        )}

                        {isItemError && (
                          <span className="text-red-700 flex items-center gap-1 font-medium">
                            <AlertTriangle className="w-3.5 h-3.5 text-red-600 shrink-0" />
                            <span className="truncate max-w-[240px]" title={item.error}>
                              {item.error || item.stageMessage}
                            </span>
                          </span>
                        )}

                        {isItemActive && (
                          <span className="text-[#475569] flex items-center gap-1.5">
                            {item.status === "queued" ? (
                              <span className="w-1.5 h-1.5 bg-slate-400 rounded-full"></span>
                            ) : (
                              <Loader2 className="w-3 h-3 text-blue-600 animate-spin shrink-0" />
                            )}
                            <span>{item.stageMessage}</span>
                          </span>
                        )}
                      </div>

                      {/* Per-file Project Assignment (Pro-only) */}
                      {projects && projects.length > 0 && (
                        <div className="flex items-center gap-1.5 pt-0.5">
                          <FolderKanban className="w-3 h-3 text-[#94A3B8] shrink-0" />
                          <select
                            data-testid={`batch-item-project-${item.id}`}
                            value={item.projectId || ""}
                            onChange={(e) => void handleQueueItemProject(item.id, e.target.value)}
                            onClick={(e) => e.stopPropagation()}
                            className="min-w-0 bg-[#F8FAFC] border border-[#E2E8F0] px-1.5 py-0.5 font-mono text-[10px] text-black focus:border-black focus:outline-none cursor-pointer"
                          >
                            <option value="">{isDe ? "Kein Projekt" : "No project"}</option>
                            {projects.map((p) => (
                              <option key={p.id} value={p.id}>
                                {p.name}
                              </option>
                            ))}
                          </select>
                        </div>
                      )}

                      {/* Item-specific Progress Bar Track */}
                      <div className="w-full h-1 bg-[#EEF4FC] overflow-hidden">
                        <div
                          className={`h-full transition-all duration-300 ${
                            isItemError
                              ? "bg-red-500"
                              : isItemSuccess
                              ? "bg-emerald-500"
                              : "bg-blue-600"
                          }`}
                          style={{ width: `${item.progressPercent}%` }}
                        ></div>
                      </div>
                    </div>

                    {/* Actions: Retry & Remove */}
                    <div className="flex items-center gap-1 shrink-0">
                      {isItemError && (
                        <button
                          type="button"
                          onClick={() => handleRetryItem(item.id)}
                          className="p-1.5 bg-white border border-red-300 hover:border-black text-red-700 hover:text-black transition-colors"
                          title={isDe ? "Wiederholen" : "Retry"}
                          data-testid={`retry-item-${item.id}`}
                        >
                          <RefreshCw className="w-3.5 h-3.5" />
                        </button>
                      )}

                      <button
                        type="button"
                        onClick={() => handleRemoveItem(item.id)}
                        className="p-1.5 hover:bg-[#EEF4FC] text-[#64748B] hover:text-red-600 transition-colors"
                        title={isDe ? "Aus Liste entfernen" : "Remove"}
                        data-testid={`remove-item-${item.id}`}
                      >
                        <Trash2 className="w-3.5 h-3.5" />
                      </button>
                    </div>
                  </div>
                );
              })
            )}
          </div>

          {/* Footer Controls */}
          <div className="p-4 bg-[#F8FAFC] border-t border-[#E2E8F0] flex flex-wrap items-center justify-end gap-3 shrink-0">
            <div className="flex items-center gap-2">
              <button
                type="button"
                onClick={() => fileInputRef.current?.click()}
                className="px-3 py-2 bg-white border border-[#E2E8F0] hover:border-black text-black font-mono text-xs uppercase tracking-caps transition-colors flex items-center gap-1.5"
              >
                <Plus className="w-3.5 h-3.5" />
                <span>{isDe ? "+ Weiterer Beleg" : "+ Add More"}</span>
              </button>

              <button
                type="button"
                onClick={handleCompleteAndClose}
                disabled={inProgressCount > 0}
                className="px-4 py-2 bg-black text-white font-mono text-xs uppercase tracking-caps hover:bg-[#334155] transition-colors font-medium flex items-center gap-2 shadow-sm disabled:opacity-50 disabled:cursor-not-allowed"
                data-testid="batch-done-btn"
              >
                {inProgressCount > 0 ? (
                  <>
                    <Loader2 className="w-3.5 h-3.5 animate-spin" />
                    <span>{isDe ? "Wird gescannt..." : "Scanning..."}</span>
                  </>
                ) : (
                  <>
                    <CheckCircle2 className="w-3.5 h-3.5 text-emerald-400" />
                    <span>{isDe ? "Schließen" : "Close"}</span>
                  </>
                )}
              </button>
            </div>
          </div>
        </div>
      )}

      {/* Paywall Modal for Batch Quota Limit */}
      <PaywallModal
        isOpen={showPaywall}
        onClose={() => setShowPaywall(false)}
        reason="scanLimit"
        onSuccessUpgrade={() => setShowPaywall(false)}
      />
    </>
  );
}
