'use client';

import { useCallback, useEffect, useMemo, useState } from 'react';
import DiscountPanel from '@/components/DiscountPanel';
import FreeItemModal from '@/components/FreeItemModal';
import PaymentModal from '@/components/PaymentModal';
import ReceiptModal from '@/components/ReceiptModal';
import ReturnModal from '@/components/ReturnModal';
import { posApi } from '@/lib/api-client';
import { generateIdempotencyKey } from '@/lib/idempotency';
import { formatPrice } from '@/lib/money';
import type {
  PaymentAllocation,
  PaymentMethod,
  PosPendingSale,
  PosReceipt,
  PosSaleResponse,
  RecoveredOrderItem,
} from '@/types/checkout';

interface CartItem {
  lineId: string;
  kind: 'stock' | 'free';
  variantId: string | null;
  productId: string | null;
  sku: string;
  name: string;
  ean: string | null;
  unitPriceCents: number;
  discountCents: number;
  taxCents: number;
  quantity: number;
  stock: number | null;
}

interface SearchResult {
  variantId: string;
  productId: string;
  name: string;
  sku: string;
  ean: string | null;
  stock: number;
  priceCents: number;
  categoryId?: string | null;
}

interface TouchCategory {
  id: string;
  parentId: string | null;
  name: string;
  emoji: string | null;
  bgColor: string | null;
  textColor: string | null;
}

interface TouchCatalog {
  enabled: boolean;
  categories: TouchCategory[];
  products: SearchResult[];
  quickProducts: Array<SearchResult | null>;
}

interface Config {
  session: { id: string; storeId: string; status: string } | null;
  terminal: {
    id: string;
    name: string;
    settings?: { lineDiscountsEnabled?: boolean; selfpayMode?: boolean } & Record<string, unknown>;
  };
  store: { id: string; name: string };
  paymentMethods: PaymentMethod[];
}

interface Customer {
  id: string;
  email: string;
  displayName?: string | null;
  phone?: string | null;
}

