"use client";

import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useRef,
  useState,
  type ReactNode,
} from "react";
import type { Product } from "@/data/products";
import { useAuth } from "@/context/AuthContext";
import {
  CART_ID_KEY,
  addLineItem,
  cartSubtotal,
  createCart,
  deleteLineItem,
  isMissingCartError,
  mapCartItems,
  readCartSnapshot,
  clearCartSnapshot,
  retrieveCart,
  saveCartSnapshot,
  transferCartToCustomer,
  updateCartShipping,
  updateLineItem,
  friendlyCartError,
  type CartItem,
  type ShippingInput,
} from "@/lib/store-cart";
import { skirtLabel } from "@/lib/skirt-options";

export type { CartItem };

type CartContextValue = {
  items: CartItem[];
  count: number;
  subtotal: number;
  loading: boolean;
  adding: boolean;
  error: string | null;
  cartId: string | null;
  addItem: (
    product: Product,
    opts: { size: string; color: string; skirt?: string; qty?: number }
  ) => Promise<void>;
  removeItem: (lineItemId: string) => Promise<void>;
  /** Cambia cantidad absoluta (debounced + UI inmediata). */
  updateQty: (lineItemId: string, qty: number) => void;
  /** Suma/resta 1 de forma inmediata (recomendado para botones +/-). */
  bumpQty: (lineItemId: string, delta: number) => void;
  clear: () => Promise<void>;
  saveShipping: (input: ShippingInput) => Promise<void>;
  itemKey: (item: Pick<CartItem, "id">) => string;
};

const CartContext = createContext<CartContextValue | null>(null);

function findVariant(product: Product, size: string, color: string) {
  const variants = product.variants || [];
  const exact = variants.find((v) => v.size === size && v.color === color);
  if (exact) return exact;

  // Set Pareja: "Dama S · Caballero M"
  const couple = size.match(/^Dama\s+(\S+)\s+·\s+Caballero\s+(\S+)$/i);
  if (couple) {
    const [, dama, cab] = couple;
    const dual = variants.find(
      (v) =>
        v.sizeDama === dama &&
        v.sizeCaballero === cab &&
        v.color === color
    );
    if (dual) return dual;
  }

  const norm = (s: string) =>
    s
      .toLowerCase()
      .normalize("NFD")
      .replace(/\p{Diacritic}/gu, "");
  const c = norm(color);
  const base = c.includes("rojo")
    ? "rojo"
    : c.includes("blanco")
      ? "blanco"
      : c.includes("azul") || (c.includes("marino") && !c.includes("rojo"))
        ? "azul"
        : "";
  if (!base) return undefined;
  return variants.find(
    (v) => v.size === size && norm(v.color).includes(base)
  );
}

function totalOf(items: CartItem[]) {
  return items.reduce((n, i) => n + i.price * i.qty, 0);
}

