"use client";

import {
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
  type KeyboardEvent as ReactKeyboardEvent,
} from "react";
import Link from "next/link";
import {
  AlertCircle,
  ArrowRight,
  ArrowUpDown,
  Banknote,
  Building2,
  Check,
  ChevronDown,
  Lock,
  Search,
  Smartphone,
  Tag,
  X,
} from "lucide-react";
import { AnimatePresence, m } from "framer-motion";
import { CURRENCIES } from "@/data/currencies";
import { calcTransfer, getLocalCouponAmount, isValidCoupon } from "@/lib/calculator";
import { formatNumber, cn } from "@/lib/utils";
import { useTranslation } from "@/hooks/useTranslation";
import type { Currency, TransferMethod } from "@/types";
import type { ApiCoupon, ApiCurrency, ApiImagePaths, ApiTransactionType } from "@/api/sendRemittance.api";

const TX_TYPE_SLUG: Record<TransferMethod, string> = {
  bank: "bank-transfer",
  cash: "cash-pickup",
  mobile: "mobile_money",
};

export interface CalculatorContinueParams {
  sendAmount: string;
  from: string;
  to: string;
  method: TransferMethod;
  coupon: string;
}

/** Shape both the public /calculator quote and the authenticated send-remittance
 *  index satisfy. `coupons` is optional — the public endpoint doesn't return them,
 *  so the widget falls back to the local hardcoded coupon table in that case. */
export interface CalculatorApiData {
  sender_currency: ApiCurrency[];
  receiver_currency: ApiCurrency[];
  image_paths: ApiImagePaths;
  transaction_type: ApiTransactionType[];
  coupons?: ApiCoupon[];
}

interface Props {
  variant?: "hero" | "full";
  /** Live API data — when present, overrides local static rates/fees/currencies.
   *  Falls back to local data silently when undefined (e.g. still loading). */
  apiData?: CalculatorApiData;
  /** When provided, Continue button triggers this instead of linking to /calculator. */
  onContinue?: (params: CalculatorContinueParams) => void | Promise<void>;
}

interface MethodDef {
  value: TransferMethod;
  icon: React.ComponentType<{ className?: string }>;
  labelKey: string;
  subKey: string;
  badge?: "fastest" | "popular";
}

const METHODS: MethodDef[] = [
  {
    value: "bank",
    icon: Building2,
    labelKey: "calculator.bankTransfer",
    subKey: "calculator.deliveryBank",
    badge: "popular",
  },
  {
    value: "cash",
    icon: Banknote,
    labelKey: "calculator.cashPickup",
    subKey: "calculator.deliveryCash",
  },
  {
    value: "mobile",
    icon: Smartphone,
    labelKey: "calculator.mobileMoney",
    subKey: "calculator.deliveryMobile",
    badge: "fastest",
  },
];

const LABEL_CLS =
  "mb-2 block text-xs font-semibold uppercase tracking-[0.12em] text-slate-500 dark:text-slate-400";

