import {
  Alert,
  Badge,
  Button,
  Checkbox,
  CloseButton,
  Drawer,
  Box,
  Group,
  Loader,
  LoadingOverlay,
  Select,
  Skeleton,
  Stack,
  Switch,
  Text,
  TextInput,
  Title,
} from "@mantine/core";
import { useForm } from "@mantine/form";
import { useDebouncedValue, useDisclosure } from "@mantine/hooks";
import { notifications } from "@mantine/notifications";
import {
  IconCalendarStats,
  IconHistory,
  IconLogin,
  IconPlus,
  IconRefresh,
  IconTicket,
  IconUsers,
  IconWorld,
  IconSearch,
} from "@tabler/icons-react";
import React, { useEffect, useMemo, useState } from "react";
import { useNavigate, useSearchParams } from "react-router";
import { toast } from "sonner";
import { useRoleAccess } from "@/hooks/useRoleAccess";
import { useSessionStorageState } from "@/hooks/useSessionStorageState";
import {
  createPromoCode,
  deletePromoCode,
  updatePromoCode,
} from "@/lib/features/promo-codes/action";
import {
  getPromoCodeByCode,
  listPromoCodes,
  unwrapPromo,
} from "@/lib/features/promo-codes/query";
import {
  getPromoCodeAnalytics,
  getPromoCodesAnalytics,
  getSignupAggregate,
  listCountriesFromDB,
  type SignupAggregateRow,
} from "@/lib/features/promo-code-campaigns/query";
import type { CampaignAnalyticsResponse } from "@/lib/features/promo-code-campaigns/types";
import { CampaignAnalyticsCard } from "@/routes/promo-code-campaigns/_components/CampaignAnalyticsCard";
import {
  ActiveFilterChips,
  type FilterChip,
  FilterDrawer,
  FilterSection,
  FilterTrigger,
} from "@/routes/promo-code-campaigns/_components/FilterDrawer";
import {
  StatTiles,
  type StatTileSpec,
} from "@/routes/promo-code-campaigns/_components/StatTiles";
import {
  AnimatedNumber,
  PageArrival,
  PageSection,
} from "@/routes/promo-code-campaigns/_components/motion";
import { usePromoTheme } from "@/routes/promo-code-campaigns/_components/appearance";
import { AnalyticsCardSkeleton } from "@/routes/promo-code-campaigns/_components/AnalyticsCardSkeleton";
import { ExportButton } from "@/lib/export/ExportButton";
import {
  ExportLimitError,
  MAX_EXPORT_ROWS,
  withTimeout,
} from "@/lib/export/exportLimits";
import {
  usePromoCodeSignupData,
  type PromoSignupRow,
} from "@/lib/features/promo-code-campaigns/usePromoCodeSignupData";
import {
  CountryMultiSelect,
  DEFAULT_SIGNUP_WINDOW,
  isWindowActive,
  resolveSignupWindow,
  SignupWindowControl,
  type SignupWindowState,
} from "@/routes/reports/_shared";
import ConfirmModal from "@/layouts/shared/ConfirmModal";
import type {
  PromoCode,
  PromoCodeListQueryParams,
  PromoCodeReferralFilter,
  PromoCodeStatusFilter,
  PromoCodeExpiredFilter,
} from "@/lib/features/promo-codes/types";
import { PromoCodeFilterFields } from "@/lib/features/promo-codes/components/PromoCodeFilters";
import { extractErrorMessage, extractFieldErrors } from "./errors";
import { PromoCodesTable } from "./PromoCodesTable";
import { ListTableCard, ListTableFooter } from "@/lib/components/ListTableCard";
import { InfoHint } from "@/lib/components/InfoHint";
import { PromoLogsPanel } from "./_components/PromoLogsPanel";
import {
  buildPayload,
  emptyValues,
  mapPromoToForm,
  validateForm,
} from "./form";
import type { PromoCodeFormValues } from "@/lib/features/promo-codes/types";
import type { AccessScope } from "@/lib/features/types";

interface PromoCodesClientPageProps {
  initialPromoCodes: PromoCode[];
  initialIsActive: PromoCodeStatusFilter;
  initialOnlyPromoCodes: PromoCodeReferralFilter;
  initialMemberReferral: PromoCodeReferralFilter;
  initialIncludeExpired: PromoCodeExpiredFilter;
  initialSearchTerm: string;
  initialLimit: number;
  initialOffset: number;
  initialTotalCount: number;
  initialTotalCountKnown: boolean;
  initialHasNextPage: boolean;
  initialError?: string;
  accessScope: AccessScope;
}

const DEFAULT_PROMO_CODES_LIMIT = 25;

const PROMO_CODES_PAGE_SIZE_OPTIONS = ["10", "25", "50", "100"];

// Session cache so switching away and back to this tab renders the analytics
// instantly while a fresh copy revalidates in the background. Lives for the SPA
// session (cleared on full reload). Mirrors the Member Referrals page.
let cachedPromoCodesAnalytics: CampaignAnalyticsResponse | null = null;

const parsePositiveInteger = (value: string | null, fallback: number) => {
  const parsed = Number.parseInt(value ?? "", 10);
  return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
};

const fmtDate = (value: unknown) => {
  if (!value) return "";
  const d = new Date(value as string);
  return Number.isFinite(d.getTime()) ? d.toLocaleDateString() : "";
};

async function mapWithConcurrency<T, R>(
  items: T[],
  limit: number,
  fn: (item: T) => Promise<R>,
  signal?: AbortSignal,
): Promise<R[]> {
  const out: R[] = [];
  for (let i = 0; i < items.length; i += limit) {
    if (signal?.aborted)
      throw new DOMException("Export cancelled", "AbortError");
    out.push(...(await Promise.all(items.slice(i, i + limit).map(fn))));
  }
  return out;
}