export function CartProvider({ children }: { children: ReactNode }) {
  const { customer } = useAuth();
  const [cartId, setCartId] = useState<string | null>(null);
  const [items, setItems] = useState<CartItem[]>([]);
  const [subtotal, setSubtotal] = useState(0);
  const [loading, setLoading] = useState(true);
  const [adding, setAdding] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const cartIdRef = useRef<string | null>(null);
  const itemsRef = useRef<CartItem[]>([]);
  const ensuring = useRef<Promise<string> | null>(null);
  const transferred = useRef<string | null>(null);
  const qtyTarget = useRef<Record<string, number>>({});
  const qtyTimer = useRef<Record<string, ReturnType<typeof setTimeout>>>({});
  const qtySeq = useRef(0);

  useEffect(() => {
    itemsRef.current = items;
  }, [items]);

  const applySnapshot = useCallback(() => {
    const snap = readCartSnapshot();
    if (!snap) return false;
    setCartId(snap.cartId);
    cartIdRef.current = snap.cartId;
    setItems(snap.items);
    setSubtotal(snap.subtotal);
    return true;
  }, []);

  const applyCart = useCallback((cart: Awaited<ReturnType<typeof retrieveCart>>) => {
    const mapped = mapCartItems(cart);
    const total = cartSubtotal(cart, mapped);
    setCartId(cart.id);
    cartIdRef.current = cart.id;
    setItems(mapped);
    setSubtotal(total);
    saveCartSnapshot(cart.id, mapped, total);
  }, []);

  const applyLocalItems = useCallback((next: CartItem[]) => {
    itemsRef.current = next;
    setItems(next);
    setSubtotal(totalOf(next));
    const id = cartIdRef.current;
    if (id) saveCartSnapshot(id, next, totalOf(next));
  }, []);

  const ensureCartId = useCallback(async () => {
    if (cartIdRef.current) return cartIdRef.current;
    if (ensuring.current) return ensuring.current;
    ensuring.current = (async () => {
      const stored = window.localStorage.getItem(CART_ID_KEY);
      if (stored) {
        try {
          const cart = await retrieveCart(stored);
          applyCart(cart);
          return cart.id;
        } catch (error) {
          if (!isMissingCartError(error)) {
            cartIdRef.current = stored;
            applySnapshot();
            return stored;
          }
          window.localStorage.removeItem(CART_ID_KEY);
          clearCartSnapshot();
          cartIdRef.current = null;
        }
      }
      const cart = await createCart();
      applyCart(cart);
      return cart.id;
    })();
    try {
      return await ensuring.current;
    } finally {
      ensuring.current = null;
    }
  }, [applyCart, applySnapshot]);

  const flushQty = useCallback(
    async (lineItemId: string) => {
      const target = qtyTarget.current[lineItemId];
      if (target === undefined) return;
      const seq = ++qtySeq.current;
      setError(null);
      try {
        const id = cartIdRef.current || (await ensureCartId());
        if (target <= 0) {
          const cart = await deleteLineItem(id, lineItemId);
          if (seq === qtySeq.current) applyCart(cart);
          delete qtyTarget.current[lineItemId];
          return;
        }
        const cart = await updateLineItem(id, lineItemId, target);
        // Solo aplicar si no hubo clicks más nuevos
        if (seq === qtySeq.current && qtyTarget.current[lineItemId] === target) {
          applyCart(cart);
          delete qtyTarget.current[lineItemId];
        }
      } catch (err) {
        const message =
          err instanceof Error ? err.message : "No se pudo actualizar la cantidad.";
        setError(message);
        try {
          const id = cartIdRef.current;
          if (id) applyCart(await retrieveCart(id));
        } catch {
          /* ignore */
        }
      }
    },
    [applyCart, ensureCartId]
  );

  const scheduleQty = useCallback(
    (lineItemId: string, qty: number) => {
      qtyTarget.current[lineItemId] = qty;
      if (qtyTimer.current[lineItemId]) {
        clearTimeout(qtyTimer.current[lineItemId]);
      }
      qtyTimer.current[lineItemId] = setTimeout(() => {
        void flushQty(lineItemId);
      }, 250);
    },
    [flushQty]
  );

  const setOptimisticQty = useCallback(
    (lineItemId: string, qty: number) => {
      const current = itemsRef.current;
      let next: CartItem[];
      if (qty <= 0) {
        next = current.filter((i) => i.id !== lineItemId);
      } else {
        next = current.map((i) =>
          i.id === lineItemId ? { ...i, qty } : i
        );
      }
      applyLocalItems(next);
      scheduleQty(lineItemId, qty);
    },
    [applyLocalItems, scheduleQty]
  );

  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const stored = window.localStorage.getItem(CART_ID_KEY);
        if (!stored) {
          applySnapshot();
          return;
        }
        try {
          const cart = await retrieveCart(stored);
          if (!cancelled) applyCart(cart);
        } catch (error) {
          if (!cancelled) {
            if (isMissingCartError(error)) {
              window.localStorage.removeItem(CART_ID_KEY);
              clearCartSnapshot();
              cartIdRef.current = null;
              setCartId(null);
              setItems([]);
              setSubtotal(0);
            } else {
              applySnapshot();
            }
          }
        }
      } finally {
        if (!cancelled) setLoading(false);
      }
    })();
    return () => {
      cancelled = true;
      Object.values(qtyTimer.current).forEach(clearTimeout);
    };
  }, [applyCart, applySnapshot]);

  useEffect(() => {
    const id = cartIdRef.current;
    if (!customer || !id) return;
    const stamp = `${customer.id}:${id}`;
    if (transferred.current === stamp) return;
    transferred.current = stamp;
    transferCartToCustomer(id).then(applyCart).catch(() => undefined);
  }, [customer, cartId, applyCart]);

  const addItem = useCallback(
    async (
      product: Product,
      opts: { size: string; color: string; skirt?: string; qty?: number }
    ) => {
      setError(null);
      const variant = findVariant(product, opts.size, opts.color);
      if (!variant?.id) {
        throw new Error("Esa talla y color no están disponibles.");
      }
      const meta = opts.skirt
        ? {
            skirt_type: opts.skirt,
            skirt_label: skirtLabel(opts.skirt),
          }
        : undefined;
      setAdding(true);
      try {
        const id = await ensureCartId();
        try {
          const cart = await addLineItem(id, variant.id, opts.qty ?? 1, meta);
          applyCart(cart);
        } catch (err) {
          if (!isMissingCartError(err)) throw err;
          window.localStorage.removeItem(CART_ID_KEY);
          clearCartSnapshot();
          cartIdRef.current = null;
          setCartId(null);
          setItems([]);
          setSubtotal(0);
          const fresh = await createCart();
          applyCart(fresh);
          const cart = await addLineItem(
            fresh.id,
            variant.id,
            opts.qty ?? 1,
            meta
          );
          applyCart(cart);
        }
      } catch (err) {
        const message = friendlyCartError(
          err,
          "No se pudo agregar al carrito."
        );
        setError(message);
        throw new Error(message);
      } finally {
        setAdding(false);
      }
    },
    [applyCart, ensureCartId]
  );

  const removeItem = useCallback(
    async (lineItemId: string) => {
      setError(null);
      if (qtyTimer.current[lineItemId]) {
        clearTimeout(qtyTimer.current[lineItemId]);
        delete qtyTimer.current[lineItemId];
      }
      delete qtyTarget.current[lineItemId];
      const next = itemsRef.current.filter((i) => i.id !== lineItemId);
      applyLocalItems(next);
      try {
        const id = cartIdRef.current || (await ensureCartId());
        const cart = await deleteLineItem(id, lineItemId);
        applyCart(cart);
      } catch (err) {
        const message =
          err instanceof Error ? err.message : "No se pudo quitar el producto.";
        setError(message);
        try {
          const id = cartIdRef.current;
          if (id) applyCart(await retrieveCart(id));
        } catch {
          /* ignore */
        }
      }
    },
    [applyCart, applyLocalItems, ensureCartId]
  );

  const updateQty = useCallback(
    (lineItemId: string, qty: number) => {
      setOptimisticQty(lineItemId, Math.max(0, Math.floor(qty)));
    },
    [setOptimisticQty]
  );

  const bumpQty = useCallback(
    (lineItemId: string, delta: number) => {
      const current =
        qtyTarget.current[lineItemId] ??
        itemsRef.current.find((i) => i.id === lineItemId)?.qty ??
        0;
      setOptimisticQty(lineItemId, Math.max(0, current + delta));
    },
    [setOptimisticQty]
  );

  const clear = useCallback(async () => {
    Object.values(qtyTimer.current).forEach(clearTimeout);
    qtyTimer.current = {};
    qtyTarget.current = {};
    window.localStorage.removeItem(CART_ID_KEY);
    clearCartSnapshot();
    cartIdRef.current = null;
    setCartId(null);
    setItems([]);
    setSubtotal(0);
  }, []);

  const saveShipping = useCallback(
    async (input: ShippingInput) => {
      const id = cartIdRef.current;
      if (!id) throw new Error("No hay un carrito activo.");
      const cart = await updateCartShipping(id, input);
      applyCart(cart);
    },
    [applyCart]
  );

  const value = useMemo<CartContextValue>(
    () => ({
      items,
      count: items.reduce((n, i) => n + i.qty, 0),
      subtotal,
      loading,
      adding,
      error,
      cartId,
      itemKey: (item) => item.id,
      addItem,
      removeItem,
      updateQty,
      bumpQty,
      clear,
      saveShipping,
    }),
    [
      items,
      subtotal,
      loading,
      adding,
      error,
      cartId,
      addItem,
      removeItem,
      updateQty,
      bumpQty,
      clear,
      saveShipping,
    ]
  );

  return <CartContext.Provider value={value}>{children}</CartContext.Provider>;
}

export function useCart() {
  const ctx = useContext(CartContext);
  if (!ctx) throw new Error("useCart must be used within CartProvider");
  return ctx;
}