export function CalculatorWidget({ variant = "full", apiData, onContinue }: Props) {
  const { t, dir } = useTranslation();

  const [sendAmount, setSendAmount] = useState<string>("500");
  const [from, setFrom] = useState("USD");
  const [to, setTo] = useState("BDT");
  const [method, setMethod] = useState<TransferMethod>("bank");
  const [couponInput, setCouponInput] = useState("");
  const [appliedCoupon, setAppliedCoupon] = useState<string>("");
  const [couponError, setCouponError] = useState("");
  const [couponShake, setCouponShake] = useState(0);
  const [showCoupon, setShowCoupon] = useState(false);

  const sendAmountNum = parseFloat(sendAmount) || 0;
  const sameCurrency = from === to;

  // Coupons are cash-pickup only — drop any applied coupon (and the input state
  // behind it) the moment the method changes away from cash, so a hidden
  // discount can never silently keep applying once the UI to see/remove it
  // is gone (same rule as the authenticated send flow).
  useEffect(() => {
    if (method !== "cash" && appliedCoupon) {
      setAppliedCoupon("");
      setCouponInput("");
      setCouponError("");
    }
  }, [method, appliedCoupon]);

  // Sync default currencies when API data loads. Deliberately depends only on
  // `apiData`, not `from`/`to` — including those would re-run this on every
  // currency change, including the swap button's flip, and immediately snap
  // the flipped values back (sender/receiver currency lists don't overlap).
  useEffect(() => {
    if (!apiData?.sender_currency.length) return;
    if (!apiData.sender_currency.some((c) => c.code === from)) {
      setFrom(apiData.sender_currency[0].code);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [apiData]);

  useEffect(() => {
    if (!apiData?.receiver_currency.length) return;
    if (!apiData.receiver_currency.some((c) => c.code === to)) {
      setTo(apiData.receiver_currency[0].code);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [apiData]);

  const senderCodes = useMemo(
    () => (apiData ? new Set(apiData.sender_currency.map((c) => c.code)) : null),
    [apiData],
  );
  const receiverCodes = useMemo(
    () => (apiData ? new Set(apiData.receiver_currency.map((c) => c.code)) : null),
    [apiData],
  );
  const flagUrlMap = useMemo(() => {
    if (!apiData?.image_paths) return null;
    const { base_url, path_location } = apiData.image_paths;
    const map = new Map<string, string>();
    for (const c of [...apiData.sender_currency, ...apiData.receiver_currency]) {
      if (!map.has(c.code)) map.set(c.code, `${base_url}${path_location}/${c.flag}`);
    }
    return map;
  }, [apiData]);
  const availableMethods = useMemo(() => {
    if (!apiData) return METHODS;
    const slugs = new Set(apiData.transaction_type.map((tx) => tx.slug));
    return METHODS.filter((m) => slugs.has(TX_TYPE_SLUG[m.value]));
  }, [apiData]);

  // Live calculation from API data
  const apiResult = useMemo(() => {
    if (!apiData || sendAmountNum <= 0 || sameCurrency) return null;
    const senderCur = apiData.sender_currency.find((c) => c.code === from);
    const receiverCur = apiData.receiver_currency.find((c) => c.code === to);
    const slug = TX_TYPE_SLUG[method];
    const txType = apiData.transaction_type.find((tx) => tx.slug === slug) ?? apiData.transaction_type[0];
    if (!senderCur || !receiverCur || !txType) return null;
    const exchangeRate = parseFloat(receiverCur.rate) / parseFloat(senderCur.rate);
    const interval = txType.intervals.find(
      (i) => sendAmountNum >= parseFloat(i.min_limit) && sendAmountNum <= parseFloat(i.max_limit),
    );
    const fixedFee = parseFloat(interval?.fixed ?? txType.fixed_charge);
    const pctFee = parseFloat(interval?.percent ?? txType.percent_charge);
    const transferFee = +(fixedFee + sendAmountNum * pctFee / 100).toFixed(2);
    const receive = +(sendAmountNum * exchangeRate).toFixed(2);
    let discount = 0;
    if (appliedCoupon) {
      if (apiData.coupons) {
        const coupon = apiData.coupons.find((c) => c.name.toLowerCase() === appliedCoupon.toLowerCase());
        if (coupon) {
          const price = parseFloat(coupon.price) || 0;
          const maxLimit = parseFloat(coupon.max_limit) || 0;
          const raw = coupon.coupon_type.toLowerCase().includes("percent")
            ? (sendAmountNum * price) / 100
            : price;
          discount = +(maxLimit > 0 ? Math.min(raw, maxLimit) : raw).toFixed(2);
        }
      } else {
        discount = getLocalCouponAmount(appliedCoupon);
      }
    }
    return { receive, rate: +exchangeRate.toFixed(4), transferFee, serviceCharge: 0, totalToPay: +(sendAmountNum + transferFee - discount).toFixed(2), discount };
  }, [apiData, sendAmountNum, sameCurrency, from, to, method, appliedCoupon]);

  const localResult = useMemo(
    () => calcTransfer(sendAmountNum, from, to, method, appliedCoupon),
    [sendAmountNum, from, to, method, appliedCoupon],
  );
  const result = apiResult ?? localResult;

  const [ctaLoading, setCtaLoading] = useState(false);
  const ctaDisabled = sameCurrency || sendAmountNum <= 0;

  function applyCoupon() {
    const code = couponInput.trim().toUpperCase();
    if (!code) return;
    if (apiData?.coupons) {
      const match = apiData.coupons.find((c) => c.name.toLowerCase() === code.toLowerCase());
      if (match && match.remaining > 0) {
        setAppliedCoupon(match.name);
        setCouponError("");
      } else {
        setCouponError(t("calculator.invalidCoupon"));
        setAppliedCoupon("");
        setCouponShake((s) => s + 1);
      }
      return;
    }
    if (isValidCoupon(code)) {
      setAppliedCoupon(code);
      setCouponError("");
    } else {
      setCouponError(t("calculator.invalidCoupon"));
      setAppliedCoupon("");
      setCouponShake((s) => s + 1);
    }
  }

  function removeCoupon() {
    setAppliedCoupon("");
    setCouponInput("");
    setCouponError("");
  }

  function swap() {
    setFrom(to);
    setTo(from);
  }

  return (
    <div className="relative w-full">
      {/* Animated 1px gradient border */}
      <div
        aria-hidden
        className="pointer-events-none absolute -inset-px overflow-hidden rounded-[28px] opacity-40"
      >
        <m.div
          className="absolute left-1/2 top-1/2 h-[260%] w-[260%] -translate-x-1/2 -translate-y-1/2"
          style={{
            background:
              "conic-gradient(from 0deg, rgba(50,109,255,0) 0deg, rgba(50,109,255,0.55) 90deg, rgba(168,85,247,0.5) 180deg, rgba(16,185,129,0.4) 270deg, rgba(50,109,255,0) 360deg)",
          }}
          animate={{ rotate: 360 }}
          transition={{ duration: 8, ease: "linear", repeat: Infinity }}
        />
      </div>

      <div
        className={cn(
          "relative rounded-[28px] bg-white p-4 sm:p-5 dark:bg-slate-900/80 dark:backdrop-blur-xl",
          "shadow-[0_24px_60px_-15px_rgba(15,23,42,0.15),0_8px_24px_-8px_rgba(15,23,42,0.08)]",
          "dark:shadow-[0_24px_60px_-15px_rgba(0,0,0,0.5),0_8px_24px_-8px_rgba(0,0,0,0.3)]",
        )}
        data-variant={variant}
      >
        {/* You Send */}
        <label className={LABEL_CLS}>{t("calculator.youSend")}</label>
        <div className="group relative flex items-stretch gap-2 rounded-2xl border-2 border-transparent bg-slate-50/60 transition-all duration-200 ease-snappy focus-within:border-brand-400 focus-within:bg-white dark:bg-slate-800/40 dark:focus-within:border-brand-400/70 dark:focus-within:bg-slate-900/60">
          <input
            type="number"
            inputMode="decimal"
            min={0}
            value={sendAmount}
            onChange={(e) => setSendAmount(e.target.value)}
            aria-label={t("calculator.youSend")}
            className="min-w-0 flex-1 rounded-2xl bg-transparent px-4 py-3.5 text-3xl font-bold leading-none tabular-nums text-slate-900 outline-none placeholder:text-slate-400 sm:text-display-md dark:text-white"
            placeholder="0"
          />
          <div className="flex items-center pe-3 ps-1">
            <CurrencyPicker value={from} onChange={setFrom} allowedCodes={senderCodes ?? undefined} flagUrlMap={flagUrlMap ?? undefined} />
          </div>
        </div>

        {/* Swap divider */}
        <div className="relative my-3 flex items-center justify-center">
          <div className="h-px flex-1 bg-gradient-to-r from-transparent via-slate-200 to-transparent dark:via-slate-700" />
          <m.button
            type="button"
            onClick={swap}
            aria-label="Swap currencies"
            whileTap={{ scale: 0.92 }}
            whileHover={{ rotate: 180 }}
            transition={{ type: "spring", stiffness: 260, damping: 18, mass: 0.6 }}
            className="mx-3 grid h-11 w-11 place-items-center rounded-full bg-gradient-to-br from-brand-600 to-brand-700 text-white shadow-[0_8px_20px_-6px_rgba(50,109,255,0.55)] outline-none transition-shadow duration-300 hover:shadow-[0_10px_28px_-6px_rgba(50,109,255,0.7)] focus-visible:ring-2 focus-visible:ring-brand-400 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-slate-900"
          >
            <ArrowUpDown className="h-4 w-4" aria-hidden />
          </m.button>
          <div className="h-px flex-1 bg-gradient-to-r from-transparent via-slate-200 to-transparent dark:via-slate-700" />
        </div>

        {/* Recipient Gets */}
        <label className={LABEL_CLS}>{t("calculator.recipientGets")}</label>
        <div
          className={cn(
            "relative rounded-2xl border px-4 py-3 transition-colors",
            sameCurrency
              ? "border-rose-200/80 bg-rose-50/60 dark:border-rose-400/30 dark:bg-rose-500/10"
              : "border-brand-200/60 bg-gradient-to-br from-brand-50/80 via-white to-accent-50/40 dark:border-brand-400/20 dark:from-brand-500/[0.10] dark:via-slate-900/60 dark:to-accent-500/[0.08]",
          )}
        >
          <div className="flex items-center gap-2">
            <div className="min-w-0 flex-1">
              <span className="mb-1 inline-block rounded-full bg-brand-100 px-2 py-0.5 text-2xs font-semibold uppercase tracking-wider text-brand-600 dark:bg-brand-500/15 dark:text-brand-300">
                {t("calculator.estimated")}
              </span>
              {sameCurrency ? (
                <div className="flex items-center gap-2 text-sm font-medium text-rose-600 dark:text-rose-400">
                  <AlertCircle className="h-4 w-4 shrink-0" />
                  <span>{t("calculator.sameCurrencyError")}</span>
                </div>
              ) : (
                <div aria-live="polite" aria-atomic="true">
                  <AnimatedAmount
                    value={formatNumber(result.receive)}
                    keyId={`${result.receive}-${to}-${method}`}
                    className="text-3xl font-bold leading-none tabular-nums text-brand-700 sm:text-display-md dark:text-brand-300"
                  />
                </div>
              )}
            </div>
            <CurrencyPicker value={to} onChange={setTo} variant="receive" allowedCodes={receiverCodes ?? undefined} flagUrlMap={flagUrlMap ?? undefined} />
          </div>
        </div>

        {/* Receive method */}
        <div className="mt-4">
          <label className={LABEL_CLS}>{t("calculator.receiveMethod")}</label>
          <div
            role="radiogroup"
            aria-label={t("calculator.receiveMethod")}
            className="mt-5 grid grid-cols-1 gap-2 sm:grid-cols-3"
          >
            {availableMethods.map((m) => (
              <MethodCard
                key={m.value}
                def={m}
                active={m.value === method}
                onSelect={() => setMethod(m.value)}
              />
            ))}
          </div>
        </div>

        {/* Breakdown */}
        <div className="mt-4 rounded-2xl border border-slate-100 bg-slate-50/60 p-3 dark:border-slate-800 dark:bg-slate-900/60">
          <BreakdownRow
            label={t("calculator.exchangeRate")}
            value={
              sameCurrency
                ? "—"
                : `1 ${from} = ${formatNumber(result.rate, undefined, 4)} ${to}`
            }
            animateKey={`rate-${result.rate}-${from}-${to}`}
          />
          <BreakdownRow
            label={t("calculator.transferFee")}
            value={`${formatNumber(result.transferFee)} ${from}`}
          />
          <BreakdownRow
            label={t("calculator.serviceCharge")}
            value={`${formatNumber(result.serviceCharge)} ${from}`}
          />
          <BreakdownRow
            label={t("calculator.estDelivery")}
            value={t(
              method === "mobile"
                ? "calculator.deliveryMobile"
                : method === "cash"
                  ? "calculator.deliveryCash"
                  : "calculator.deliveryBank",
            )}
          />

          {appliedCoupon && (
            <div className="flex items-center justify-between py-1.5 text-emerald-600 dark:text-emerald-400">
              <span className="flex items-center gap-2 text-sm">
                <span>Coupon</span>
                <span className="rounded-full bg-emerald-100 px-1.5 py-0.5 text-2xs font-semibold uppercase tracking-wider dark:bg-emerald-500/15">
                  {appliedCoupon}
                </span>
              </span>
              <span className="text-sm font-semibold tabular-nums">
                −{formatNumber(result.discount)} {from}
              </span>
            </div>
          )}

          {/* Gradient divider */}
          <div className="my-3 h-px bg-gradient-to-r from-transparent via-slate-200 to-transparent dark:via-slate-700" />

          {/* Total */}
          <div className="flex items-center justify-between">
            <span className="text-base font-semibold text-slate-700 dark:text-slate-200">
              {t("calculator.totalToPay")}
            </span>
            <span className="text-lg font-bold tabular-nums text-slate-900 dark:text-white">
              {formatNumber(result.totalToPay * result.rate)} {to}
            </span>
          </div>

          {/* Coupon section — cash pickup only */}
          {method === "cash" && (
            <div className="mt-3 border-t border-slate-200/70 pt-3 dark:border-slate-800/70">
              <CouponSection
                show={showCoupon}
                setShow={setShowCoupon}
                applied={appliedCoupon}
                input={couponInput}
                setInput={(v) => {
                  setCouponInput(v.toUpperCase());
                  setCouponError("");
                }}
                onApply={applyCoupon}
                onRemove={removeCoupon}
                error={couponError}
                shakeKey={couponShake}
              />
            </div>
          )}
        </div>

        {/* CTA */}
        <CtaButton
          disabled={ctaDisabled || ctaLoading}
          loading={ctaLoading}
          dir={dir}
          onContinue={
            onContinue
              ? async () => {
                  setCtaLoading(true);
                  try {
                    await onContinue({ sendAmount, from, to, method, coupon: appliedCoupon });
                  } finally {
                    setCtaLoading(false);
                  }
                }
              : undefined
          }
        >
          {t("calculator.continue")}
        </CtaButton>

        <div className="mt-3 flex items-center justify-center gap-1.5 text-xs text-slate-500 dark:text-slate-400">
          <Lock className="h-3 w-3" aria-hidden />
          <span>{t("calculator.securityNote")}</span>
        </div>
      </div>
    </div>
  );
}

/* ────────────────────────────────────────────────────────────────────────── */

function AnimatedAmount({
  value,
  keyId,
  className,
}: {
  value: string;
  keyId: string;
  className?: string;
}) {
  return (
    <AnimatePresence mode="popLayout" initial={false}>
      <m.div
        key={keyId}
        initial={{ opacity: 0.4 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
        transition={{ duration: 0.2, ease: "easeOut" }}
        className={className}
      >
        {value}
      </m.div>
    </AnimatePresence>
  );
}

/* ────────────────────────── MethodCard ──────────────────────── */

function MethodCard({
  def,
  active,
  onSelect,
}: {
  def: MethodDef;
  active: boolean;
  onSelect: () => void;
}) {
  const { t } = useTranslation();
  const Icon = def.icon;

  return (
    <button
      type="button"
      role="radio"
      aria-checked={active}
      onClick={onSelect}
      className={cn(
        "group relative flex flex-row items-center gap-3 overflow-visible rounded-2xl border p-3 text-start transition-all duration-200 ease-snappy sm:flex-col sm:items-start sm:gap-1.5 sm:p-3.5",
        active
          ? "border-brand-500 bg-brand-50/60 shadow-[0_6px_18px_-8px_rgba(50,109,255,0.4)] dark:border-brand-400/60 dark:bg-brand-500/10"
          : "border-slate-200 bg-white hover:-translate-y-0.5 hover:border-slate-300 hover:bg-slate-50 dark:border-slate-800 dark:bg-slate-900/60 dark:hover:border-slate-700 dark:hover:bg-slate-800/60",
      )}
    >
      {/* "Most popular" badge — centered above the card edge */}
      {def.badge === "popular" && (
        <span className="pointer-events-none absolute -top-2.5 left-1/2 z-10 -translate-x-1/2 whitespace-nowrap rounded-full bg-amber-400 px-2 py-0.5 text-2xs font-bold uppercase tracking-wider text-amber-950 shadow-sm">
          {t("calculator.mostPopular")}
        </span>
      )}

      {/* Active check — top-end corner, sits inside the card */}
      {active && (
        <span className="absolute end-2.5 top-2.5 z-10 grid h-5 w-5 place-items-center rounded-full bg-brand-600 text-white shadow-sm">
          <Check className="h-3 w-3" strokeWidth={3} aria-hidden />
        </span>
      )}

      {/* Icon in its own subtle container */}
      <span
        className={cn(
          "grid h-8 w-8 place-items-center rounded-lg transition-colors",
          active
            ? "bg-brand-100 text-brand-600 dark:bg-brand-500/20 dark:text-brand-300"
            : "bg-slate-100 text-slate-500 dark:bg-slate-800/60 dark:text-slate-400",
        )}
      >
        <Icon className="h-5 w-5" aria-hidden />
      </span>

      <span className="flex min-w-0 flex-col gap-0.5">
        <span
          className={cn(
            "text-sm font-semibold leading-tight",
            active
              ? "text-brand-700 dark:text-brand-200"
              : "text-slate-900 dark:text-white",
          )}
        >
          {t(def.labelKey)}
        </span>
        <span className="flex flex-wrap items-center text-2xs leading-tight text-slate-500 dark:text-slate-400">
          <span>{t(def.subKey)}</span>
          {def.badge === "fastest" && (
            <span className="ms-1 text-emerald-600 dark:text-emerald-400">
              {t("calculator.fastest")}
            </span>
          )}
        </span>
      </span>
    </button>
  );
}

/* ────────────────────────── BreakdownRow ──────────────────────── */

function BreakdownRow({
  label,
  value,
  icon: Icon,
  animateKey,
}: {
  label: string;
  value: string;
  icon?: React.ComponentType<{ className?: string }>;
  animateKey?: string;
}) {
  return (
    <div className="flex items-center justify-between gap-2 py-1.5">
      <span className="flex shrink-0 items-center gap-1.5 text-sm text-slate-500 dark:text-slate-400">
        {Icon && <Icon className="h-3.5 w-3.5" aria-hidden />}
        {label}
      </span>
      {animateKey ? (
        <AnimatePresence mode="popLayout" initial={false}>
          <m.span
            key={animateKey}
            initial={{ opacity: 0.5 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            transition={{ duration: 0.2 }}
            className="text-sm tabular-nums text-slate-700 dark:text-slate-200"
          >
            {value}
          </m.span>
        </AnimatePresence>
      ) : (
        <span className="text-sm tabular-nums text-slate-700 dark:text-slate-200">
          {value}
        </span>
      )}
    </div>
  );
}

/* ────────────────────────── CouponSection ──────────────────────── */

function CouponSection({
  show,
  setShow,
  applied,
  input,
  setInput,
  onApply,
  onRemove,
  error,
  shakeKey,
}: {
  show: boolean;
  setShow: (v: boolean) => void;
  applied: string;
  input: string;
  setInput: (v: string) => void;
  onApply: () => void;
  onRemove: () => void;
  error: string;
  shakeKey: number;
}) {
  const { t } = useTranslation();

  if (applied) {
    return (
      <div className="flex items-center justify-between gap-2">
        <span className="inline-flex items-center gap-1.5 text-sm text-slate-600 dark:text-slate-300">
          <Tag className="h-3.5 w-3.5" aria-hidden />
          {t("calculator.couponApplied")}
        </span>
        <span className="inline-flex items-center gap-1.5 rounded-full border border-emerald-200 bg-emerald-50 px-2.5 py-1 text-xs font-semibold text-emerald-700 dark:border-emerald-400/30 dark:bg-emerald-500/10 dark:text-emerald-300">
          <Check className="h-3 w-3" strokeWidth={3} aria-hidden />
          {applied}
          <button
            type="button"
            onClick={onRemove}
            aria-label="Remove coupon"
            className="ms-0.5 rounded-full p-0.5 transition-colors hover:bg-emerald-100 dark:hover:bg-emerald-500/20"
          >
            <X className="h-3 w-3" aria-hidden />
          </button>
        </span>
      </div>
    );
  }

  return (
    <div>
      <button
        type="button"
        onClick={() => setShow(!show)}
        aria-expanded={show}
        className="flex w-full items-center justify-between gap-2 rounded-lg py-1 text-sm text-slate-600 transition-colors hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200"
      >
        <span className="inline-flex items-center gap-1.5">
          <Tag className="h-3.5 w-3.5" aria-hidden />
          {t("calculator.applyCouponCode")}
        </span>
        <ChevronDown
          className={cn(
            "h-3.5 w-3.5 transition-transform duration-200",
            show && "rotate-180",
          )}
          aria-hidden
        />
      </button>

      <AnimatePresence initial={false}>
        {show && (
          <m.div
            initial={{ height: 0, opacity: 0 }}
            animate={{ height: "auto", opacity: 1 }}
            exit={{ height: 0, opacity: 0 }}
            transition={{ duration: 0.2, ease: "easeOut" }}
            className="overflow-hidden"
          >
            <m.div
              key={shakeKey}
              animate={
                error
                  ? { x: [0, -4, 4, -3, 3, 0] }
                  : { x: 0 }
              }
              transition={{ duration: 0.35 }}
              className="mt-2 flex gap-2"
            >
              <input
                type="text"
                value={input}
                onChange={(e) => setInput(e.target.value)}
                onKeyDown={(e) => {
                  if (e.key === "Enter") {
                    e.preventDefault();
                    onApply();
                  }
                }}
                placeholder={t("calculator.couponCode")}
                aria-label={t("calculator.couponCode")}
                aria-invalid={error ? true : undefined}
                style={{ outline: "none", boxShadow: "none" }}
                className="flex-1 rounded-xl border border-slate-200 bg-slate-50 px-3.5 py-2 text-sm font-semibold uppercase tracking-wider transition-colors placeholder:font-normal placeholder:normal-case placeholder:tracking-normal placeholder:text-slate-400 focus:border-brand-300 focus:bg-white dark:border-slate-700 dark:bg-slate-800/50 dark:text-white dark:focus:border-brand-500/40 dark:focus:bg-slate-900"
              />
              <button
                type="button"
                onClick={onApply}
                disabled={!input}
                className="rounded-xl bg-slate-900 px-4 py-2 text-sm font-semibold text-white transition-colors hover:bg-slate-800 disabled:opacity-40 dark:bg-white dark:text-slate-900 dark:hover:bg-slate-100"
              >
                {t("calculator.applyCoupon")}
              </button>
            </m.div>

            {error && (
              <p className="mt-1.5 flex items-center gap-1 text-xs font-medium text-rose-500">
                <AlertCircle className="h-3 w-3" aria-hidden />
                {error}
              </p>
            )}
          </m.div>
        )}
      </AnimatePresence>
    </div>
  );
}

/* ────────────────────────── CtaButton ──────────────────────── */

function CtaButton({
  disabled,
  loading,
  dir,
  children,
  onContinue,
}: {
  disabled: boolean;
  loading?: boolean;
  dir: "ltr" | "rtl";
  children: React.ReactNode;
  onContinue?: () => void | Promise<void>;
}) {
  const baseCls =
    "group mt-4 flex h-[46px] w-full items-center justify-center gap-2 rounded-2xl text-base font-semibold text-white outline-none focus-visible:ring-2 focus-visible:ring-brand-400 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-slate-900";
  const activeCls =
    "bg-gradient-to-r from-brand-600 to-brand-700 shadow-[0_8px_24px_-8px_rgba(50,109,255,0.6)] transition-all duration-300 hover:from-brand-700 hover:to-brand-800 hover:shadow-[0_12px_30px_-8px_rgba(50,109,255,0.75)]";
  const disabledCls =
    "cursor-not-allowed bg-slate-300 text-slate-500 shadow-none dark:bg-slate-700 dark:text-slate-400";

  const arrow = (
    <ArrowRight
      className={cn(
        "h-4 w-4 transition-transform duration-200",
        !disabled && "group-hover:translate-x-1",
      )}
      style={dir === "rtl" ? { transform: "scaleX(-1)" } : undefined}
      aria-hidden
    />
  );

  if (disabled) {
    return (
      <button
        type="button"
        disabled
        aria-disabled="true"
        className={cn(baseCls, disabledCls)}
      >
        {children}
        {arrow}
      </button>
    );
  }

  if (onContinue) {
    return (
      <button
        type="button"
        onClick={onContinue}
        disabled={loading}
        className={cn(baseCls, loading ? disabledCls : activeCls)}
      >
        {loading ? "Please wait…" : children}
        {!loading && arrow}
      </button>
    );
  }

  return (
    <Link href="/calculator" className={cn(baseCls, activeCls)}>
      {children}
      {arrow}
    </Link>
  );
}

/* ────────────────────────── CurrencyPicker ──────────────────────── */

function CurrencyPicker({
  value,
  onChange,
  variant = "send",
  allowedCodes,
  flagUrlMap,
}: {
  value: string;
  onChange: (v: string) => void;
  variant?: "send" | "receive";
  allowedCodes?: Set<string>;
  flagUrlMap?: Map<string, string>;
}) {
  const { t } = useTranslation();
  const [open, setOpen] = useState(false);
  const [query, setQuery] = useState("");
  const [highlighted, setHighlighted] = useState(0);
  const rootRef = useRef<HTMLDivElement>(null);
  const inputRef = useRef<HTMLInputElement>(null);
  const listRef = useRef<HTMLDivElement>(null);
  const triggerRef = useRef<HTMLButtonElement>(null);

  const current = CURRENCIES.find((c) => c.code === value);

  const filtered = useMemo(() => {
    const base = allowedCodes ? CURRENCIES.filter((c) => allowedCodes.has(c.code)) : CURRENCIES;
    const q = query.trim().toLowerCase();
    if (!q) return base;
    return base.filter(
      (c) =>
        c.code.toLowerCase().includes(q) ||
        c.name.toLowerCase().includes(q) ||
        c.country.toLowerCase().includes(q),
    );
  }, [query, allowedCodes]);

  // Close on outside click
  useEffect(() => {
    if (!open) return;
    function onDocClick(e: MouseEvent) {
      if (!rootRef.current?.contains(e.target as Node)) {
        setOpen(false);
      }
    }
    document.addEventListener("mousedown", onDocClick);
    return () => document.removeEventListener("mousedown", onDocClick);
  }, [open]);

  // Reset query and focus input on open
  useEffect(() => {
    if (open) {
      setQuery("");
      const idx = Math.max(
        0,
        CURRENCIES.findIndex((c) => c.code === value),
      );
      setHighlighted(idx);
      // Focus search after open animation
      requestAnimationFrame(() => inputRef.current?.focus());
    }
  }, [open, value]);

  // Reset highlight when filter changes
  useEffect(() => {
    setHighlighted(0);
  }, [query]);

  // Scroll highlighted into view
  useEffect(() => {
    if (!open || !listRef.current) return;
    const el = listRef.current.querySelector<HTMLElement>(
      `[data-idx="${highlighted}"]`,
    );
    el?.scrollIntoView({ block: "nearest" });
  }, [highlighted, open]);

  const select = useCallback(
    (code: string) => {
      onChange(code);
      setOpen(false);
      triggerRef.current?.focus();
    },
    [onChange],
  );

  function onKeyDown(e: ReactKeyboardEvent<HTMLDivElement>) {
    if (!open) return;
    if (e.key === "Escape") {
      e.preventDefault();
      setOpen(false);
      triggerRef.current?.focus();
    } else if (e.key === "ArrowDown") {
      e.preventDefault();
      setHighlighted((i) => Math.min(filtered.length - 1, i + 1));
    } else if (e.key === "ArrowUp") {
      e.preventDefault();
      setHighlighted((i) => Math.max(0, i - 1));
    } else if (e.key === "Home") {
      e.preventDefault();
      setHighlighted(0);
    } else if (e.key === "End") {
      e.preventDefault();
      setHighlighted(filtered.length - 1);
    } else if (e.key === "Enter") {
      e.preventDefault();
      const c = filtered[highlighted];
      if (c) select(c.code);
    }
  }

  const listboxId = `cur-list-${variant}`;

  return (
    <div ref={rootRef} className="relative" onKeyDown={onKeyDown}>
      <button
        ref={triggerRef}
        type="button"
        role="combobox"
        aria-expanded={open}
        aria-haspopup="listbox"
        aria-controls={listboxId}
        aria-label="Currency"
        onClick={() => setOpen((o) => !o)}
        className={cn(
          "inline-flex h-11 items-center gap-2 rounded-xl border border-slate-200 bg-white px-3 text-sm font-bold text-slate-900 transition-all duration-200 ease-snappy hover:-translate-y-px hover:border-slate-300 hover:shadow-sm dark:border-slate-700 dark:bg-slate-900 dark:text-white dark:hover:border-slate-600",
          open && "border-brand-400 dark:border-brand-400/70",
        )}
      >
        {flagUrlMap?.has(value) ? (
          <img src={flagUrlMap.get(value)} alt="" aria-hidden className="h-5 w-5 shrink-0 rounded-full object-cover" />
        ) : current ? (
          <span className="text-base leading-none" aria-hidden>{current.flag}</span>
        ) : null}
        <span className="tabular-nums">{value}</span>
        <ChevronDown
          className={cn(
            "h-3.5 w-3.5 text-slate-400 transition-transform duration-200",
            open && "rotate-180",
          )}
          aria-hidden
        />
      </button>

      <AnimatePresence>
        {open && (
          <m.div
            initial={{ opacity: 0, scale: 0.96, y: -4 }}
            animate={{ opacity: 1, scale: 1, y: 0 }}
            exit={{ opacity: 0, scale: 0.96, y: -4 }}
            transition={{ duration: 0.15, ease: [0.16, 1, 0.3, 1] }}
            className="absolute end-0 z-50 mt-2 w-72 origin-top overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-[0_20px_50px_-12px_rgba(15,23,42,0.25)] dark:border-slate-700 dark:bg-slate-900 dark:shadow-[0_20px_50px_-12px_rgba(0,0,0,0.6)]"
          >
            {/* Search */}
            <div className="border-b border-slate-100 p-2 dark:border-slate-800">
              <div className="relative">
                <Search
                  className="pointer-events-none absolute start-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-slate-400"
                  aria-hidden
                />
                <input
                  ref={inputRef}
                  type="text"
                  value={query}
                  onChange={(e) => setQuery(e.target.value)}
                  placeholder={t("calculator.searchCurrency")}
                  aria-label={t("calculator.searchCurrency")}
                  className="w-full rounded-lg bg-slate-50 py-2 ps-9 pe-3 text-sm outline-none placeholder:text-slate-400 focus:bg-white focus:ring-1 focus:ring-brand-400 dark:bg-slate-800 dark:text-white dark:focus:bg-slate-800/80"
                />
              </div>
            </div>

            {/* List */}
            <div
              ref={listRef}
              id={listboxId}
              role="listbox"
              className="max-h-64 overflow-y-auto p-1"
            >
              {filtered.length === 0 ? (
                <div className="px-3 py-6 text-center text-sm text-slate-500 dark:text-slate-400">
                  {t("calculator.noCurrencyResults")}
                </div>
              ) : (
                filtered.map((c, i) => (
                  <CurrencyOption
                    key={c.code}
                    item={c}
                    idx={i}
                    selected={c.code === value}
                    highlighted={i === highlighted}
                    onHover={() => setHighlighted(i)}
                    onSelect={() => select(c.code)}
                    flagUrl={flagUrlMap?.get(c.code)}
                  />
                ))
              )}
            </div>
          </m.div>
        )}
      </AnimatePresence>
    </div>
  );
}

function CurrencyOption({
  item,
  idx,
  selected,
  highlighted,
  onHover,
  onSelect,
  flagUrl,
}: {
  item: Currency;
  idx: number;
  selected: boolean;
  highlighted: boolean;
  onHover: () => void;
  onSelect: () => void;
  flagUrl?: string;
}) {
  return (
    <button
      type="button"
      role="option"
      aria-selected={selected}
      data-idx={idx}
      onMouseEnter={onHover}
      onClick={onSelect}
      className={cn(
        "flex w-full items-center gap-3 rounded-lg px-2.5 py-2 text-start transition-colors",
        highlighted && !selected && "bg-slate-100 dark:bg-slate-800/80",
        selected &&
          "bg-brand-50 text-brand-700 dark:bg-brand-500/10 dark:text-brand-200",
      )}
    >
      {flagUrl ? (
        <img src={flagUrl} alt="" aria-hidden className="h-6 w-6 shrink-0 rounded-full object-cover" />
      ) : (
        <span className="text-lg leading-none" aria-hidden>{item.flag}</span>
      )}
      <span className="min-w-0 flex-1">
        <span className="block text-sm font-bold tabular-nums">
          {item.code}
        </span>
        <span className="block truncate text-xs text-slate-500 dark:text-slate-400">
          {item.name}
        </span>
      </span>
      {selected && (
        <Check
          className="h-4 w-4 text-brand-600 dark:text-brand-300"
          aria-hidden
        />
      )}
    </button>
  );
}