export default function RegisterPage() {
  const [config, setConfig] = useState<Config | null>(null);
  const [configError, setConfigError] = useState('');
  const [needsBinding, setNeedsBinding] = useState(false);
  const [bindingCode, setBindingCode] = useState('');
  const [binding, setBinding] = useState(false);
  const [openingCash, setOpeningCash] = useState('0');
  const [openingSession, setOpeningSession] = useState(false);
  const [sessionError, setSessionError] = useState('');
  const [cart, setCart] = useState<CartItem[]>([]);
  const [search, setSearch] = useState('');
  const [searchResults, setSearchResults] = useState<SearchResult[]>([]);
  const [searching, setSearching] = useState(false);
  const [searchError, setSearchError] = useState('');
  const [touchCatalog, setTouchCatalog] = useState<TouchCatalog | null>(null);
  const [categoryPath, setCategoryPath] = useState<TouchCategory[]>([]);
  const [selectedItem, setSelectedItem] = useState<CartItem | null>(null);
  const [showDiscountPanel, setShowDiscountPanel] = useState(false);
  const [showFreeItem, setShowFreeItem] = useState(false);
  const [paymentMethod, setPaymentMethod] = useState<PaymentMethod | null>(null);
  const [payments, setPayments] = useState<PaymentAllocation[]>([]);
  const [receipt, setReceipt] = useState<PosReceipt | null>(null);
  const [customer, setCustomer] = useState<Customer | null>(null);
  const [showCustomerSearch, setShowCustomerSearch] = useState(false);
  const [customerQuery, setCustomerQuery] = useState('');
  const [customerResults, setCustomerResults] = useState<Customer[]>([]);
  const [customerMode, setCustomerMode] = useState<'search' | 'create'>('search');
  const [newCustomer, setNewCustomer] = useState({ email: '', displayName: '', phone: '' });
  const [customerError, setCustomerError] = useState('');
  const [creatingCustomer, setCreatingCustomer] = useState(false);
  const [processing, setProcessing] = useState(false);
  const [error, setError] = useState('');
  const [pendingSales, setPendingSales] = useState<PosPendingSale[]>([]);
  const [todaySales, setTodaySales] = useState<PosPendingSale[]>([]);
  const [loadingPending, setLoadingPending] = useState(false);
  const [salesTab, setSalesTab] = useState<'pending' | 'day'>('day');
  const [showSidebar, setShowSidebar] = useState(true);
  const [printingSaleId, setPrintingSaleId] = useState<string | null>(null);
  const [restPaymentFor, setRestPaymentFor] = useState<PosPendingSale | null>(null);
  const [processingRest, setProcessingRest] = useState(false);
  const [returnOrder, setReturnOrder] = useState<{ orderId: string; receipt: PosReceipt } | null>(
    null,
  );
  const [showClearConfirm, setShowClearConfirm] = useState(false);
  // POS-FIX-8: delete confirmation for pending sales
  const [deleteConfirmFor, setDeleteConfirmFor] = useState<PosPendingSale | null>(null);
  const [recoveringSaleId, setRecoveringSaleId] = useState<string | null>(null);
  // POS-FIX-9: merge dialog when recovering with cart items
  const [mergePendingSale, setMergePendingSale] = useState<PosPendingSale | null>(null);
  // POS-FIX-11: park with name dialog
  const [pendingParkName, setPendingParkName] = useState<string | null>(null);
  // POS-FIX-3/POS-FIX-5: close session
  const [showCloseSession, setShowCloseSession] = useState(false);
  const [closingActualCash, setClosingActualCash] = useState('');
  const [closingPin, setClosingPin] = useState('');
  const [closingPinError, setClosingPinError] = useState('');
  const [closingPinStep, setClosingPinStep] = useState(false); // true = PIN entered, show cash dialog
  const [closing, setClosing] = useState(false);
  const [closeError, setCloseError] = useState('');

  // POS-FIX-4: toast notification when product added to cart
  const [addedToast, setAddedToast] = useState<string | null>(null);
  let toastTimer: ReturnType<typeof setTimeout> | undefined;
  const showAddedToast = (name: string) => {
    clearTimeout(toastTimer);
    setAddedToast(name);
    toastTimer = setTimeout(() => setAddedToast(null), 2000);
  };

  // Clock state for TPV header
  const [currentTime, setCurrentTime] = useState(new Date());
  useEffect(() => {
    const timer = setInterval(() => setCurrentTime(new Date()), 1000);
    return () => clearInterval(timer);
  }, []);

  // POS-FIX-5: verify PIN then close session
  const handleVerifyPin = async () => {
    if (!config?.terminal) return;
    if (!closingPin || closingPin.length < 4) { setClosingPinError('PIN requerido'); return; }
    setClosingPinError('');
    setClosingPinStep(true); // proceed to cash amount dialog
  };

  const handleCloseSession = async () => {
    if (!config?.session) return;
    const actual = parseInt(closingActualCash, 10);
    if (isNaN(actual) || actual < 0) { setCloseError('Cantidad inválida'); return; }
    setClosing(true);
    setCloseError('');
    try {
      await posApi.closeSession(config.session.id, actual, closingPin);
      window.location.reload();
    } catch (err) {
      setCloseError(err instanceof Error ? err.message : 'Error al cerrar');
      setClosing(false);
    }
  };

  const loadConfig = useCallback(async () => {
    setConfigError('');
    try {
      setConfig(await posApi.config<Config>());
      setNeedsBinding(false);
    } catch (err: unknown) {
      const apiError = err as { code?: string; message?: string };
      setConfig(null);
      setNeedsBinding(apiError.code === 'MISSING_TERMINAL_ID');
      setConfigError(apiError.message ?? 'No se pudo cargar la configuración del TPV');
    }
  }, []);

  useEffect(() => {
    void loadConfig();
  }, [loadConfig]);

  // FEAT-200: pending sales by terminalId enables cross-day resumption
  const loadPendingSales = useCallback(async () => {
    if (!config?.terminal || config.session?.status !== 'OPEN') {
      setPendingSales([]);
      return;
    }
    setLoadingPending(true);
    try {
      const data = await posApi.listSales<{ items: PosPendingSale[] }>({
        state: 'PENDING',
        terminalId: config.terminal.id,
      });
      setPendingSales(data.items ?? []);
    } catch {
      setPendingSales([]);
    } finally {
      setLoadingPending(false);
    }
  }, [config?.terminal?.id, config?.session?.status]);

  // Load today's completed sales for the sidebar
  const loadTodaySales = useCallback(async () => {
    if (!config?.terminal) {
      setTodaySales([]);
      return;
    }
    try {
      const data = await posApi.listSales<{ items: PosPendingSale[] }>({
        terminalId: config.terminal.id,
      });
      // Filter to today's date (completed or any state)
      const today = new Date();
      today.setHours(0, 0, 0, 0);
      const todayItems = (data.items ?? []).filter((sale) => {
        const saleDate = new Date(sale.createdAt);
        saleDate.setHours(0, 0, 0, 0);
        return saleDate.getTime() === today.getTime();
      });
      setTodaySales(todayItems);
    } catch {
      setTodaySales([]);
    }
  }, [config?.terminal?.id]);

  // POS-FIX-9: removed 10s polling — only manual refresh via button

  // POS-FIX-6: re-run when config loads (terminalId changes from undefined to real id)
  useEffect(() => {
    if (!config?.terminal?.id || config.session?.status !== 'OPEN') return;
    void loadPendingSales();
    void loadTodaySales();
  }, [config?.terminal?.id, config?.session?.status, loadPendingSales, loadTodaySales]);

  useEffect(() => {
    if (!config?.session || config.session.status !== 'OPEN') return;
    void posApi
      .touchCatalog<TouchCatalog>()
      .then((catalog) => {
        setTouchCatalog(catalog);
        setCategoryPath([]);
      })
      .catch(() => setTouchCatalog(null));
  }, [config?.session?.id, config?.session?.status]);

  const bindTerminal = async (event: React.FormEvent) => {
    event.preventDefault();
    setBinding(true);
    setConfigError('');
    try {
      await posApi.bind(bindingCode.trim().toUpperCase());
      setBindingCode('');
      await loadConfig();
    } catch (err) {
      setConfigError(err instanceof Error ? err.message : 'No se pudo vincular el terminal');
    } finally {
      setBinding(false);
    }
  };

  const openCashSession = async (event: React.FormEvent) => {
    event.preventDefault();
    const amount = Number(openingCash.replace(',', '.'));
    if (!Number.isFinite(amount) || amount < 0) {
      setSessionError('Introduce un importe válido');
      return;
    }
    setOpeningSession(true);
    setSessionError('');
    try {
      await posApi.openSession(Math.round(amount * 100));
      await loadConfig();
    } catch (err) {
      setSessionError(err instanceof Error ? err.message : 'No se pudo abrir la caja');
    } finally {
      setOpeningSession(false);
    }
  };

  const doSearch = useCallback(
    async (query: string) => {
      if (query.trim().length < 2) {
        setSearchResults([]);
        setSearchError('');
        return;
      }
      setSearching(true);
      setSearchError('');
      try {
        const normalized = query.trim();
        if (/^\d{8,14}$/.test(normalized)) {
          try {
            setSearchResults([(await posApi.productByEan(normalized)) as SearchResult]);
            return;
          } catch (err) {
            if ((err as { status?: number }).status !== 404) throw err;
          }
        }
        const response = (await posApi.searchProducts(normalized, config?.session?.storeId)) as {
          items: SearchResult[];
        };
        setSearchResults(response.items ?? []);
      } catch (err) {
        setSearchResults([]);
        setSearchError(err instanceof Error ? err.message : 'No se pudo buscar productos');
      } finally {
        setSearching(false);
      }
    },
    [config?.session?.storeId],
  );

  useEffect(() => {
    const timer = window.setTimeout(() => {
      void doSearch(search);
    }, 300);
    return () => window.clearTimeout(timer);
  }, [search, doSearch]);

  const resetAllocations = () => {
    setPayments([]);
    setPaymentMethod(null);
    setError('');
  };

  const addToCart = (product: SearchResult) => {
    setCart((current) => {
      const existing = current.find(
        (item) => item.kind === 'stock' && item.variantId === product.variantId,
      );
      if (existing) {
        return current.map((item) =>
          item.lineId === existing.lineId ? { ...item, quantity: item.quantity + 1 } : item,
        );
      }
      return [
        ...current,
        {
          lineId: product.variantId,
          kind: 'stock',
          variantId: product.variantId,
          productId: product.productId,
          sku: product.sku,
          name: product.name,
          ean: product.ean,
          unitPriceCents: product.priceCents,
          discountCents: 0,
          taxCents: 0,
          quantity: 1,
          stock: product.stock,
        },
      ];
    });
    showAddedToast(product.name);
    resetAllocations();
    setSearch('');
    setSearchResults([]);
  };

  const addSearchResultWithEnter = async () => {
    const normalized = search.trim();
    if (!normalized) return;
    setSearchError('');
    try {
      if (/^\d{8,14}$/.test(normalized)) {
        addToCart((await posApi.productByEan(normalized)) as SearchResult);
      } else if (searchResults.length === 1 && searchResults[0]) {
        addToCart(searchResults[0]);
      }
    } catch (err) {
      setSearchError(
        (err as { status?: number }).status === 404
          ? `No existe ningún producto con EAN ${normalized}`
          : err instanceof Error
            ? err.message
            : 'No se pudo buscar el producto',
      );
    }
  };

  const addFreeItem = ({ name, unitPriceCents }: { name: string; unitPriceCents: number }) => {
    setCart((current) => [
      ...current,
      {
        lineId: `free-${generateIdempotencyKey()}`,
        kind: 'free',
        variantId: null,
        productId: null,
        sku: 'LIBRE',
        name,
        ean: null,
        unitPriceCents,
        discountCents: 0,
        taxCents: 0,
        quantity: 1,
        stock: null,
      },
    ]);
    resetAllocations();
    setShowFreeItem(false);
  };

  const removeFromCart = (lineId: string) => {
    setCart((current) => current.filter((item) => item.lineId !== lineId));
    resetAllocations();
  };

  const updateQuantity = (lineId: string, quantity: number) => {
    if (quantity <= 0) {
      removeFromCart(lineId);
      return;
    }
    setCart((current) =>
      current.map((item) => (item.lineId === lineId ? { ...item, quantity } : item)),
    );
    resetAllocations();
  };

  const totals = useMemo(() => {
    const subtotal = cart.reduce((sum, item) => sum + item.unitPriceCents * item.quantity, 0);
    const discount = cart.reduce((sum, item) => sum + item.discountCents * item.quantity, 0);
    const tax = cart.reduce((sum, item) => sum + item.taxCents * item.quantity, 0);
    return { subtotal, discount, tax, total: subtotal - discount + tax };
  }, [cart]);
  const paidCents = payments.reduce((sum, payment) => sum + payment.amountCents, 0);
  const remainingCents = Math.max(0, totals.total - paidCents);
  const isSelfpayMode = config?.terminal.settings?.selfpayMode === true;
  const lineDiscountsEnabled = !isSelfpayMode && config?.terminal.settings?.lineDiscountsEnabled !== false;

  const applyDiscount = (lineId: string, discountCents: number) => {
    setCart((current) =>
      current.map((item) => (item.lineId === lineId ? { ...item, discountCents } : item)),
    );
    resetAllocations();
  };

  const addPayment = (allocation: PaymentAllocation) => {
    setPayments((current) => [...current, allocation]);
    setPaymentMethod(null);
    setError('');
  };

  const confirmSale = async () => {
    if (!config?.session) {
      setError('No hay sesión abierta');
      return;
    }
    if (cart.length === 0) {
      setError('Carrito vacío');
      return;
    }
    if (payments.length === 0 || paidCents <= 0) {
      setError('Asigna al menos un pago antes de confirmar');
      return;
    }
    if (paidCents > totals.total) {
      setError('Los pagos asignados superan el total');
      return;
    }
    setProcessing(true);
    setError('');
    try {
      // TPV-FIXES: items without variantId (recovered sales) must be sent as free items
      const saleItems = cart.map((item) => {
        if (item.kind === 'free' || !item.variantId) {
          return {
            kind: 'free' as const,
            name: item.name,
            unitPriceCents: item.unitPriceCents,
            quantity: item.quantity,
          };
        }
        return {
          kind: 'stock' as const,
          variantId: item.variantId,
          quantity: item.quantity,
          discountCents: item.discountCents,
        };
      });
      const result = await posApi.createSale<PosSaleResponse>({
        idempotencyKey: generateIdempotencyKey(),
        cashSessionId: config.session.id,
        terminalId: config.terminal.id,
        items: saleItems,
        payments: payments.map((payment) => ({
          methodCode: payment.methodCode,
          amountCents: payment.amountCents,
          ...(payment.kind === 'cash' ? { tenderedCents: payment.tenderedCents } : {}),
        })),
        ...(customer ? { customerId: customer.id } : {}),
      });
      setReceipt(result.receipt);
      setRestPaymentFor(null);
      void loadPendingSales();
    } catch (err) {
      setError(err instanceof Error ? err.message : 'No se pudo confirmar la venta');
    } finally {
      setProcessing(false);
    }
  };

  const parkSale = async () => {
    if (!config?.session) {
      setError('No hay sesión abierta');
      return;
    }
    if (cart.length === 0) {
      setError('Carrito vacío');
      return;
    }
    // POS-FIX-11: if no customer, ask for a name
    if (!customer) {
      setPendingParkName('');
      return;
    }
    await doParkSale();
  };

  // POS-FIX-11: actual parking with optional name
  const doParkSale = async (name?: string) => {
    if (!config?.session) {
      setError('No hay sesión abierta');
      return;
    }
    setProcessing(true);
    setError('');
    try {
      // TPV-FIXES: items without variantId (recovered sales) must be sent as free items
      const saleItems = cart.map((item) => {
        if (item.kind === 'free' || !item.variantId) {
          return {
            kind: 'free' as const,
            name: item.name,
            unitPriceCents: item.unitPriceCents,
            quantity: item.quantity,
          };
        }
        return {
          kind: 'stock' as const,
          variantId: item.variantId,
          quantity: item.quantity,
          discountCents: item.discountCents,
        };
      });
      const result = await posApi.createSale<PosSaleResponse>({
        idempotencyKey: generateIdempotencyKey(),
        cashSessionId: config.session.id,
        terminalId: config.terminal.id,
        items: saleItems,
        payments: [],
        ...(customer ? { customerId: customer.id } : {}),
        ...(name ? { posLabel: name } : {}),
      });
      setRestPaymentFor(null);
      setPendingParkName(null);
      void loadPendingSales();
      // Refresh the pending panel so the new parked sale appears
      setLoadingPending(true);
      try {
        const data = (await posApi.listSales({ state: 'PENDING', terminalId: config.terminal.id })) as {
          items: PosPendingSale[];
        };
        setPendingSales(data.items ?? []);
      } catch {
        // Non-fatal: the sale was created, panel refresh is best-effort
      } finally {
        setLoadingPending(false);
      }
      resetCashier();
    } catch (err) {
      setError(err instanceof Error ? err.message : 'No se pudo aparcar la venta');
    } finally {
      setProcessing(false);
    }
  };

  const openRestPayment = (sale: PosPendingSale) => {
    setRestPaymentFor(sale);
    setError('');
  };

  const submitRestPayment = async (allocation: PaymentAllocation) => {
    if (!config?.session || !restPaymentFor) return;
    setProcessingRest(true);
    setError('');
    try {
      const result = await posApi.payRest<PosSaleResponse>(restPaymentFor.id, {
        idempotencyKey: generateIdempotencyKey(),
        cashSessionId: config.session.id,
        terminalId: config.terminal.id,
        payments: [
          {
            methodCode: allocation.methodCode,
            amountCents: allocation.amountCents,
            ...(allocation.kind === 'cash' ? { tenderedCents: allocation.tenderedCents } : {}),
          },
        ],
      });
      setRestPaymentFor(null);
      if (result.state === 'COMPLETED' && result.outstandingCents === 0) {
        setReceipt(result.receipt);
      }
      void loadPendingSales();
    } catch (err) {
      setError(err instanceof Error ? err.message : 'No se pudo aplicar el cobro');
    } finally {
      setProcessingRest(false);
    }
  };

  // POS-FIX-12: reprint ticket — load receipt modal for printing
  const reprintSale = async (sale: PosPendingSale) => {
    setPrintingSaleId(sale.id);
    setError('');
    try {
      const result = await posApi.getReceipt<{ receipt: PosReceipt }>(sale.id);
      // Show the receipt modal for printing
      setReceipt(result.receipt);
      // Also trigger print immediately after modal renders
      setTimeout(() => {
        const printBtn = document.querySelector('button[class*="bg-\[\#2D6A4F\]"]') as HTMLButtonElement | null;
        if (printBtn) printBtn.click();
      }, 100);
    } catch (err) {
      setError(err instanceof Error ? err.message : 'No se pudo reimprimir el ticket');
    } finally {
      setPrintingSaleId(null);
    }
  };

  // POS-FIX-8: recover a parked sale into the current cart
  const recoverSale = async (sale: PosPendingSale) => {
    // POS-FIX-9: if cart has items, show merge dialog instead of auto-merge
    if (cart.length > 0) {
      // Don't set recoveringSaleId here — buttons in the dialog would be disabled
      setMergePendingSale(sale);
      setError('');
      return;
    }
    await doRecoverSale(sale);
  };

  // POS-FIX-9: actual recovery after merge dialog decision
  const doRecoverSale = async (sale: PosPendingSale) => {
    setRecoveringSaleId(sale.id);
    setError('');
    try {
      const data = (await posApi.listOrderItems<{ items: RecoveredOrderItem[] }>(sale.id));
      const items: CartItem[] = data.items
        .filter((item) => !item.freeItem)
        .map((item) => ({
          lineId: `${item.id}-recovered-${Date.now()}`,
          kind: 'stock' as const,
          variantId: null,
          productId: null,
          sku: item.sku,
          name: item.name,
          ean: null,
          unitPriceCents: Math.max(item.unitPriceCents - item.discountCents, 0),
          discountCents: 0,
          taxCents: 0,
          quantity: item.quantity - item.returnedQuantity,
          stock: null,
        }));
      setCart(items);
      setMergePendingSale(null);
      void loadPendingSales();
    } catch (err) {
      setError(err instanceof Error ? err.message : 'No se pudo recuperar la venta');
    } finally {
      setRecoveringSaleId(null);
    }
  };

  // POS-FIX-9: park current cart and recover selected sale
  const parkAndRecover = async () => {
    if (!config?.session || !mergePendingSale) return;
    setRecoveringSaleId(mergePendingSale.id);
    setError('');
    try {
      // TPV-FIXES: items without variantId (recovered sales) must be sent as free items
      const parkItems = cart.map((item) => {
        if (item.kind === 'free' || !item.variantId) {
          // Free item or recovered item without variantId
          return {
            kind: 'free' as const,
            name: item.name,
            unitPriceCents: item.unitPriceCents,
            quantity: item.quantity,
          };
        }
        return {
          kind: 'stock' as const,
          variantId: item.variantId,
          quantity: item.quantity,
          discountCents: item.discountCents,
        };
      });
      // Park current cart first (with customer if available)
      await posApi.createSale<PosSaleResponse>({
        idempotencyKey: generateIdempotencyKey(),
        cashSessionId: config.session.id,
        terminalId: config.terminal.id,
        items: parkItems,
        payments: [],
        ...(customer ? { customerId: customer.id } : {}),
      });
      // Then recover the selected sale
      await doRecoverSale(mergePendingSale);
    } catch (err) {
      setError(err instanceof Error ? err.message : 'No se pudo aparcar y recuperar');
    }
  };

  // POS-FIX-9: add recovered items to current cart (merge)
  const mergeRecoveredItems = async () => {
    if (!mergePendingSale) return;
    setRecoveringSaleId(mergePendingSale.id);
    setError('');
    try {
      const data = (await posApi.listOrderItems<{ items: RecoveredOrderItem[] }>(mergePendingSale.id));
      const items: CartItem[] = data.items
        .filter((item) => !item.freeItem)
        .map((item) => ({
          lineId: `${item.id}-recovered-${Date.now()}`,
          kind: 'stock' as const,
          variantId: null,
          productId: null,
          sku: item.sku,
          name: item.name,
          ean: null,
          unitPriceCents: Math.max(item.unitPriceCents - item.discountCents, 0),
          discountCents: 0,
          taxCents: 0,
          quantity: item.quantity - item.returnedQuantity,
          stock: null,
        }));
      setCart((prev) => [...prev, ...items]);
      setMergePendingSale(null);
      void loadPendingSales();
    } catch (err) {
      setError(err instanceof Error ? err.message : 'No se pudo añadir artículos');
    } finally {
      setRecoveringSaleId(null);
    }
  };

  // POS-FIX-8: delete a parked sale
  const deleteSale = async (sale: PosPendingSale) => {
    setError('');
    try {
      await posApi.deleteSale(sale.id);
      setDeleteConfirmFor(null);
      void loadPendingSales();
    } catch (err) {
      setError(err instanceof Error ? err.message : 'No se pudo eliminar la venta');
    }
  };

  const resetCashier = () => {
    setReceipt(null);
    setCart([]);
    setPayments([]);
    setCustomer(null);
    setError('');
    setSearch('');
    setShowClearConfirm(false);
  };

  const searchCustomers = async (query: string) => {
    setCustomerQuery(query);
    if (query.trim().length < 2) {
      setCustomerResults([]);
      return;
    }
    setCustomerError('');
    try {
      const data = (await posApi.searchCustomers(query)) as { items: Customer[] };
      setCustomerResults(data.items ?? []);
    } catch (err) {
      setCustomerResults([]);
      setCustomerError(err instanceof Error ? err.message : 'No se pudieron buscar clientes');
    }
  };

  const createCustomer = async (event: React.FormEvent) => {
    event.preventDefault();
    setCreatingCustomer(true);
    setCustomerError('');
    try {
      const created = await posApi.createCustomer<Customer>({
        email: newCustomer.email.trim(),
        ...(newCustomer.displayName.trim() ? { displayName: newCustomer.displayName.trim() } : {}),
        ...(newCustomer.phone.trim() ? { phone: newCustomer.phone.trim() } : {}),
      });
      setCustomer(created);
      setShowCustomerSearch(false);
      setCustomerMode('search');
      setNewCustomer({ email: '', displayName: '', phone: '' });
    } catch (err) {
      setCustomerError(err instanceof Error ? err.message : 'No se pudo crear el cliente');
    } finally {
      setCreatingCustomer(false);
    }
  };

  const currentCategory = categoryPath.at(-1) ?? null;
  const visibleCategories = (touchCatalog?.categories ?? []).filter(
    (category) => category.parentId === (currentCategory?.id ?? null),
  );
  const visibleTouchProducts = currentCategory
    ? (touchCatalog?.products ?? []).filter((product) => product.categoryId === currentCategory.id)
    : [];

  if (!config && needsBinding) {
    return (
      <div className="flex min-h-screen items-center justify-center bg-gray-100 p-6">
        <form
          onSubmit={bindTerminal}
          className="w-full max-w-sm space-y-4 rounded-2xl bg-white p-8 shadow-lg"
        >
          <div>
            <h1 className="text-2xl font-bold">Vincular terminal</h1>
            <p className="mt-1 text-sm text-gray-500">
              Introduce el código generado en administración.
            </p>
          </div>
          <input
            value={bindingCode}
            onChange={(event) => setBindingCode(event.target.value.toUpperCase())}
            minLength={8}
            maxLength={8}
            autoComplete="off"
            className="w-full rounded-xl border px-4 py-3 text-center font-mono text-xl tracking-widest"
            placeholder="AB12CD34"
            required
            autoFocus
          />
          {configError && <p className="text-sm text-red-600">{configError}</p>}
          <button
            disabled={binding || bindingCode.trim().length !== 8}
            className="min-h-12 w-full rounded-xl bg-[#2D6A4F] font-semibold text-white disabled:opacity-50"
          >
            {binding ? 'Vinculando…' : 'Vincular TPV'}
          </button>
        </form>
      </div>
    );
  }

  if (!config) {
    return (
      <div className="flex min-h-screen flex-col items-center justify-center gap-3 text-gray-500">
        <p>{configError || 'Cargando TPV…'}</p>
        {configError && (
          <button
            onClick={() => void loadConfig()}
            className="font-medium text-[#2D6A4F] underline"
          >
            Reintentar
          </button>
        )}
      </div>
    );
  }

  if (config.session?.status !== 'OPEN') {
    return (
      <div className="flex min-h-screen items-center justify-center bg-gray-100 p-6">
        <form
          onSubmit={openCashSession}
          className="w-full max-w-md space-y-5 rounded-2xl bg-white p-8 shadow-lg"
        >
          <div>
            <p className="text-sm font-medium text-[#2D6A4F]">
              {config.store.name} · {config.terminal.name}
            </p>
            <h1 className="mt-1 text-3xl font-bold">Abrir sesión de caja</h1>
            <p className="mt-2 text-sm text-gray-500">
              Indica el efectivo disponible al comenzar el turno.
            </p>
          </div>
          <label className="block text-sm font-medium">
            Efectivo inicial (€)
            <input
              type="number"
              min="0"
              step="0.01"
              value={openingCash}
              onChange={(event) => setOpeningCash(event.target.value)}
              className="mt-1 w-full rounded-xl border px-4 py-3 text-xl"
              required
              autoFocus
            />
          </label>
          {sessionError && <p className="text-sm text-red-600">{sessionError}</p>}
          <button
            disabled={openingSession}
            className="min-h-14 w-full rounded-xl bg-[#2D6A4F] font-semibold text-white disabled:opacity-50"
          >
            {openingSession ? 'Abriendo…' : 'Abrir caja y comenzar'}
          </button>
        </form>
      </div>
    );
  }

  return (
    <div className="flex h-screen" style={{ '--color-primary': '#2D6A4F' } as React.CSSProperties}>
      {!isSelfpayMode && showSidebar && (
      <aside
        className="hidden w-[380px] shrink-0 flex-col border-r bg-gray-50 p-3 lg:flex"
        aria-label="Pedidos"
      >
        {/* Tab buttons */}
        <div className="mb-3 flex gap-1 rounded-xl bg-white p-1 shadow-sm">
          <button
            type="button"
            onClick={() => { setSalesTab('pending'); void loadPendingSales(); }}
            className={`flex-1 rounded-lg py-2 text-sm font-bold transition-colors ${
              salesTab === 'pending'
                ? 'bg-[#2D6A4F] text-white'
                : 'text-gray-600 hover:bg-gray-100'
            }`}
          >
            Pendientes
            {pendingSales.length > 0 && (
              <span className={`ml-1 rounded-full px-1.5 py-0.5 text-xs ${
                salesTab === 'pending' ? 'bg-white/20 text-white' : 'bg-amber-100 text-amber-700'
              }`}>
                {pendingSales.length}
              </span>
            )}
          </button>
          <button
            type="button"
            onClick={() => { setSalesTab('day'); void loadTodaySales(); }}
            className={`flex-1 rounded-lg py-2 text-sm font-bold transition-colors ${
              salesTab === 'day'
                ? 'bg-[#2D6A4F] text-white'
                : 'text-gray-600 hover:bg-gray-100'
            }`}
          >
            Día
            {todaySales.length > 0 && (
              <span className={`ml-1 rounded-full px-1.5 py-0.5 text-xs ${
                salesTab === 'day' ? 'bg-white/20 text-white' : 'bg-green-100 text-green-700'
              }`}>
                {todaySales.length}
              </span>
            )}
          </button>
        </div>

        {/* Pending tab */}
        {salesTab === 'pending' && (
          <>
            <div className="mb-3 flex items-center justify-between">
              <p className="text-xs text-gray-500">Ventas con saldo pendiente.</p>
              <button
                type="button"
                onClick={() => void loadPendingSales()}
                disabled={loadingPending}
                title="Actualizar"
                className="rounded-lg p-1.5 text-gray-400 hover:bg-gray-100 hover:text-gray-600 disabled:opacity-40"
              >
                <svg className={loadingPending ? 'animate-spin' : ''} width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
                  <path d="M23 4v6h-6M1 20v-6h6" strokeLinecap="round" strokeLinejoin="round"/>
                  <path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15" strokeLinecap="round" strokeLinejoin="round"/>
                </svg>
              </button>
            </div>
            {loadingPending ? (
              <p className="text-xs text-gray-500">Cargando…</p>
            ) : pendingSales.length === 0 ? (
              <p className="text-xs text-gray-500">Sin ventas pendientes.</p>
            ) : (
              <ul className="flex flex-1 flex-col gap-2 overflow-y-auto">
                {pendingSales.map((sale) => {
                  const saleDate = new Date(sale.createdAt);
                  const dateStr = saleDate.toLocaleDateString('es-ES', { day: '2-digit', month: '2-digit' });
                  const timeStr = saleDate.toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' });
                  const displayName = sale.posLabel || sale.receiptNumber || sale.id.slice(0, 8);
                  return (
                  <li
                    key={sale.id}
                    className="rounded-xl border border-amber-200 bg-white p-3 text-sm shadow-sm"
                  >
                    <div className="flex items-start justify-between">
                      <div>
                        <p className="font-semibold text-gray-800">{displayName}</p>
                        <p className="text-xs text-gray-400">
                          {dateStr} {timeStr}
                        </p>
                      </div>
                      <div className="text-right">
                        <p className="font-bold text-gray-800">{formatPrice(sale.totalCents)}</p>
                        <p className="text-xs font-medium text-amber-600">
                          Pdo: {formatPrice(sale.outstandingCents)}
                        </p>
                      </div>
                    </div>
                    <div className="mt-2 flex gap-1">
                      <button
                        type="button"
                        onClick={() => void recoverSale(sale)}
                        disabled={recoveringSaleId === sale.id}
                        className="flex-1 rounded-lg bg-[#2D6A4F] px-2 py-1.5 text-xs font-bold text-white disabled:opacity-50"
                      >
                        {recoveringSaleId === sale.id ? '...' : 'Recuperar'}
                      </button>
                      <button
                        type="button"
                        onClick={() => void reprintSale(sale)}
                        disabled={printingSaleId === sale.id}
                        className="flex items-center justify-center rounded-lg bg-blue-50 px-2 py-1.5 text-xs font-bold text-blue-600 hover:bg-blue-100 disabled:opacity-50"
                        title="Reimprimir ticket"
                      >
                        {printingSaleId === sale.id ? '...' : '🖨'}
                      </button>
                      <button
                        type="button"
                        onClick={() => setDeleteConfirmFor(sale)}
                        className="flex items-center justify-center rounded-lg bg-red-50 px-2 py-1.5 text-xs font-bold text-red-600 hover:bg-red-100"
                        title="Eliminar"
                      >
                        🗑
                      </button>
                    </div>
                  </li>
                  );
                })}
              </ul>
            )}
          </>
        )}

        {/* Today's sales tab */}
        {salesTab === 'day' && (
          <>
            <div className="mb-3 flex items-center justify-between">
              <p className="text-xs text-gray-500">Ventas completadas hoy.</p>
              <button
                type="button"
                onClick={() => void loadTodaySales()}
                title="Actualizar"
                className="rounded-lg p-1.5 text-gray-400 hover:bg-gray-100 hover:text-gray-600"
              >
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
                  <path d="M23 4v6h-6M1 20v-6h6" strokeLinecap="round" strokeLinejoin="round"/>
                  <path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15" strokeLinecap="round" strokeLinejoin="round"/>
                </svg>
              </button>
            </div>
            {todaySales.length === 0 ? (
              <p className="text-xs text-gray-500">Sin ventas hoy.</p>
            ) : (
              <ul className="flex flex-1 flex-col gap-2 overflow-y-auto">
                {todaySales
                  .filter((s) => s.state !== 'PENDING')
                  .map((sale) => {
                    const saleDate = new Date(sale.createdAt);
                    const timeStr = saleDate.toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' });
                    const displayName = sale.posLabel || sale.receiptNumber || sale.id.slice(0, 8);
                    const state = sale.state as string;
                    const isCompleted = state === 'COMPLETED';
                    const isCancelled = state === 'CANCELLED';
                    const isRefunded = state === 'REFUNDED' || state === 'PARTIAL_REFUNDED';
                    const stateStyles = isCancelled
                      ? 'border-red-200 bg-red-50'
                      : isRefunded
                        ? 'border-purple-200 bg-purple-50'
                        : 'border-green-200 bg-green-50';
                    const refundLabel = state === 'REFUNDED' ? '✕ Reintegrada' :
                      state === 'PARTIAL_REFUNDED' ? '↩ Reintegrada parcialmente' : '';
                    return (
                    <li
                      key={sale.id}
                      className={`rounded-xl border p-3 text-sm shadow-sm ${stateStyles}`}
                    >
                      <div className="flex items-start justify-between">
                        <div>
                          <p className="font-semibold text-gray-800">{displayName}</p>
                          <p className="text-xs text-gray-400">{timeStr}</p>
                        </div>
                        <div className="text-right">
                          <p className="font-bold text-gray-800">{formatPrice(sale.totalCents)}</p>
                          <p className={`text-xs font-medium ${
                            isCancelled ? 'text-red-600' :
                            isRefunded ? 'text-purple-600' : 'text-gray-500'
                          }`}>
                            {isCompleted ? '✓ Completada' :
                             isCancelled ? '✕ Cancelada' :
                             isRefunded ? refundLabel : sale.state}
                          </p>
                        </div>
                      </div>
                      {isCompleted && (
                      <div className="mt-2 flex gap-1">
                        <button
                          type="button"
                          onClick={() => void reprintSale(sale)}
                          disabled={printingSaleId === sale.id}
                          className="flex flex-1 items-center justify-center gap-1 rounded-lg bg-blue-50 px-2 py-1.5 text-xs font-bold text-blue-600 hover:bg-blue-100 disabled:opacity-50"
                        >
                          {printingSaleId === sale.id ? '...' : '🖨'} Reimprimir
                        </button>
                        <button
                          type="button"
                          onClick={async () => {
                            try {
                              const result = await posApi.getReceipt<{ receipt: PosReceipt }>(sale.id);
                              setReturnOrder({ orderId: sale.id, receipt: result.receipt });
                            } catch {
                              setError('No se pudo cargar el ticket para reintegrar');
                            }
                          }}
                          className="flex items-center justify-center rounded-lg bg-amber-50 px-2 py-1.5 text-xs font-bold text-amber-600 hover:bg-amber-100"
                          title="Reintegrar"
                        >
                          ↩ Reintegrar
                        </button>
                      </div>
                      )}
                      {isRefunded && (
                      <div className="mt-2 flex gap-1">
                        <button
                          type="button"
                          onClick={() => void reprintSale(sale)}
                          disabled={printingSaleId === sale.id}
                          className="flex flex-1 items-center justify-center gap-1 rounded-lg bg-blue-50 px-2 py-1.5 text-xs font-bold text-blue-600 hover:bg-blue-100 disabled:opacity-50"
                        >
                          {printingSaleId === sale.id ? '...' : '🖨'} Ver ticket
                        </button>
                        <button
                          type="button"
                          onClick={async () => {
                            try {
                              const [receiptResult, itemsResult] = await Promise.all([
                                posApi.getReceipt<{ receipt: PosReceipt }>(sale.id),
                                posApi.listOrderItems<{ items: RecoveredOrderItem[] }>(sale.id),
                              ]);
                              // Show which items were returned
                              const returnedItems = itemsResult.items.filter((i) => i.returnedQuantity > 0);
                              if (returnedItems.length === 0) {
                                setError('No hay artículos reintegrados en este ticket');
                              } else {
                                setReturnOrder({ orderId: sale.id, receipt: receiptResult.receipt });
                              }
                            } catch {
                              setError('No se pudo cargar el detalle del reintegro');
                            }
                          }}
                          className="flex items-center justify-center rounded-lg bg-purple-50 px-2 py-1.5 text-xs font-bold text-purple-600 hover:bg-purple-100"
                          title="Ver reintegros"
                        >
                          📋 Ver reintegrado
                        </button>
                      </div>
                      )}
                      {isCancelled && (
                      <p className="mt-2 text-xs text-red-500">Ticket cancelado — no imprimible</p>
                      )}
                    </li>
                    );
                  })}
              </ul>
            )}
          </>
        )}
      </aside>
      )}
      {/* POS-FIX-4: product added toast */}
      {addedToast && (
        <div className="pointer-events-none fixed top-6 right-6 z-50 animate-in slide-in-from-top-2 fade-in duration-200">
          <div className="flex items-center gap-2 rounded-xl bg-green-600 px-4 py-3 text-sm font-bold text-white shadow-lg">
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.5}>
              <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7"/>
            </svg>
            {addedToast}
          </div>
        </div>
      )}

      <main className="flex min-w-0 flex-1 flex-col overflow-hidden border-r p-4">
        <div className="mb-3">
          <div className="mb-2 flex items-center gap-3">
            {/* Panel toggle button - LEFT */}
            {!isSelfpayMode && (
              <button
                type="button"
                onClick={() => setShowSidebar((v) => !v)}
                title={showSidebar ? 'Ocultar panel' : 'Mostrar panel'}
                className="flex items-center gap-1.5 rounded-lg border border-gray-300 bg-white px-2.5 py-1.5 text-xs font-medium text-gray-600 hover:bg-gray-50 transition-colors"
              >
                <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
                  <path strokeLinecap="round" strokeLinejoin="round" d={showSidebar ? "M4 6h16M4 12h16M4 18h7" : "M4 6h16M4 12h16M4 18h16"} />
                </svg>
                {showSidebar ? 'Ocultar' : 'Panel'}
              </button>
            )}

            {/* Store name */}
            <span className="text-sm font-bold text-[#2D6A4F]">{config.store.name}</span>

            {/* Status badge */}
            <span className="flex items-center gap-1 rounded-full bg-green-100 px-2 py-0.5 text-xs text-green-700">
              <span className="relative flex h-2 w-2">
                <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
                <span className="relative inline-flex rounded-full h-2 w-2 bg-green-500"></span>
              </span>
              Caja abierta
            </span>

            {/* Clock */}
            <span className="ml-auto flex items-center gap-1.5 rounded-lg bg-white px-3 py-1.5 text-xs font-medium text-gray-700 border border-gray-200">
              <svg className="w-4 h-4 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
                <path strokeLinecap="round" strokeLinejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
              </svg>
              {currentTime.toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit', second: '2-digit' })}
              <span className="text-gray-400">·</span>
              {currentTime.toLocaleDateString('es-ES', { day: '2-digit', month: 'short', year: 'numeric' })}
            </span>

            {!isSelfpayMode && (
              <button
                type="button"
                onClick={() => {
                  setClosingActualCash('');
                  setClosingPin('');
                  setClosingPinError('');
                  setClosingPinStep(false);
                  setCloseError('');
                  setShowCloseSession(true);
                }}
                className="flex items-center gap-1.5 rounded-lg border border-red-200 bg-red-50 px-3 py-1.5 text-xs font-bold text-red-600 hover:bg-red-100 transition-colors"
              >
                <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
                  <path strokeLinecap="round" strokeLinejoin="round" d="M9 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
                </svg>
                Cerrar caja
              </button>
            )}
          </div>
          <div className="flex gap-2">
            <input
              value={search}
              onChange={(event) => setSearch(event.target.value)}
              onKeyDown={(event) => {
                if (event.key === 'Enter') {
                  event.preventDefault();
                  void addSearchResultWithEnter();
                }
              }}
              placeholder="Buscar producto o escanear EAN…"
              className="min-w-0 flex-1 rounded-xl border-2 border-gray-200 px-4 py-3 text-lg outline-none focus:border-[#2D6A4F]"
              autoFocus
            />
            <button
              type="button"
              onClick={() => setShowFreeItem(true)}
              className="min-h-12 rounded-xl border-2 border-[#2D6A4F] px-4 font-bold text-[#2D6A4F]"
            >
              ＋ Artículo libre
            </button>
          </div>
        </div>

        {!search.trim() && touchCatalog?.enabled && (
          <div className="flex-1 overflow-y-auto pb-3">
            <div className="mb-3 flex items-center gap-2">
              <button
                type="button"
                disabled={categoryPath.length === 0}
                onClick={() => setCategoryPath((path) => path.slice(0, -1))}
                className="min-h-12 rounded-xl border px-4 font-semibold disabled:opacity-30"
              >
                ← Atrás
              </button>
              <button
                type="button"
                onClick={() => setCategoryPath([])}
                className="font-semibold text-[#2D6A4F]"
              >
                Categorías
              </button>
              {categoryPath.map((category, index) => (
                <span key={category.id} className="text-sm text-gray-500">
                  ›{' '}
                  <button
                    type="button"
                    onClick={() => setCategoryPath((path) => path.slice(0, index + 1))}
                  >
                    {category.name}
                  </button>
                </span>
              ))}
            </div>
            <div className="grid grid-cols-2 gap-3 xl:grid-cols-3">
              {visibleCategories.map((category) => (
                <button
                  key={category.id}
                  type="button"
                  onClick={() => setCategoryPath((path) => [...path, category])}
                  className="min-h-24 rounded-2xl border border-black/5 p-4 text-left text-lg font-bold shadow-sm active:scale-95"
                  style={{
                    backgroundColor: category.bgColor || '#eef7e8',
                    color: category.textColor || '#2D6A4F',
                  }}
                >
                  <span className="mr-2 text-2xl">{category.emoji || '📁'}</span>
                  {category.name}
                </button>
              ))}
              {visibleTouchProducts.map((product) => (
                <button
                  key={`${currentCategory?.id}-${product.variantId}`}
                  type="button"
                  onClick={() => addToCart(product)}
                  className="min-h-24 rounded-2xl border-2 border-gray-100 bg-white p-4 text-left shadow-sm active:scale-95"
                >
                  <span className="block font-bold">{product.name}</span>
                  <span className="mt-2 flex justify-between text-sm">
                    <span>{product.stock} uds</span>
                    <strong className="text-[#2D6A4F]">{formatPrice(product.priceCents)}</strong>
                  </span>
                </button>
              ))}
            </div>
          </div>
        )}

        {searchResults.length > 0 && (
          <div className="flex-1 space-y-2 overflow-y-auto">
            {searchResults.map((product) => (
              <button
                key={product.variantId}
                onClick={() => addToCart(product)}
                className="flex w-full items-center justify-between rounded-xl bg-white p-3 text-left shadow-sm"
              >
                <div>
                  <p className="font-medium">{product.name}</p>
                  <p className="text-xs text-gray-400">
                    {product.sku}
                    {product.ean ? ` · ${product.ean}` : ''}
                  </p>
                </div>
                <div className="text-right">
                  <p className="font-bold text-[#2D6A4F]">{formatPrice(product.priceCents)}</p>
                  <p
                    className={
                      product.stock > 0 ? 'text-xs text-green-600' : 'text-xs text-red-500'
                    }
                  >
                    {product.stock > 0 ? `${product.stock} uds` : 'Sin stock'}
                  </p>
                </div>
              </button>
            ))}
          </div>
        )}
        {searching && <p className="py-4 text-center text-gray-400">Buscando…</p>}
        {searchError && <p className="py-4 text-center text-red-600">{searchError}</p>}

        {!search.trim() && touchCatalog?.enabled && (
          <div
            className="grid shrink-0 grid-cols-4 gap-2 border-t pt-3"
            aria-label="Productos rápidos"
          >
            {Array.from({ length: 8 }, (_, slot) => touchCatalog.quickProducts[slot] ?? null).map(
              (product, slot) =>
                product ? (
                  <button
                    key={`${slot}-${product.variantId}`}
                    type="button"
                    onClick={() => addToCart(product)}
                    className="min-h-20 rounded-xl bg-[#2D6A4F] px-2 py-2 text-sm font-bold text-white shadow active:scale-95"
                  >
                    <span className="line-clamp-2">{product.name}</span>
                    <span className="mt-1 block text-xs text-white/80">
                      {formatPrice(product.priceCents)}
                    </span>
                  </button>
                ) : (
                  <div
                    key={slot}
                    className="flex min-h-20 items-center justify-center rounded-xl border-2 border-dashed text-xs text-gray-400"
                  >
                    Rápido {slot + 1}
                  </div>
                ),
            )}
          </div>
        )}
      </main>

      <aside className="flex w-[440px] flex-col bg-gray-50 p-4">
        <div className="mb-3 flex items-center justify-between">
          <h2 className="text-lg font-bold text-[#2D6A4F]">Cashier</h2>
          {customer ? (
            <button
              type="button"
              onClick={() => setCustomer(null)}
              className="min-h-10 rounded-full bg-blue-100 px-3 text-xs text-blue-700"
            >
              {customer.displayName || customer.email} ✕
            </button>
          ) : (
            <button
              type="button"
              onClick={() => setShowCustomerSearch((value) => !value)}
              className="min-h-10 rounded-xl px-3 text-sm font-semibold text-blue-600"
            >
              ＋ Cliente
            </button>
          )}
        </div>

        {showCustomerSearch && (
          <div className="mb-3 space-y-3 rounded-xl bg-white p-3 shadow">
            <div className="flex rounded-lg bg-gray-100 p-1 text-xs font-medium">
              <button
                type="button"
                onClick={() => setCustomerMode('search')}
                className={`flex-1 rounded-md py-2 ${customerMode === 'search' ? 'bg-white text-[#2D6A4F] shadow-sm' : ''}`}
              >
                Buscar
              </button>
              <button
                type="button"
                onClick={() => setCustomerMode('create')}
                className={`flex-1 rounded-md py-2 ${customerMode === 'create' ? 'bg-white text-[#2D6A4F] shadow-sm' : ''}`}
              >
                Crear
              </button>
            </div>
            {customerMode === 'search' ? (
              <>
                <input
                  type="search"
                  value={customerQuery}
                  onChange={(event) => {
                    void searchCustomers(event.target.value);
                  }}
                  placeholder="Email, nombre o teléfono…"
                  className="min-h-11 w-full rounded-lg border px-3"
                />
                <div className="max-h-32 overflow-y-auto">
                  {customerResults.map((result) => (
                    <button
                      key={result.id}
                      type="button"
                      onClick={() => {
                        setCustomer(result);
                        setShowCustomerSearch(false);
                      }}
                      className="w-full rounded-lg p-2 text-left text-sm hover:bg-blue-50"
                    >
                      <strong className="block">{result.displayName || result.email}</strong>
                      {result.displayName && (
                        <span className="text-xs text-gray-500">{result.email}</span>
                      )}
                    </button>
                  ))}
                </div>
              </>
            ) : (
              <form onSubmit={createCustomer} className="space-y-2">
                <input
                  type="email"
                  required
                  value={newCustomer.email}
                  onChange={(event) =>
                    setNewCustomer({ ...newCustomer, email: event.target.value })
                  }
                  placeholder="Email *"
                  className="min-h-11 w-full rounded-lg border px-3"
                />
                <input
                  value={newCustomer.displayName}
                  onChange={(event) =>
                    setNewCustomer({ ...newCustomer, displayName: event.target.value })
                  }
                  placeholder="Nombre"
                  className="min-h-11 w-full rounded-lg border px-3"
                />
                <input
                  value={newCustomer.phone}
                  onChange={(event) =>
                    setNewCustomer({ ...newCustomer, phone: event.target.value })
                  }
                  placeholder="Teléfono"
                  className="min-h-11 w-full rounded-lg border px-3"
                />
                <button
                  disabled={creatingCustomer}
                  className="min-h-11 w-full rounded-lg bg-[#2D6A4F] font-semibold text-white"
                >
                  {creatingCustomer ? 'Creando…' : 'Crear y asociar'}
                </button>
              </form>
            )}
            {customerError && <p className="text-xs text-red-600">{customerError}</p>}
          </div>
        )}

        <div className="flex-1 space-y-2 overflow-y-auto">
          {cart.length === 0 && (
            <p className="py-12 text-center text-gray-400">Añade productos o un artículo libre.</p>
          )}
          {cart.map((item) => (
            <div key={item.lineId} className="rounded-xl bg-white p-3 shadow-sm">
              <div className="flex items-start justify-between">
                <div>
                  <p className="text-sm font-medium">{item.name}</p>
                  <p className="text-xs text-gray-400">
                    {formatPrice(item.unitPriceCents - item.discountCents)}/ud{' '}
                    {item.kind === 'free' ? '· libre' : ''}
                  </p>
                </div>
                <button
                  type="button"
                  onClick={() => removeFromCart(item.lineId)}
                  className="min-h-10 min-w-10 rounded-lg text-gray-400 hover:bg-red-50 hover:text-red-600"
                  aria-label={`Eliminar ${item.name}`}
                >
                  ✕
                </button>
              </div>
              <div className="mt-2 flex items-center justify-between">
                <div className="flex items-center gap-2">
                  <button
                    type="button"
                    onClick={() => updateQuantity(item.lineId, item.quantity - 1)}
                    className="min-h-10 min-w-10 rounded-lg bg-gray-100 text-lg font-bold"
                  >
                    −
                  </button>
                  <span className="w-7 text-center font-medium">{item.quantity}</span>
                  <button
                    type="button"
                    onClick={() => updateQuantity(item.lineId, item.quantity + 1)}
                    className="min-h-10 min-w-10 rounded-lg bg-gray-100 text-lg font-bold"
                  >
                    ＋
                  </button>
                </div>
                <div className="flex items-center gap-2">
                  {lineDiscountsEnabled && item.kind === 'stock' && (
                    <button
                      type="button"
                      onClick={() => {
                        setSelectedItem(item);
                        setShowDiscountPanel(true);
                      }}
                      className={`min-h-10 rounded-lg px-3 text-xs font-bold ${item.discountCents > 0 ? 'bg-red-100 text-red-700' : 'bg-gray-100 text-gray-700'}`}
                    >
                      {item.discountCents > 0
                        ? `Dto. −${formatPrice(item.discountCents)}`
                        : 'Descuento'}
                    </button>
                  )}
                  <strong>
                    {formatPrice(
                      (item.unitPriceCents - item.discountCents + item.taxCents) * item.quantity,
                    )}
                  </strong>
                </div>
              </div>
            </div>
          ))}
        </div>

        <div className="mt-3 space-y-1 border-t pt-3 text-sm">
          <div className="flex justify-between">
            <span>Subtotal</span>
            <span>{formatPrice(totals.subtotal)}</span>
          </div>
          {!isSelfpayMode && totals.discount > 0 && (
            <div className="flex justify-between text-red-600">
              <span>Descuentos</span>
              <span>−{formatPrice(totals.discount)}</span>
            </div>
          )}
          <div className="flex justify-between">
            <span>IVA</span>
            <span>{formatPrice(totals.tax)}</span>
          </div>
          <div className="flex justify-between text-xl font-bold">
            <span>Total</span>
            <span className="text-[#2D6A4F]">{formatPrice(totals.total)}</span>
          </div>
        </div>

        {payments.length > 0 && (
          <section className="mt-3 rounded-xl border border-blue-200 bg-blue-50 p-3">
            <div className="mb-2 flex justify-between text-sm font-bold text-blue-900">
              <span>Pagado {formatPrice(paidCents)}</span>
              <span>Pendiente {formatPrice(remainingCents)}</span>
            </div>
            {payments.map((payment) => (
              <div
                key={payment.id}
                className="flex items-center justify-between border-t border-blue-100 py-2 text-sm"
              >
                <span>
                  {payment.methodLabel}
                  {payment.changeCents > 0 ? ` · cambio ${formatPrice(payment.changeCents)}` : ''}
                </span>
                <span className="flex items-center gap-2">
                  <strong>{formatPrice(payment.amountCents)}</strong>
                  <button
                    type="button"
                    onClick={() =>
                      setPayments((current) => current.filter((item) => item.id !== payment.id))
                    }
                    className="min-h-9 min-w-9 rounded-lg bg-white text-red-600"
                    aria-label={`Quitar pago ${payment.methodLabel}`}
                  >
                    ✕
                  </button>
                </span>
              </div>
            ))}
          </section>
        )}

        {error && (
          <p className="mt-2 text-center text-sm font-medium text-red-600" aria-live="polite">
            {error}
          </p>
        )}
        <div className="mt-3 grid grid-cols-2 gap-2">
          {config.paymentMethods.map((method) => (
            <button
              key={method.id}
              type="button"
              onClick={() => setPaymentMethod(method)}
              disabled={cart.length === 0 || processing || remainingCents === 0}
              className={`min-h-14 rounded-xl px-2 font-bold text-white disabled:opacity-40 ${method.kind === 'cash' ? 'bg-green-600' : method.kind === 'card' ? 'bg-blue-600' : 'bg-slate-700'}`}
            >
              {method.kind === 'cash' ? '💵' : method.kind === 'card' ? '💳' : '◉'} {method.label}
            </button>
          ))}
        </div>
        <div className="mt-3 grid grid-cols-2 gap-2">
          {!isSelfpayMode && (
            <button
              type="button"
              onClick={() => void parkSale()}
              disabled={processing || cart.length === 0}
              className="min-h-16 rounded-xl bg-amber-500 text-lg font-bold text-white disabled:opacity-40"
            >
              {processing ? 'Guardando…' : 'Guardar pendiente'}
            </button>
          )}
          <button
            type="button"
            onClick={() => void confirmSale()}
            disabled={
              processing ||
              cart.length === 0 ||
              payments.length === 0 ||
              paidCents <= 0 ||
              paidCents > totals.total
            }
            className={`min-h-16 rounded-xl bg-[#1B4332] text-lg font-bold text-white disabled:opacity-40 ${
              isSelfpayMode ? 'col-span-2' : ''
            }`}
          >
            {processing ? 'Confirmando…' : 'Cobrar e imprimir'}
          </button>
        </div>
        <button
          type="button"
          onClick={() => setShowClearConfirm(true)}
          disabled={processing || cart.length === 0}
          className="mt-2 w-full rounded-xl border border-gray-300 bg-white py-2 text-sm font-bold text-red-600 disabled:opacity-40"
        >
          {isSelfpayMode ? '🧹 Limpiar Ticket' : 'Vaciar caja'}
        </button>
      </aside>

      {!isSelfpayMode && showDiscountPanel && selectedItem && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/30">
          <DiscountPanel
            unitPriceCents={selectedItem.unitPriceCents}
            onApply={(discountCents) => {
              applyDiscount(selectedItem.lineId, discountCents);
              setShowDiscountPanel(false);
              setSelectedItem(null);
            }}
            onClose={() => {
              setShowDiscountPanel(false);
              setSelectedItem(null);
            }}
          />
        </div>
      )}
      {showFreeItem && <FreeItemModal onAdd={addFreeItem} onClose={() => setShowFreeItem(false)} />}
      {paymentMethod && (
        <PaymentModal
          method={paymentMethod}
          remainingCents={remainingCents}
          onAdd={addPayment}
          onClose={() => setPaymentMethod(null)}
        />
      )}
      {restPaymentFor && (
        <div
          className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4"
          role="dialog"
          aria-modal="true"
          aria-labelledby="rest-payment-title"
        >
          <div className="w-full max-w-md space-y-4 rounded-2xl bg-white p-6 shadow-2xl">
            <div className="flex items-start justify-between gap-4">
              <div>
                <p className="text-sm font-semibold text-amber-600">
                  Pendiente {formatPrice(restPaymentFor.outstandingCents)}
                </p>
                <h2 id="rest-payment-title" className="text-2xl font-bold text-gray-900">
                  Cobrar resto
                </h2>
                <p className="mt-1 text-xs text-gray-500">
                  Venta {restPaymentFor.receiptNumber ?? restPaymentFor.id.slice(0, 8)}
                </p>
              </div>
              <button
                type="button"
                onClick={() => {
                  if (!processingRest) setRestPaymentFor(null);
                }}
                aria-label="Cerrar"
                className="text-2xl text-gray-400 hover:text-gray-700"
              >
                ✕
              </button>
            </div>
            <div className="grid grid-cols-2 gap-2">
              {config.paymentMethods.map((method) => (
                <button
                  key={method.id}
                  type="button"
                  onClick={() => setPaymentMethod(method)}
                  disabled={processingRest}
                  className={`min-h-14 rounded-xl px-2 font-bold text-white disabled:opacity-40 ${method.kind === 'cash' ? 'bg-green-600' : method.kind === 'card' ? 'bg-blue-600' : 'bg-slate-700'}`}
                >
                  {method.kind === 'cash' ? '💵' : method.kind === 'card' ? '💳' : '◉'} {method.label}
                </button>
              ))}
            </div>
            {error && <p className="text-sm text-red-600">{error}</p>}
          </div>
        </div>
      )}
      {paymentMethod && restPaymentFor && (
        <PaymentModal
          method={paymentMethod}
          remainingCents={restPaymentFor.outstandingCents}
          onAdd={(allocation) => {
            void submitRestPayment(allocation);
          }}
          onClose={() => setPaymentMethod(null)}
        />
      )}
      {/* POS-FIX-7: confirmar antes de vaciar */}
      {showClearConfirm && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
          <div className="w-full max-w-xs rounded-2xl bg-white p-6 shadow-xl">
            <h2 className="mb-2 text-lg font-bold text-gray-900">¿Vaciar ticket?</h2>
            <p className="mb-4 text-sm text-gray-600">
              Se borrarán {cart.length} {cart.length === 1 ? 'artículo' : 'artículos'} del ticket actual.
              Esta acción no se puede deshacer.
            </p>
            <div className="flex gap-2">
              <button
                type="button"
                onClick={() => setShowClearConfirm(false)}
                className="flex-1 rounded-xl border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50"
              >
                Cancelar
              </button>
              <button
                type="button"
                onClick={() => {
                  setShowClearConfirm(false);
                  resetCashier();
                }}
                className="flex-1 rounded-xl bg-red-600 px-4 py-2 text-sm font-bold text-white hover:bg-red-700"
              >
                Vaciar
              </button>
            </div>
          </div>
        </div>
      )}

      {/* POS-FIX-11: name dialog when parking without customer */}
      {pendingParkName !== null && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
          <div className="w-full max-w-xs rounded-2xl bg-white p-6 shadow-xl">
            <h2 className="mb-2 text-lg font-bold text-gray-900">Nombre para identificar</h2>
            <p className="mb-4 text-sm text-gray-500">
              ¿A nombre de quién es este ticket pendiente?
            </p>
            <input
              type="text"
              value={pendingParkName}
              onChange={(e) => setPendingParkName(e.target.value)}
              placeholder="Ej: María García"
              maxLength={100}
              className="mb-4 w-full rounded-xl border border-gray-300 px-3 py-2 text-sm outline-none focus:border-[#2D6A4F]"
              autoFocus
              onKeyDown={(e) => {
                if (e.key === 'Enter' && pendingParkName.trim()) {
                  void doParkSale(pendingParkName.trim());
                }
              }}
            />
            <div className="flex gap-2">
              <button
                type="button"
                onClick={() => setPendingParkName(null)}
                className="flex-1 rounded-xl border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50"
              >
                Cancelar
              </button>
              <button
                type="button"
                onClick={() => {
                  if (pendingParkName.trim()) {
                    void doParkSale(pendingParkName.trim());
                  }
                }}
                disabled={!pendingParkName.trim()}
                className="flex-1 rounded-xl bg-[#2D6A4F] px-4 py-2 text-sm font-bold text-white disabled:opacity-50"
              >
                Guardar
              </button>
            </div>
          </div>
        </div>
      )}

      {/* POS-FIX-8: delete confirmation for pending sale */}
      {deleteConfirmFor && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
          <div className="w-full max-w-xs rounded-2xl bg-white p-6 shadow-xl">
            <h2 className="mb-2 text-lg font-bold text-gray-900">¿Eliminar venta?</h2>
            <p className="mb-1 text-sm text-gray-600">
              Ticket {deleteConfirmFor.receiptNumber ?? deleteConfirmFor.id.slice(0, 8)}
            </p>
            <p className="mb-4 text-sm text-gray-500">
              Total {formatPrice(deleteConfirmFor.totalCents)} · pendiente {formatPrice(deleteConfirmFor.outstandingCents)}
            </p>
            <p className="mb-4 text-xs text-amber-600">
              ⚠️ Se liberará el stock reservado y no se podrá deshacer.
            </p>
            <div className="flex gap-2">
              <button
                type="button"
                onClick={() => setDeleteConfirmFor(null)}
                className="flex-1 rounded-xl border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50"
              >
                Cancelar
              </button>
              <button
                type="button"
                onClick={() => void deleteSale(deleteConfirmFor)}
                className="flex-1 rounded-xl bg-red-600 px-4 py-2 text-sm font-bold text-white hover:bg-red-700"
              >
                Eliminar
              </button>
            </div>
            {error && <p className="mt-2 text-center text-xs text-red-600">{error}</p>}
          </div>
        </div>
      )}

      {/* POS-FIX-9: merge dialog when recovering a sale with items in cart */}
      {mergePendingSale && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
          <div className="w-full max-w-xs rounded-2xl bg-white p-6 shadow-xl">
            <h2 className="mb-2 text-lg font-bold text-gray-900">Ticket con artículos</h2>
            <p className="mb-1 text-sm text-gray-600">
              Hay {cart.length} {cart.length === 1 ? 'artículo' : 'artículos'} en el ticket actual.
            </p>
            <p className="mb-4 text-sm text-gray-500">
              ¿Qué deseas hacer con la venta {mergePendingSale.receiptNumber ?? mergePendingSale.id.slice(0, 8)}?
            </p>
            <div className="flex flex-col gap-2">
              <button
                type="button"
                onClick={() => {
                  setError('');
                  void parkAndRecover().catch((err) => setError(err instanceof Error ? err.message : String(err)));
                }}
                disabled={recoveringSaleId !== null}
                className="flex-1 rounded-xl border border-amber-300 bg-amber-50 px-4 py-2.5 text-sm font-bold text-amber-700 hover:bg-amber-100 disabled:opacity-50"
              >
                💾 Dejar actual pendiente y recuperar
              </button>
              <button
                type="button"
                onClick={() => {
                  setError('');
                  void mergeRecoveredItems().catch((err) => setError(err instanceof Error ? err.message : String(err)));
                }}
                disabled={recoveringSaleId !== null}
                className="flex-1 rounded-xl border border-green-300 bg-green-50 px-4 py-2.5 text-sm font-bold text-green-700 hover:bg-green-100 disabled:opacity-50"
              >
                ➕ Añadir al ticket actual
              </button>
              <button
                type="button"
                onClick={() => {
                  setMergePendingSale(null);
                }}
                className="flex-1 rounded-xl border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50"
              >
                Cancelar
              </button>
            </div>
            {error && <p className="mt-2 text-center text-xs text-red-600">{error}</p>}
          </div>
        </div>
      )}

      {/* POS-FIX-3/POS-FIX-5: close session — 2-step: PIN then cash */}
      {showCloseSession && (
        (config?.terminal?.settings?.closeSessionRequiresPin && !closingPinStep) ? (
        // STEP 1: PIN
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
          <div className="w-full max-w-xs rounded-2xl bg-white p-6 shadow-xl">
            <h2 className="mb-2 text-lg font-bold text-gray-900">Cerrar caja</h2>
            <p className="mb-4 text-sm text-gray-500">Introduce tu PIN de cajero para autorizar el cierre.</p>
            <input
              type="password"
              inputMode="numeric"
              maxLength={6}
              value={closingPin}
              onChange={(e) => { setClosingPin(e.target.value); setClosingPinError(''); }}
              placeholder="PIN"
              className="mb-1 w-full rounded-xl border border-gray-300 px-3 py-2 text-center text-2xl tracking-widest outline-none focus:border-[#2D6A4F]"
              autoFocus
            />
            {closingPinError && <p className="mb-3 text-xs text-red-600">{closingPinError}</p>}
            <div className="flex gap-2">
              <button
                type="button"
                onClick={() => { setShowCloseSession(false); setClosingPinStep(false); }}
                className="flex-1 rounded-xl border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50"
              >
                Cancelar
              </button>
              <button
                type="button"
                onClick={() => void handleVerifyPin()}
                className="flex-1 rounded-xl bg-red-600 px-4 py-2 text-sm font-bold text-white hover:bg-red-700"
              >
                Confirmar PIN
              </button>
            </div>
          </div>
        </div>
        ) : (
        // STEP 2: cash amount
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
          <div className="w-full max-w-sm rounded-2xl bg-white p-6 shadow-xl">
            <h2 className="mb-4 text-lg font-bold text-gray-900">Cerrar caja</h2>
            <p className="mb-4 text-sm text-gray-600">
              Indica el efectivo real en caja para calcular la diferencia.
            </p>
            <label className="mb-1 block text-sm font-medium text-gray-700">
              Efectivo real (céntimos)
            </label>
            <input
              type="number"
              min="0"
              value={closingActualCash}
              onChange={(e) => setClosingActualCash(e.target.value)}
              className="mb-1 w-full rounded-xl border border-gray-300 px-3 py-2 text-lg outline-none focus:border-[#2D6A4F]"
              autoFocus
            />
            {closeError && <p className="mb-3 text-xs text-red-600">{closeError}</p>}
            <div className="flex gap-2">
              <button
                type="button"
                onClick={() => { setShowCloseSession(false); setClosingPinStep(false); }}
                disabled={closing}
                className="flex-1 rounded-xl border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:opacity-50"
              >
                Cancelar
              </button>
              <button
                type="button"
                onClick={() => void handleCloseSession()}
                disabled={closing}
                className="flex-1 rounded-xl bg-red-600 px-4 py-2 text-sm font-bold text-white hover:bg-red-700 disabled:opacity-50"
              >
                {closing ? 'Cerrando…' : 'Confirmar cierre'}
              </button>
            </div>
          </div>
        </div>
        )
      )}

      {receipt && (
        <ReceiptModal
          receipt={receipt}
          initialEmail={customer?.email ?? receipt.customerEmail ?? ''}
          onDelivered={resetCashier}
          onReturn={
            config?.session && config?.terminal
              ? (orderId, latest) => {
                  setReturnOrder({ orderId, receipt: latest });
                }
              : undefined
          }
        />
      )}
      {returnOrder && config?.session && config?.terminal && (
        <ReturnModal
          receipt={returnOrder.receipt}
          orderId={returnOrder.orderId}
          cashSessionId={config.session.id}
          terminalId={config.terminal.id}
          onClose={() => setReturnOrder(null)}
          onReturned={(latest) => {
            setReturnOrder(null);
            setReceipt(latest);
          }}
        />
      )}
    </div>
  );
}
