"use client";

import { useEffect, useMemo, useRef, useState } from "react";
import Image from "next/image";
import { useProducts } from "../../lib/products";
import { formatPrice } from "../../lib/format";
import { Reveal } from "../../components/Reveal";
import { AddToCartInlineButton, FEEDBACK_MS } from "../../components/AddToCartInlineButton";
import { useCart } from "../../lib/cart";

const DISPLAY_COUNT = 3;

function pickRandom(allIds: string[], excludeIds: string[], count: number): string[] {
  const pool = allIds.filter((id) => !excludeIds.includes(id));
  const shuffled = [...pool].sort(() => Math.random() - 0.5);
  return shuffled.slice(0, count);
}

// Picks up to `count` active products not already in the cart, on top of
// whatever's already in `keep`. Deliberately does NOT fall back to
// re-suggesting a cart item when the non-cart pool runs short (e.g. 2
// active products total, 1 already in the cart) — the grid just renders
// fewer, genuinely-relevant cards instead (see the centering logic in the
// component below), rather than padding itself out with something the
// shopper has already added.
function pickAvailable(allIds: string[], excludeIds: string[], keep: string[], count: number): string[] {
  const missing = count - keep.length;
  if (missing <= 0) return keep;
  return [...keep, ...pickRandom(allIds, [...excludeIds, ...keep], missing)];
}

export function RelatedProducts() {
  const cart = useCart();
  const products = useProducts();
  // Cart/checkout resolve any product regardless of `active` (see
  // Product's own comment in lib/payload.ts) — this is the one discovery
  // surface among the useProducts() consumers, so it filters here itself.
  const activeProducts = useMemo(() => products.filter((p) => p.active), [products]);
  const hasItems = cart.length > 0;
  const cartKey = cart
    .map((i) => i.id)
    .sort()
    .join(",");
  // useMemo, not a plain .map() — .map() would return a new array
  // reference on every render regardless of whether `activeProducts` itself
  // changed, which would make the effect below re-run (and re-pick) every
  // single render if `productIds` were listed as its dependency.
  const productIds = useMemo(() => activeProducts.map((p) => p.id), [activeProducts]);

  // Starts empty — the catalog itself is now fetched (useProducts()), so
  // there's nothing to pick a random set from until that resolves. The
  // effect below picks as soon as products arrive, same idea as
  // CartContent's productsLoading guard.
  const [displayIds, setDisplayIds] = useState<string[]>([]);

  // First run once products have loaded: pick the initial random set.
  // After that, don't reshuffle everything on every cart change — one of
  // these cards' own AddToCartInlineButton adds to the cart without
  // navigating away, and a full reshuffle would yank the other two cards
  // out from under the user mid-browse. Instead, only swap out whichever
  // displayed card just became "already in the cart" (no longer relevant
  // to recommend) and top that one slot back up — the rest stay exactly
  // where they were.
  const pickedRef = useRef(false);
  const swapTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);

  useEffect(() => () => clearTimeout(swapTimeoutRef.current), []);

  useEffect(() => {
    if (productIds.length === 0) return;
    const cartIds = cartKey ? cartKey.split(",") : [];

    if (!pickedRef.current) {
      pickedRef.current = true;
      setDisplayIds(pickAvailable(productIds, cartIds, [], DISPLAY_COUNT));
      return;
    }

    // Delayed by FEEDBACK_MS, not immediate — the add usually came from
    // clicking one of THESE cards' own AddToCartInlineButton, which shows
    // a ~2s green "Hinzugefügt ✓" state. Swapping the card out right away
    // unmounts that button before the confirmation is ever visible.
    clearTimeout(swapTimeoutRef.current);
    swapTimeoutRef.current = setTimeout(() => {
      setDisplayIds((prev) => {
        const stillRelevant = prev.filter((id) => !cartIds.includes(id));
        return pickAvailable(productIds, cartIds, stillRelevant, DISPLAY_COUNT);
      });
    }, FEEDBACK_MS);
  }, [cartKey, productIds]);

  const displayProducts = displayIds
    .map((id) => activeProducts.find((p) => p.id === id))
    .filter((p): p is NonNullable<typeof p> => Boolean(p));

  // Section-wide gate, independent of cart contents: a "related products"
  // section makes no sense with fewer than 2 active products total to
  // ever offer, even before considering what's already in the cart.
  if (activeProducts.length < 2 || displayProducts.length === 0) return null;

  return (
    <section className="w-full bg-bg-base flex flex-col gap-8 items-center py-12 md:py-16 px-[var(--layout-padding-x)]">
      <Reveal className="flex flex-col gap-2 items-center text-center">
        <p
          className="font-semibold text-h-section text-text-primary"
          style={{ fontFamily: "var(--font-lora)" }}
        >
          {hasItems ? "Passt perfekt dazu" : "Beliebte Produkte"}
        </p>
        <p className="text-body text-text-muted">
          {hasItems ? "Weitere Tools für deinen klaren Alltag." : "Entdecke, was zu dir passt."}
        </p>
      </Reveal>

      {/* No separate price-disclosure footnote here — the single
          "* inkl. MwSt., zzgl. Versandkosten" note lives directly under
          the cart's own product table instead (CartContent.tsx), close
          enough on the same page view to cover these cards too.

          Plain divs, not RevealGroup/RevealItem — this is the one grid on
          the site whose items get swapped after the initial mount (see
          the swap-in-place effect above). RevealItem has no viewport
          trigger of its own; it only ever renders visible because it
          inherits the parent RevealGroup's already-resolved "show" state
          at first mount. A card swapped in later doesn't get a fresh
          trigger and can end up stuck at its hidden variant
          (opacity: 0) — invisible. Not worth chasing a fix for a
          scroll-reveal nicety on a list that mutates; a static grid
          renders correctly with no animation risk. */}
      <div className="grid grid-cols-1 md:grid-cols-12 gap-6 md:gap-[var(--layout-grid-gap)] w-full max-w-[75rem]">
        {displayProducts.map((product, i) => (
          <div
            key={product.id}
            className={
              "group md:col-span-4 bg-bg-base border border-border rounded-md overflow-hidden flex flex-col gap-4 transition-transform duration-300 hover:-translate-y-1 " +
              // Center the row when there are fewer than 3 cards to show
              // (e.g. only 1 active product left once the others are
              // already in the cart) — only the first card needs an
              // explicit start column, later ones auto-flow right after
              // it. 3-card case keeps the default left-to-right flow.
              (i === 0
                ? displayProducts.length === 1
                  ? "md:col-start-5"
                  : displayProducts.length === 2
                    ? "md:col-start-3"
                    : ""
                : "")
            }
          >
            <div className="relative w-full aspect-[320/210] overflow-hidden">
              <Image
                src={product.image}
                alt={product.name}
                fill
                sizes="(min-width: 768px) 320px, 100vw"
                className="object-cover transition-transform duration-500 group-hover:scale-105"
              />
            </div>
            <div className="flex flex-col gap-4 items-start px-5 pb-5 pt-2 w-full">
              <p
                className="font-semibold text-h4 text-text-primary w-full"
                style={{ fontFamily: "var(--font-lora)" }}
              >
                {product.name}
              </p>
              <p className="font-bold text-h4 text-text-primary">{formatPrice(product.price)}</p>
              <AddToCartInlineButton id={product.id} />
            </div>
          </div>
        ))}
      </div>
    </section>
  );
}
