"use client";

import { useEffect, useRef, useState } from "react";
import { addToCart, useCart } from "../lib/cart";
import { useCartFly } from "./CartFly";
import { NotifyMeForm } from "./NotifyMeForm";

const FEEDBACK_MS = 2000;

/**
 * Shared by /todo-cards's Hero + pricing panel and Home's product
 * spotlight. Used to navigate straight to /cart on click; now stays on the
 * page instead (matching AddToCartInlineButton's behavior everywhere else)
 * — adds to the cart, plays the fly-to-navbar-icon animation, and shows a
 * brief inline success state instead of leaving the page.
 */
export function AddToCartButton({
  label,
  className,
  productId = "todo-karten",
  numericId,
  outOfStock = false,
  maxQty = null,
  variants = [],
  showQuantityStepper = false,
}: {
  label: string;
  className?: string;
  /** Defaults to "todo-karten" for /todo-cards' own hardcoded usage — Home's
   * ProductSpotlight passes the actual CMS-selected spotlight product's id
   * explicitly, since that can now be a different product. */
  productId?: string;
  /** Payload's real numeric product id (`product.numericId`) — only used to
   * scope a NotifyMeForm signup once out of stock, never for the cart/
   * checkout path itself (that stays on the slug `productId` above). */
  numericId: number;
  /** Product-level — only meaningful when `variants` is empty, same split as
   * AddToCartInlineButton. */
  outOfStock?: boolean;
  /** Product-level cap on total cart quantity — only meaningful when
   * `variants` is empty, same split as `outOfStock`. null means no cap. */
  maxQty?: number | null;
  /** Optional — same shape/semantics as AddToCartInlineButton's own
   * `variants` prop; all three callers already fetch the full product
   * server-side, so this is just threaded straight through. */
  variants?: { name: string; priceOverride: number | null; outOfStock: boolean; lowStock: boolean; maxQty: number | null }[];
  /** Opt-in +/- quantity control before the button, adding `qty` at once
   * instead of one click per unit. Off by default — every existing caller
   * (grid cards, spotlight) keeps today's exact one-click-adds-one
   * behavior; only the PDP hero/pricing panel enable this. */
  showQuantityStepper?: boolean;
}) {
  const [added, setAdded] = useState(false);
  const [qty, setQty] = useState(1);
  const [selectedVariant, setSelectedVariant] = useState(variants.find((v) => !v.outOfStock)?.name ?? variants[0]?.name);
  const timeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
  const buttonRef = useRef<HTMLButtonElement>(null);
  const { fly } = useCartFly();
  const cart = useCart();

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

  const currentlyOutOfStock = variants.length > 0 ? (variants.find((v) => v.name === selectedVariant)?.outOfStock ?? false) : outOfStock;
  const currentMaxQty = variants.length > 0 ? (variants.find((v) => v.name === selectedVariant)?.maxQty ?? null) : maxQty;
  const qtyInCart = cart.find((i) => i.id === productId && i.variant === selectedVariant)?.qty ?? 0;
  const remainingQty = currentMaxQty != null ? Math.max(0, currentMaxQty - qtyInCart) : null;
  const limitReached = currentMaxQty != null && qtyInCart >= currentMaxQty;
  const disabled = currentlyOutOfStock || limitReached;
  const clampedQty = remainingQty != null ? Math.min(qty, Math.max(1, remainingQty)) : qty;

  function handleClick() {
    if (disabled) return;
    addToCart(productId, showQuantityStepper ? clampedQty : 1, selectedVariant);
    if (buttonRef.current) fly(buttonRef.current);
    setAdded(true);
    setQty(1);
    clearTimeout(timeoutRef.current);
    timeoutRef.current = setTimeout(() => setAdded(false), FEEDBACK_MS);
  }

  const base =
    className ??
    "inline-flex items-center justify-center px-6 py-[0.8125rem] rounded-sm bg-brand hover:bg-brand-hover active:scale-[0.97] transition-all font-bold text-body text-text-primary whitespace-nowrap focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand focus-visible:ring-offset-2 focus-visible:ring-offset-bg-base";
  // Trailing `!` — Tailwind v4's important-modifier syntax moved from a
  // leading `!` to this trailing suffix — forces the success color to win
  // over whatever bg-brand/text-text-primary each caller already baked
  // into `base`, since appending on top can't rely on CSS source order
  // the way branching a whole className (AddToCartInlineButton's
  // approach) can when the base itself varies per caller.
  // Pale success-subtle fill + success text + a success-colored border, not
  // a solid success-green fill with white text — same restrained pairing
  // AddToCartInlineButton already uses (border-success + bg-success-subtle),
  // a solid bright-green button read as too loud here. `border` (width) is
  // added here too since `base` has none by default, unlike
  // AddToCartInlineButton's own base which already carries a plain border.
  const stateClasses = disabled
    ? "opacity-60 cursor-not-allowed"
    : added
      ? "border border-success! bg-success-subtle! hover:bg-success-subtle! text-success!"
      : "";
  // currentlyOutOfStock has no branch here — that state renders
  // NotifyMeForm instead of this button entirely (see below).
  const displayLabel = limitReached ? "Maximale Menge im Warenkorb" : label;

  return (
    // Low stock is deliberately NOT surfaced here as its own text line
    // (it used to be) — that made this block's height vary card-to-card
    // in every grid that renders this component, breaking equal-height
    // card alignment (ProductSpotlight's CTA row, RelatedProducts' grid).
    // The image-overlaid pill badge (ProductGrid.tsx/ProductSpotlight.tsx/
    // RelatedProducts.tsx, position: absolute, doesn't participate in
    // layout flow) is the one place this now shows, same as
    // Ausverkauft/discount already do. The variant-select suffix below is
    // unaffected — a native <select>'s own height doesn't vary with its
    // option text.
    <div className="flex flex-col gap-2">
      {variants.length > 0 && (
        <select
          value={selectedVariant}
          onChange={(e) => setSelectedVariant(e.target.value)}
          className="w-full rounded-sm border border-border px-3 py-2 text-body-sm text-text-primary bg-bg-base focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand"
          aria-label="Variante auswählen"
        >
          {variants.map((v) => (
            <option key={v.name} value={v.name}>
              {v.name}
              {v.outOfStock ? " (ausverkauft)" : v.lowStock ? " (nur noch wenige)" : ""}
            </option>
          ))}
        </select>
      )}
      {showQuantityStepper && !currentlyOutOfStock && !limitReached && (
        <div className="flex items-center gap-3 w-fit rounded-sm border border-border">
          <button
            type="button"
            onClick={() => setQty((q) => Math.max(1, q - 1))}
            disabled={qty <= 1}
            aria-label="Menge verringern"
            className="flex items-center justify-center size-9 shrink-0 text-body font-bold text-text-primary disabled:opacity-40 hover:bg-bg-muted transition-colors"
          >
            –
          </button>
          <span className="min-w-4 text-center text-body-sm text-text-primary tabular-nums">{clampedQty}</span>
          <button
            type="button"
            onClick={() => setQty((q) => (remainingQty != null ? Math.min(remainingQty, q + 1) : q + 1))}
            disabled={remainingQty != null && clampedQty >= remainingQty}
            aria-label="Menge erhöhen"
            className="flex items-center justify-center size-9 shrink-0 text-body font-bold text-text-primary disabled:opacity-40 hover:bg-bg-muted transition-colors"
          >
            +
          </button>
        </div>
      )}
      {currentlyOutOfStock ? (
        // Replaces the button slot entirely rather than stacking below a
        // disabled "Ausverkauft" button — same reasoning as
        // AddToCartInlineButton's identical swap (see that file's own
        // comment on the `items-start` grid fix this relies on).
        <NotifyMeForm productId={numericId} variantName={variants.length > 0 ? (selectedVariant ?? "") : ""} />
      ) : (
        <button
          ref={buttonRef}
          type="button"
          onClick={handleClick}
          disabled={disabled}
          className={`${base} ${stateClasses}`}
        >
          {/* CSS-grid text-stack, not just swapping the button's text node
              directly — this button is inline-flex/content-sized (no w-full),
              so "Hinzugefügt ✓" being shorter than most labels made the whole
              button visibly shrink while showing the success state. Stacking
              both possible texts in the same grid cell (both invisible ones
              still contribute to sizing) reserves width for whichever is
              wider, so the button's box never changes size either way. Now
              also reserves space for "Maximale Menge im Warenkorb" — the
              widest of the three wins regardless of which is showing. */}
          {/* whitespace-nowrap — inherited by every stacked span below. On a
              w-full button (e.g. this page's mobile layout), "Maximale Menge
              im Warenkorb" is long enough to wrap to two lines without this,
              and since every stacked span shares the same grid cell, that
              inflated the row height for whichever text is actually showing
              too (fixed 2026-07-24). */}
          <span className="relative grid whitespace-nowrap">
            <span className="invisible [grid-area:1/1]" aria-hidden="true">
              {label}
            </span>
            <span className="invisible [grid-area:1/1]" aria-hidden="true">
              Hinzugefügt ✓
            </span>
            <span className="invisible [grid-area:1/1]" aria-hidden="true">
              Maximale Menge im Warenkorb
            </span>
            <span className="[grid-area:1/1]">{added ? "Hinzugefügt ✓" : displayLabel}</span>
          </span>
        </button>
      )}
    </div>
  );
}
