"use client";

import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useReducer,
  useState,
  type ReactNode,
} from "react";

import type { Product } from "@/types/product";

/**
 * Frontend-only cart.
 *
 * State lives in React context and is mirrored to `localStorage` so the cart
 * survives a reload. Product snapshots (name, price, image) are stored
 * alongside the id so the cart renders without waiting for the catalogue, and
 * they are reconciled against `/api/products` on start-up.
 */

const STORAGE_KEY = "apnabazzar.cart.v1";
const MAX_PER_LINE = 99;

export interface CartLine {
  productId: number;
  name: string;
  price: number;
  image: string;
  brand: string;
  /** Quantity the product had in stock when it was added. */
  maxStock: number;
  quantity: number;
}

interface CartState {
  lines: CartLine[];
  /** Product added most recently, used for the "added" confirmation. */
  lastAddedId: number | null;
}

type CartAction =
  | { type: "hydrate"; lines: CartLine[] }
  | { type: "add"; line: CartLine }
  | { type: "setQuantity"; productId: number; quantity: number }
  | { type: "remove"; productId: number }
  | { type: "clear" };

const initialState: CartState = { lines: [], lastAddedId: null };

function clampQuantity(quantity: number, maxStock: number): number {
  if (!Number.isFinite(quantity)) return 1;
  return Math.max(1, Math.min(MAX_PER_LINE, Math.min(Math.floor(quantity), maxStock || MAX_PER_LINE)));
}

function reducer(state: CartState, action: CartAction): CartState {
  switch (action.type) {
    case "hydrate":
      return { ...state, lines: action.lines };

    case "add": {
      const existing = state.lines.find((line) => line.productId === action.line.productId);
      if (!existing) {
        return {
          lines: [...state.lines, action.line],
          lastAddedId: action.line.productId,
        };
      }
      return {
        lines: state.lines.map((line) =>
          line.productId === action.line.productId
            ? {
                ...line,
                quantity: clampQuantity(line.quantity + action.line.quantity, line.maxStock),
                maxStock: Math.max(line.maxStock, action.line.maxStock),
              }
            : line,
        ),
        lastAddedId: action.line.productId,
      };
    }

    case "setQuantity": {
      if (action.quantity <= 0) {
        return { ...state, lines: state.lines.filter((line) => line.productId !== action.productId) };
      }
      return {
        ...state,
        lines: state.lines.map((line) =>
          line.productId === action.productId
            ? { ...line, quantity: clampQuantity(action.quantity, line.maxStock) }
            : line,
        ),
      };
    }

    case "remove":
      return { ...state, lines: state.lines.filter((line) => line.productId !== action.productId) };

    case "clear":
      return initialState;

    default:
      return state;
  }
}

function readStoredCart(): CartLine[] | null {
  try {
    const raw = window.localStorage.getItem(STORAGE_KEY);
    if (!raw) return null;
    const parsed = JSON.parse(raw) as unknown;
    if (!Array.isArray(parsed)) return null;

    return parsed.flatMap((entry): CartLine[] => {
      if (typeof entry !== "object" || entry === null) return [];
      const line = entry as Partial<CartLine>;
      if (typeof line.productId !== "number" || typeof line.quantity !== "number") return [];
      if (typeof line.name !== "string" || typeof line.price !== "number") return [];
      return [
        {
          productId: line.productId,
          name: line.name,
          price: line.price,
          image: typeof line.image === "string" ? line.image : "",
          brand: typeof line.brand === "string" ? line.brand : "",
          maxStock: typeof line.maxStock === "number" ? line.maxStock : MAX_PER_LINE,
          quantity: line.quantity,
        },
      ];
    });
  } catch {
    return null;
  }
}

/** Drop stored lines whose product disappeared, and resync prices and stock. */
async function reconcile(lines: CartLine[]): Promise<CartLine[]> {
  const response = await fetch("/api/products", { headers: { accept: "application/json" } });
  if (!response.ok) throw new Error(`Catalogue request failed: ${response.status}`);

  const payload = (await response.json()) as {
    products: {
      id: number;
      name: string;
      price: number;
      image: string;
      brand: string;
      stock: number;
    }[];
  };

  const catalogue = new Map(payload.products.map((product) => [product.id, product]));

  const reconciled: CartLine[] = [];
  for (const line of lines) {
    const product = catalogue.get(line.productId);
    if (!product || product.stock <= 0) continue;
    reconciled.push({
      productId: product.id,
      name: product.name,
      price: product.price,
      image: product.image,
      brand: product.brand,
      maxStock: product.stock,
      quantity: clampQuantity(line.quantity, product.stock),
    });
  }
  return reconciled;
}

export interface CartContextValue {
  lines: CartLine[];
  itemCount: number;
  subtotal: number;
  hydrated: boolean;
  lastAddedId: number | null;
  addItem: (product: Product, quantity?: number) => void;
  setQuantity: (productId: number, quantity: number) => void;
  removeItem: (productId: number) => void;
  clear: () => void;
}

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

export function CartProvider({ children }: { children: ReactNode }) {
  const [state, dispatch] = useReducer(reducer, initialState);
  const [hydrated, setHydrated] = useState(false);

  useEffect(() => {
    let cancelled = false;

    void (async () => {
      const stored = readStoredCart();
      let lines: CartLine[] = stored ?? [];

      if (lines.length > 0) {
        try {
          lines = await reconcile(lines);
        } catch {
          // The catalogue could not be reached: keep the stored snapshot as-is
          // rather than silently emptying the user's cart.
        }
      }

      if (cancelled) return;
      if (lines.length > 0) dispatch({ type: "hydrate", lines });
      setHydrated(true);
    })();

    return () => {
      cancelled = true;
    };
  }, []);

  // Persist only after hydration, so an empty initial state cannot overwrite a
  // stored cart before it has been read.
  useEffect(() => {
    if (!hydrated) return;
    try {
      window.localStorage.setItem(STORAGE_KEY, JSON.stringify(state.lines));
    } catch {
      // Private browsing or a full quota: the in-memory cart still works.
    }
  }, [state.lines, hydrated]);

  const addItem = useCallback((product: Product, quantity = 1) => {
    if (product.stock <= 0) return;
    dispatch({
      type: "add",
      line: {
        productId: product.id,
        name: product.name,
        price: product.price,
        image: product.image,
        brand: product.brand,
        maxStock: product.stock,
        quantity: clampQuantity(quantity, product.stock),
      },
    });
  }, []);

  const setQuantity = useCallback((productId: number, quantity: number) => {
    dispatch({ type: "setQuantity", productId, quantity });
  }, []);

  const removeItem = useCallback((productId: number) => {
    dispatch({ type: "remove", productId });
  }, []);

  const clear = useCallback(() => dispatch({ type: "clear" }), []);

  const value = useMemo<CartContextValue>(() => {
    const itemCount = state.lines.reduce((total, line) => total + line.quantity, 0);
    const subtotal = state.lines.reduce(
      (total, line) => total + line.price * line.quantity,
      0,
    );
    return {
      lines: state.lines,
      itemCount,
      subtotal,
      hydrated,
      lastAddedId: state.lastAddedId,
      addItem,
      setQuantity,
      removeItem,
      clear,
    };
  }, [state.lines, state.lastAddedId, hydrated, addItem, setQuantity, removeItem, clear]);

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

export function useCart(): CartContextValue {
  const context = useContext(CartContext);
  if (!context) throw new Error("useCart must be used inside <CartProvider>");
  return context;
}