const PromoCodesClientPage: React.FC<PromoCodesClientPageProps> = ({
  initialPromoCodes,
  initialIsActive,
  initialOnlyPromoCodes,
  initialMemberReferral,
  initialIncludeExpired,
  initialSearchTerm,
  initialLimit,
  initialOffset,
  initialTotalCount,
  initialTotalCountKnown,
  initialHasNextPage,
  initialError,
  accessScope,
}) => {
  const navigate = useNavigate();
  const [searchParams, setSearchParams] = useSearchParams();

  const [promoCodes, setPromoCodes] = useState<PromoCode[]>(initialPromoCodes);
  // Per-(code,country) signup rows for the displayed page, within the window.
  const [aggRows, setAggRows] = useState<SignupAggregateRow[]>([]);
  const [sortBy, setSortBy] = useSessionStorageState<string>(
    "promoCodes.sort",
    "signups-desc",
  );
  // Bumped after create/delete so the dataset-wide signup view re-fetches.
  const [refreshNonce, setRefreshNonce] = useState(0);
  const [clientPage, setClientPage] = useState(1);
  const [dbCountryOptions, setDbCountryOptions] = useState<string[]>([]);

  // Fetch all countries from the DB `countries` table once on mount.
  // This drives the CountryMultiSelect options independently of the signup data.
  useEffect(() => {
    let cancelled = false;
    void (async () => {
      const res = await listCountriesFromDB();
      if (cancelled || !res.success) return;
      setDbCountryOptions(res.data.map((c) => c.name));
    })();
    return () => {
      cancelled = true;
    };
  }, []);

  // ---- signup window + country (business filters, on top) ----
  const [signupWindow, setSignupWindow] =
    useSessionStorageState<SignupWindowState>(
      "promoList.window",
      DEFAULT_SIGNUP_WINDOW,
    );
  const [countrySel, setCountrySel] = useSessionStorageState<string[]>(
    "promoList.country",
    [],
  );
  const [onlyWithSignups, setOnlyWithSignups] = useSessionStorageState(
    "promoList.onlyWithSignups",
    false,
  );
  const windowRange = useMemo(
    () => resolveSignupWindow(signupWindow),
    [signupWindow],
  );

  // ---- analytics (signups per code + country × code) across the whole promo
  // universe, window-aware — same card the Member Referrals section uses. ----
  const [analytics, setAnalytics] = useState<CampaignAnalyticsResponse | null>(
    cachedPromoCodesAnalytics,
  );
  const [analyticsLoading, setAnalyticsLoading] = useState(
    !cachedPromoCodesAnalytics,
  );
  const [analyticsError, setAnalyticsError] = useState<string | null>(null);

  useEffect(() => {
    let cancelled = false;
    if (!cachedPromoCodesAnalytics) setAnalyticsLoading(true);
    (async () => {
      try {
        const res = await getPromoCodesAnalytics({
          from: windowRange.from,
          to: windowRange.to,
        });
        if (cancelled) return;
        if (res.success) {
          // Only cache the all-time view for instant revisits; windowed views
          // are transient.
          if (!isWindowActive(signupWindow))
            cachedPromoCodesAnalytics = res.data;
          setAnalytics(res.data);
          setAnalyticsError(null);
        } else {
          setAnalyticsError(
            res.message || "Failed to load promo code analytics",
          );
        }
      } catch (e) {
        if (cancelled) return;
        setAnalyticsError(
          e instanceof Error
            ? e.message
            : "Failed to load promo code analytics",
        );
      } finally {
        if (!cancelled) setAnalyticsLoading(false);
      }
    })();
    return () => {
      cancelled = true;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [windowRange.from, windowRange.to]);

  // (visiblePromoCodeIds is defined after datasetRows so the analytics charts
  // can follow the currently filtered set — see below.)

  // Country options + window/country-aware signup counts for the loaded page.
  const countryOptions = useMemo(() => {
    const set = new Set<string>();
    for (const r of aggRows) {
      const c = (r.country ?? "").trim();
      if (c) set.add(c);
    }
    return Array.from(set).sort();
  }, [aggRows]);

  const { signupsByCode, loggedInByCode } = useMemo(() => {
    const s: Record<string, number> = {};
    const l: Record<string, number> = {};
    const set = countrySel.length ? new Set(countrySel) : null;
    for (const r of aggRows) {
      if (set && !(r.country && set.has(r.country))) continue;
      const k = String(r.code).toUpperCase();
      s[k] = (s[k] ?? 0) + Number(r.signups || 0);
      l[k] = (l[k] ?? 0) + Number(r.loggedIn || 0);
    }
    return { signupsByCode: s, loggedInByCode: l };
  }, [aggRows, countrySel]);
  const [totalCount, setTotalCount] = useState(initialTotalCount);
  const [totalCountKnown, setTotalCountKnown] = useState(
    initialTotalCountKnown,
  );
  const [hasNextPage, setHasNextPage] = useState(initialHasNextPage);

  const [grandTotal, setGrandTotal] = useState<number | null>(null);
  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const res = await listPromoCodes({
          includeExpired: "true",
          onlyPromoCodes: "true",
          limit: 1,
        });
        if (cancelled) return;
        if (res.success) {
          const total = res.data?.pagination?.total;
          if (typeof total === "number") setGrandTotal(total);
        }
      } catch {
        // best-effort; badge falls back to a skeleton until this resolves
      }
    })();
    return () => {
      cancelled = true;
    };
  }, [refreshNonce]);
  const [globalLogsOpened, { open: openGlobalLogs, close: closeGlobalLogs }] =
    useDisclosure(false);
  const [promoLogsOpened, { open: openPromoLogs, close: closePromoLogs }] =
    useDisclosure(false);
  const [logsPromo, setLogsPromo] = useState<PromoCode | null>(null);
  // Source code when the create modal was opened via Duplicate (super admin).
  const { checkClientAccess } = useRoleAccess();
  const canViewAdminLogs = checkClientAccess("read", "admin-logs");

  const [busy, setBusy] = useState(false);
  const [isRefreshing, setIsRefreshing] = useState(false);
  const [refreshStartedAt, setRefreshStartedAt] = useState<number | null>(null);
  const [lookupCode, setLookupCode] = useState("");
  const [debouncedLookupCode] = useDebouncedValue(lookupCode, 1000);
  const [isSearchActive, setIsSearchActive] = useState(false);

  const [searchTermInput, setSearchTermInput] = useSessionStorageState(
    "promoList.search",
    initialSearchTerm,
  );
  const [debouncedSearchTerm] = useDebouncedValue(searchTermInput, 600);

  const [confirmState, setConfirmState] = useState<{
    opened: boolean;
    title: string;
    message: string;
    onConfirm: () => void | Promise<void>;
  }>({ opened: false, title: "", message: "", onConfirm: () => {} });

  const openConfirm = React.useCallback(
    (opts: {
      title: string;
      message: string;
      onConfirm: () => void | Promise<void>;
    }) => {
      setConfirmState({ opened: true, ...opts });
    },
    [],
  );
  const closeConfirm = React.useCallback(
    () => setConfirmState((s) => ({ ...s, opened: false })),
    [],
  );

  const getPromoId = (promo: PromoCode) =>
    promo.id || promo.promoCodeId || promo.promo_code_id || "";

  const handleRefresh = () => {
    setRefreshStartedAt(Date.now());
    setIsRefreshing(true);
    setRefreshNonce((n) => n + 1);
    navigate(".", { replace: true });
  };

  // Apply the promo-type/status filters live from the given values (server-side
  // via URL params). Inline filters call this on every change.
  const applyFilterValues = (vals: {
    isActive: boolean;
    inactiveOnly?: boolean;
    includeExpired: boolean;
    onlyPromoCodes: boolean;
    memberReferral: boolean;
  }) => {
    const nextIsActive: PromoCodeStatusFilter = vals.inactiveOnly
      ? "false"
      : vals.isActive
        ? "true"
        : "all";

    const next = new URLSearchParams(searchParams);

    next.set("isActive", nextIsActive);
    if (vals.includeExpired) next.set("includeExpired", "true");
    else next.delete("includeExpired");
    // Only Promo Code defaults ON: drop the param when checked (default), write
    // "false" only when unchecked so the deviation is remembered.
    if (vals.onlyPromoCodes) next.delete("onlyPromoCodes");
    else next.set("onlyPromoCodes", "false");
    if (vals.memberReferral) next.set("onlyMemberReferralCodes", "true");
    else next.delete("onlyMemberReferralCodes");
    next.set("offset", "0");
    setSearchParams(next);
  };

  /**
   * Flips one of the four URL-backed filters.
   *
   * The chips read from the URL (`currentIsActive` and friends) but used to be removed
   * with the plain `setFilterIsActive`-style setters, which only touch local state — so
   * a chip's X changed nothing and the default "Active only" could not be cleared. Every
   * mutation now goes through `applyFilterValues`, which is the one place that writes the
   * params the query actually reads.
   *
   * Overrides are layered onto the URL's values, not the local ones, so this is also
   * correct after a back/forward navigation.
   */
  const setStatusFilters = (
    overrides: Partial<{
      isActive: boolean;
      inactiveOnly: boolean;
      includeExpired: boolean;
      onlyPromoCodes: boolean;
      memberReferral: boolean;
    }>,
  ) => {
    applyFilterValues({
      isActive: currentIsActive === "true",
      inactiveOnly: currentIsActive === "false",
      includeExpired: currentShowExpired === "true",
      onlyPromoCodes: currentOnlyPromoCodes === "true",
      memberReferral: currentMemberReferral === "true",
      ...overrides,
    });
  };

  const handleResetFilters = () => {
    // Reset restores the default view (Active + Only Promo Code). Clearing the
    // params lets the loader re-apply those defaults.
    setSignupWindow(DEFAULT_SIGNUP_WINDOW);
    setCountrySel([]);
    setOnlyWithSignups(false);

    const next = new URLSearchParams(searchParams);
    next.delete("isActive");
    next.delete("includeExpired");
    next.delete("onlyPromoCodes");
    next.delete("onlyMemberReferralCodes");
    next.delete("searchTerm");
    next.set("offset", "0");
    setSearchParams(next);
    setSearchTermInput("");
  };

  /**
   * The quick-search box, wired to the URL.
   *
   * Search is server-side: both the loader and the client refetch read
   * `searchParams.get("searchTerm")`. The box only held local state, so typing in it
   * did nothing at all — the debounced value was computed and then dropped. This is
   * the missing half.
   *
   * `replace: true` because a debounced keystroke is not a navigation; without it Back
   * would walk letter by letter through whatever was typed.
   */
  useEffect(() => {
    const inUrl = searchParams.get("searchTerm") ?? "";
    const next = debouncedSearchTerm.trim();
    if (next === inUrl) return;
    const params = new URLSearchParams(searchParams);
    if (next) params.set("searchTerm", next);
    else params.delete("searchTerm");
    // A new term changes which codes match, so page 1 is the only sensible page.
    params.set("offset", "0");
    setSearchParams(params, { replace: true });
    // Deliberately keyed on the debounced term alone: including `searchParams` would
    // re-run this on every unrelated filter change.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [debouncedSearchTerm]);

  /*
   * Clears the box and the param together, rather than clearing the box and waiting
   * out the 600ms debounce — an X that takes half a second to do anything reads as
   * broken.
   */
  const clearSearch = () => {
    setSearchTermInput("");
    const next = new URLSearchParams(searchParams);
    next.delete("searchTerm");
    next.set("offset", "0");
    setSearchParams(next);
  };

  const handleLimitChange = (value: string | null) => {
    const nextLimit = parsePositiveInteger(value, DEFAULT_PROMO_CODES_LIMIT);
    const next = new URLSearchParams(searchParams);
    next.set("limit", String(nextLimit));
    next.set("offset", "0");
    setSearchParams(next);
  };

  const handlePageChange = (page: number) => {
    const next = new URLSearchParams(searchParams);
    next.set("limit", String(currentLimit));
    next.set("offset", String((page - 1) * currentLimit));
    setSearchParams(next);
  };

  /**
   * The detail URL for one code.
   *
   * Shared by the table rows and the Analytics donut/legend so both land in the
   * same place with the same Back target — the `from` carries this list's current
   * filters and page, otherwise Back drops the reader on the campaigns page.
   */
  const promoDetailPath = (slug: string) => {
    const qs = searchParams.toString();
    const here = `/admin/promo-codes${qs ? `?${qs}` : ""}`;
    return `/admin/promo-codes/${encodeURIComponent(slug)}?from=${encodeURIComponent(here)}`;
  };

  const goToDetails = (promo: PromoCode) => {
    const slug = promo.code || getPromoId(promo);
    if (!slug) return;
    navigate(promoDetailPath(slug));
  };

  const handleEditClick = (promo: PromoCode) => goToDetails(promo);

  // Super admin only: copy an existing promo code's data into the create form.
  // The list rows can be trimmed (dataset-wide view), so re-fetch the full
  // record by code first and fall back to the row if that fails.
  /**
   * Duplicating an existing code.
   *
   * Hands the source code to the create page rather than fetching it here and seeding
   * a modal. The page does the loading, so there is one create form, one submit path,
   * and a duplicate-in-progress survives a reload.
   */
  const handleDuplicateClick = (promo: PromoCode) => {
    if (!accessScope.superAdmin) {
      toast.error("Only super admins can duplicate promo codes.");
      return;
    }
    const code = (promo.code || "").trim();
    if (!code) {
      toast.error("That promo code has no code to duplicate.");
      return;
    }
    navigate(`/admin/promo-codes/new?duplicate=${encodeURIComponent(code)}`);
  };

  const handleDeleteClick = (promo: PromoCode) => {
    if (!accessScope.delete) {
      toast.error("You do not have permission to delete promo codes.");
      return;
    }
    const nextId = getPromoId(promo);
    openConfirm({
      title: "Delete Promo Code",
      message: `Are you sure you want to delete promo code "${promo.name || promo.code}"? This action cannot be undone.`,
      onConfirm: async () => {
        setBusy(true);
        try {
          const res = await deletePromoCode(nextId);
          if (res.success) {
            toast.success(res.message || "Promo code deleted successfully");
            setPromoCodes((prev) =>
              prev.filter((p) => getPromoId(p) !== nextId),
            );
            setTotalCount((c) => Math.max(0, c - 1));
            setRefreshNonce((n) => n + 1);
          } else {
            const message = extractErrorMessage(
              res,
              "Failed to delete promo code",
            );
            toast.error(message);
          }
        } catch (e: any) {
          const message = extractErrorMessage(e, "Failed to delete promo code");
          toast.error(message);
        } finally {
          setBusy(false);
          closeConfirm();
        }
      },
    });
  };

  // Active defaults ON: an absent param means checked.
  const searchParamIsActive = searchParams.get("isActive");
  const currentIsActive: PromoCodeStatusFilter =
    searchParamIsActive === "all" || searchParamIsActive === "false"
      ? searchParamIsActive
      : "true";
  // Only Promo Code defaults ON: an absent param means checked.
  const currentOnlyPromoCodes: PromoCodeReferralFilter =
    searchParams.get("onlyPromoCodes") === "false" ? "false" : "true";
  const currentMemberReferral: PromoCodeReferralFilter =
    searchParams.get("onlyMemberReferralCodes") === "true" ? "true" : "false";
  const currentShowExpired: PromoCodeExpiredFilter =
    searchParams.get("includeExpired") === "true" ? "true" : "false";
  const currentSearchTerm = searchParams.get("searchTerm") || "";

  const [filtersOpen, setFiltersOpen] = useState(false);

  /*
   * One chip per applied filter.
   *
   * Note this page ships with two filters ON by default (Active only, Only
   * promo codes), so the chips double as a reminder that the default view is
   * already narrowed — previously that was only visible inside the panel.
   */
  const filterChips = useMemo<FilterChip[]>(() => {
    const chips: FilterChip[] = [];
    if (isWindowActive(signupWindow)) {
      chips.push({
        key: "window",
        label: "Created in window",
        onRemove: () => setSignupWindow(DEFAULT_SIGNUP_WINDOW),
      });
    }
    if (currentSearchTerm) {
      chips.push({
        key: "searchTerm",
        label: `Search: ${currentSearchTerm}`,
        onRemove: clearSearch,
      });
    }
    if (currentIsActive !== "all") {
      chips.push({
        key: "isActive",
        label: currentIsActive === "true" ? "Active only" : "Inactive only",
        onRemove: () =>
          setStatusFilters({ isActive: false, inactiveOnly: false }),
      });
    }
    if (currentShowExpired === "true") {
      chips.push({
        key: "expired",
        label: "Including expired",
        onRemove: () => setStatusFilters({ includeExpired: false }),
      });
    }
    if (currentOnlyPromoCodes === "true") {
      chips.push({
        key: "onlyPromo",
        label: "Only promo codes",
        onRemove: () => setStatusFilters({ onlyPromoCodes: false }),
      });
    }
    if (currentMemberReferral === "true") {
      chips.push({
        key: "referral",
        label: "Member referral codes",
        onRemove: () => setStatusFilters({ memberReferral: false }),
      });
    }
    for (const country of countrySel) {
      chips.push({
        key: `country:${country}`,
        label: `Country: ${country}`,
        onRemove: () => setCountrySel(countrySel.filter((c) => c !== country)),
      });
    }
    if (onlyWithSignups) {
      chips.push({
        key: "withSignups",
        label: "With signups only",
        onRemove: () => setOnlyWithSignups(false),
      });
    }
    return chips;
  }, [
    signupWindow,
    currentSearchTerm,
    currentIsActive,
    currentShowExpired,
    currentOnlyPromoCodes,
    currentMemberReferral,
    countrySel,
    onlyWithSignups,
  ]);
  const currentLimit = parsePositiveInteger(
    searchParams.get("limit"),
    initialLimit || DEFAULT_PROMO_CODES_LIMIT,
  );
  const currentOffset = parsePositiveInteger(
    searchParams.get("offset"),
    initialOffset,
  );
  const currentPage = Math.floor(currentOffset / Math.max(currentLimit, 1)) + 1;
  const totalPages = totalCountKnown
    ? Math.max(1, Math.ceil(totalCount / Math.max(currentLimit, 1)))
    : Math.max(currentPage, currentPage + (hasNextPage ? 1 : 0));

  // ---- dataset-wide signup view ----
  // When a signup window / country / has-signups filter is active, the page
  // switches to a dataset-wide view: every matching code is fetched, signups
  // are scoped to the window/country, zero-signup codes are dropped, and the
  // result is paginated client-side. Otherwise the normal server-paginated
  // list is shown.
  const signupsActive =
    isWindowActive(signupWindow) || countrySel.length > 0 || onlyWithSignups;

  // The upstream list API returns a `total` that ignores the type filters (Only
  // Promo Codes / Referral). So whenever a type filter is active — or any signup
  // filter is — we switch to the dataset-wide view: every matching code is
  // fetched and filtered / counted / charted / paginated client-side, keeping
  // the count badge and analytics in sync with the rows actually shown.
  const datasetWide =
    signupsActive ||
    currentOnlyPromoCodes === "true" ||
    currentMemberReferral === "true";

  const baseQuery = useMemo<PromoCodeListQueryParams>(
    () => ({
      isActive: currentIsActive === "all" ? undefined : currentIsActive,
      onlyPromoCodes: currentOnlyPromoCodes === "true" ? "true" : undefined,
      onlyMemberReferralCodes:
        currentMemberReferral === "true" ? "true" : undefined,
      includeExpired: currentShowExpired === "true" ? "true" : undefined,
      searchTerm: searchParams.get("searchTerm") || undefined,
    }),
    [
      currentIsActive,
      currentOnlyPromoCodes,
      currentMemberReferral,
      currentShowExpired,
      searchParams,
    ],
  );

  const dataset = usePromoCodeSignupData({
    baseQuery,
    window: windowRange,
    country: countrySel,
    enabled: datasetWide,
    refreshKey: refreshNonce,
  });

  const datasetRows = useMemo(() => {
    let rows = dataset.rows;

    // Filter promo codes by their own Creation Date (Created At) when date window is active
    if (isWindowActive(signupWindow)) {
      const fromTime = windowRange.from
        ? new Date(windowRange.from).getTime()
        : null;
      const toTime = windowRange.to ? new Date(windowRange.to).getTime() : null;

      rows = rows.filter((p) => {
        const createdStr = p.createdAt ?? p.created_at;
        if (!createdStr) return false;
        const createdTime = new Date(createdStr as string).getTime();
        if (!Number.isFinite(createdTime)) return false;

        if (fromTime !== null && createdTime < fromTime) return false;
        if (toTime !== null && createdTime > toTime) return false;
        return true;
      });
    }

    if (onlyWithSignups) {
      rows = rows.filter((r) => r._registered > 0);
    }

    const ts = (p: PromoSignupRow) =>
      new Date(String(p.createdAt ?? p.created_at ?? 0)).getTime() || 0;
    switch (sortBy) {
      case "code-asc":
      case "code-desc":
        return [...rows].sort((a, b) => {
          const aKey = String(a.code ?? a.name ?? "").toLowerCase();
          const bKey = String(b.code ?? b.name ?? "").toLowerCase();
          return sortBy === "code-asc"
            ? aKey.localeCompare(bKey)
            : bKey.localeCompare(aKey);
        });
      case "signups-desc":
        return [...rows].sort((a, b) => b._registered - a._registered);
      case "signups-asc":
        return [...rows].sort((a, b) => a._registered - b._registered);
      case "default":
      default:
        return [...rows].sort((a, b) => ts(b) - ts(a));
    }
  }, [dataset.rows, sortBy, signupWindow, windowRange, onlyWithSignups]);

  const datasetSignupsByCode = useMemo(() => {
    const m: Record<string, number> = {};
    for (const r of dataset.rows)
      if (r.code) m[r.code.toUpperCase()] = r._registered;
    return m;
  }, [dataset.rows]);
  const datasetLoggedInByCode = useMemo(() => {
    const m: Record<string, number> = {};
    for (const r of dataset.rows)
      if (r.code) m[r.code.toUpperCase()] = r._loggedIn;
    return m;
  }, [dataset.rows]);

  const effectiveSignupsByCode = datasetWide
    ? datasetSignupsByCode
    : signupsByCode;
  const effectiveLoggedInByCode = datasetWide
    ? datasetLoggedInByCode
    : loggedInByCode;
  const effectiveCountryOptions = dbCountryOptions;

  // Which codes the analytics charts should cover. Dataset-wide ⇒ only the
  // currently filtered codes (matched to analytics by code name, since the
  // analytics keys on promo_code_id); otherwise every code in the analytics set.
  const visiblePromoCodeIds = useMemo(() => {
    if (!datasetWide) {
      return new Set(
        (analytics?.promo_codes ?? []).map((p) => p.promo_code_id),
      );
    }
    const visibleNames = new Set(
      datasetRows
        .map((r) => String(r.code ?? "").toUpperCase())
        .filter(Boolean),
    );
    return new Set(
      (analytics?.promo_codes ?? [])
        .filter((p) => visibleNames.has(p.promo_code_name.toUpperCase()))
        .map((p) => p.promo_code_id),
    );
  }, [datasetWide, datasetRows, analytics]);

  const { series: seriesPalette } = usePromoTheme();

  /*
   * Headline figures across the codes currently in scope.
   *
   * Signups/logged-in are summed from `effective*ByCode`, which already respect
   * the country filter and the dataset-wide vs page-only mode — so these totals
   * track the same rows the table and charts show rather than the whole DB.
   */
  const statTiles = useMemo<StatTileSpec[]>(() => {
    const signups = Object.values(effectiveSignupsByCode).reduce(
      (sum, n) => sum + Number(n || 0),
      0,
    );
    const loggedIn = Object.values(effectiveLoggedInByCode).reduce(
      (sum, n) => sum + Number(n || 0),
      0,
    );
    const pending = Math.max(0, signups - loggedIn);
    const countries = countrySel.length > 0 ? countrySel.length : null;
    const paint = seriesPalette(5);
    const tiles: StatTileSpec[] = [
      {
        key: "codes",
        label: "Promo codes",
        // grandTotal is still loading on first paint; the table count is the
        // best available stand-in rather than showing a bare 0.
        value: grandTotal ?? datasetRows.length,
        icon: <IconTicket size={17} />,
        color: paint[0].from,
        colorTo: paint[0].to,
        hint: grandTotal === null ? "Counting…" : "Matching current filters",
      },
      {
        key: "signups",
        label: "Signups",
        value: signups,
        icon: <IconUsers size={17} />,
        color: paint[1].from,
        colorTo: paint[1].to,
        hint: isWindowActive(signupWindow) ? "In selected window" : "All time",
      },
      {
        key: "logged-in",
        label: "Logged in",
        value: loggedIn,
        icon: <IconLogin size={17} />,
        color: paint[2].from,
        colorTo: paint[2].to,
        hint: "Rewards granted",
        percent: signups > 0 ? (loggedIn / signups) * 100 : 0,
      },
      {
        key: "pending",
        label: "Pending login",
        value: pending,
        icon: <IconCalendarStats size={17} />,
        color: paint[3].from,
        colorTo: paint[3].to,
        hint: "Nothing granted yet",
        percent: signups > 0 ? (pending / signups) * 100 : 0,
      },
    ];
    // Only meaningful while a country filter is applied — otherwise the number
    // would just restate the filter's own emptiness.
    if (countries !== null) {
      tiles.push({
        key: "countries",
        label: "Countries",
        value: countries,
        icon: <IconWorld size={17} />,
        color: paint[4].from,
        colorTo: paint[4].to,
        hint: "In current filter",
      });
    }
    return tiles;
  }, [
    effectiveSignupsByCode,
    effectiveLoggedInByCode,
    grandTotal,
    datasetRows.length,
    countrySel,
    signupWindow,
    seriesPalette,
  ]);

  const datasetTotalPages = Math.max(
    1,
    Math.ceil(datasetRows.length / Math.max(currentLimit, 1)),
  );
  const datasetSafePage = Math.min(clientPage, datasetTotalPages);
  const datasetPageItems = datasetRows.slice(
    (datasetSafePage - 1) * currentLimit,
    datasetSafePage * currentLimit,
  );

  // Reset client page when the dataset view shape changes.
  useEffect(() => {
    setClientPage(1);
  }, [datasetWide, sortBy, currentLimit, datasetRows.length]);

  const getExportData = async (signal: AbortSignal) => {
    const all: PromoCode[] = [];
    const pageSize = 200;
    let offset = 0;
    while (offset < 10000) {
      if (signal.aborted)
        throw new DOMException("Export cancelled", "AbortError");
      const res = await withTimeout(
        listPromoCodes({
          isActive: currentIsActive === "all" ? undefined : currentIsActive,
          onlyPromoCodes: currentOnlyPromoCodes === "true" ? "true" : undefined,
          onlyMemberReferralCodes:
            currentMemberReferral === "true" ? "true" : undefined,
          includeExpired: currentShowExpired === "true" ? "true" : undefined,
          searchTerm: searchParams.get("searchTerm") || undefined,
          limit: pageSize,
          offset,
        }),
      );
      if (!res.success)
        throw new Error(res.message || "Failed to load promo codes");
      const items = res.data?.data || [];
      all.push(...items);
      const total = res.data?.pagination?.total;
      if (typeof total === "number" && total > MAX_EXPORT_ROWS) {
        throw new ExportLimitError(total);
      }
      if (all.length > MAX_EXPORT_ROWS) throw new ExportLimitError(all.length);
      const hasNext =
        res.data?.pagination?.hasNext ?? items.length === pageSize;
      if (items.length < pageSize || !hasNext) break;
      offset += pageSize;
    }
    const codes = all.map((p) => p.code).filter((c): c is string => Boolean(c));
    const analytics: Record<string, { total: number; loggedIn: number }> = {};
    if (codes.length > 0) {
      const res = await withTimeout(
        getSignupAggregate(codes, {
          from: windowRange.from,
          to: windowRange.to,
        }),
      );
      if (res.success && Array.isArray(res.data)) {
        for (const item of res.data) {
          const upper = String(item.code || "").toUpperCase();
          if (!analytics[upper]) {
            analytics[upper] = { total: 0, loggedIn: 0 };
          }
          analytics[upper].total += Number(item.signups || 0);
          analytics[upper].loggedIn += Number(item.loggedIn || 0);
        }
      }
    }
    const rows = all.map((p, i) => {
      const upper = String(p.code ?? "").toUpperCase();
      const a = analytics[upper];
      return {
        "#": i + 1,
        Name: String(p.name ?? ""),
        Code: String(p.code ?? ""),
        Registered: a?.total ?? 0,
        "Logged In": a?.loggedIn ?? 0,
        Status: (p.isActive ?? p.is_active) ? "Active" : "Inactive",
        "Expires At": fmtDate(p.expiresAt ?? p.expires_at),
        "Created At": fmtDate(p.createdAt ?? p.created_at),
      };
    });
    return {
      sheetName: "Promo Codes",
      columns: [
        { key: "#" as const, label: "#", width: 6 },
        { key: "Name" as const, label: "Name", width: 28 },
        { key: "Code" as const, label: "Code", width: 22 },
        { key: "Registered" as const, label: "Registered", width: 12 },
        { key: "Logged In" as const, label: "Logged In", width: 12 },
        { key: "Status" as const, label: "Status", width: 12 },
        { key: "Expires At" as const, label: "Expires At", width: 14 },
        { key: "Created At" as const, label: "Created At", width: 14 },
      ],
      rows,
    };
  };

  return (
    <PageArrival>
      <Stack px={28} py={20} gap="lg">
        <ConfirmModal
          opened={confirmState.opened}
          onClose={closeConfirm}
          title={confirmState.title}
          message={confirmState.message}
          confirmLabel="Delete"
          confirmColor="red"
          onConfirm={confirmState.onConfirm}
        />
        <Group justify="space-between" align="center">
          <Group gap="sm">
            <Title order={3}>Karma Subito Promo Code</Title>
            <InfoHint
              width={340}
              ariaLabel="Account / Servicing Center ID note"
              label="Note: Make sure to pass Core DB ID of Account Type ID and Servicing Center ID (VP id is stored in Core DB and the API expects Core DB ID rather than VP ID. Example: Account type id (vp) 48 is 44 in Core DB)."
            />
            {grandTotal !== null ? (
              /* No explicit colour — follows the theme accent. */
              <Badge variant="light" size="lg">
                <AnimatedNumber value={grandTotal} /> promo code
                {grandTotal === 1 ? "" : "s"}
              </Badge>
            ) : (
              <Skeleton height={26} width={96} radius="xl" />
            )}
            {datasetWide && dataset.loading && <Loader size="xs" />}
          </Group>
          <Group>
            <FilterTrigger
              activeCount={filterChips.length}
              onClick={() => setFiltersOpen(true)}
            />
            <ExportButton
              label="Export Promo Codes"
              filename="karma-subito-promo-codes"
              getData={getExportData}
              section="promoCodes"
            />
            {canViewAdminLogs && (
              <Button
                variant="light"
                color="gray"
                leftSection={<IconHistory size={16} />}
                onClick={openGlobalLogs}
              >
                Activity
              </Button>
            )}
            <Button
              variant="light"
              leftSection={<IconRefresh size={16} />}
              onClick={handleRefresh}
              loading={isRefreshing}
              loaderProps={{ size: "sm" }}
            >
              {isRefreshing ? "Refreshing..." : "Refresh"}
            </Button>
            {accessScope.create && (
              <Button
                size="compact-lg"
                fz={12}
                variant="filled"
                autoContrast
                leftSection={<IconPlus size={12} style={{ marginRight: -6 }} />}
                // Its own page now: the form is ~30 controls across five groups,
                // which a dialog cannot lay out.
                onClick={() => navigate("/admin/promo-codes/new")}
              >
                Create Promo Code
              </Button>
            )}
          </Group>
        </Group>

        <ActiveFilterChips chips={filterChips} onReset={handleResetFilters} />

        <PageSection>
          <StatTiles tiles={statTiles} loading={analyticsLoading} />
        </PageSection>

        <FilterDrawer
          opened={filtersOpen}
          onClose={() => setFiltersOpen(false)}
          activeCount={filterChips.length}
          onReset={handleResetFilters}
        >
          <FilterSection
            icon={<IconCalendarStats size={15} />}
            title="Created in"
            description="Restricts to codes created inside this window."
          >
            <SignupWindowControl
              value={signupWindow}
              onChange={setSignupWindow}
              label="Code Created in"
            />
          </FilterSection>

          <FilterSection
            icon={<IconTicket size={15} />}
            title="Code type"
            description="Status, expiry and referral flags."
          >
            <PromoCodeFilterFields
              showTitle={false}
              showInactive
              /* Read from the URL, not the local mirrors: the params are what the
                 query uses, and a chip removal or a back navigation changes those. */
              values={{
                isActive: currentIsActive === "true",
                inactiveOnly: currentIsActive === "false",
                includeExpired: currentShowExpired === "true",
                onlyPromoCodes: currentOnlyPromoCodes === "true",
                memberReferral: currentMemberReferral === "true",
              }}
              onChange={applyFilterValues}
            />
          </FilterSection>

          <FilterSection
            icon={<IconWorld size={15} />}
            title="Signups"
            description="Where signups came from, and whether to hide empty codes."
            withDivider={false}
          >
            <CountryMultiSelect
              value={countrySel}
              onChange={setCountrySel}
              options={effectiveCountryOptions}
            />
            <Switch
              label="Only codes with signups in window"
              checked={onlyWithSignups}
              onChange={(e) => setOnlyWithSignups(e.currentTarget.checked)}
            />
            {signupsActive && (
              <Text size="xs" c="dimmed">
                Showing every matching code with signups in the selected window
                {countrySel.length ? " / country" : ""} — {datasetRows.length}{" "}
                code
                {datasetRows.length === 1 ? "" : "s"}.
              </Text>
            )}
          </FilterSection>
        </FilterDrawer>

        {analyticsError && (
          <Alert color="red" variant="light">
            {analyticsError}
          </Alert>
        )}

        {analyticsLoading ? (
          <AnalyticsCardSkeleton />
        ) : (
          <CampaignAnalyticsCard
            analytics={analytics}
            visiblePromoCodeIds={visiblePromoCodeIds}
            countryFilter={countrySel}
            showBookingsAnalytics={false}
            /* Same destination as a table row. This page has its own
               campaign-free detail route, so it can drill in even though it
               isn't scoped to one campaign. */
            onPromoCodeClick={(promo) => navigate(promoDetailPath(promo.name))}
          />
        )}

        <ListTableCard
          title="Promo Codes"
          count={
            datasetWide
              ? datasetRows.length
              : totalCountKnown
                ? totalCount
                : promoCodes.length
          }
          controls={
            <>
              <TextInput
                placeholder="Quick search by code…"
                leftSection={<IconSearch size={16} />}
                value={searchTermInput}
                onChange={(e) => setSearchTermInput(e.currentTarget.value)}
                rightSection={
                  searchTermInput && (
                    <CloseButton
                      size="sm"
                      onClick={clearSearch}
                      title="Clear search"
                    />
                  )
                }
                style={{ flex: 1, minWidth: 240, maxWidth: 360 }}
              />
              <Select
                size="sm"
                variant="filled"
                w={180}
                value={sortBy}
                onChange={(v) => setSortBy(v || "default")}
                allowDeselect={false}
                data={[
                  { value: "code-asc", label: "Code (A–Z)" },
                  { value: "code-desc", label: "Code (Z–A)" },
                  { value: "default", label: "Newest first" },
                  { value: "signups-desc", label: "Most signups" },
                  { value: "signups-asc", label: "Least signups" },
                ]}
              />
            </>
          }
          footer={
            <ListTableFooter
              pageSize={currentLimit}
              pageSizeOptions={PROMO_CODES_PAGE_SIZE_OPTIONS}
              onPageSizeChange={handleLimitChange}
              rangeStart={
                datasetWide
                  ? datasetRows.length === 0
                    ? 0
                    : (datasetSafePage - 1) * currentLimit + 1
                  : promoCodes.length === 0
                    ? 0
                    : currentOffset + 1
              }
              rangeEnd={
                datasetWide
                  ? Math.min(datasetSafePage * currentLimit, datasetRows.length)
                  : currentOffset + promoCodes.length
              }
              total={
                datasetWide
                  ? datasetRows.length
                  : totalCountKnown
                    ? totalCount
                    : promoCodes.length
              }
              page={datasetWide ? datasetSafePage : currentPage}
              totalPages={datasetWide ? datasetTotalPages : totalPages}
              onPageChange={datasetWide ? setClientPage : handlePageChange}
            />
          }
        >
          <Box pos="relative">
            {/* `duplicating` is gone with the modal: duplicating now navigates to the
                create page, which does its own loading, so the table no longer waits. */}
            <LoadingOverlay
              visible={datasetWide && dataset.loading}
              zIndex={5}
              overlayProps={{ blur: 1 }}
            />
            <PromoCodesTable
              promoCodes={
                datasetWide
                  ? datasetPageItems
                  : (() => {
                      if (sortBy === "default") return promoCodes;
                      const signupsOf = (p: PromoCode) =>
                        (p.code
                          ? signupsByCode[p.code.toUpperCase()]
                          : undefined) ?? 0;
                      if (sortBy === "code-asc" || sortBy === "code-desc") {
                        return [...promoCodes].sort((a, b) => {
                          const aKey = String(
                            a.code ?? a.name ?? "",
                          ).toLowerCase();
                          const bKey = String(
                            b.code ?? b.name ?? "",
                          ).toLowerCase();
                          return sortBy === "code-asc"
                            ? aKey.localeCompare(bKey)
                            : bKey.localeCompare(aKey);
                        });
                      }
                      return [...promoCodes].sort((a, b) =>
                        sortBy === "signups-desc"
                          ? signupsOf(b) - signupsOf(a)
                          : signupsOf(a) - signupsOf(b),
                      );
                    })()
              }
              onEdit={handleEditClick}
              onDelete={handleDeleteClick}
              onDuplicate={
                accessScope.superAdmin ? handleDuplicateClick : undefined
              }
              onViewLogs={
                canViewAdminLogs
                  ? (promo) => {
                      setLogsPromo(promo);
                      openPromoLogs();
                    }
                  : undefined
              }
              onRowClick={goToDetails}
              canUpdate={accessScope.update}
              canDelete={accessScope.delete}
              rowOffset={
                datasetWide
                  ? (datasetSafePage - 1) * currentLimit
                  : currentOffset
              }
              signupsByCode={effectiveSignupsByCode}
              signupsLabel="Registered"
              loggedInByCode={effectiveLoggedInByCode}
            />
          </Box>
        </ListTableCard>

        {/* Global promo codes activity */}
        {canViewAdminLogs && (
          <Drawer
            opened={globalLogsOpened}
            onClose={closeGlobalLogs}
            title="Promo Code Activity"
            position="right"
            size="md"
          >
            <PromoLogsPanel promoId={null} />
          </Drawer>
        )}

        {canViewAdminLogs && (
          <Drawer
            opened={promoLogsOpened}
            onClose={closePromoLogs}
            title={
              logsPromo
                ? `Logs — ${logsPromo.name || logsPromo.code}`
                : "Promo Code Logs"
            }
            position="right"
            size="md"
          >
            <PromoLogsPanel
              promoId={logsPromo ? getPromoId(logsPromo) : null}
            />
          </Drawer>
        )}
      </Stack>
    </PageArrival>
  );
};

export default PromoCodesClientPage;
