"use client";

import {
  ActionIcon,
  Alert,
  Badge,
  Button,
  Card,
  Checkbox,
  Box,
  Container,
  Drawer,
  Group,
  Indicator,
  Loader,
  LoadingOverlay,
  MultiSelect,
  NumberInput,
  Pagination,
  Modal,
  Popover,
  Select,
  Stack,
  Switch,
  Table,
  Tabs,
  Text,
  TextInput,
  Title,
  Tooltip,
} from "@mantine/core";
import { notifications } from "@mantine/notifications";
import {
  IconArrowLeft,
  IconCalendarStats,
  IconFilter,
  IconPlus,
  IconTicket,
  IconTrash,
  IconSearch,
  IconUsers,
  IconWorld,
} from "@tabler/icons-react";
import { useEffect, useMemo, useRef, useState } from "react";
import { useNavigate } from "react-router";
import {
  attachPromoCodes,
  deleteCampaign,
  detachPromoCode,
  detachPromoCodes,
  updateCampaign,
} from "@/lib/features/promo-code-campaigns/action";
import {
  getCampaignAnalytics,
  getCampaignPromoCodes,
} from "@/lib/features/promo-code-campaigns/query";
import { useSessionStorageState } from "@/hooks/useSessionStorageState";
import ConfirmModal from "@/layouts/shared/ConfirmModal";
import { HScrollTable } from "@/lib/components/HScrollTable";
import {
  createPromoCode,
  type CreatePromoCodePayload,
} from "@/lib/features/promo-codes/action";
import {
  getPromoCodeByCode,
  listPromoCodes,
} from "@/lib/features/promo-codes/query";
import type {
  PromoCode,
  PromoCodeFormValues,
} from "@/lib/features/promo-codes/types";
import { PromoCodeFilterFields } from "@/lib/features/promo-codes/components/PromoCodeFilters";
import { useForm } from "@mantine/form";
import {
  buildPayload,
  emptyValues,
  validateForm,
} from "@/routes/promo-codes/form";
import { CreatePromoCodeModal } from "@/routes/promo-codes/CreatePromoCodeModal";
import { PromoCodesTable } from "@/routes/promo-codes/PromoCodesTable";
import { PromoLogsPanel } from "@/routes/promo-codes/_components/PromoLogsPanel";
import { CampaignLogsPanel } from "@/routes/promo-code-campaigns/_components/CampaignLogsPanel";
import { useRoleAccess } from "@/hooks/useRoleAccess";
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 {
  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 { useDisclosure } from "@mantine/hooks";
import {
  DEFAULT_SIGNUP_WINDOW,
  isWindowActive,
  resolveSignupWindow,
  SignupWindowControl,
  type SignupWindowState,
} from "@/routes/reports/_shared";
import { ExportButton } from "@/lib/export/ExportButton";
import { buildAnalyticsSheets } from "@/lib/export/analyticsSheets";
import {
  ExportLimitError,
  MAX_EXPORT_ROWS,
  withTimeout,
} from "@/lib/export/exportLimits";
import type {
  CampaignAnalyticsResponse,
  PromoCodeCampaign,
  PromoCodeCampaignMap,
} from "@/lib/features/promo-code-campaigns/types";
import type { AccessScope } from "@/lib/features/types";

interface Props {
  campaignId: string;
  initialCampaign:
    (PromoCodeCampaign & { promo_codes: PromoCodeCampaignMap[] }) | null;
  initialAnalytics: CampaignAnalyticsResponse | null;
  initialError?: string;
  accessScope: AccessScope;
}

export default function CampaignDetailsClientPage({
  campaignId,
  initialCampaign,
  initialAnalytics,
  initialError,
  accessScope,
}: Props) {
  const navigate = useNavigate();
  const [campaign, setCampaign] = useState(initialCampaign);
  const [analytics, setAnalytics] = useState(initialAnalytics);
  // Analytics is fetched client-side so the static page renders instantly and
  // the (slower) analytics shows a skeleton instead of blocking navigation.
  const [analyticsLoading, setAnalyticsLoading] = useState(!initialAnalytics);
  const [promoCodes, setPromoCodes] = useState<PromoCodeCampaignMap[]>(
    initialCampaign?.promo_codes ?? [],
  );
  const [promoDetailsById, setPromoDetailsById] = useState<
    Record<string, PromoCode>
  >({});
  const [name, setName] = useState(initialCampaign?.name ?? "");
  const [saving, setSaving] = useState(false);
  // Scope all persisted UI state to this campaign so each detail page keeps its
  // own tab / search / filters independently of other campaigns.
  const ck = (key: string) => `${key}:${campaignId}`;
  const [signupWindow, setSignupWindow] =
    useSessionStorageState<SignupWindowState>(
      ck("campaignDetail.window"),
      DEFAULT_SIGNUP_WINDOW,
    );
  const [attachOpen, setAttachOpen] = useState(false);
  const [createOpen, setCreateOpen] = useState(false);
  const [promoLogsOpened, { open: openPromoLogs, close: closePromoLogs }] =
    useDisclosure(false);
  const [logsPromo, setLogsPromo] = useState<PromoCode | null>(null);
  const { checkClientAccess } = useRoleAccess();
  const canViewAdminLogs = checkClientAccess("read", "admin-logs");
  // ---- Global filters (committed values, persisted per campaign) ----
  const [pcSearch, setPcSearch] = useSessionStorageState(
    ck("campaignDetail.pcSearch"),
    "",
  );
  const [pcOnlyWithSignups, setPcOnlyWithSignups] = useSessionStorageState(
    ck("campaignDetail.pcOnlyWithSignups"),
    false,
  );
  const [pcMinSignups, setPcMinSignups] = useSessionStorageState<number | null>(
    ck("campaignDetail.pcMinSignups"),
    null,
  );
  const [pcMaxSignups, setPcMaxSignups] = useSessionStorageState<number | null>(
    ck("campaignDetail.pcMaxSignups"),
    null,
  );
  // Default ordering: most registered (signups) first, not alphabetical.
  const [pcSortBy, setPcSortBy] = useSessionStorageState<string | null>(
    ck("campaignDetail.pcSort"),
    "signups-desc",
  );
  const [analyticsCountry, setAnalyticsCountry] = useSessionStorageState<
    string[]
  >(ck("campaignDetail.analyticsCountry"), []);

  // ---- Promo-code tab pagination + lazy detail loading ----
  // The attached promo codes (id + name) are cheap and load with the campaign,
  // but each row's full detail (status / expiry) needs a per-code fetch. To
  // avoid firing hundreds of requests on mount we paginate the table and only
  // fetch details for the rows on the visible page, caching what we've loaded.
  const [pcPageSize, setPcPageSize] = useState(10);
  const [activeTab, setActiveTab] = useSessionStorageState<string | null>(
    ck("campaignDetail.tab"),
    "details",
  );
  const [pcPage, setPcPage] = useState(1);
  const pcFetchingRef = useRef<Set<string>>(new Set());
  const [pcDetailsLoading, setPcDetailsLoading] = useState(false);

  const setPcWindow = (next: SignupWindowState) => {
    setSignupWindow(next);
    void refreshAnalytics(next);
  };

  const resetPcFilters = () => {
    setPcOnlyWithSignups(false);
    setPcMinSignups(null);
    setPcMaxSignups(null);
    setSignupWindow(DEFAULT_SIGNUP_WINDOW);
    setAnalyticsCountry([]);
    void refreshAnalytics(DEFAULT_SIGNUP_WINDOW);
  };

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

  /*
   * One chip per applied filter, shown on the page rather than in the drawer.
   * The drawer hides its own state once dismissed, so without these a narrowed
   * dataset would look like the full one.
   */
  const filterChips = useMemo<FilterChip[]>(() => {
    const chips: FilterChip[] = [];
    if (isWindowActive(signupWindow)) {
      chips.push({
        key: "window",
        label: "Signup window",
        onRemove: () => setPcWindow(DEFAULT_SIGNUP_WINDOW),
      });
    }
    for (const country of analyticsCountry) {
      chips.push({
        key: `country:${country}`,
        label: `Country: ${country}`,
        onRemove: () =>
          setAnalyticsCountry(analyticsCountry.filter((c) => c !== country)),
      });
    }
    if (pcMinSignups !== null || pcMaxSignups !== null) {
      const lo = pcMinSignups ?? 0;
      chips.push({
        key: "signup-range",
        label:
          pcMaxSignups === null
            ? `Signups ≥ ${lo}`
            : `Signups ${lo}–${pcMaxSignups}`,
        onRemove: () => {
          setPcMinSignups(null);
          setPcMaxSignups(null);
        },
      });
    }
    if (pcOnlyWithSignups) {
      chips.push({
        key: "only-with-signups",
        label: "With signups only",
        onRemove: () => setPcOnlyWithSignups(false),
      });
    }
    return chips;
  }, [
    signupWindow,
    analyticsCountry,
    pcMinSignups,
    pcMaxSignups,
    pcOnlyWithSignups,
  ]);
  const [pcSelected, setPcSelected] = useState<Set<string>>(new Set());
  const [pcBulkDetachOpen, setPcBulkDetachOpen] = useState(false);
  const [pcBulkBusy, setPcBulkBusy] = useState(false);

  const performBulkDetach = async () => {
    setPcBulkBusy(true);
    try {
      const ids = Array.from(pcSelected);
      // Single bulk call → one DELETE → one activity-log entry (mirrors attach).
      const res = await detachPromoCodes(campaignId, ids);
      if (res.success) {
        const count = Array.isArray(res.data) ? res.data.length : ids.length;
        notifications.show({ color: "green", message: `Detached ${count}` });
      } else {
        notifications.show({
          color: "red",
          message: res.message || "Failed to detach",
        });
      }
      setPcSelected(new Set());
      void refreshPromoCodes();
    } finally {
      setPcBulkBusy(false);
      setPcBulkDetachOpen(false);
    }
  };

  // Detach every attached code across ALL pages (not just the current selection).
  // `promoCodes` holds the full attached list, so one bulk DELETE clears the campaign.
  const [pcDetachAllOpen, setPcDetachAllOpen] = useState(false);
  const [pcDetachAllBusy, setPcDetachAllBusy] = useState(false);

  const performDetachAll = async () => {
    setPcDetachAllBusy(true);
    try {
      const ids = promoCodes.map((p) => p.promo_code_id);
      const res = await detachPromoCodes(campaignId, ids);
      if (res.success) {
        const count = Array.isArray(res.data) ? res.data.length : ids.length;
        notifications.show({
          color: "green",
          message: `Detached all ${count}`,
        });
      } else {
        notifications.show({
          color: "red",
          message: res.message || "Failed to detach all",
        });
      }
      setPcSelected(new Set());
      void refreshPromoCodes();
    } finally {
      setPcDetachAllBusy(false);
      setPcDetachAllOpen(false);
    }
  };
  const [creating, setCreating] = useState(false);
  const createForm = useForm<PromoCodeFormValues>({
    initialValues: emptyValues,
    validate: validateForm,
  });

  const handleCreatePromoCode = async (values: PromoCodeFormValues) => {
    setCreating(true);
    try {
      const payload = buildPayload(values) as CreatePromoCodePayload;
      const res = await createPromoCode(payload);
      if (!res.success) {
        notifications.show({
          color: "red",
          message: res.message || "Failed to create promo code",
        });
        createForm.setErrors({
          _form: res.message || "Failed to create promo code",
        });
        return;
      }
      // Auto-attach the new promo code to this campaign
      const newId = String(
        (res.data as any)?.id ??
          (res.data as any)?.promoCodeId ??
          (res.data as any)?.code ??
          values.code,
      );
      const newName = String(values.code || values.name || newId);
      if (newId) {
        await attachPromoCodes(campaignId, [
          { promo_code_id: newId, promo_code_name: newName },
        ]);
      }
      notifications.show({
        color: "green",
        message: "Promo code created and attached",
      });
      createForm.reset();
      setCreateOpen(false);
      void refreshPromoCodes();
    } catch (e: any) {
      notifications.show({
        color: "red",
        message: e?.message || "Failed to create promo code",
      });
      createForm.setErrors({
        _form: e?.message || "Failed to create promo code",
      });
    } finally {
      setCreating(false);
    }
  };

  useEffect(() => {
    setName(initialCampaign?.name ?? "");
  }, [initialCampaign?.name]);

  const refreshAnalytics = async (win: SignupWindowState) => {
    setAnalyticsLoading(true);
    try {
      const { from, to } = resolveSignupWindow(win);
      const res = await getCampaignAnalytics(campaignId, { from, to });
      if (res.success) setAnalytics(res.data);
    } finally {
      setAnalyticsLoading(false);
    }
  };

  // Fetch analytics on mount (the loader no longer blocks on it). Runs once.
  // Also refetch when a non-default signup window was restored from a previous
  // visit, since the loader's initialAnalytics is computed for the full window.
  useEffect(() => {
    if (!initialAnalytics || isWindowActive(signupWindow)) {
      void refreshAnalytics(signupWindow);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const refreshPromoCodes = async () => {
    // Refetch BOTH the promo-codes list and analytics so that newly attached
    // codes immediately show their signups instead of "0" until reload.
    const [codesRes] = await Promise.all([
      getCampaignPromoCodes(campaignId),
      refreshAnalytics(signupWindow),
    ]);
    if (codesRes.success) setPromoCodes(codesRes.data);
  };

  // Promo-code IDs that have signups from the selected countries (empty = all).
  const countryFilteredCodeIds = useMemo(() => {
    if (analyticsCountry.length === 0 || !analytics) return null;
    const ids = new Set<string>();
    for (const row of analytics.analytics.byCountryPromoCode) {
      if (
        analyticsCountry.includes(String(row.country ?? "")) &&
        row.signups > 0
      )
        ids.add(row.promo_code_id);
    }
    return ids;
  }, [analytics, analyticsCountry]);

  // Available country options derived from the current analytics window.
  const analyticsCountries = useMemo(() => {
    if (!analytics) return [];
    const seen = new Set<string>();
    for (const row of analytics.analytics.byCountryPromoCode) {
      if (row.country) seen.add(String(row.country));
    }
    return [...seen].sort();
  }, [analytics]);

  // Promo-code IDs that pass the current table filters (search / signups /
  // country / window). Charts use this set so they react to the same filters as
  // the table. An active signup window also drops codes with no signups in that
  // window, keeping the charts and list in sync with the analytics.
  const visiblePromoCodeIds = useMemo(() => {
    if (!analytics) return new Set<string>();
    const signupsById = new Map(
      analytics.analytics.byPromoCode.map(
        (row) => [row.promo_code_id, row.signups] as const,
      ),
    );
    const ids = new Set<string>();
    for (const p of promoCodes) {
      const name = p.promo_code_name || "";
      if (
        pcSearch.trim() &&
        !name.toLowerCase().includes(pcSearch.trim().toLowerCase())
      )
        continue;
      const s = signupsById.get(p.promo_code_id) ?? 0;
      if ((pcOnlyWithSignups || isWindowActive(signupWindow)) && s <= 0)
        continue;
      if (pcMinSignups !== null && s < pcMinSignups) continue;
      if (pcMaxSignups !== null && s > pcMaxSignups) continue;
      if (
        countryFilteredCodeIds &&
        !countryFilteredCodeIds.has(p.promo_code_id)
      )
        continue;
      ids.add(p.promo_code_id);
    }
    return ids;
  }, [
    analytics,
    promoCodes,
    pcSearch,
    pcOnlyWithSignups,
    pcMinSignups,
    pcMaxSignups,
    countryFilteredCodeIds,
    signupWindow,
  ]);

  const campaignSignupsByCode = useMemo<Record<string, number>>(() => {
    if (!analytics) return {};
    const idToSignups = new Map(
      analytics.analytics.byPromoCode.map(
        (row) => [row.promo_code_id, row.signups] as const,
      ),
    );
    const out: Record<string, number> = {};
    for (const p of analytics.promo_codes) {
      out[p.promo_code_name.toUpperCase()] =
        idToSignups.get(p.promo_code_id) ?? 0;
    }
    return out;
  }, [analytics]);

  // Logged-in members per code (members.first_access_attempt IS TRUE), keyed by
  // uppercase code name. Feeds the highlighted "Logged in" column in the table.
  const campaignLoggedInByCode = useMemo<Record<string, number>>(() => {
    if (!analytics) return {};
    const idToLoggedIn = new Map(
      analytics.analytics.byPromoCode.map(
        (row) => [row.promo_code_id, row.logged_in] as const,
      ),
    );
    const out: Record<string, number> = {};
    for (const p of analytics.promo_codes) {
      out[p.promo_code_name.toUpperCase()] =
        idToLoggedIn.get(p.promo_code_id) ?? 0;
    }
    return out;
  }, [analytics]);

  const campaignInternalBookingsByCode = useMemo<Record<string, number>>(() => {
    if (!analytics) return {};
    const idToInternal = new Map(
      analytics.analytics.byPromoCode.map(
        (row) => [row.promo_code_id, row.internal_bookings ?? 0] as const,
      ),
    );
    const out: Record<string, number> = {};
    for (const p of analytics.promo_codes) {
      out[p.promo_code_name.toUpperCase()] =
        idToInternal.get(p.promo_code_id) ?? 0;
    }
    return out;
  }, [analytics]);

  const campaignExternalBookingsByCode = useMemo<Record<string, number>>(() => {
    if (!analytics) return {};
    const idToExternal = new Map(
      analytics.analytics.byPromoCode.map(
        (row) => [row.promo_code_id, row.external_bookings ?? 0] as const,
      ),
    );
    const out: Record<string, number> = {};
    for (const p of analytics.promo_codes) {
      out[p.promo_code_name.toUpperCase()] =
        idToExternal.get(p.promo_code_id) ?? 0;
    }
    return out;
  }, [analytics]);

  // Signups (registered count) for a given code (0 when none). Drives the
  // signups sort and filters.
  const signupsOf = (code: string) =>
    campaignSignupsByCode[code.toUpperCase()] ?? 0;

  // Apply the shared search / signup / date filters and the active sort to a
  // list of attached codes. Pure + cheap — it only touches the lightweight
  // {id, name, created_at} metadata, never the per-code detail.
  const filterAndSortCodes = (list: PromoCodeCampaignMap[]) => {
    const filtered = list.filter((r) => {
      if (
        pcSearch.trim() &&
        !r.promo_code_name.toLowerCase().includes(pcSearch.trim().toLowerCase())
      )
        return false;
      const s = signupsOf(r.promo_code_name);
      // A signup window narrows the list to codes with signups in that window so
      // the rows match the analytics (mirrors the campaigns list page).
      if ((pcOnlyWithSignups || isWindowActive(signupWindow)) && s <= 0)
        return false;
      if (pcMinSignups !== null && s < pcMinSignups) return false;
      if (pcMaxSignups !== null && s > pcMaxSignups) return false;
      if (countryFilteredCodeIds) {
        const id = r.promo_code_id;
        if (!countryFilteredCodeIds.has(id)) return false;
      }
      return true;
    });
    return [...filtered].sort((a, b) => {
      switch (pcSortBy) {
        case "name-desc":
          return b.promo_code_name.localeCompare(a.promo_code_name);
        case "signups-desc":
          return signupsOf(b.promo_code_name) - signupsOf(a.promo_code_name);
        case "signups-asc":
          return signupsOf(a.promo_code_name) - signupsOf(b.promo_code_name);
        case "newest":
          return (
            new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
          );
        case "oldest":
          return (
            new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
          );
        case "name-asc":
        default:
          return a.promo_code_name.localeCompare(b.promo_code_name);
      }
    });
  };

  // Whole attached list after filters/sort — drives the count labels so they
  // stay in sync with the rows actually shown.
  const filteredPromoCodes = useMemo(
    () => filterAndSortCodes(promoCodes),
    [
      promoCodes,
      pcSearch,
      pcSortBy,
      pcOnlyWithSignups,
      pcMinSignups,
      pcMaxSignups,
      countryFilteredCodeIds,
      campaignSignupsByCode,
      signupWindow,
    ],
  );

  const { series: seriesPalette } = usePromoTheme();

  /*
   * Headline figures for the campaign, all restricted to the promo codes that
   * pass the current filters — so the tiles and the analytics charts below can
   * never disagree about the totals.
   */
  const statTiles = useMemo<StatTileSpec[]>(() => {
    const rows = (analytics?.analytics.byPromoCode ?? []).filter((row) =>
      visiblePromoCodeIds.has(row.promo_code_id),
    );
    const signups = rows.reduce(
      (sum, row) => sum + Number(row.signups || 0),
      0,
    );
    const internal = rows.reduce(
      (sum, row) => sum + Number(row.internal_bookings || 0),
      0,
    );
    const curated = rows.reduce(
      (sum, row) => sum + Number(row.external_bookings || 0),
      0,
    );
    const countries = new Set(
      (analytics?.analytics.byCountryPromoCode ?? [])
        .filter(
          (row) =>
            visiblePromoCodeIds.has(row.promo_code_id) &&
            row.signups > 0 &&
            row.country,
        )
        .map((row) => row.country),
    );
    const bookings = internal + curated;
    const paint = seriesPalette(4);
    return [
      {
        key: "codes",
        label: "Promo codes",
        value: filteredPromoCodes.length,
        icon: <IconTicket size={17} />,
        color: paint[0].from,
        colorTo: paint[0].to,
        hint:
          filteredPromoCodes.length === promoCodes.length
            ? "All attached codes"
            : `of ${promoCodes.length} attached`,
      },
      {
        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: "bookings",
        label: "Bookings",
        value: bookings,
        icon: <IconCalendarStats size={17} />,
        color: paint[2].from,
        colorTo: paint[2].to,
        hint: `${internal} Karma Subito · ${curated} curated`,
      },
      {
        key: "countries",
        label: "Countries",
        value: countries.size,
        icon: <IconWorld size={17} />,
        color: paint[3].from,
        colorTo: paint[3].to,
        hint: "With at least one signup",
      },
    ];
  }, [
    analytics,
    visiblePromoCodeIds,
    filteredPromoCodes.length,
    promoCodes.length,
    signupWindow,
    seriesPalette,
  ]);

  // The fully filtered/sorted list and the slice for the visible page of the
  // promo-codes tab. Used both to render and to decide which details to
  // lazy-fetch, so the two always stay in sync. We no longer split codes into
  // signed-up / non-signed-up — a single "Promo Codes" list is shown.
  const activePartition = activeTab === "promo-codes" ? promoCodes : [];
  const activeSorted = useMemo(
    () => filterAndSortCodes(activePartition),
    [
      activePartition,
      pcSearch,
      pcSortBy,
      pcOnlyWithSignups,
      pcMinSignups,
      pcMaxSignups,
      countryFilteredCodeIds,
      campaignSignupsByCode,
      signupWindow,
    ],
  );
  const activePageCodes = useMemo(
    () => activeSorted.slice((pcPage - 1) * pcPageSize, pcPage * pcPageSize),
    [activeSorted, pcPage, pcPageSize],
  );

  // Reset to the first page whenever the visible set or page size changes.
  useEffect(() => {
    setPcPage(1);
  }, [
    activeTab,
    pcSearch,
    pcSortBy,
    pcOnlyWithSignups,
    pcMinSignups,
    pcMaxSignups,
    countryFilteredCodeIds,
    pcPageSize,
  ]);

  useEffect(() => {
    const toFetch = activePageCodes.filter(
      (r) => !pcFetchingRef.current.has(r.promo_code_id),
    );
    if (toFetch.length === 0) return;
    toFetch.forEach((r) => pcFetchingRef.current.add(r.promo_code_id));
    setPcDetailsLoading(true);
    (async () => {
      const fetchRow = async (row: (typeof toFetch)[number]) => {
        const code = row.promo_code_name || row.promo_code_id;
        if (!code) return null;
        try {
          const res = await getPromoCodeByCode(code);
          if (res?.success && res.data) {
            const inner = res.data as any;
            const promo: PromoCode =
              inner && typeof inner === "object" && "data" in inner
                ? (inner.data as PromoCode)
                : (inner as PromoCode);
            if (promo) return { id: row.promo_code_id, promo };
          }
        } catch {
          /* ignore */
        }
        pcFetchingRef.current.delete(row.promo_code_id);
        return null;
      };
      for (let i = 0; i < toFetch.length; i += 6) {
        const batch = await Promise.all(toFetch.slice(i, i + 6).map(fetchRow));
        const found = batch.filter((r): r is { id: string; promo: PromoCode } =>
          Boolean(r),
        );
        if (found.length > 0) {
          setPromoDetailsById((prev) => {
            const next = { ...prev };
            for (const r of found) next[r.id] = r.promo;
            return next;
          });
        }
      }
      setPcDetailsLoading(false);
    })();
  }, [activePageCodes]);

  const onSaveDetails = async () => {
    if (!name.trim()) {
      notifications.show({ color: "red", message: "Name is required" });
      return;
    }
    setSaving(true);
    try {
      const res = await updateCampaign(campaignId, { name: name.trim() });
      if (res.success) {
        notifications.show({ color: "green", message: "Campaign updated" });
        setCampaign((prev) => (prev ? { ...prev, name: res.data.name } : prev));
      } else {
        notifications.show({
          color: "red",
          message: res.message || "Failed to update",
        });
      }
    } finally {
      setSaving(false);
    }
  };

  const [deleteCampaignOpen, setDeleteCampaignOpen] = useState(false);
  const [detachTarget, setDetachTarget] = useState<{
    promoCodeId: string;
    promoCodeName: string;
  } | null>(null);

  const performDelete = async () => {
    const res = await deleteCampaign(campaignId);
    if (res.success) {
      notifications.show({ color: "green", message: "Campaign deleted" });
      navigate("/admin/promo-code-campaigns");
    } else {
      notifications.show({
        color: "red",
        message: res.message || "Failed to delete",
      });
    }
  };

  const performDetach = async () => {
    if (!detachTarget) return;
    const res = await detachPromoCode(campaignId, detachTarget.promoCodeId);
    if (res.success) {
      notifications.show({ color: "green", message: "Promo code detached" });
      void refreshPromoCodes();
    } else {
      notifications.show({
        color: "red",
        message: res.message || "Failed to detach",
      });
    }
  };

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

  const getCampaignExportData = async (signal: AbortSignal) => {
    const list = filterAndSortCodes(promoCodes);
    // Block oversized exports up front (before fetching details for every code).
    if (list.length > MAX_EXPORT_ROWS) {
      throw new ExportLimitError(list.length);
    }
    const details: Record<string, any> = { ...promoDetailsById };
    let missing = list.filter((p) => !details[p.promo_code_id]);
    if (missing.length > 0) {
      const missingByName = new Map<string, string>(); // code name (uppercase) -> promo_code_id
      for (const p of list) {
        if (!details[p.promo_code_id]) {
          missingByName.set(p.promo_code_name.toUpperCase(), p.promo_code_id);
        }
      }
      let offset = 0;
      const pageSize = 250;
      while (missing.length > 0) {
        if (signal.aborted)
          throw new DOMException("Export cancelled", "AbortError");
        const res = await withTimeout(
          listPromoCodes({ limit: pageSize, offset }),
        );
        if (!res.success) break;
        const items = res.data?.data || [];
        if (items.length === 0) break;

        for (const item of items) {
          const itemCode = String(item.code || item.name || "").toUpperCase();
          const targetId = missingByName.get(itemCode);
          if (targetId) {
            details[targetId] = item;
          }
          // Also check by ID directly
          const itemId = item.id || item.promoCodeId || item.promo_code_id;
          if (itemId && list.some((p) => p.promo_code_id === itemId)) {
            details[itemId as string] = item;
          }
        }

        missing = list.filter((p) => !details[p.promo_code_id]);
        if (items.length < pageSize) break;
        offset += pageSize;
      }
    }
    const rows = list.map((p, i) => {
      const upper = p.promo_code_name.toUpperCase();
      const d = details[p.promo_code_id];
      return {
        "#": i + 1,
        Name: String(d?.name ?? p.promo_code_name),
        Code: p.promo_code_name,
        Registered: campaignSignupsByCode[upper] ?? 0,
        "Logged In": campaignLoggedInByCode[upper] ?? 0,
        Status: d ? ((d.isActive ?? d.is_active) ? "Active" : "Inactive") : "",
        "Expires At": fmtDateValue(d?.expiresAt ?? d?.expires_at),
        "Created At": fmtDateValue(d?.createdAt ?? d?.created_at),
        "Attached At": fmtDateValue(p.created_at),
      };
    });
    return {
      sheetName: "Promo Codes",
      columns: [
        { key: "#" as const, label: "#", width: 6 },
        { key: "Name" as const, label: "Name", width: 26 },
        { key: "Code" as const, label: "Code", width: 24 },
        { 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 },
        { key: "Attached At" as const, label: "Attached At", width: 14 },
      ],
      rows,
      extraSheets: buildAnalyticsSheets(analytics, visiblePromoCodeIds),
    };
  };

  if (!campaign) {
    return (
      <Container fluid px="xl" py="xl">
        <Alert color="red">{initialError ?? "Campaign not found"}</Alert>
      </Container>
    );
  }

  const renderPromoCodesPanel = () => {
    const partitioned = promoCodes;
    return (
      <Card withBorder radius="lg" p="lg">
        <Group justify="space-between" mb="sm">
          <Text fw={700} size="lg">
            Promo Codes ({filteredPromoCodes.length})
          </Text>
          <Group gap="xs">
            {accessScope.update && (
              <>
                {pcSelected.size > 0 && (
                  <Button
                    size="sm"
                    color="red"
                    variant="light"
                    leftSection={<IconTrash size={14} />}
                    onClick={() => setPcBulkDetachOpen(true)}
                  >
                    Detach {pcSelected.size} selected
                  </Button>
                )}
                <Button
                  leftSection={<IconPlus size={16} />}
                  variant="light"
                  onClick={() => setAttachOpen(true)}
                >
                  Attach
                </Button>
                <Button
                  leftSection={<IconPlus size={16} />}
                  onClick={() => {
                    createForm.reset();
                    setCreateOpen(true);
                  }}
                >
                  Create new
                </Button>
                {promoCodes.length > 0 && (
                  <Button
                    color="red"
                    variant="outline"
                    leftSection={<IconTrash size={14} />}
                    onClick={() => setPcDetachAllOpen(true)}
                  >
                    Detach all
                  </Button>
                )}
              </>
            )}
          </Group>
        </Group>
        <Group
          gap="sm"
          mb="md"
          align="center"
          wrap="wrap"
          justify="space-between"
        >
          <TextInput
            placeholder="Quick search by code…"
            leftSection={<IconSearch size={16} />}
            value={pcSearch}
            onChange={(e) => setPcSearch(e.currentTarget.value)}
            style={{ flex: 1, minWidth: 240, maxWidth: 360 }}
          />
          <Select
            size="sm"
            variant="filled"
            w={180}
            value={pcSortBy ?? "signups-desc"}
            onChange={(v) => setPcSortBy(v ?? "signups-desc")}
            allowDeselect={false}
            data={[
              { value: "signups-desc", label: "Most signups" },
              { value: "signups-asc", label: "Least signups" },
              { value: "name-asc", label: "Name (A–Z)" },
              { value: "name-desc", label: "Name (Z–A)" },
              { value: "newest", label: "Newest attached" },
              { value: "oldest", label: "Oldest attached" },
            ]}
          />
        </Group>
        {(() => {
          const sorted = filterAndSortCodes(partitioned);
          const pageCount = Math.max(1, Math.ceil(sorted.length / pcPageSize));
          const safePage = Math.min(pcPage, pageCount);
          const pageItems = sorted.slice(
            (safePage - 1) * pcPageSize,
            safePage * pcPageSize,
          );
          const tableRows: PromoCode[] = pageItems.map((row) => {
            const detail = promoDetailsById[row.promo_code_id];
            const code = row.promo_code_name;
            return (
              detail ?? {
                id: row.promo_code_id,
                code,
                name: code,
              }
            );
          });
          const addedAtByCode: Record<string, string> = {};
          for (const r of pageItems) {
            if (r.promo_code_name && r.created_at) {
              addedAtByCode[r.promo_code_name.toUpperCase()] = r.created_at;
            }
          }
          const codeToMap = new Map(
            pageItems.map((r) => [r.promo_code_name.toUpperCase(), r] as const),
          );
          const rangeStart =
            sorted.length === 0 ? 0 : (safePage - 1) * pcPageSize + 1;
          const rangeEnd = Math.min(safePage * pcPageSize, sorted.length);
          return (
            <>
              <PromoCodesTable
                promoCodes={tableRows}
                signupsByCode={campaignSignupsByCode}
                signupsLabel="Registered"
                loggedInByCode={campaignLoggedInByCode}
                internalBookingsByCode={campaignInternalBookingsByCode}
                externalBookingsByCode={campaignExternalBookingsByCode}
                addedAtByCode={addedAtByCode}
                selection={
                  accessScope.update
                    ? {
                        ids: pcSelected,
                        onChange: setPcSelected,
                        getId: (p) =>
                          codeToMap.get(String(p.code || "").toUpperCase())
                            ?.promo_code_id || "",
                      }
                    : undefined
                }
                onRowClick={(p) =>
                  navigate(
                    `/admin/promo-code-campaigns/${campaignId}/promo-codes/${encodeURIComponent(
                      p.code || "",
                    )}`,
                  )
                }
                hideUpdatedAt
                onDetach={
                  accessScope.update
                    ? (p) => {
                        const map = codeToMap.get(
                          String(p.code || "").toUpperCase(),
                        );
                        if (map) {
                          setDetachTarget({
                            promoCodeId: map.promo_code_id,
                            promoCodeName: map.promo_code_name,
                          });
                        }
                      }
                    : undefined
                }
                canUpdate={accessScope.update}
                emptyMessage="No promo codes match the filters."
              />
              {sorted.length > 0 && (
                <Group justify="space-between" mt="md" align="center">
                  <Group gap="xs" align="center">
                    <Select
                      size="sm"
                      variant="filled"
                      w={90}
                      value={String(pcPageSize)}
                      onChange={(v) => setPcPageSize(Number(v) || 25)}
                      data={["10", "25", "50", "100"]}
                      aria-label="Entries per page"
                      allowDeselect={false}
                      searchable={false}
                    />
                    <Text size="sm" c="dimmed">
                      Entries per page
                    </Text>
                    <Text size="sm" c="dimmed">
                      · Showing {rangeStart}–{rangeEnd} of {sorted.length}
                    </Text>
                    {pcDetailsLoading && <Loader size="xs" />}
                  </Group>
                  {pageCount > 1 && (
                    <Pagination
                      value={safePage}
                      onChange={setPcPage}
                      total={pageCount}
                      size="sm"
                    />
                  )}
                </Group>
              )}
            </>
          );
        })()}
      </Card>
    );
  };

  return (
    <PageArrival>
      <Container fluid px="xl" py="xl">
        <Group justify="space-between" mb="lg">
          <Group>
            <ActionIcon
              variant="subtle"
              onClick={() => navigate("/admin/promo-code-campaigns")}
            >
              <IconArrowLeft size={18} />
            </ActionIcon>
            <Title order={2}>{campaign.name}</Title>
            {/* No explicit colour — follows the theme accent. */}
            <Badge variant="light" size="lg">
              {promoCodes.length} promo code{promoCodes.length === 1 ? "" : "s"}
            </Badge>
          </Group>
          <Group gap="xs" align="center">
            <FilterTrigger
              activeCount={filterChips.length}
              onClick={() => setFiltersOpen(true)}
            />
            <ExportButton
              label="Export Promo Codes"
              filename={`campaign-${campaign.name}-promo-codes`}
              getData={getCampaignExportData}
              section="campaigns"
            />
          </Group>
        </Group>

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

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

        <FilterDrawer
          opened={filtersOpen}
          onClose={() => setFiltersOpen(false)}
          activeCount={filterChips.length}
          onReset={resetPcFilters}
        >
          <FilterSection
            icon={<IconCalendarStats size={15} />}
            title="Signup window"
            description="Limits signups, bookings and every chart below."
          >
            <SignupWindowControl value={signupWindow} onChange={setPcWindow} />
          </FilterSection>

          <FilterSection
            icon={<IconWorld size={15} />}
            title="Country"
            description="Keeps only codes with signups from these countries."
          >
            <MultiSelect
              label="Country (signups from)"
              placeholder={analyticsCountry.length ? undefined : "Any country"}
              value={analyticsCountry}
              onChange={setAnalyticsCountry}
              data={analyticsCountries}
              clearable
              searchable
              hidePickedOptions
            />
          </FilterSection>

          <FilterSection
            icon={<IconUsers size={15} />}
            title="Signup volume"
            description="Bounds on a code's signup count."
            withDivider={false}
          >
            <Group gap="sm" grow>
              <NumberInput
                label="Min signups"
                placeholder="Any"
                value={pcMinSignups ?? ""}
                onChange={(v) => setPcMinSignups(v === "" ? null : Number(v))}
                min={0}
              />
              <NumberInput
                label="Max signups"
                placeholder="Any"
                value={pcMaxSignups ?? ""}
                onChange={(v) => setPcMaxSignups(v === "" ? null : Number(v))}
                min={0}
              />
            </Group>
            <Switch
              label="Only show promo codes with signups"
              checked={pcOnlyWithSignups}
              onChange={(e) => setPcOnlyWithSignups(e.currentTarget.checked)}
            />
          </FilterSection>
        </FilterDrawer>

        <Box mt="md">
          {!analytics && analyticsLoading ? (
            <AnalyticsCardSkeleton />
          ) : (
            <Box pos="relative">
              <LoadingOverlay
                visible={analyticsLoading}
                zIndex={5}
                overlayProps={{ blur: 1 }}
              />
              <CampaignAnalyticsCard
                analytics={analytics}
                visiblePromoCodeIds={visiblePromoCodeIds}
                /* Same destination the promo codes table rows go to: the detail
                   route is keyed by the code itself, which is what
                   `promo_code_name` holds. */
                onPromoCodeClick={(promo) =>
                  navigate(
                    `/admin/promo-code-campaigns/${campaignId}/promo-codes/${encodeURIComponent(
                      promo.name,
                    )}`,
                  )
                }
              />
            </Box>
          )}
        </Box>

        <Tabs value={activeTab} onChange={setActiveTab} mt="md">
          <Tabs.List>
            <Tabs.Tab value="details">Campaign Details</Tabs.Tab>
            <Tabs.Tab value="promo-codes">
              Promo Codes ({filteredPromoCodes.length})
            </Tabs.Tab>
          </Tabs.List>

          <Tabs.Panel value="details" pt="md">
            <Stack>
              <Card withBorder radius="lg" p="lg">
                <Text fw={700} size="lg" mb="sm">
                  Campaign Details
                </Text>
                <Stack>
                  <TextInput
                    label="Campaign Name"
                    required
                    value={name}
                    onChange={(e) => setName(e.currentTarget.value)}
                    disabled={!accessScope.update}
                  />
                  <Group justify="flex-end">
                    <Button
                      onClick={onSaveDetails}
                      loading={saving}
                      disabled={!accessScope.update || name === campaign.name}
                    >
                      Save
                    </Button>
                  </Group>
                </Stack>
              </Card>

              {canViewAdminLogs && (
                <Card withBorder radius="lg" p="lg">
                  <CampaignLogsPanel campaignId={campaignId} />
                </Card>
              )}
            </Stack>
          </Tabs.Panel>

          <Tabs.Panel value="promo-codes" pt="md" keepMounted={false}>
            {activeTab === "promo-codes" && renderPromoCodesPanel()}
          </Tabs.Panel>
        </Tabs>

        <ConfirmModal
          opened={deleteCampaignOpen}
          onClose={() => setDeleteCampaignOpen(false)}
          title="Delete campaign?"
          message={`Are you sure you want to delete campaign "${campaign.name}"?`}
          confirmLabel="Delete"
          cancelLabel="Cancel"
          confirmColor="red"
          onConfirm={performDelete}
        />

        <ConfirmModal
          opened={pcBulkDetachOpen}
          onClose={() => !pcBulkBusy && setPcBulkDetachOpen(false)}
          title={`Detach ${pcSelected.size} attachment${pcSelected.size === 1 ? "" : "s"} from "${campaign?.name ?? "this campaign"}"?`}
          message={`This removes ${pcSelected.size === 1 ? "the link" : "the links"} between the selected promo code${pcSelected.size === 1 ? "" : "s"} and this campaign.\n\nThe promo code${pcSelected.size === 1 ? "" : "s"} themselves are NOT deleted — they will still exist and remain attached to any other campaigns.`}
          confirmLabel="Detach attachment"
          cancelLabel="Cancel"
          confirmColor="red"
          onConfirm={performBulkDetach}
        />

        <ConfirmModal
          opened={pcDetachAllOpen}
          onClose={() => !pcDetachAllBusy && setPcDetachAllOpen(false)}
          title={`Detach all ${promoCodes.length} promo code${promoCodes.length === 1 ? "" : "s"} from "${campaign?.name ?? "this campaign"}"?`}
          message={`This removes every promo-code link from this campaign — all ${promoCodes.length} attachment${promoCodes.length === 1 ? "" : "s"} across all pages, not just the current page.\n\nThe promo codes themselves are NOT deleted — they will still exist and remain attached to any other campaigns.`}
          confirmLabel="Detach all"
          cancelLabel="Cancel"
          confirmColor="red"
          onConfirm={performDetachAll}
        />

        <ConfirmModal
          opened={detachTarget !== null}
          onClose={() => setDetachTarget(null)}
          title={
            detachTarget
              ? `Detach attachment of "${detachTarget.promoCodeName}" from "${campaign?.name ?? "this campaign"}"?`
              : "Detach attachment?"
          }
          message={
            detachTarget
              ? `This only removes the link between "${detachTarget.promoCodeName}" and this campaign.\n\nThe promo code itself is NOT deleted — it will still exist and remain attached to any other campaigns.`
              : ""
          }
          confirmLabel="Detach attachment"
          cancelLabel="Cancel"
          confirmColor="red"
          onConfirm={performDetach}
        />

        <CreatePromoCodeModal
          opened={createOpen}
          onClose={() => setCreateOpen(false)}
          form={createForm}
          onSubmit={handleCreatePromoCode}
          busy={creating}
        />

        <AttachPromoCodesModal
          opened={attachOpen}
          onClose={() => setAttachOpen(false)}
          existingIds={new Set(promoCodes.map((p) => p.promo_code_id))}
          onAttach={async (codes) => {
            const res = await attachPromoCodes(campaignId, codes);
            if (res.success) {
              notifications.show({
                color: "green",
                message: "Promo codes attached",
              });
              setAttachOpen(false);
              void refreshPromoCodes();
            } else {
              notifications.show({
                color: "red",
                message: res.message || "Failed to attach",
              });
            }
          }}
        />

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

function AttachPromoCodesModal({
  opened,
  onClose,
  existingIds,
  onAttach,
}: {
  opened: boolean;
  onClose: () => void;
  existingIds: Set<string>;
  onAttach: (
    codes: Array<{ promo_code_id: string; promo_code_name: string }>,
  ) => Promise<void>;
}) {
  // Page size for the server-side paginated list. The API (Updot Core proxy)
  // supports searchTerm + filter flags + limit/offset, so search/filter/
  // pagination are all applied globally on the server — never client-side over
  // a partial dataset. There is no sort param, so the list has no sort control.
  const [attachPageSize, setAttachPageSize] = useState(10);
  const [searchTerm, setSearchTerm] = useState("");
  const [debouncedSearch, setDebouncedSearch] = useState("");
  const [page, setPage] = useState(1);
  const [items, setItems] = useState<any[]>([]);
  const [total, setTotal] = useState(0);
  const [totalKnown, setTotalKnown] = useState(false);
  const [hasNextPage, setHasNextPage] = useState(false);
  const [selectedMap, setSelectedMap] = useState<
    Map<string, { promo_code_id: string; promo_code_name: string }>
  >(new Map());
  const [loading, setLoading] = useState(false);
  const [submitting, setSubmitting] = useState(false);
  const [activeOnly, setActiveOnly] = useState(true);
  const [showExpired, setShowExpired] = useState(false);
  const [onlyPromoCodes, setOnlyPromoCodes] = useState(true);
  const [memberReferral, setMemberReferral] = useState(false);

  // Reset transient UI state when the modal closes so reopening starts fresh
  // (default view: active + only promo codes).
  useEffect(() => {
    if (opened) return;
    setSearchTerm("");
    setDebouncedSearch("");
    setPage(1);
    setAttachPageSize(25);
    setItems([]);
    setSelectedMap(new Map());
    setActiveOnly(true);
    setShowExpired(false);
    setOnlyPromoCodes(true);
    setMemberReferral(false);
  }, [opened]);

  // Debounce the search box and jump back to the first page whenever the query
  // changes, so the server fetch below picks up the new term from page 1.
  useEffect(() => {
    const t = setTimeout(() => {
      setDebouncedSearch(searchTerm);
      setPage(1);
    }, 350);
    return () => clearTimeout(t);
  }, [searchTerm]);

  // Helper for filter toggles: apply the change and reset to the first page.
  const applyFilterChange = (next: {
    isActive: boolean;
    includeExpired: boolean;
    onlyPromoCodes: boolean;
    memberReferral: boolean;
  }) => {
    setActiveOnly(next.isActive);
    setShowExpired(next.includeExpired);
    setOnlyPromoCodes(next.onlyPromoCodes);
    setMemberReferral(next.memberReferral);
    setPage(1);
  };

  // Fetch exactly one page of results from the server for the current search +
  // filters + page. Selections persist across pages via selectedMap.
  useEffect(() => {
    if (!opened) return;
    let cancelled = false;
    setLoading(true);
    (async () => {
      const res = await listPromoCodes({
        limit: attachPageSize,
        offset: (page - 1) * attachPageSize,
        searchTerm: debouncedSearch.trim() || undefined,
        isActive: activeOnly ? "true" : undefined,
        includeExpired: showExpired ? "true" : undefined,
        onlyPromoCodes: onlyPromoCodes ? "true" : undefined,
        onlyMemberReferralCodes: memberReferral ? "true" : undefined,
      });
      if (cancelled) return;
      const inner = res?.success ? res.data : null;
      const list = Array.isArray(inner?.data) ? inner!.data : [];
      const pagination: any = inner?.pagination;
      const rawTotal = pagination?.total;
      const parsedTotal =
        typeof rawTotal === "number"
          ? rawTotal
          : Number.parseInt(String(rawTotal ?? ""), 10);
      setItems(list);
      setTotalKnown(Number.isFinite(parsedTotal));
      setTotal(Number.isFinite(parsedTotal) ? parsedTotal : 0);
      setHasNextPage(
        Boolean(pagination?.hasNext) || list.length === attachPageSize,
      );
    })().finally(() => !cancelled && setLoading(false));
    return () => {
      cancelled = true;
    };
  }, [
    opened,
    page,
    attachPageSize,
    debouncedSearch,
    activeOnly,
    showExpired,
    onlyPromoCodes,
    memberReferral,
  ]);

  const totalPages = totalKnown
    ? Math.max(1, Math.ceil(total / attachPageSize))
    : Math.max(page, page + (hasNextPage ? 1 : 0));

  // IDs that the "select all" / "deselect all" toggle should operate on:
  // every currently-visible (filtered) row that isn't already attached.
  const selectableIds = useMemo(
    () =>
      items
        .map((it: any) =>
          String(it.id ?? it.promoCodeId ?? it.promo_code_id ?? it.code ?? ""),
        )
        .filter((id) => id && !existingIds.has(id)),
    [items, existingIds],
  );
  const allSelectableSelected =
    selectableIds.length > 0 &&
    selectableIds.every((id) => selectedMap.has(id));
  const someSelectableSelected =
    !allSelectableSelected && selectableIds.some((id) => selectedMap.has(id));

  const toggle = (id: string, nameVal: string) => {
    setSelectedMap((prev) => {
      const next = new Map(prev);
      if (next.has(id)) next.delete(id);
      else next.set(id, { promo_code_id: id, promo_code_name: nameVal });
      return next;
    });
  };

  const addSelectable = () => {
    setSelectedMap((prev) => {
      const next = new Map(prev);
      for (const it of items) {
        const id = String(
          it.id ?? it.promoCodeId ?? it.promo_code_id ?? it.code ?? "",
        );
        if (!id || existingIds.has(id) || next.has(id)) continue;
        const nameVal = String(it.code ?? it.name ?? id);
        next.set(id, { promo_code_id: id, promo_code_name: nameVal });
      }
      return next;
    });
  };

  const removeSelectable = () => {
    setSelectedMap((prev) => {
      const next = new Map(prev);
      for (const id of selectableIds) next.delete(id);
      return next;
    });
  };

  const onSubmit = async () => {
    const chosen = Array.from(selectedMap.values());
    if (chosen.length === 0) return;
    setSubmitting(true);
    try {
      await onAttach(chosen);
      setSelectedMap(new Map());
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <Modal
      opened={opened}
      onClose={onClose}
      title="Attach Promo Codes"
      size="lg"
      centered
    >
      <Stack pt="xs">
        <Group gap="xs" align="flex-end">
          <TextInput
            placeholder="Search promo codes…"
            value={searchTerm}
            onChange={(e) => setSearchTerm(e.currentTarget.value)}
            style={{ flex: 1 }}
          />
          <Popover position="bottom-end" withArrow shadow="md" width={260}>
            <Popover.Target>
              <Indicator
                disabled={
                  !activeOnly &&
                  !showExpired &&
                  !onlyPromoCodes &&
                  !memberReferral
                }
                label={
                  (activeOnly ? 1 : 0) +
                  (showExpired ? 1 : 0) +
                  (onlyPromoCodes ? 1 : 0) +
                  (memberReferral ? 1 : 0)
                }
                size={16}
                color="dark"
                withBorder
              >
                <Button
                  variant="light"
                  color="gray"
                  leftSection={<IconFilter size={16} />}
                >
                  Filters
                </Button>
              </Indicator>
            </Popover.Target>
            <Popover.Dropdown>
              <PromoCodeFilterFields
                checkboxColor="dark"
                values={{
                  isActive: activeOnly,
                  includeExpired: showExpired,
                  onlyPromoCodes: onlyPromoCodes,
                  memberReferral: memberReferral,
                }}
                onChange={applyFilterChange}
              />
            </Popover.Dropdown>
          </Popover>
        </Group>
        <Group justify="space-between" gap="xs">
          <Text size="xs" c="dimmed">
            {totalKnown
              ? `${total} match${total === 1 ? "" : "es"}`
              : `${items.length} on this page`}
            {selectedMap.size > 0 && ` · ${selectedMap.size} selected`}
          </Text>
          <Button
            size="compact-xs"
            variant="subtle"
            onClick={() => {
              if (allSelectableSelected) removeSelectable();
              else addSelectable();
            }}
            disabled={selectableIds.length === 0}
          >
            {allSelectableSelected
              ? `Deselect all (${selectableIds.length})`
              : `Select all (${selectableIds.length})`}
          </Button>
        </Group>
        <Card withBorder radius="sm" p={0}>
          <HScrollTable minWidth={600} maxHeight={360}>
            <Table stickyHeader style={{ minWidth: 600 }}>
              <Table.Thead>
                <Table.Tr>
                  <Table.Th style={{ width: 40 }}>
                    <Checkbox
                      color="dark"
                      aria-label="Select all visible"
                      checked={allSelectableSelected}
                      indeterminate={someSelectableSelected}
                      onChange={(e) => {
                        const check = Boolean(e?.target?.checked);
                        if (check) addSelectable();
                        else removeSelectable();
                      }}
                    />
                  </Table.Th>
                  <Table.Th>Code</Table.Th>
                  <Table.Th>Name</Table.Th>
                </Table.Tr>
              </Table.Thead>
              <Table.Tbody>
                {loading ? (
                  <Table.Tr>
                    <Table.Td colSpan={3}>
                      <Text c="dimmed" ta="center" py="md">
                        Loading…
                      </Text>
                    </Table.Td>
                  </Table.Tr>
                ) : items.length === 0 ? (
                  <Table.Tr>
                    <Table.Td colSpan={3}>
                      <Text c="dimmed" ta="center" py="md">
                        No promo codes found.
                      </Text>
                    </Table.Td>
                  </Table.Tr>
                ) : (
                  items.map((it: any) => {
                    const id = String(
                      it.id ??
                        it.promoCodeId ??
                        it.promo_code_id ??
                        it.code ??
                        "",
                    );
                    if (!id) return null;
                    const already = existingIds.has(id);
                    return (
                      <Table.Tr
                        key={id}
                        onClick={() =>
                          !already &&
                          toggle(id, String(it.code ?? it.name ?? id))
                        }
                        style={{
                          cursor: already ? "not-allowed" : "pointer",
                          opacity: already ? 0.5 : 1,
                        }}
                      >
                        <Table.Td onClick={(e) => e.stopPropagation()}>
                          <Checkbox
                            color="dark"
                            checked={selectedMap.has(id)}
                            disabled={already}
                            onChange={() =>
                              toggle(id, String(it.code ?? it.name ?? id))
                            }
                          />
                        </Table.Td>
                        <Table.Td>{String(it.code ?? id)}</Table.Td>
                        <Table.Td>
                          <Text size="sm" c="dimmed">
                            {String(it.name ?? "—")}
                            {already && " (already attached)"}
                          </Text>
                        </Table.Td>
                      </Table.Tr>
                    );
                  })
                )}
              </Table.Tbody>
            </Table>
          </HScrollTable>
        </Card>
        {items.length > 0 && (
          <Group justify="space-between" align="center">
            <Group gap="xs" align="center">
              <Select
                size="sm"
                variant="filled"
                w={90}
                value={String(attachPageSize)}
                onChange={(v) => {
                  setAttachPageSize(Number(v) || 25);
                  setPage(1);
                }}
                data={["10", "25", "50", "100"]}
                aria-label="Entries per page"
                allowDeselect={false}
                searchable={false}
              />
              <Text size="xs" c="dimmed">
                Entries per page · Page {page}
                {totalKnown ? ` of ${totalPages}` : ""}
              </Text>
            </Group>
            {totalPages > 1 && (
              <Pagination
                value={page}
                onChange={setPage}
                total={totalPages}
                size="sm"
                disabled={loading}
              />
            )}
          </Group>
        )}
        <Group justify="flex-end">
          <Button variant="subtle" onClick={onClose}>
            Cancel
          </Button>
          <Button
            onClick={onSubmit}
            disabled={selectedMap.size === 0}
            loading={submitting}
          >
            Attach {selectedMap.size > 0 ? `(${selectedMap.size})` : ""}
          </Button>
        </Group>
      </Stack>
    </Modal>
  );
}
