"use client";

import { useEffect, useRef, useState } from "react";
import Image from "next/image";
import { Cookie } from "lucide-react";
import { withCDN } from "@/src/lib/utils";
import LanguageAwareLink from "@/src/lib/utils/LanguageAwareLink";
import {
  needsCookieConsent,
  saveCookieConsent,
  type CookieConsentDecision,
} from "@/src/lib/utils/cookieConsent";
import { recordCookieConsentAction } from "@/src/lib/actions/cookieConsentAction";

/** Length of the fade-out; keep in step with duration-200 on the banner. */
const EXIT_MS = 200;

interface CookieBannerProps {
  /**
   * Version of the Cookie Notice published on the hub right now, e.g.
   * "2026-09-04-v1". A visitor who responded to an older version is asked
   * again. Null when the hub could not be reached.
   */
  currentVersion: string | null;
}

/**
 * Footer banner asking the visitor to accept or reject cookies.
 *
 * The visitor must choose - there is no close button - and both choices are
 * stored in their browser and recorded in the backend. Rejecting declines
 * optional cookies (see hasAcceptedOptionalCookies); the strictly necessary
 * ones - payment session data, language, the payment provider's cookies at
 * checkout - are used either way, as the Cookie Notice explains.
 */
export default function CookieBanner({ currentVersion }: CookieBannerProps) {
  // Two states, because the banner animates in both directions:
  //   mounted - the banner is in the DOM
  //   shown   - it is in its on-screen position (off: faded and lowered)
  // Both start false and are decided after mount, from the visitor's own
  // cookie. Rendering nothing on the server avoids a hydration mismatch, and a
  // returning visitor never sees the banner flash in and out.
  //
  // The animation is a plain CSS transition, not framer-motion. framer-motion
  // hands opacity to the browser's animation engine and, when the exit ends,
  // drops that animation a frame before the element is removed - long enough
  // for the finished-and-invisible banner to blink back at full opacity. With a
  // CSS transition the hidden state is simply the element's class, so there is
  // nothing to fall back to.
  const [mounted, setMounted] = useState(false);
  const [shown, setShown] = useState(false);
  const bannerRef = useRef<HTMLDivElement>(null);
  const unmountTimer = useRef<ReturnType<typeof setTimeout> | null>(null);

  useEffect(() => {
    setMounted(needsCookieConsent(currentVersion));
  }, [currentVersion]);

  // Slide in once the hidden starting position has been painted; switching
  // classes in the same frame as mounting would skip the transition.
  useEffect(() => {
    if (!mounted) return;
    let second = 0;
    const first = requestAnimationFrame(() => {
      second = requestAnimationFrame(() => setShown(true));
    });
    return () => {
      cancelAnimationFrame(first);
      cancelAnimationFrame(second);
    };
  }, [mounted]);

  useEffect(() => {
    return () => {
      if (unmountTimer.current) clearTimeout(unmountTimer.current);
    };
  }, []);

  // While the banner is open, publish how much room it takes at the bottom of
  // the screen as --cookie-banner-offset. The scroll-to-top button adds that to
  // its own position, so it floats just above the banner instead of sitting on
  // top of it - which lets the banner be full width, as on a phone it must be.
  useEffect(() => {
    const root = document.documentElement;
    const el = bannerRef.current;

    // Released as soon as the banner starts leaving, so the button glides back
    // down alongside the fade instead of waiting for it.
    if (!shown || !el) {
      root.style.removeProperty("--cookie-banner-offset");
      return;
    }

    const update = () => {
      // Measured from layout, not getBoundingClientRect, so the slide-in
      // translate does not skew it.
      const fromBottom = parseFloat(getComputedStyle(el).bottom) || 0;
      const GAP = 12; // space between banner and button
      const BUTTON_BASE = 24; // the button's own bottom-6
      root.style.setProperty(
        "--cookie-banner-offset",
        `${Math.max(0, fromBottom + el.offsetHeight + GAP - BUTTON_BASE)}px`,
      );
    };

    update();
    const observer = new ResizeObserver(update);
    observer.observe(el);
    window.addEventListener("resize", update);

    return () => {
      observer.disconnect();
      window.removeEventListener("resize", update);
      root.style.removeProperty("--cookie-banner-offset");
    };
  }, [shown]);

  const handleDecision = (decision: CookieConsentDecision) => {
    // Ignore a second tap while the banner is already leaving.
    if (!shown) return;

    const consent = saveCookieConsent(decision, currentVersion);
    setShown(false);

    // Removed once the fade has finished. A timer rather than transitionend:
    // the event never fires when reduced motion turns the transition off, or
    // when the tab is in the background.
    unmountTimer.current = setTimeout(() => setMounted(false), EXIT_MS + 50);

    // Fire and forget. The choice is already stored in the browser, so the
    // banner closes immediately; waiting on the network would only make the
    // buttons feel broken on a slow connection.
    void recordCookieConsentAction({ visitorId: consent.id, decision });
  };

  if (!mounted) return null;

  return (
    <div
      role="region"
      aria-label="Cookie notice"
      aria-live="polite"
      ref={bannerRef}
      // Leaving: out of the accessibility tree and not clickable, so the fading
      // card cannot take a second tap or be read out.
      aria-hidden={!shown || undefined}
      // A compact card in the bottom-right corner from tablet up; full width on
      // phones, where a narrow card would squeeze the text. The scroll-to-top
      // button shares that corner and moves up out of the way (see
      // --cookie-banner-offset above).
      className={`fixed inset-x-3 bottom-3 md:left-auto md:right-6 md:bottom-6 md:w-[380px] z-[999] transition-[opacity,transform] motion-reduce:transition-none motion-reduce:translate-y-0 ${
        shown
          ? // In: a quick ease-out with a touch of overshoot.
            "opacity-100 translate-y-0 duration-300 ease-[cubic-bezier(0.34,1.4,0.64,1)]"
          : // Out: shorter, accelerating away.
            "opacity-0 translate-y-6 pointer-events-none duration-200 ease-in"
      }`}
    >
      {/* A compact notice, not a modal. The border keeps the card's edge
          crisp over photos and video, where a soft shadow alone disappears. */}
      <div className="relative rounded-[20px] bg-white border border-[#E3EAF0] shadow-[0_20px_45px_-15px_rgba(0,41,74,0.45)]">
        <div className="p-4 md:p-5 flex flex-col gap-3">
          <div>
            {/* Icon sits beside the title rather than beside the whole text
                block, so the text gets the card's full width. On a phone a
                tall icon column pushed the copy to four lines. */}
            <div className="flex items-center gap-2.5">
              <span
                aria-hidden="true"
                className="flex-shrink-0 grid place-items-center w-8 h-8 rounded-lg bg-gradient-to-br from-[#0097DC] to-[#00639A] text-white"
              >
                <Cookie size={17} />
              </span>
              {/* A styled paragraph, not a heading: a site-wide overlay must
                  not add an entry to every page's heading outline. */}
              <p className="font-[Signika] font-semibold text-[16px] leading-tight text-[#00294A]">
                Cookies on this site
              </p>
            </div>

            {/* The notice is a text link here so the button row holds just
                the two answers, and still fits on a 320px phone. */}
            <p className="mt-2 text-[13px] leading-[1.5] text-[#58585A]">
              We use essential cookies to remember your language and keep
              payments secure. See our{" "}
              <LanguageAwareLink
                href="/cookie-policy"
                className="font-medium text-[#0097DC] underline underline-offset-2 hover:text-[#00639A]"
              >
                Cookie Notice
              </LanguageAwareLink>
              .
            </p>
          </div>

          {/* Accept and Reject are the same size, so rejecting is as easy as
              accepting. One row at every size; on phones they share it
              equally. */}
          <div className="flex items-center gap-2 md:justify-end">
            {/* The site's own CTA: blue pill with the gear mark. */}
            <button
              type="button"
              onClick={() => handleDecision("accepted")}
              className="flex-1 md:flex-none md:min-w-[120px] inline-flex items-center justify-center gap-1.5 h-10 px-3 md:px-5 rounded-full bg-[#0097DC] text-white font-[Signika] text-[13px] uppercase tracking-[0.4px] whitespace-nowrap cursor-pointer transition hover:shadow-[10px_14px_14px_#0000000D] active:bg-[#0084C1] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#0097DC] focus-visible:ring-offset-2"
            >
              <Image
                src={withCDN("/gear-white.png")}
                alt=""
                aria-hidden="true"
                width={16}
                height={16}
                className="w-4 h-4 object-contain"
              />
              Accept
            </button>

            <button
              type="button"
              onClick={() => handleDecision("rejected")}
              className="flex-1 md:flex-none md:min-w-[120px] inline-flex items-center justify-center h-10 px-3 md:px-5 rounded-full border border-[#0097DC] text-[#0097DC] font-[Signika] text-[13px] uppercase tracking-[0.4px] whitespace-nowrap cursor-pointer transition-colors hover:bg-[#E8F4FB] active:bg-[#D6ECF8] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#0097DC] focus-visible:ring-offset-2"
            >
              Reject
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}
