"use client";

import { useEffect, useRef, useState } from "react";
import { AnimatePresence, motion, useInView } from "motion/react";
import { formatPrice } from "../../lib/format";

const SUCCESS_VISIBLE_MS = 2500;

type Phase = "progress" | "success" | "hidden";

/**
 * Progress nudge toward free shipping — shown only in the cart, not on
 * /shop, per the deliberately narrower scope agreed with the user (a cart
 * item can leave/re-enter the threshold as quantities change, so this
 * needs to react to that, not just fire once).
 *
 * `threshold` is the lowest freeShippingThreshold among /checkout's active
 * ShippingMethods (computed by the caller) — not every method necessarily
 * has one (Express never does, it always costs extra), so this shows the
 * easiest one to reach rather than an arbitrary/average value. `null`
 * means no active method has a threshold at all, so there's nothing to
 * nudge toward — the banner just doesn't render.
 */
export function FreeShippingBanner({ subtotal, threshold }: { subtotal: number; threshold: number | null }) {
  if (threshold === null) return null;

  return <FreeShippingBannerInner subtotal={subtotal} threshold={threshold} />;
}

function FreeShippingBannerInner({ subtotal, threshold }: { subtotal: number; threshold: number }) {
  const reached = subtotal >= threshold;
  const [phase, setPhase] = useState<Phase>(reached ? "success" : "progress");

  // React's documented pattern for "adjust state when a prop changes" —
  // done during render (guarded by comparing against a mirrored previous
  // value), not inside a useEffect. `reached` isn't an external system to
  // sync with, it's plain derived input; setState synchronously inside an
  // effect body would just cost an extra render pass for no benefit and
  // trips the react-hooks/set-state-in-effect rule. No ref/timer touched
  // here either — the success→hidden timer below owns its own cleanup.
  const [prevReached, setPrevReached] = useState(reached);
  if (reached !== prevReached) {
    setPrevReached(reached);
    if (!reached) {
      // Dropping back below the threshold (e.g. removing an item after
      // having qualified) always re-shows the progress bar — not just the
      // first time, every time.
      setPhase("progress");
    } else {
      // Just crossed the threshold — celebrate briefly, then collapse the
      // banner entirely rather than let it sit there taking up space.
      setPhase((prev) => (prev === "progress" ? "success" : prev));
    }
  }

  // Gates the hide-timer on the banner being CURRENTLY visible — deliberately
  // not `{ once: true }`: since the banner sits at the very top of the cart
  // page, it's already visible the instant the page loads (well before the
  // user ever scrolls anywhere), so a lifetime "has this ever been seen"
  // flag flips true immediately and defeats the whole point — reaching the
  // threshold later while scrolled away would still start the timer right
  // then, exactly the bug this was meant to fix. Continuous tracking
  // instead: the effect below only runs the countdown while `isInView` is
  // true, and its own cleanup cancels it the moment the banner scrolls back
  // out of view — so it always takes a full uninterrupted 2.5s of the
  // banner actually being on screen before it's allowed to hide, restarting
  // if the user looks away mid-countdown and comes back.
  const ref = useRef<HTMLDivElement>(null);
  const isInView = useInView(ref);

  // Effect's own cleanup (not a ref) cancels the pending hide-timer
  // whenever phase changes away from "success", the banner scrolls out of
  // view, or on unmount.
  useEffect(() => {
    if (phase !== "success" || !isInView) return;
    const t = setTimeout(() => setPhase("hidden"), SUCCESS_VISIBLE_MS);
    return () => clearTimeout(t);
  }, [phase, isInView]);

  const remaining = Math.max(0, threshold - subtotal);
  const progressPct = Math.min(100, (subtotal / threshold) * 100);

  return (
    // AnimatePresence + exit, not a plain `if (phase === "hidden") return
    // null` — that cut the banner instantly with no transition. initial={false}
    // keeps the appearance/re-appearance instant (only the disappearance
    // fades) since that's the specific bit that was asked to be smoother.
    <AnimatePresence>
      {phase !== "hidden" && (
        <motion.div
          ref={ref}
          initial={false}
          exit={{ opacity: 0 }}
          transition={{ duration: 0.4, ease: "easeOut" }}
          className={
            "w-full rounded-md border p-4 flex flex-col gap-2 transition-colors duration-300 " +
            (phase === "success" ? "border-success bg-success-subtle" : "border-border bg-bg-base")
          }
        >
          <p
            className={
              "text-body-sm font-semibold " + (phase === "success" ? "text-success" : "text-text-primary")
            }
          >
            {phase === "success"
              ? "Kostenloser Versand freigeschaltet ✓"
              : `Noch ${formatPrice(remaining)} bis zum kostenlosen Versand!`}
          </p>
          <div className="h-1.5 w-full rounded-full bg-border overflow-hidden">
            <div
              className={
                "h-full rounded-full transition-[width] duration-500 ease-out " +
                (phase === "success" ? "bg-success" : "bg-brand")
              }
              style={{ width: `${progressPct}%` }}
            />
          </div>
        </motion.div>
      )}
    </AnimatePresence>
  );
}
