"use client";

import * as React from "react";
import equal from "fast-deep-equal";
import { useSearchParams } from "next/navigation";
import { toast } from "sonner";
import { useTeamsQuery } from "@/hooks/use-teams-query";
import {
  useGroupedStagesQuery,
  useCreatePipelineMutation,
  useUpdatePipelineMutation,
  useDeletePipelineMutation,
  useAddPipelineStageMutation,
  useUpdatePipelineStageMutation,
} from "@/hooks/use-pipelines-query";
import { useAppSelector, useAppDispatch } from "@/store/hooks";
import { openBusinessDealDialog } from "@/store/slices/business-deal-dialog-slice";
import { openMoveToLostDialog } from "@/store/slices/move-to-lost-dialog-slice";
import { openDiscoveryMeetingBookedDialog } from "@/store/slices/discovery-meeting-booked-dialog-slice";
import {
  openDealsEditModal,
  openDealsAddModal,
  openDealsPipelineModal,
  closeDealsModals,
} from "@/store/slices/deals-modals-slice";
import {
  clearDealsModalsHandlers,
  setDealsModalsHandlers,
} from "@/lib/deals-modals-handlers-store";
import {
  clearDealsContentHandlers,
  setDealsContentHandlers,
  type InterceptDiscoveryMeetingKanbanMovePayload,
  type InterceptLostKanbanMovePayload,
} from "@/lib/deals-content-handlers-store";
import {
  markKanbanReturnResetPending,
  consumeKanbanDataRefetchPending,
} from "@/components/deals/kanban-helpers/kanban-return-reset";
import { replaceDealsPipelineContentState } from "@/store/slices/deals-pipeline-content-slice";
import {
  setBulkAssignSelection,
  type BulkAssignLeadRow,
} from "@/store/slices/bulk-assign-leads-slice";
import {
  setDateField,
  setDateRange as setReduxDateRange,
  setTeamFilterDisplay,
  setAvailableTeams,
  setIsAdmin,
  setIsCreatingDeal,
  setAddDealDisabled,
  setHeaderStaticConfig,
  setStageFilterId,
  setServiceFilterId,
  setShowLeadOwnerFilter,
  setDivisionFilterId,
  selectDealsHeader
} from "@/store/slices/deals-header-slice";
import { useAuthToken } from "@/hooks/use-auth-token";
import { selectDealsByPipeline, buildDealsCacheKey } from "@/store/slices/deals-slice";
import {
  setSelectedTeam,
  selectSelectedTeam,
} from "@/store/slices/team-filter-slice";
import {
  isDivisionFilter,
  isTeamSlugFilter,
  isValidTeamFilterValue,
  parseDivisionFilterId,
  parseTeamFilter,
  teamFilterQueryParams,
} from "@/lib/team-filter";
import {
  useUpdateDealMutation,
  useMoveDealMutation,
  useReorderDealsInStageMutation,
  useDeleteDealMutation,
  type Deal as ApiDeal,
  useGetConvertedDealIdsQuery,
} from "@/api/rtk/deals-api";
import {
  businessDealsApi,
  useGetBusinessDealsQuery,
  useUpdateBusinessDealMutation,
  useDeleteBusinessDealMutation,
  type BusinessDealsListQuery,
} from "@/api/rtk/business-deals-api";
import { useGetProfileQuery } from "@/api/rtk/auth-api";
import {
  Deal,
  DealStage,
  Stage,
  DEFAULT_BUSINESS_DEAL_FILTERS,
} from "@/components/deals/types";
import { DealsPageHeader } from "./deals-page-header";
import {
  filterTeamsVisibleToUser,
  hasPermission,
  isAdminUser,
  isLeadsAssignedScopeRole,
  type PermissionSource,
} from "@/lib/permissions";
import { buildLeadUpdatePayload } from "@/lib/lead-update-payload";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useSession } from "next-auth/react";
import { leadsKeys } from "@/features/leads/api/query-keys";
import {
  collectLeadsFromBoardColumnCaches,
  findLeadInBoardColumnCaches,
} from "@/features/leads/api/find-lead-in-board-cache";
import { invalidateLeadsBoardColumnsForStages } from "@/features/leads/api/invalidate-leads-board-columns";
import {
  applyOptimisticBoardColumnMove,
  applyOptimisticLeadBoardMoveWithCounts,
  revertOptimisticLeadBoardMoveWithCounts,
  revertOptimisticBoardColumnMove,
  applyOptimisticBoardColumnReorder,
  removeLeadDealFromBoardCaches,
  type OptimisticLeadBoardMoveSnapshot,
} from "@/features/leads/api/optimistic-board-column-move";
import { usePipelinesCacheItems } from "@/hooks/use-pipelines-cache-items";
import { useSyncPipelinesQueryToRedux } from "@/hooks/use-sync-pipelines-query-to-redux";
import { useLeadsListInfiniteQuery } from "@/features/leads/hooks/use-leads-list-infinite-query";
import { useLeadsBoardCountsQuery } from "@/features/leads/hooks/use-leads-board-counts-query";
import { useCreateLeadMutation } from "@/features/leads/hooks/use-create-lead-mutation";
import { useMoveLeadMutation } from "@/features/leads/hooks/use-move-lead-mutation";
import { useDeleteLeadMutation } from "@/features/leads/hooks/use-delete-lead-mutation";
import type { LeadsListPageResult } from "@/features/leads/types";
import type { InfiniteData } from "@tanstack/react-query";
import { fetchBusinessDealsBoardStageCountsClient } from "@/features/business-deals/fetch-business-deals-board-stage-counts";
import { businessDealsBoardKeys } from "@/features/business-deals/query-keys";
import { invalidateBusinessBoardColumnsForStages } from "@/features/business-deals/invalidate-board-columns";
import {
  applyOptimisticBusinessBoardColumnMove,
  revertOptimisticBusinessBoardColumnMove,
  applyOptimisticBusinessBoardColumnReorder,
  removeBusinessDealFromBoardCaches,
} from "@/features/business-deals/optimistic-business-board-column-move";
import { mapBusinessRecordToBoardDeal } from "@/features/business-deals/map-business-record-to-board-deal";
import { setDeals } from "@/store/slices/deals-slice";
import { usePathname, useRouter } from "next/navigation";
import {
  markLocalDealBoardMove,
  markLocalDealBoardDelete,
  shouldIgnoreEchoDealUpdated,
  clearLocalDealBoardMoveEcho,
} from "@/lib/deal-board-socket-coalesce";
import { useDealBoardSocketEvent } from "@/lib/deal-board-event-store";
import {
  readPipelinesRevisionPayload,
  writeLeadsPipelineStagesReady,
} from "@/lib/query-persist-storage";
import dynamic from "next/dynamic";
import { Suspense } from "react";
import { DealsSkeleton } from "./deals-content";
import { dealMatchesClientBoardSearch } from "@/components/deals/deal-client-board-search";
import {
  isDiscoveryMeetingMoveRequiredError,
  isEnteringDiscoverMeetingBookedStage,
  isLostDestinationStageId,
} from "@/lib/deal-stage-labels";
import {
  dealMatchesKanbanClientFilters,
  kanbanClientBoardFiltersActive,
  kanbanServerBoardFiltersActive,
  type KanbanClientBoardFilters,
} from "@/components/deals/kanban-helpers/deal-kanban-client-filters";
import { kanbanBusinessBoardServerFiltersActive } from "@/features/business-deals/serialize-business-board-api-filters";
import { useGetCustomerPaymentKpisQuery } from "@/api/rtk/customers-api";
import type { DealsPipelinePageProps } from "./deals-pipeline-page/types";
import {
  EMPTY_STAGE_COUNT_MAP,
  buildKanbanBoardUiKey,
  getDealPipelineErrorMessage,
  mergeBoardColumnStageIdsWithFirstColumn,
  PIPELINE_STAGE_COLORS,
  ymdFromIsoOrNull,
} from "./deals-pipeline-page/helpers";
import { getDealsPipelineUiCopy } from "./deals-pipeline-page/pipeline-ui-copy";
import { sortDealsForPipelineView } from "./deals-pipeline-page/sort-deals-for-view";
import { DealsPipelineBulkSelectionBar } from "./deals-pipeline-page/bulk-selection-bar";
import { usePipelineBoardStages } from "./deals-pipeline-page/use-pipeline-board-stages";

export type { DealsPipelinePageProps } from "./deals-pipeline-page/types";

const DealsContent = dynamic(
  () => import("./deals-content").then((m) => m.DealsContent),
  {
    ssr: false,
    loading: () => <DealsSkeleton />,
  },
);

const DealsModals = dynamic(
  () => import("./deals-modals").then((m) => m.DealsModals),
  {
    ssr: false,
  },
);

const BulkStageTransferModal = dynamic(
  () =>
    import("@/components/ui/bulk-stage-transfer-modal").then(
      (m) => m.BulkStageTransferModal,
    ),
  {
    ssr: false,
  },
);

const BulkEditLeadsModal = dynamic(
  () =>
    import("./bulk-edit-leads-modal").then((m) => m.BulkEditLeadsModal),
  {
    ssr: false,
  },
);

const DeleteConfirmationAlert = dynamic(
  () =>
    import("@/components/ui/delete-confirmation-alert").then(
      (m) => m.DeleteConfirmationAlert,
    ),
  {
    ssr: false,
  },
);

export function DealsPipelinePage({
  pageTitle = "Leads Pipeline",
  boardStageType = "Lead",
  basePath = "/leads",
}: DealsPipelinePageProps) {
  const isBusinessDealBoard = boardStageType === "Deal";
  const { token } = useAuthToken();
  const { data: session } = useSession();
  const dispatch = useAppDispatch();
  const headerDateField = useAppSelector((s) => s.dealsHeader.dateField);
  React.useEffect(() => {
    if (headerDateField !== "createdDate") {
      dispatch(setDateField("createdDate"));
    }
  }, [dispatch, headerDateField]);
  const { mutateAsync: createPipeline } = useCreatePipelineMutation();
  const { mutateAsync: updatePipeline } = useUpdatePipelineMutation();

  const { data: teams } = useTeamsQuery();
  const { pipelinesLoadingRaw, pipelinesFetching } =
    useSyncPipelinesQueryToRedux({
      backgroundRefetchOnMount: !isBusinessDealBoard,
    });
  const pipelines = usePipelinesCacheItems();
  /** Show loading while pipelines resolve or refetch when the list is still empty (avoids empty-state flash). */
  const pipelinesLoading =
    pipelinesLoadingRaw ||
    (pipelinesFetching && pipelines.length === 0);
  const { data: groupedStages, isLoading: groupedStagesLoading } =
    useGroupedStagesQuery({ stageType: isBusinessDealBoard ? "Deal" : "Lead" });

  React.useEffect(() => {
    if (isBusinessDealBoard) return;
    if (groupedStagesLoading) return;
    if (!groupedStages?.length) return;
    if (typeof window === "undefined") return;
    const sessionUser = session?.user as
      | { email?: string | null; id?: string | null }
      | undefined;
    const userKey =
      sessionUser?.email?.trim().toLowerCase() ??
      (typeof sessionUser?.id === "string" && sessionUser.id.trim()
        ? `id:${sessionUser.id.trim()}`
        : "");
    if (!userKey) return;
    const rev = readPipelinesRevisionPayload()?.stagesGroupedLead;
    if (!rev) return;
    writeLeadsPipelineStagesReady({
      userKey,
      stagesGroupedLeadRev: rev,
      at: Date.now(),
    });
  }, [
    isBusinessDealBoard,
    groupedStagesLoading,
    groupedStages,
    session,
  ]);
  const { data: convertedIdsList } = useGetConvertedDealIdsQuery(undefined, {
    skip: !token || isBusinessDealBoard,
  });
  const { data: profile } = useGetProfileQuery(undefined, { skip: !token });

  const searchParams = useSearchParams();
  const [pipelineParam, setPipelineParam] = React.useState<string | null>(
    searchParams.get("pipeline")
  );
  const [viewParam, setViewParam] = React.useState<string>("grid");
  const dealsHeaderDateField = useAppSelector((s) => s.dealsHeader.dateField);
  /** List + business table APIs support createdAt | updatedAt | createdDate only. */
  const listDateFieldForApi = React.useMemo(():
    | "createdAt"
    | "updatedAt"
    | "createdDate" => {
    const df = dealsHeaderDateField;
    if (df === "createdAt" || df === "updatedAt" || df === "createdDate") {
      return df;
    }
    return "createdDate";
  }, [dealsHeaderDateField]);

  const view: "kanban" | "list" =
    viewParam === "table" || viewParam === "list" ? "list" : "kanban";
  const setView = React.useCallback(
    (nextView: "kanban" | "list") => {
      const target = nextView === "list" ? "table" : "grid";
      if (viewParam !== target) setViewParam(target);
    },
    [setViewParam, viewParam],
  );

  const firstPipelineId = pipelines?.[0]?.id ?? null;

  // Resolve the active board: prefer URL param if it matches a loaded pipeline,
  // otherwise fall back to the first pipeline. While pipelines are still loading,
  // keep whatever pipelineParam is set so we don't skip the query unnecessarily.
  const activeBoardId = React.useMemo(() => {
    if (!Array.isArray(pipelines) || pipelines.length === 0) return pipelineParam ?? null;

    // 1. If URL param is present and valid, use it
    if (pipelineParam && pipelines.some((p) => p.id === pipelineParam)) {
      return pipelineParam;
    }

    // 2. Try to find a pipeline that specifies the current module in its dependencies
    const matchingPipeline = pipelines.find((p) => {
      if (boardStageType === "Lead") {
        return p.moduleDependencies?.includes("Leads");
      }
      // Pipelines may store legacy "Deals" or renamed "Sales" in moduleDependencies.
      return ["Deals", "Sales"].some((m) => p.moduleDependencies?.includes(m));
    });

    if (matchingPipeline) return matchingPipeline.id;

    // 3. Last fallback
    return firstPipelineId;
  }, [pipelines, pipelineParam, firstPipelineId, boardStageType]);

  React.useEffect(() => {
    if (pipelines?.length && !pipelineParam && activeBoardId) {
      setPipelineParam(activeBoardId);
    }
  }, [pipelines, pipelineParam, activeBoardId, setPipelineParam]);

  React.useEffect(() => {
    if (viewParam === null || viewParam === undefined || viewParam === "") {
      setViewParam("grid");
    }
  }, [viewParam, setViewParam]);

  const teamFilterSlug = useAppSelector(selectSelectedTeam);
  const teamFilterParams = teamFilterQueryParams(teamFilterSlug);

  /**
   * Committed search for list API + kanban filters. The header debounces typing into Redux
   * so this value only changes after the user pauses (or blur/Enter).
   */
  const dealsHeaderSearch = useAppSelector((s) => s.dealsHeader.search);
  const leadChannelFilterId = useAppSelector((s) => s.dealsHeader.leadChannelFilterId);
  const stageFilterId = useAppSelector((s) => s.dealsHeader.stageFilterId);
  const serviceFilterId = useAppSelector((s) => s.dealsHeader.serviceFilterId ?? "all");
  const timelineIntentFilterId = useAppSelector(
    (s) => s.dealsHeader.timelineIntentFilterId ?? "all",
  );
  const leadOwnerFilterId = useAppSelector(
    (s) => s.dealsHeader.leadOwnerFilterId ?? "all",
  );
  const contributorFilterId = useAppSelector(
    (s) => s.dealsHeader.contributorFilterId ?? "all",
  );
  const assignAeFilterId = useAppSelector(
    (s) => s.dealsHeader.assignAeFilterId ?? "all",
  );
  const assignBdrFilterId = useAppSelector(
    (s) => s.dealsHeader.assignBdrFilterId ?? "all",
  );
  const divisionFilterId = useAppSelector(
    (s) => s.dealsHeader.divisionFilterId ?? "all",
  );
  /** Dedicated division filter wins when team is all; else team dropdown `division:*` value. */
  const resolvedDivisionFilterId = React.useMemo(() => {
    if (isBusinessDealBoard) return undefined;
    if (divisionFilterId !== "all" && teamFilterSlug === "all") {
      return divisionFilterId;
    }
    return teamFilterQueryParams(teamFilterSlug).divisionId;
  }, [isBusinessDealBoard, divisionFilterId, teamFilterSlug]);
  const businessDealFilters = useAppSelector(
    (s) => s.dealsHeader.businessDealFilters ?? DEFAULT_BUSINESS_DEAL_FILTERS,
  );
  /** Kanban date filter is Redux-only (no `from`/`to` URL sync) so paged columns are not wiped/refetched on every range change. */
  const headerDateRangeForKanban = useAppSelector((s) => s.dealsHeader.dateRange);
  const selectedTagFilters = useAppSelector((s) => s.dealsHeader.selectedTags);
  const [dateFrom, setDateFrom] = React.useState<string | null>(searchParams.get("from"));
  const [dateTo, setDateTo] = React.useState<string | null>(searchParams.get("to"));

  const dateRange = React.useMemo(() => {
    if (!dateFrom) return undefined;
    return {
      from: new Date(dateFrom + "T00:00:00"),
      to: dateTo ? new Date(dateTo + "T00:00:00") : undefined,
    };
  }, [dateFrom, dateTo]);

  const setDateRange = React.useCallback(
    (range: import("react-day-picker").DateRange | undefined) => {
      const toYMD = (d?: Date) => {
        if (!d) return null;
        const year = d.getFullYear();
        const month = String(d.getMonth() + 1).padStart(2, "0");
        const day = String(d.getDate()).padStart(2, "0");
        return `${year}-${month}-${day}`;
      };
      setDateFrom(toYMD(range?.from));
      setDateTo(toYMD(range?.to));
    },
    [setDateFrom, setDateTo],
  );

  const handleClearDealUrlDates = React.useCallback(() => {
    setDateFrom(null);
    setDateTo(null);
  }, [setDateFrom, setDateTo]);

  const getDealsQueryArgs = React.useMemo(
    () => ({
      pipelineId: activeBoardId ?? undefined,
      ...teamFilterParams,
      divisionId: resolvedDivisionFilterId,
      dateFrom: view === "list" ? (dateFrom ?? undefined) : undefined,
      dateTo: view === "list" ? (dateTo ?? undefined) : undefined,
      dateField: listDateFieldForApi,
      search: dealsHeaderSearch.trim() || undefined,
      tags:
        selectedTagFilters.length > 0
          ? [...selectedTagFilters].sort((a, b) => a.localeCompare(b)).join(",")
          : undefined,
      stageType: boardStageType,
      leadChannelId: leadChannelFilterId !== "all" ? leadChannelFilterId : undefined,
      serviceId:
        view === "list" && !isBusinessDealBoard && serviceFilterId !== "all"
          ? serviceFilterId
          : undefined,
      timelineIntentId:
        !isBusinessDealBoard && timelineIntentFilterId !== "all"
          ? timelineIntentFilterId
          : undefined,
      stage:
        view === "list" && !isBusinessDealBoard && stageFilterId !== "all"
          ? stageFilterId
          : undefined,
      userId:
        !isBusinessDealBoard && leadOwnerFilterId !== "all"
          ? leadOwnerFilterId
          : undefined,
      contributorUserId:
        !isBusinessDealBoard && contributorFilterId !== "all"
          ? contributorFilterId
          : undefined,
      aeUserId:
        !isBusinessDealBoard && assignAeFilterId !== "all"
          ? assignAeFilterId
          : undefined,
      bdrUserId:
        !isBusinessDealBoard && assignBdrFilterId !== "all"
          ? assignBdrFilterId
          : undefined,
    }),
    [
      activeBoardId,
      teamFilterParams,
      view,
      dateFrom,
      dateTo,
      dealsHeaderSearch,
      selectedTagFilters,
      boardStageType,
      listDateFieldForApi,
      leadChannelFilterId,
      isBusinessDealBoard,
      serviceFilterId,
      timelineIntentFilterId,
      stageFilterId,
      leadOwnerFilterId,
      contributorFilterId,
      assignAeFilterId,
      assignBdrFilterId,
      resolvedDivisionFilterId,
    ],
  );

  /**
   * Leads kanban board API params (stage-counts, by-stage, board search).
   * Mirrors `kanbanClientBoardFilters` for anything the backend can filter so
   * column totals and payloads stay aligned with the visible cards.
   */
  const kanbanBoardFetchParams = React.useMemo(() => {
    const fromIso = headerDateRangeForKanban?.from;
    const toIso = headerDateRangeForKanban?.to;
    const dateFromYmd =
      view === "kanban" && fromIso
        ? (ymdFromIsoOrNull(fromIso) ?? undefined)
        : (dateFrom ?? undefined);
    const dateToYmd =
      view === "kanban" && fromIso
        ? toIso
          ? (ymdFromIsoOrNull(toIso) ?? undefined)
          : undefined
        : (dateTo ?? undefined);
    return {
      pipelineId: activeBoardId ?? undefined,
      stageType: boardStageType,
      ...teamFilterParams,
      divisionId: resolvedDivisionFilterId,
      leadChannelId: leadChannelFilterId !== "all" ? leadChannelFilterId : undefined,
      serviceId: serviceFilterId !== "all" ? serviceFilterId : undefined,
      timelineIntentId:
        timelineIntentFilterId !== "all" ? timelineIntentFilterId : undefined,
      tags:
        selectedTagFilters.length > 0
          ? [...selectedTagFilters].sort((a, b) => a.localeCompare(b)).join(",")
          : undefined,
      dateFrom: dateFromYmd,
      dateTo: dateToYmd,
      dateField: listDateFieldForApi,
      search: dealsHeaderSearch.trim() || undefined,
      stage:
        !isBusinessDealBoard && stageFilterId !== "all"
          ? stageFilterId
          : undefined,
      userId:
        !isBusinessDealBoard && leadOwnerFilterId !== "all"
          ? leadOwnerFilterId
          : undefined,
      contributorUserId:
        !isBusinessDealBoard && contributorFilterId !== "all"
          ? contributorFilterId
          : undefined,
      aeUserId:
        !isBusinessDealBoard && assignAeFilterId !== "all"
          ? assignAeFilterId
          : undefined,
      bdrUserId:
        !isBusinessDealBoard && assignBdrFilterId !== "all"
          ? assignBdrFilterId
          : undefined,
    };
  }, [
    activeBoardId,
    boardStageType,
    teamFilterParams,
    leadChannelFilterId,
    serviceFilterId,
    timelineIntentFilterId,
    selectedTagFilters,
    view,
    dateFrom,
    dateTo,
    headerDateRangeForKanban?.from,
    headerDateRangeForKanban?.to,
    listDateFieldForApi,
    dealsHeaderSearch,
    isBusinessDealBoard,
    stageFilterId,
    leadOwnerFilterId,
    contributorFilterId,
    assignAeFilterId,
    assignBdrFilterId,
    resolvedDivisionFilterId,
  ]);

  const kanbanClientBoardFilters = React.useMemo((): KanbanClientBoardFilters => {
    const fromIso = headerDateRangeForKanban?.from;
    const toIso = headerDateRangeForKanban?.to;
    const dateFromYmd =
      view === "kanban" && fromIso
        ? (ymdFromIsoOrNull(fromIso) ?? undefined)
        : (dateFrom ?? undefined);
    const dateToYmd =
      view === "kanban" && fromIso
        ? toIso
          ? (ymdFromIsoOrNull(toIso) ?? undefined)
          : undefined
        : (dateTo ?? undefined);
    const base: KanbanClientBoardFilters = {
      teamSlug: isTeamSlugFilter(teamFilterSlug) ? teamFilterSlug : undefined,
      tagSlugs: selectedTagFilters,
      dateFromYmd,
      dateToYmd,
      dateField: listDateFieldForApi,
      leadChannelId:
        leadChannelFilterId !== "all" ? leadChannelFilterId : undefined,
      serviceId: serviceFilterId !== "all" ? serviceFilterId : undefined,
      timelineIntentId:
        !isBusinessDealBoard && timelineIntentFilterId !== "all"
          ? timelineIntentFilterId
          : undefined,
      ownerUserId:
        !isBusinessDealBoard && leadOwnerFilterId !== "all"
          ? leadOwnerFilterId
          : undefined,
      contributorUserId:
        !isBusinessDealBoard && contributorFilterId !== "all"
          ? contributorFilterId
          : undefined,
      aeUserId:
        !isBusinessDealBoard && assignAeFilterId !== "all"
          ? assignAeFilterId
          : undefined,
      bdrUserId:
        !isBusinessDealBoard && assignBdrFilterId !== "all"
          ? assignBdrFilterId
          : undefined,
      divisionId: resolvedDivisionFilterId,
    };
    if (!isBusinessDealBoard) {
      return base;
    }
    const bf = businessDealFilters;
    return {
      ...base,
      tagSlugs: [],
      dealType:
        bf.dealType !== "all" && bf.dealType.trim() ? bf.dealType : undefined,
      region: bf.region !== "all" && bf.region.trim() ? bf.region : undefined,
    };
  }, [
    teamFilterSlug,
    selectedTagFilters,
    view,
    dateFrom,
    dateTo,
    headerDateRangeForKanban?.from,
    headerDateRangeForKanban?.to,
    listDateFieldForApi,
    leadChannelFilterId,
    serviceFilterId,
    timelineIntentFilterId,
    leadOwnerFilterId,
    contributorFilterId,
    assignAeFilterId,
    assignBdrFilterId,
    resolvedDivisionFilterId,
    isBusinessDealBoard,
    businessDealFilters,
  ]);

  /** Sales pipeline list view: server-side filters on GET /business-deals */
  const businessDealsTableQueryArgs =
    React.useMemo((): BusinessDealsListQuery | undefined => {
      if (!isBusinessDealBoard || view !== "list") return undefined;
      const bf = businessDealFilters;
      return {
        pipelineId: activeBoardId ?? undefined,
        ...teamFilterParams,
        dateFrom: dateFrom ?? undefined,
        dateTo: dateTo ?? undefined,
        dateField: listDateFieldForApi,
        search: dealsHeaderSearch.trim() || undefined,
        dealType:
          bf.dealType !== "all" && bf.dealType.trim() ? bf.dealType : undefined,
        region: bf.region !== "all" && bf.region.trim() ? bf.region : undefined,
        leadChannelId:
          leadChannelFilterId !== "all" ? leadChannelFilterId : undefined,
        serviceId:
          serviceFilterId !== "all" ? serviceFilterId : undefined,
      };
    }, [
      isBusinessDealBoard,
      view,
      activeBoardId,
      teamFilterParams,
      dateFrom,
      dateTo,
      dealsHeaderSearch,
      listDateFieldForApi,
      businessDealFilters,
      leadChannelFilterId,
      serviceFilterId,
    ]);

  const { activeBoard, allowedStageIds, firstKanbanColumnStageId } =
    usePipelineBoardStages(pipelines, activeBoardId, boardStageType);

  const pipelineUi = React.useMemo(
    () => getDealsPipelineUiCopy(boardStageType),
    [boardStageType],
  );

  const queryClient = useQueryClient();
  const kanbanLeadMoveRollbackRef = React.useRef(
    new Map<string, OptimisticLeadBoardMoveSnapshot>(),
  );
  const kanbanDragOptimisticAppliedRef = React.useRef(new Set<string>());

  const leadsBoardPaging = !isBusinessDealBoard && view === "kanban";
  const dealBoardPaging = isBusinessDealBoard && view === "kanban";

  const { data: boardStageCounts = EMPTY_STAGE_COUNT_MAP } =
    useLeadsBoardCountsQuery(kanbanBoardFetchParams, {
      enabled: Boolean(
        leadsBoardPaging &&
        !isBusinessDealBoard &&
        token &&
        kanbanBoardFetchParams.pipelineId,
      ),
    });

  const { data: businessBoardStageCounts = EMPTY_STAGE_COUNT_MAP } = useQuery({
    queryKey: businessDealsBoardKeys.counts(activeBoardId ?? "", kanbanClientBoardFilters),
    queryFn: () =>
      fetchBusinessDealsBoardStageCountsClient(activeBoardId as string, kanbanClientBoardFilters),
    enabled: Boolean(dealBoardPaging && activeBoardId && token),
    staleTime: 1000 * 60 * 5,
    refetchOnWindowFocus: false,
    refetchOnReconnect: false,
  });

  const [businessBoardDealMap, setBusinessBoardDealMap] = React.useState<
    Record<string, Deal[]>
  >({});
  const [leadsBoardCacheTick, setLeadsBoardCacheTick] = React.useState(0);

  const handleColumnDeals = React.useCallback(
    (columnKey: string, next: Deal[]) => {
      if (dealBoardPaging) {
        setBusinessBoardDealMap((prev) => {
          const current = prev[columnKey];
          if (current) {
            const isSame =
              current.length === next.length &&
              current.every(
                (d, i) =>
                  d.id === next[i].id &&
                  d.updatedAt === next[i].updatedAt &&
                  d.stage === next[i].stage &&
                  (d.stageOrder ?? 0) === (next[i].stageOrder ?? 0),
              );
            if (isSame) return prev;
          }
          return { ...prev, [columnKey]: next };
        });
        return;
      }
      if (leadsBoardPaging) {
        setLeadsBoardCacheTick((t) => t + 1);
      }
    },
    [dealBoardPaging, leadsBoardPaging],
  );

  const businessBoardMergedDeals = React.useMemo(() => {
    const byId = new Map<string, Deal>();
    for (const col of Object.values(businessBoardDealMap)) {
      for (const d of col) {
        byId.set(d.id, d);
      }
    }
    return Array.from(byId.values());
  }, [businessBoardDealMap]);

  const leadsBoardCachedDeals = React.useMemo(() => {
    if (!leadsBoardPaging) return [];
    return collectLeadsFromBoardColumnCaches(
      queryClient,
      kanbanBoardFetchParams,
    );
  }, [leadsBoardPaging, queryClient, kanbanBoardFetchParams, leadsBoardCacheTick]);

  // Paged kanban: clear column caches when any board filter changes.
  React.useEffect(() => {
    if (!dealBoardPaging) return;
    setBusinessBoardDealMap({});
  }, [
    activeBoardId,
    teamFilterSlug,
    selectedTagFilters,
    boardStageType,
    leadsBoardPaging,
    dealBoardPaging,
    leadChannelFilterId,
    serviceFilterId,
    timelineIntentFilterId,
    dateFrom,
    dateTo,
    headerDateRangeForKanban?.from,
    headerDateRangeForKanban?.to,
    listDateFieldForApi,
    dealsHeaderSearch,
    view,
    stageFilterId,
    leadOwnerFilterId,
    contributorFilterId,
    assignAeFilterId,
    assignBdrFilterId,
    resolvedDivisionFilterId,
    businessDealFilters,
  ]);

  React.useEffect(() => {
    if (leadsBoardPaging || dealBoardPaging) return;
    setBusinessBoardDealMap({});
  }, [
    activeBoardId,
    teamFilterSlug,
    dateFrom,
    dateTo,
    selectedTagFilters,
    boardStageType,
    leadsBoardPaging,
    dealBoardPaging,
  ]);

  const leadsListInfiniteEnabled =
    !isBusinessDealBoard &&
    Boolean(activeBoardId) &&
    Boolean(token) &&
    view === "list";

  const {
    data: leadsInfiniteData,
    refetch: refetchLeadsListInfinite,
    fetchNextPage: fetchNextLeadsListPage,
    hasNextPage: leadsListHasNextPage,
    isFetchingNextPage: leadsListFetchingNextPage,
    isLoading: leadsDealsLoading,
    isFetching: leadsDealsFetchingRaw,
  } = useLeadsListInfiniteQuery(getDealsQueryArgs, {
    enabled: leadsListInfiniteEnabled,
  });

  const leadsDealsFetching =
    leadsDealsFetchingRaw && !leadsListFetchingNextPage;

  const handleLeadsListLoadMore = React.useCallback(() => {
    void fetchNextLeadsListPage();
  }, [fetchNextLeadsListPage]);

  const leadsListInfiniteLoadConfig = React.useMemo(
    () =>
      leadsListInfiniteEnabled
        ? {
          hasMore: Boolean(leadsListHasNextPage),
          isLoadingMore: leadsListFetchingNextPage,
          onLoadMore: handleLeadsListLoadMore,
        }
        : undefined,
    [
      leadsListInfiniteEnabled,
      leadsListHasNextPage,
      leadsListFetchingNextPage,
      handleLeadsListLoadMore,
    ],
  );

  const leadsListFlattened = React.useMemo(() => {
    if (!leadsInfiniteData?.pages?.length) return undefined;
    return leadsInfiniteData.pages.flatMap((p) => p.items);
  }, [leadsInfiniteData]);

  const leadsListServerTotal = leadsInfiniteData?.pages[0]?.total;

  const apiDeals =
    isBusinessDealBoard || leadsBoardPaging ? undefined : leadsListFlattened;

  const persistedDeals = useAppSelector(
    selectDealsByPipeline(activeBoardId, teamFilterSlug),
  );

  React.useEffect(() => {
    if (!activeBoardId) return;
    // Paged kanban: column data lives in TanStack Query (+ businessBoardDealMap for sales deals).
    // into Redux on every column scroll refired the whole board pipeline (O(all leads)).
    if (isBusinessDealBoard) {
      if (dealBoardPaging) return;
      return;
    }
    if (leadsBoardPaging) return;
    if (!leadsListFlattened) return;
    
    // 🎯 Optimization: Only dispatch if the deals actually changed to avoid middleware overhead
    const currentCached = persistedDeals; // Already selected via selectDealsByPipeline
    if (!equal(currentCached, leadsListFlattened)) {
      dispatch(
        setDeals({
          pipelineId: activeBoardId,
          team: teamFilterSlug !== "all" ? teamFilterSlug : null,
          deals: leadsListFlattened,
        }),
      );
    }
  }, [
    isBusinessDealBoard,
    dealBoardPaging,
    activeBoardId,
    leadsBoardPaging,
    leadsListFlattened,
    teamFilterSlug,
    persistedDeals,
    dispatch,
  ]);

  const {
    data: apiBusinessDeals,
    refetch: refetchBusinessDeals,
    isLoading: businessDealsLoading,
    isFetching: businessDealsFetching,
  } = useGetBusinessDealsQuery(businessDealsTableQueryArgs, {
    skip:
      !isBusinessDealBoard ||
      !activeBoardId ||
      !token ||
      view !== "list" ||
      !businessDealsTableQueryArgs,
    refetchOnMountOrArgChange: true,
  });

  const refetchBoardDeals = React.useCallback(
    (
      stageIds?: string | string[],
      opts?: {
        invalidateList?: boolean;
        invalidateCounts?: boolean;
        invalidateColumns?: boolean;
      },
    ) => {
      const stagesInput = stageIds
        ? Array.isArray(stageIds)
          ? stageIds
          : [stageIds]
        : [];

      const stages =
        stagesInput.length > 0
          ? mergeBoardColumnStageIdsWithFirstColumn(
            stagesInput,
            firstKanbanColumnStageId,
          )
          : [];

      const invalidateList = opts?.invalidateList ?? true;
      const invalidateCounts = opts?.invalidateCounts ?? false;
      const invalidateColumns = opts?.invalidateColumns ?? true;

      if (isBusinessDealBoard) {
        if (dealBoardPaging && activeBoardId) {
          if (stages.length > 0) {
            invalidateBusinessBoardColumnsForStages(
              queryClient,
              activeBoardId,
              stages,
              { invalidateCounts },
            );
          } else {
            void queryClient.invalidateQueries({
              predicate: (query) => {
                const k = query.queryKey;
                if (!Array.isArray(k) || k[0] !== "businessDealsBoard") {
                  return false;
                }
                if (!invalidateCounts && k[1] === "counts") return false;
                return true;
              },
            });
          }
          if (invalidateCounts) {
            void queryClient.invalidateQueries({
              queryKey: businessDealsBoardKeys.counts(activeBoardId),
            });
          }
          return;
        }
        void refetchBusinessDeals();
      } else if (leadsBoardPaging) {
        if (stages.length > 0) {
          invalidateLeadsBoardColumnsForStages(
            queryClient,
            kanbanBoardFetchParams,
            stages,
            { invalidateList, invalidateCounts, invalidateColumns },
          );
        } else {
          /** Prefix `leadsKeys.board()` would also match `boardCounts` — exclude counts here. */
          void queryClient.invalidateQueries({
            predicate: (query) => {
              const k = query.queryKey;
              if (!Array.isArray(k) || k[0] !== "leads" || k[1] !== "board") {
                return false;
              }
              if (!invalidateCounts && k[2] === "counts") return false;
              return true;
            },
          });
          if (invalidateList) {
            void queryClient.invalidateQueries({ queryKey: leadsKeys.lists() });
          }
        }
      } else {
        void refetchLeadsListInfinite();
      }
    },
    [
      isBusinessDealBoard,
      dealBoardPaging,
      activeBoardId,
      queryClient,
      refetchBusinessDeals,
      leadsBoardPaging,
      kanbanBoardFetchParams,
      refetchLeadsListInfinite,
      firstKanbanColumnStageId,
    ],
  );

  const invalidateLeadsQueries = React.useCallback(() => {
    void queryClient.invalidateQueries({ queryKey: leadsKeys.lists() });
    /** Avoid `leadsKeys.board()` — prefix match refetches every column `useInfiniteQuery`. */
    /** Do not invalidate `boardCounts` here — stage totals refetch only on bulk move (explicit). */
  }, [queryClient]);

  /** Refetch paged kanban columns + stage counts when returning from a lead/deal detail page. */
  React.useLayoutEffect(() => {
    if (!consumeKanbanDataRefetchPending()) return;
    if (!leadsBoardPaging && !dealBoardPaging) return;
    setBusinessBoardDealMap({});
    setLeadsBoardCacheTick((t) => t + 1);
    refetchBoardDeals(undefined, {
      invalidateList: true,
      invalidateCounts: true,
      invalidateColumns: true,
    });
  }, [leadsBoardPaging, dealBoardPaging, refetchBoardDeals]);
  const dealsLoading = isBusinessDealBoard
    ? dealBoardPaging
      ? false
      : businessDealsLoading
    : leadsBoardPaging
      ? false
      : leadsDealsLoading;
  const dealsFetching = isBusinessDealBoard
    ? dealBoardPaging
      ? false
      : businessDealsFetching
    : leadsBoardPaging
      ? false
      : leadsDealsFetching;

  const boardUiKey = React.useMemo(
    () =>
      buildKanbanBoardUiKey({
        entity: isBusinessDealBoard ? "business" : "leads",
        boardId: activeBoardId,
        team: null,
      }),
    [activeBoardId, isBusinessDealBoard],
  );
  // To avoid showing stale data from a different date range, we only fall back to persistedDeals
  // if we aren't currently filtering by date. Scope fallback to columns visible on this board.
  const deals = React.useMemo<Deal[]>(() => {
    if (isBusinessDealBoard) {
      if (dealBoardPaging) {
        return businessBoardMergedDeals;
      }
      if (!apiBusinessDeals) return [];
      return apiBusinessDeals.map((b) =>
        mapBusinessRecordToBoardDeal(
          b,
          activeBoardId ?? "",
          activeBoard.stages,
        ),
      );
    }
    if (leadsBoardPaging) {
      return [];
    }
    if (apiDeals) return apiDeals;
    if (!dateFrom && !dateTo) {
      return persistedDeals.filter((d) => allowedStageIds.has(d.stage));
    }
    return [];
  }, [
    isBusinessDealBoard,
    dealBoardPaging,
    apiBusinessDeals,
    apiDeals,
    leadsBoardPaging,
    businessBoardMergedDeals,
    persistedDeals,
    dateFrom,
    dateTo,
    allowedStageIds,
    activeBoardId,
    activeBoard.stages,
  ]);

  const findBoardDealById = React.useCallback(
    (dealId: string): Deal | undefined => {
      if (leadsBoardPaging) {
        return findLeadInBoardColumnCaches(
          queryClient,
          kanbanBoardFetchParams,
          dealId,
        ) as Deal | undefined;
      }
      if (dealBoardPaging) {
        return businessBoardMergedDeals.find((d) => d.id === dealId);
      }
      return deals.find((d) => d.id === dealId);
    },
    [
      leadsBoardPaging,
      dealBoardPaging,
      queryClient,
      kanbanBoardFetchParams,
      businessBoardMergedDeals,
      deals,
    ],
  );

  const { mutateAsync: createLeadMut, isPending: isCreatingDeal } =
    useCreateLeadMutation();
  const [updateDeal, { isLoading: isUpdatingDeal }] = useUpdateDealMutation();
  const { mutateAsync: moveLeadTs, isPending: isMovingLeadTs } = useMoveLeadMutation();
  const [moveDeal, { isLoading: isMovingDealRtk }] = useMoveDealMutation();

  const isMovingDeal = isMovingLeadTs || isMovingDealRtk;

  const [reorderDealsInStage, { isLoading: isReorderingDeal }] =
    useReorderDealsInStageMutation();

  const { mutateAsync: deleteLeadTs, isPending: isDeletingLeadTs } = useDeleteLeadMutation();
  const [deleteDealMutation, { isLoading: isDeletingDealRtk }] = useDeleteDealMutation();

  const isDeletingDeal = isDeletingLeadTs || isDeletingDealRtk;
  const [updateBusinessDealMut] = useUpdateBusinessDealMutation();
  const [deleteBusinessDealMut, { isLoading: isDeletingBusinessDeal }] =
    useDeleteBusinessDealMutation();
  const [dealToDeleteId, setDealToDeleteId] = React.useState<string | null>(
    null,
  );

  const convertedDealIds = React.useMemo(
    () => new Set((convertedIdsList ?? []) as string[]),
    [convertedIdsList],
  );
  const memoizedPermissionSource = React.useMemo(
    () => (session as { backendUser?: unknown } | null)?.backendUser ?? profile,
    [session, profile],
  );

  const stableTeams = teams ?? [];
  const stableProfile = profile ?? null;

  const availableTeams = React.useMemo(() => {
    if (!stableTeams.length || !stableProfile) return [];
    const filtered = filterTeamsVisibleToUser(
      stableTeams,
      stableProfile.id,
      memoizedPermissionSource,
    );
    const seen = new Set<string>();
    return filtered.filter((t) => {
      const s = t.name.toLowerCase().replace(/\s+/g, "-");
      if (seen.has(s)) return false;
      seen.add(s);
      return true;
    });
  }, [stableTeams, stableProfile, memoizedPermissionSource]);

  const isAdmin = React.useMemo(
    () => isAdminUser(memoizedPermissionSource),
    [memoizedPermissionSource],
  );
  const showLeadOwnerFilter = React.useMemo(
    () => !isBusinessDealBoard && !isLeadsAssignedScopeRole(memoizedPermissionSource),
    [isBusinessDealBoard, memoizedPermissionSource],
  );
  const canUpdateDeal = React.useMemo(
    () => hasPermission(memoizedPermissionSource, "UPDATE", "DEAL"),
    [memoizedPermissionSource],
  );
  const canBulkAssignLead = React.useMemo(
    () => hasPermission(memoizedPermissionSource, "UPDATE", "BULK_ASSIGN_LEAD"),
    [memoizedPermissionSource],
  );
  const canBulkEditLead = React.useMemo(
    () => hasPermission(memoizedPermissionSource, "UPDATE", "BULK_EDIT_LEAD"),
    [memoizedPermissionSource],
  );
  const router = useRouter();
  const dealBoardSocketEvent = useDealBoardSocketEvent();
  const canCreateDeal = React.useMemo(
    () => hasPermission(memoizedPermissionSource, "CREATE", "DEAL"),
    [memoizedPermissionSource],
  );

  const teamFilterDisplay = React.useMemo(() => {
    const parsed = parseTeamFilter(teamFilterSlug);
    if (parsed.type === "team") return parsed.teamSlug;
    return "all";
  }, [teamFilterSlug]);

  const { data: paymentKpis, isFetching: paymentKpisLoading } =
    useGetCustomerPaymentKpisQuery(
      { team: teamFilterDisplay, status: "all" },
      { skip: !token || !isBusinessDealBoard },
    );

  const handleTeamChange = React.useCallback(
    (value: string) => {
      if (value !== teamFilterSlug) {
        dispatch(setSelectedTeam(value));
      }
    },
    [dispatch, teamFilterSlug],
  );

  const handlePipelineChange = React.useCallback(
    (value: string | null) => {
      const target = value || null;
      if (pipelineParam !== target) {
        setPipelineParam(target);
      }
    },
    [pipelineParam, setPipelineParam],
  );

  React.useEffect(() => {
    if (isDivisionFilter(teamFilterSlug)) {
      const divisionId = parseDivisionFilterId(teamFilterSlug);
      dispatch(setSelectedTeam("all"));
      if (divisionId) {
        dispatch(setDivisionFilterId(divisionId));
      }
      return;
    }
    if (teamFilterSlug === "all") return;
    if (availableTeams.length === 0) return;
    if (!isValidTeamFilterValue(teamFilterSlug, availableTeams)) {
      dispatch(setSelectedTeam("all"));
    }
  }, [teamFilterSlug, availableTeams, dispatch]);

  const lastProcessedBoardEventVersionRef = React.useRef(0);

  React.useEffect(() => {
    if (
      dealBoardSocketEvent.version === 0 ||
      dealBoardSocketEvent.version ===
      lastProcessedBoardEventVersionRef.current
    ) {
      return;
    }

    const payload = dealBoardSocketEvent.event;
    if (!payload?.dealId) return;

    if (payload.removed) {
      lastProcessedBoardEventVersionRef.current = dealBoardSocketEvent.version;
      if (
        shouldIgnoreEchoDealUpdated({
          dealId: payload.dealId,
          fromStageId: payload.fromStageId,
          toStageId: payload.toStageId,
          removed: true,
        })
      ) {
        return;
      }

      if (payload.isBusinessDeal) {
        if (!isBusinessDealBoard) return;
        if (dealBoardPaging && activeBoardId && payload.fromStageId) {
          removeBusinessDealFromBoardCaches(queryClient, {
            pipelineId: activeBoardId,
            dealId: payload.dealId,
            fromStage: payload.fromStageId,
          });
          void queryClient.invalidateQueries({
            queryKey: businessDealsBoardKeys.counts(activeBoardId),
          });
        } else {
          void queryClient.invalidateQueries({
            queryKey: businessDealsBoardKeys.all,
          });
          void refetchBusinessDeals();
        }
        return;
      }

      if (isBusinessDealBoard) return;
      if (
        leadsBoardPaging &&
        kanbanBoardFetchParams.pipelineId &&
        payload.fromStageId
      ) {
        removeLeadDealFromBoardCaches(queryClient, {
          dealId: payload.dealId,
          fromStage: payload.fromStageId,
          listParams: kanbanBoardFetchParams,
        });
        void queryClient.invalidateQueries({
          queryKey: leadsKeys.boardCounts(kanbanBoardFetchParams),
        });
      } else {
        void queryClient.invalidateQueries({ queryKey: leadsKeys.all });
        invalidateLeadsQueries();
      }
      return;
    }

    lastProcessedBoardEventVersionRef.current = dealBoardSocketEvent.version;

    if (!payload.toStageId) return;

    const fromStageId = payload.fromStageId;
    const toStageId = payload.toStageId;

    // Same-stage column reorder: peers need a column refetch for updated `stageOrder`
    if (fromStageId && toStageId && fromStageId === toStageId) {
      if (
        shouldIgnoreEchoDealUpdated({
          dealId: payload.dealId,
          fromStageId,
          toStageId,
        })
      ) {
        return;
      }
      if (isBusinessDealBoard) {
        if (dealBoardPaging && activeBoardId) {
          invalidateBusinessBoardColumnsForStages(
            queryClient,
            activeBoardId,
            [fromStageId],
          );
        }
        return;
      }
      if (leadsBoardPaging && kanbanBoardFetchParams.pipelineId) {
        invalidateLeadsBoardColumnsForStages(
          queryClient,
          kanbanBoardFetchParams,
          [fromStageId],
          { invalidateCounts: true, invalidateColumns: true },
        );
      } else {
        invalidateLeadsQueries();
      }
      return;
    }

    if (
      shouldIgnoreEchoDealUpdated({
        dealId: payload.dealId,
        fromStageId,
        toStageId,
      })
    ) {
      return;
    }

    if (isBusinessDealBoard) {
      if (!dealBoardPaging || !activeBoardId) return;

      const movingDeal =
        deals.find((deal) => deal.id === payload.dealId) ??
        businessBoardMergedDeals.find((deal) => deal.id === payload.dealId);

      if (movingDeal) {
        applyOptimisticBusinessBoardColumnMove(queryClient, {
          pipelineId: activeBoardId,
          dealId: payload.dealId,
          fromStage: fromStageId ?? movingDeal.stage,
          toStage: toStageId,
          deal: {
            ...movingDeal,
            stage: fromStageId ?? movingDeal.stage,
          },
        });
      }

      if (fromStageId) {
        void queryClient.invalidateQueries({
          queryKey: businessDealsBoardKeys.counts(activeBoardId),
        });
      }
      return;
    }

    if (!leadsBoardPaging || !kanbanBoardFetchParams.pipelineId) return;

    const movingLead = (
      findLeadInBoardColumnCaches(
        queryClient,
        kanbanBoardFetchParams,
        payload.dealId,
      ) ??
      deals.find((deal) => deal.id === payload.dealId && !deal.isBusinessDeal)
    ) as ApiDeal | undefined;

    if (movingLead) {
      applyOptimisticBoardColumnMove(queryClient, {
        dealId: payload.dealId,
        fromStage: fromStageId ?? movingLead.stage,
        toStage: toStageId,
        deal: {
          ...movingLead,
          stage: fromStageId ?? movingLead.stage,
        },
        listParams: kanbanBoardFetchParams,
      });
    }

    if (fromStageId) {
      void queryClient.invalidateQueries({
        queryKey: leadsKeys.boardCounts(kanbanBoardFetchParams),
      });
    }
  }, [
    activeBoardId,
    businessBoardMergedDeals,
    dealBoardPaging,
    dealBoardSocketEvent.version,
    deals,
    kanbanBoardFetchParams,
    isBusinessDealBoard,
    leadsBoardPaging,
    queryClient,
    invalidateLeadsQueries,
    refetchBusinessDeals,
  ]);
  const [sortCol, setSortCol] = React.useState("createdDate");
  const [sortDir, setSortDir] = React.useState<1 | -1>(-1);

  const handleListSort = React.useCallback(
    (col: string) => {
      setSortCol((prevCol) => {
        if (prevCol === col) {
          setSortDir((d) => (d === 1 ? -1 : 1));
          return prevCol;
        }
        setSortDir(col === "createdDate" ? -1 : 1);
        return col;
      });
    },
    [],
  );

  const handleOpenCreatePipelineModal = React.useCallback(() => {
    dispatch(openDealsPipelineModal());
  }, [dispatch]);
  const [isAddingStage, setIsAddingStage] = React.useState(false);

  // Bulk selection (list view only)
  const [selectedDealIds, setSelectedDealIds] = React.useState<Set<string>>(
    new Set(),
  );
  const [isGlobalSelectAllActive, setIsGlobalSelectAllActive] =
    React.useState(false);
  const [isStageTransferOpen, setIsStageTransferOpen] = React.useState(false);
  const [isBulkEditOpen, setIsBulkEditOpen] = React.useState(false);

  const pathname = usePathname();
  const prevPathnameRef = React.useRef<string | null>(null);

  // When navigating to this board route from another route, drop modal + deal id from the URL so the
  // detail view does not reopen from a stale ?did=&modal= state (e.g. after visiting Customers).
  // Modal state is now in memory, no need for URL cleanup

  // Clear selection when switching views
  React.useEffect(() => {
    setSelectedDealIds(new Set());
    setIsGlobalSelectAllActive(false);
  }, [view]);

  /**
   * Table/list view: search, dates, team, tags come from the server (`getDealsQueryArgs` / business deals query).
   * Kanban + paged columns: the same filters are sent on board requests; `KanbanBoard` still applies
   * `kanbanClientBoardFilters` so the merged slice matches the server-filtered column payloads.
   * Kanban without paging: client-side search on `deals` only.
   */
  const filteredDeals = React.useMemo(() => {
    if (view === "list") {
      if (isBusinessDealBoard || stageFilterId === "all") return deals;
      return deals.filter((deal) => deal.stage === stageFilterId);
    }
    if (view === "kanban" && (leadsBoardPaging || dealBoardPaging)) {
      return deals;
    }
    if (!dealsHeaderSearch.trim()) return deals;
    return deals.filter((deal) =>
      dealMatchesClientBoardSearch(deal, dealsHeaderSearch),
    );
  }, [deals, dealsHeaderSearch, view, leadsBoardPaging, dealBoardPaging, stageFilterId, isBusinessDealBoard]);

  React.useEffect(() => {
    if (stageFilterId === "all") return;
    const validStageIds = new Set((activeBoard?.stages ?? []).map((s) => s.id));
    if (!validStageIds.has(stageFilterId)) {
      dispatch(setStageFilterId("all"));
    }
  }, [activeBoard?.stages, dispatch, stageFilterId]);

  const kanbanBoardClientFilterActive = React.useMemo(
    () =>
      view === "kanban" &&
      (leadsBoardPaging || dealBoardPaging) &&
      (Boolean(dealsHeaderSearch.trim()) ||
        kanbanClientBoardFiltersActive(kanbanClientBoardFilters)),
    [
      view,
      leadsBoardPaging,
      dealBoardPaging,
      dealsHeaderSearch,
      kanbanClientBoardFilters,
    ],
  );

  const headerKanbanFilteredMerged = React.useMemo(() => {
    if (!kanbanBoardClientFilterActive) return null;
    return leadsBoardCachedDeals.filter(
      (d) =>
        dealMatchesKanbanClientFilters(d, kanbanClientBoardFilters) &&
        (!dealsHeaderSearch.trim() ||
          dealMatchesClientBoardSearch(d, dealsHeaderSearch)),
    );
  }, [
    kanbanBoardClientFilterActive,
    leadsBoardCachedDeals,
    kanbanClientBoardFilters,
    dealsHeaderSearch,
  ]);

  const kanbanBoardServerFilterActive = React.useMemo(() => {
    if (view !== "kanban" || !(leadsBoardPaging || dealBoardPaging)) {
      return false;
    }
    if (isBusinessDealBoard) {
      return kanbanBusinessBoardServerFiltersActive(kanbanClientBoardFilters);
    }
    return kanbanServerBoardFiltersActive(kanbanClientBoardFilters);
  }, [
    view,
    leadsBoardPaging,
    dealBoardPaging,
    isBusinessDealBoard,
    kanbanClientBoardFilters,
  ]);

  /** Kanban + paged columns: prefer GET /deals/board/stage-counts when server filters apply; else client merge for legacy client-only filters/search. */
  const headerDealsCount = React.useMemo(() => {
    if (dealBoardPaging && isBusinessDealBoard) {
      if (kanbanBoardClientFilterActive && headerKanbanFilteredMerged) {
        return headerKanbanFilteredMerged.length;
      }
      let sum = 0;
      for (const stageId of allowedStageIds) {
        sum += businessBoardStageCounts[stageId] ?? 0;
      }
      return sum;
    }
    if (leadsBoardPaging && !isBusinessDealBoard) {
      let sum = 0;
      for (const stageId of allowedStageIds) {
        sum += boardStageCounts[stageId] ?? 0;
      }
      return sum;
    }
    if (
      view === "kanban" &&
      (leadsBoardPaging || dealBoardPaging) &&
      kanbanBoardClientFilterActive &&
      !kanbanBoardServerFilterActive &&
      headerKanbanFilteredMerged
    ) {
      return headerKanbanFilteredMerged.length;
    }
    if (
      !isBusinessDealBoard &&
      view === "list" &&
      leadsListServerTotal != null
    ) {
      return leadsListServerTotal;
    }
    return filteredDeals.length;
  }, [
    dealBoardPaging,
    leadsBoardPaging,
    isBusinessDealBoard,
    allowedStageIds,
    boardStageCounts,
    businessBoardStageCounts,
    filteredDeals.length,
    view,
    leadsListServerTotal,
    kanbanBoardClientFilterActive,
    kanbanBoardServerFilterActive,
    headerKanbanFilteredMerged,
  ]);

  const sortedDeals = React.useMemo(
    () => sortDealsForPipelineView(filteredDeals, view, sortCol, sortDir),
    [filteredDeals, view, sortCol, sortDir],
  );

  const handleOpenDeal = (deal: Deal) => {
    markKanbanReturnResetPending();
    if (deal.isBusinessDeal) {
      router.push(`${basePath}/${deal.id}`);
      return;
    }
    router.push(`/leads/${deal.id}`);
  };

  const handleOpenEditDeal = (deal: Deal) => {
    if (deal.isBusinessDeal) {
      dispatch(
        openBusinessDealDialog({ mode: "edit", businessDealId: deal.id }),
      );
      return;
    }
    dispatch(openDealsEditModal({ deal }));
  };

  const handleDeleteDeal = (id: string | { id: string }) => {
    const dealId = typeof id === "object" ? id.id : id;
    setDealToDeleteId(dealId);
  };

  const confirmDeleteDeal = async () => {
    if (!dealToDeleteId) return;
    const dealRow =
      deals.find((d) => d.id === dealToDeleteId) ??
      findBoardDealById(dealToDeleteId) ??
      sortedDeals.find((d) => d.id === dealToDeleteId);
    const affectedStage = dealRow?.stage;

    try {
      if (isBusinessDealBoard) {
        await deleteBusinessDealMut(dealToDeleteId).unwrap();
        if (dealBoardPaging && activeBoardId && affectedStage) {
          refetchBoardDeals(affectedStage, {
            invalidateList: false,
            invalidateCounts: true,
          });
        } else if (isBusinessDealBoard) {
          void refetchBusinessDeals();
        }
      } else {
        await deleteLeadTs(dealToDeleteId);
        if (leadsBoardPaging) {
          if (affectedStage) {
            refetchBoardDeals(affectedStage, {
              invalidateList: false,
              invalidateCounts: true,
              invalidateColumns: false,
            });
          } else {
            void queryClient.invalidateQueries({
              queryKey: leadsKeys.boardCounts(kanbanBoardFetchParams),
            });
          }
        } else {
          invalidateLeadsQueries();
        }
      }
      markLocalDealBoardDelete(dealToDeleteId, affectedStage);
      toast.success(pipelineUi.removedToast);
      setDealToDeleteId(null);
    } catch (err) {
      toast.error(pipelineUi.deleteFail);
      throw err;
    }
  };

  const isProposalStage = (stageId: string) => {
    const normalizedId = String(stageId).toLowerCase();
    const stage = activeBoard.stages.find((item) => item.id === stageId);
    const name = (stage?.name ?? "").toLowerCase();
    return normalizedId.includes("proposal") || name.includes("proposal");
  };

  const openDiscoveryMeetingDialogForMove = React.useCallback(
    (
      resolvedDealId: string,
      toStage: DealStage,
      sourceStageId: string | null,
      customerName?: string | null,
    ) => {
      if (!activeBoardId) return;
      const stageInfo = activeBoard.stages.find((stage) => stage.id === toStage);
      dispatch(
        openDiscoveryMeetingBookedDialog({
          dealId: resolvedDealId,
          pipelineId: activeBoardId,
          targetStageId: toStage,
          targetProb:
            typeof stageInfo?.prob === "number" ? stageInfo.prob : 0,
          sourceStageId,
          customerName: customerName ?? null,
          moveContext: leadsBoardPaging
            ? { kind: "paged", listParams: { ...kanbanBoardFetchParams } }
            : { kind: "infinite", listArgs: { ...getDealsQueryArgs } },
        }),
      );
    },
    [
      activeBoardId,
      activeBoard.stages,
      dispatch,
      leadsBoardPaging,
      kanbanBoardFetchParams,
      getDealsQueryArgs,
    ],
  );

  const handlePagedLeadMoveOptimistic = React.useCallback(
    (payload: {
      dealId: string;
      fromStage: string;
      toStage: string;
      deal: Deal;
    }) => {
      if (!leadsBoardPaging) return;
      const snapshot = applyOptimisticLeadBoardMoveWithCounts(queryClient, {
        dealId: payload.dealId,
        fromStage: payload.fromStage,
        toStage: payload.toStage,
        listParams: kanbanBoardFetchParams,
        deal: payload.deal as ApiDeal,
      });
      if (snapshot) {
        kanbanLeadMoveRollbackRef.current.set(payload.dealId, snapshot);
      }
      kanbanDragOptimisticAppliedRef.current.add(payload.dealId);
      markLocalDealBoardMove(
        payload.dealId,
        payload.fromStage,
        payload.toStage,
      );
    },
    [leadsBoardPaging, queryClient, kanbanBoardFetchParams],
  );

  const handleMoveDeal = async (
    dealId: string | { id: string },
    toStage: DealStage,
  ) => {
    const stageInfo = activeBoard.stages.find((stage) => stage.id === toStage);
    if (!stageInfo) return;

    const resolvedDealId = typeof dealId === "object" ? dealId.id : dealId;
    const movingRow =
      deals.find((d) => d.id === resolvedDealId) ??
      findBoardDealById(resolvedDealId) ??
      sortedDeals.find((d) => d.id === resolvedDealId);
    const sourceStageId = movingRow?.stage ?? null;

    if (!isBusinessDealBoard && activeBoardId) {
      if (isLostDestinationStageId(String(toStage), activeBoard.stages)) {
        dispatch(
          openMoveToLostDialog({
            dealId: resolvedDealId,
            pipelineId: activeBoardId,
            targetStageId: toStage,
            targetProb: typeof stageInfo.prob === "number" ? stageInfo.prob : 0,
            sourceStageId,
            customerName: movingRow?.customerName ?? null,
            moveContext: leadsBoardPaging
              ? { kind: "paged", listParams: { ...kanbanBoardFetchParams } }
              : { kind: "infinite", listArgs: { ...getDealsQueryArgs } },
          }),
        );
        return;
      }

      if (
        isEnteringDiscoverMeetingBookedStage(
          sourceStageId,
          String(toStage),
          activeBoard.stages,
        )
      ) {
        openDiscoveryMeetingDialogForMove(
          resolvedDealId,
          toStage,
          sourceStageId,
          movingRow?.customerName ?? null,
        );
        return;
      }
    }

    /** Sales-deals board: always PATCH business deal. Do not require `isBusinessDeal` on the row (paged kanban can miss the flag or differ from `sortedDeals`). */
    if (isBusinessDealBoard) {
      const movingBusinessDeal =
        deals.find((d) => d.id === resolvedDealId) ??
        sortedDeals.find((d) => d.id === resolvedDealId);

      let pipelineStageId: string;
      let pipelineSubStageId: string | null;
      if (stageInfo.isSubStage && stageInfo.parentStageId) {
        pipelineStageId = stageInfo.parentStageId;
        pipelineSubStageId = toStage;
      } else {
        pipelineStageId = toStage;
        pipelineSubStageId = null;
      }

      const boardRollback =
        dealBoardPaging && activeBoardId && movingBusinessDeal
          ? applyOptimisticBusinessBoardColumnMove(queryClient, {
            pipelineId: activeBoardId,
            dealId: resolvedDealId,
            fromStage: movingBusinessDeal.stage,
            toStage,
            deal: movingBusinessDeal,
          })
          : [];
      if (activeBoardId && movingBusinessDeal) {
        markLocalDealBoardMove(
          resolvedDealId,
          movingBusinessDeal.stage,
          String(toStage),
        );
      }

      const patchResult = businessDealsTableQueryArgs
        ? dispatch(
          businessDealsApi.util.updateQueryData(
            "getBusinessDeals",
            businessDealsTableQueryArgs,
            (draft) => {
              const row = draft.find((r) => r.id === resolvedDealId);
              if (row) {
                row.pipelineStageId = pipelineStageId;
                row.pipelineSubStageId = pipelineSubStageId;
              }
            },
          ),
        )
        : { undo: () => { } };

      try {
        await updateBusinessDealMut({
          id: resolvedDealId,
          body:
            pipelineSubStageId != null
              ? { pipelineStageId, pipelineSubStageId }
              : { pipelineStageId },
        }).unwrap();
        if (dealBoardPaging && activeBoardId) {
          const stagesForRefetch = mergeBoardColumnStageIdsWithFirstColumn(
            movingBusinessDeal
              ? [movingBusinessDeal.stage, toStage]
              : [toStage],
            firstKanbanColumnStageId,
          );
          queueMicrotask(() => {
            invalidateBusinessBoardColumnsForStages(
              queryClient,
              activeBoardId,
              stagesForRefetch,
              { invalidateCounts: true },
            );
          });
        }
      } catch (error: unknown) {
        patchResult.undo();
        clearLocalDealBoardMoveEcho();
        if (boardRollback.length > 0) {
          revertOptimisticBusinessBoardColumnMove(queryClient, boardRollback);
        }
        toast.error(getDealPipelineErrorMessage(error, pipelineUi.moveFail));
        throw error;
      }
      return;
    }

    if (leadsBoardPaging) {
      const moving =
        (deals.find(
          (d) => d.id === resolvedDealId && !d.isBusinessDeal,
        ) as ApiDeal | undefined) ??
        findLeadInBoardColumnCaches(
          queryClient,
          kanbanBoardFetchParams,
          resolvedDealId,
        );
      const dragAlreadyOptimistic =
        kanbanDragOptimisticAppliedRef.current.delete(resolvedDealId);
      const optimisticSnapshot = dragAlreadyOptimistic
        ? (kanbanLeadMoveRollbackRef.current.get(resolvedDealId) ?? null)
        : applyOptimisticLeadBoardMoveWithCounts(queryClient, {
          dealId: resolvedDealId,
          fromStage: moving?.stage ?? sourceStageId ?? "",
          toStage: toStage as string,
          listParams: kanbanBoardFetchParams,
          deal: moving,
        });
      if (!dragAlreadyOptimistic && moving != null) {
        markLocalDealBoardMove(
          resolvedDealId,
          moving.stage,
          String(toStage),
        );
      }

      try {
        await moveLeadTs({
          id: resolvedDealId,
          stageId: toStage as string,
          prob: stageInfo.prob,
        });
        kanbanLeadMoveRollbackRef.current.delete(resolvedDealId);

        void queryClient.invalidateQueries({
          queryKey: leadsKeys.boardCounts(kanbanBoardFetchParams),
        });

        if (isProposalStage(toStage)) {
          router.push(`/leads/${resolvedDealId}?tab=proposals`);
          toast.info(pipelineUi.proposalToast);
        }
      } catch (error: unknown) {
        clearLocalDealBoardMoveEcho();
        kanbanLeadMoveRollbackRef.current.delete(resolvedDealId);
        if (optimisticSnapshot) {
          revertOptimisticLeadBoardMoveWithCounts(
            queryClient,
            optimisticSnapshot,
            kanbanBoardFetchParams,
          );
        }
        const errorMessage = getDealPipelineErrorMessage(error, pipelineUi.moveFail);
        if (isDiscoveryMeetingMoveRequiredError(errorMessage)) {
          openDiscoveryMeetingDialogForMove(
            resolvedDealId,
            toStage,
            moving?.stage ?? sourceStageId,
            moving?.customerName ?? null,
          );
          return;
        }
        if (errorMessage.includes("converted to a customer")) {
          toast.error(pipelineUi.moveConvertedFail);
        } else {
          toast.error(errorMessage);
        }
        throw error;
      }
      return;
    }

    const leadsInfiniteKey = leadsKeys.listInfinite(getDealsQueryArgs);
    const previousInfinite = queryClient.getQueryData<
      InfiniteData<LeadsListPageResult>
    >(leadsInfiniteKey);
    queryClient.setQueryData<InfiniteData<LeadsListPageResult>>(
      leadsInfiniteKey,
      (old) => {
        if (!old) return old;
        return {
          ...old,
          pages: old.pages.map((p) => ({
            ...p,
            items: p.items.map((d) =>
              d.id === resolvedDealId
                ? { ...d, stage: toStage, stageOrder: 0 }
                : d,
            ),
          })),
        };
      },
    );

    try {
      await moveDeal({
        id: resolvedDealId,
        stageId: toStage,
        prob: stageInfo.prob,
        ...getDealsQueryArgs,
      }).unwrap();

      const movedRow = deals.find(
        (d) => d.id === resolvedDealId && !d.isBusinessDeal,
      );
      markLocalDealBoardMove(
        resolvedDealId,
        movedRow?.stage,
        String(toStage),
      );

      if (isProposalStage(toStage)) {
        router.push(`/leads/${resolvedDealId}?tab=proposals`);
        toast.info(pipelineUi.proposalToast);
      }
    } catch (error: unknown) {
      if (previousInfinite !== undefined) {
        queryClient.setQueryData(leadsInfiniteKey, previousInfinite);
      }
      const errorMessage = getDealPipelineErrorMessage(error, pipelineUi.moveFail);
      if (isDiscoveryMeetingMoveRequiredError(errorMessage)) {
        openDiscoveryMeetingDialogForMove(
          resolvedDealId,
          toStage,
          sourceStageId,
          movingRow?.customerName ?? null,
        );
        return;
      }
      if (errorMessage.includes("converted to a customer")) {
        toast.error(pipelineUi.moveConvertedFail);
      } else {
        toast.error(errorMessage);
      }
      throw error;
    }
  };

  const handleInterceptDiscoveryMeetingKanbanMove = React.useCallback(
    (payload: InterceptDiscoveryMeetingKanbanMovePayload): boolean => {
      if (isBusinessDealBoard || !activeBoardId) return false;
      if (
        !isEnteringDiscoverMeetingBookedStage(
          payload.sourceStageId,
          String(payload.destStageId),
          activeBoard.stages,
        )
      ) {
        return false;
      }

      openDiscoveryMeetingDialogForMove(
        payload.dealId,
        payload.destStageId,
        payload.sourceStageId,
        payload.deal.customerName ?? null,
      );
      return true;
    },
    [
      isBusinessDealBoard,
      activeBoardId,
      activeBoard.stages,
      openDiscoveryMeetingDialogForMove,
    ],
  );

  const handleInterceptLostKanbanMove = React.useCallback(
    (payload: InterceptLostKanbanMovePayload): boolean => {
      if (isBusinessDealBoard || !activeBoardId) return false;
      if (
        !isLostDestinationStageId(
          String(payload.destStageId),
          activeBoard.stages,
        )
      ) {
        return false;
      }
      const stageMeta = activeBoard.stages.find(
        (s) => s.id === payload.destStageId,
      );
      dispatch(
        openMoveToLostDialog({
          dealId: payload.dealId,
          pipelineId: activeBoardId,
          targetStageId: payload.destStageId,
          targetProb: typeof stageMeta?.prob === "number" ? stageMeta.prob : 0,
          sourceStageId: payload.sourceStageId,
          customerName: payload.deal.customerName ?? null,
          moveContext: leadsBoardPaging
            ? { kind: "paged", listParams: { ...kanbanBoardFetchParams } }
            : { kind: "infinite", listArgs: { ...getDealsQueryArgs } },
        }),
      );
      return true;
    },
    [
      isBusinessDealBoard,
      activeBoardId,
      activeBoard.stages,
      dispatch,
      leadsBoardPaging,
      kanbanBoardFetchParams,
      getDealsQueryArgs,
    ],
  );

  const handleReorderInStage = async (stageId: string, dealIds: string[]) => {
    if (isBusinessDealBoard) {
      if (!activeBoardId) return;
      applyOptimisticBusinessBoardColumnReorder(queryClient, {
        pipelineId: activeBoardId,
        stageId,
        dealIds,
      });
      return;
    }
    if (!activeBoardId || !dealIds.length) return;

    const reorderRollback = leadsBoardPaging
      ? applyOptimisticBoardColumnReorder(queryClient, {
        stageId,
        dealIds,
        listParams: kanbanBoardFetchParams,
      })
      : [];
    try {
      await reorderDealsInStage({
        stageId,
        dealIds,
        ...(leadsBoardPaging ? kanbanBoardFetchParams : getDealsQueryArgs),
      }).unwrap();

      if (leadsBoardPaging) {
        markLocalDealBoardMove(dealIds[0], stageId, stageId);
        // Cache already reflects new order; socket echo uses same from/to to avoid duplicate refetch.
      } else {
        invalidateLeadsQueries();
      }
    } catch (error: unknown) {
      if (reorderRollback.length > 0) {
        revertOptimisticBoardColumnMove(queryClient, reorderRollback);
      }
      toast.error(getDealPipelineErrorMessage(error, pipelineUi.reorderFail));
    }
  };

  const handleOpenAddModal = React.useCallback((stageId?: string) => {
    if (isBusinessDealBoard) {
      const stageMeta = stageId
        ? activeBoard.stages.find((s) => s.id === stageId)
        : undefined;
      let pipelineStageId: string | undefined;
      let pipelineSubStageId: string | undefined;
      if (stageMeta?.isSubStage && stageMeta.parentStageId) {
        pipelineStageId = stageMeta.parentStageId;
        pipelineSubStageId = stageId;
      } else if (stageId) {
        pipelineStageId = stageId;
      }
      dispatch(
        openBusinessDealDialog({
          prefill: {
            linkKind: "customer",
            pipelineId: activeBoardId ?? undefined,
            pipelineStageId: pipelineStageId ?? undefined,
            pipelineSubStageId: pipelineSubStageId ?? undefined,
          },
        }),
      );
      return;
    }
    dispatch(openDealsAddModal({ initialStageId: stageId }));
  }, [isBusinessDealBoard, activeBoard, activeBoardId, dispatch]);

  const handleOpenBusinessDeal = React.useCallback(() => {
    dispatch(openBusinessDealDialog({}));
  }, [dispatch]);

  const handleAssignTask = (deal: Deal) => {
    if (deal.isBusinessDeal) return;
    router.push(`/leads/${deal.id}?tab=tasks`);
  };

  const handleCreateDeal = React.useCallback(
    async (deal: Partial<ApiDeal>) => {
      try {
        const created = await createLeadMut({ ...deal });
        toast.success(pipelineUi.createOk(deal.customerName || "Unknown"));
        const stage = created?.stage ?? deal.stage;

        // After success, refetch the affected stage(s) to replace the optimistic lead with the real one
        if (stage) {
          refetchBoardDeals(stage, {
            invalidateList: !leadsBoardPaging,
            invalidateCounts: true,
            invalidateColumns: true
          });
        } else {
          refetchBoardDeals();
        }
      } catch (error: unknown) {
        toast.error(getDealPipelineErrorMessage(error, pipelineUi.createFail));
      }
    },
    [
      createLeadMut,
      pipelineUi,
      refetchBoardDeals,
      leadsBoardPaging,
      queryClient,
      kanbanBoardFetchParams,
    ],
  );

  const handleSaveDeal = React.useCallback(
    async (deal: ApiDeal) => {
      try {
        const { id, ...rest } = deal;
        const body = buildLeadUpdatePayload(rest);
        await updateDeal({ id, body }).unwrap();
        toast.success(pipelineUi.updateOk);
        dispatch(closeDealsModals());
        const prev =
          deals.find((d) => d.id === deal.id) ??
          findBoardDealById(deal.id) ??
          sortedDeals.find((d) => d.id === deal.id);
        const stages = new Set<string>();
        if (deal.stage) stages.add(deal.stage);
        if (prev?.stage && prev.stage !== deal.stage) {
          stages.add(prev.stage);
        }
        if (
          leadsBoardPaging &&
          prev?.stage &&
          deal.stage &&
          prev.stage !== deal.stage
        ) {
          void queryClient.invalidateQueries({
            queryKey: leadsKeys.boardCounts(kanbanBoardFetchParams),
          });
        }
        if (leadsBoardPaging && stages.size > 0) {
          refetchBoardDeals(Array.from(stages), {
            invalidateList: false,
            invalidateCounts: true,
          });
        } else if (stages.size > 0) {
          refetchBoardDeals(Array.from(stages));
        } else if (deal.stage) {
          refetchBoardDeals(deal.stage);
        } else {
          refetchBoardDeals();
        }
      } catch (error: unknown) {
        toast.error(getDealPipelineErrorMessage(error, pipelineUi.updateFail));
      }
    },
    [
      dispatch,
      pipelineUi,
      refetchBoardDeals,
      updateDeal,
      deals,
      leadsBoardCachedDeals,
      sortedDeals,
      leadsBoardPaging,
      queryClient,
      kanbanBoardFetchParams,
    ],
  );

  const handleAddStage = async (name: string) => {
    if (!activeBoardId) return;

    const currentPipeline = pipelines?.find(
      (pipeline) => pipeline.id === activeBoardId,
    );
    const persistedStages = (currentPipeline?.stages ?? []) as Stage[];
    const count = persistedStages.length;
    const slug = name
      .toLowerCase()
      .replace(/\s+/g, "_")
      .replace(/[^a-z0-9_]/g, "");
    const newStage: Stage = {
      id: slug || `stage_${Date.now()}`,
      name,
      color: PIPELINE_STAGE_COLORS[count % PIPELINE_STAGE_COLORS.length],
      prob: Math.min(100, (count + 1) * 20),
      stageType: boardStageType,
    };

    setIsAddingStage(true);
    try {
      await updatePipeline({
        id: activeBoardId,
        body: { stages: [...persistedStages, newStage] as any[] },
      });
      toast.success(`Stage "${name}" added`);
    } catch {
      toast.error("Failed to add stage");
    } finally {
      setIsAddingStage(false);
    }
  };

  const handleReorderStages = React.useCallback(async (stageIds: string[]) => {
    if (!activeBoardId) return;
    const currentPipeline = pipelines?.find((p) => p.id === activeBoardId);
    if (!currentPipeline) return;
    const allStages = (currentPipeline.stages ?? []) as Stage[];
    const stageMap = new Map(allStages.map((s) => [s.id, s]));
    const seen = new Set<string>();
    const reordered: Stage[] = [];
    for (const id of stageIds) {
      const s = stageMap.get(id);
      if (s && !seen.has(id)) {
        reordered.push(s);
        seen.add(id);
      }
    }
    for (const s of allStages) {
      if (!seen.has(s.id)) reordered.push(s);
    }
    if (reordered.length !== allStages.length) return;
    try {
      await updatePipeline({
        id: activeBoardId,
        body: { stages: reordered as any[] },
      });
    } catch {
      toast.error("Failed to save stage order");
    }
  }, [activeBoardId, pipelines, updatePipeline]);

  const handleAddBoard = React.useCallback(
    async (
      name: string,
      isPublic?: boolean,
      moduleDependencies?: string[],
    ) => {
      try {
        const result = await createPipeline({
          name,
          isPublic,
          moduleDependencies,
        });
        setPipelineParam(result.id ?? null);
        dispatch(closeDealsModals());
        toast.success(`Pipeline created: ${name}`);
      } catch {
        toast.error("Failed to create pipeline");
      }
    },
    [createPipeline, dispatch, setPipelineParam],
  );
  
  const handleImportSuccess = React.useCallback(async () => {
    // Refresh all board counts and columns after a bulk import
    refetchBoardDeals(undefined, { invalidateCounts: true, invalidateColumns: true });
  }, [refetchBoardDeals]);

  React.useEffect(() => {
    setDealsModalsHandlers({
      onCreateDeal: handleCreateDeal,
      onSaveDeal: handleSaveDeal,
      onCreatePipeline: handleAddBoard,
      onImportSuccess: handleImportSuccess,
    });
    return () => {
      clearDealsModalsHandlers();
    };
  }, [handleAddBoard, handleCreateDeal, handleSaveDeal, handleImportSuccess]);

  // ── Bulk selection handlers ──────────────────────────────────────────────
  const handleSelectDealId = (id: string, checked: boolean) => {
    if (!checked && isGlobalSelectAllActive) {
      setIsGlobalSelectAllActive(false);
    }
    setSelectedDealIds((prev) => {
      const next = new Set(prev);
      if (checked) {
        next.add(id);
      } else {
        next.delete(id);
      }
      return next;
    });
  };

  const handleSelectAllDeals = (checked: boolean) => {
    setIsGlobalSelectAllActive(checked);
    setSelectedDealIds(
      checked ? new Set(sortedDeals.map((d) => d.id)) : new Set(),
    );
  };

  React.useEffect(() => {
    if (!isGlobalSelectAllActive) return;
    setSelectedDealIds(new Set(sortedDeals.map((d) => d.id)));
  }, [isGlobalSelectAllActive, sortedDeals]);

  const handleBulkDealStageTransfer = async (
    stageId: string,
    _stageName: string,
  ) => {
    const ids = Array.from(selectedDealIds);
    const stageInfo = activeBoard.stages.find((s) => s.id === stageId);
    if (!stageInfo) return;
    let pipelineStageId: string;
    let pipelineSubStageId: string | null;
    if (stageInfo.isSubStage && stageInfo.parentStageId) {
      pipelineStageId = stageInfo.parentStageId;
      pipelineSubStageId = stageId;
    } else {
      pipelineStageId = stageId;
      pipelineSubStageId = null;
    }
    let failed = 0;
    await Promise.all(
      ids.map((id) => {
        const row =
          deals.find((d) => d.id === id) ??
          findBoardDealById(id) ??
          sortedDeals.find((d) => d.id === id);
        if (row?.isBusinessDeal) {
          return updateBusinessDealMut({
            id,
            body:
              pipelineSubStageId != null
                ? { pipelineStageId, pipelineSubStageId }
                : { pipelineStageId },
          })
            .unwrap()
            .catch(() => {
              failed++;
            });
        }
        const optimisticSnapshot =
          leadsBoardPaging && row && !row.isBusinessDeal
            ? applyOptimisticLeadBoardMoveWithCounts(queryClient, {
              dealId: id,
              fromStage: row.stage,
              toStage: stageId,
              listParams: kanbanBoardFetchParams,
              deal: row as ApiDeal,
            })
            : null;
        if (optimisticSnapshot && row) {
          markLocalDealBoardMove(id, row.stage, stageId);
        }
        return moveLeadTs({
          id,
          stageId,
          prob: stageInfo.prob,
        })
          .catch(() => {
            failed++;
            if (optimisticSnapshot) {
              revertOptimisticLeadBoardMoveWithCounts(
                queryClient,
                optimisticSnapshot,
                kanbanBoardFetchParams,
              );
            }
          });
      }),
    );
    setSelectedDealIds(new Set());
    setIsGlobalSelectAllActive(false);
    if (failed === 0) {
      if (!isBusinessDealBoard) {
        if (leadsBoardPaging) {
          const affectedStages = new Set<string>();
          affectedStages.add(stageId);
          for (const id of ids) {
            const row =
              deals.find((d) => d.id === id) ??
              findBoardDealById(id) ??
              sortedDeals.find((d) => d.id === id);
            if (row && !row.isBusinessDeal && row.stage) {
              affectedStages.add(row.stage);
            }
          }
          invalidateLeadsBoardColumnsForStages(
            queryClient,
            kanbanBoardFetchParams,
            mergeBoardColumnStageIdsWithFirstColumn(
              Array.from(affectedStages),
              firstKanbanColumnStageId,
            ),
            { invalidateList: false, invalidateCounts: true },
          );
        } else {
          invalidateLeadsQueries();
        }
      } else if (dealBoardPaging && activeBoardId) {
        const affectedStages = new Set<string>();
        affectedStages.add(stageId);
        for (const id of ids) {
          const row =
            deals.find((d) => d.id === id) ??
            findBoardDealById(id) ??
            sortedDeals.find((d) => d.id === id);
          if (row?.isBusinessDeal && row.stage) {
            affectedStages.add(row.stage);
          }
        }
        invalidateBusinessBoardColumnsForStages(
          queryClient,
          activeBoardId,
          mergeBoardColumnStageIdsWithFirstColumn(
            Array.from(affectedStages),
            firstKanbanColumnStageId,
          ),
          { invalidateCounts: true },
        );
      }
      toast.success(pipelineUi.bulkMovedOk(ids.length, stageInfo.name));
    } else {
      toast.error(`${failed} transfer${failed > 1 ? "s" : ""} failed`);
    }
  };

  const sharedStageId = React.useMemo(() => {
    if (selectedDealIds.size === 0) return undefined;
    const idList = Array.from(selectedDealIds);
    const firstDeal = deals.find((d) => d.id === idList[0]);
    if (!firstDeal) return undefined;
    const allSame = idList.every((id) => {
      const d = deals.find((deal) => deal.id === id);
      return d && d.stage === firstDeal.stage;
    });
    return allSame ? firstDeal.stage : undefined;
  }, [selectedDealIds, deals]);

  const clearBulkSelection = React.useCallback(() => {
    setSelectedDealIds(new Set());
    setIsGlobalSelectAllActive(false);
  }, []);

  const openBulkStageTransfer = React.useCallback(() => {
    setIsStageTransferOpen(true);
  }, []);

  const selectedDealIdList = React.useMemo(
    () => Array.from(selectedDealIds),
    [selectedDealIds],
  );

  const bulkTransferItems = React.useMemo(() => {
    if (!isStageTransferOpen || selectedDealIds.size === 0) return [];
    return Array.from(selectedDealIds).map((id) => {
      const d = deals.find((deal) => deal.id === id);
      return {
        id,
        name: d?.customerName || d?.companyName || d?.email || "Unknown",
        currentStage: activeBoard.stages?.find((s) => s.id === d?.stage)?.name || d?.stage || undefined,
      };
    });
  }, [isStageTransferOpen, selectedDealIds, deals, activeBoard.stages]);

  const convertedDealIdList = React.useMemo(
    () => Array.from(convertedDealIds),
    [convertedDealIds],
  );

  // Bulk assign snapshots removed — now using fetch by IDs on the target page

  const onOpenBulkEdit = React.useCallback(() => {
    if (!canBulkEditLead) return;
    setIsBulkEditOpen(true);
  }, [canBulkEditLead]);

  const onGoToBulkAssignPage = React.useCallback(() => {
    if (!canBulkAssignLead) return;
    if (selectedDealIds.size === 0) {
      toast.error("Select at least one lead to bulk-assign.");
      return;
    }
    
    const rowsToAssign: BulkAssignLeadRow[] = Array.from(selectedDealIds).map((id) => {
      const d = deals.find((deal) => deal.id === id) ??
                findBoardDealById(id) ??
                sortedDeals.find((deal) => deal.id === id);
                
      return {
        id,
        customerName: d?.customerName ?? null,
        pipelineId: d?.pipelineId ?? activeBoardId ?? undefined,
        pipelineName: pipelines?.find(p => p.id === (d?.pipelineId ?? activeBoardId))?.name ?? d?.pipelineId ?? activeBoardId ?? undefined,
        stage: d?.stage,
        stageName: activeBoard.stages?.find(s => s.id === d?.stage)?.name || d?.stage,
        team: d?.team ?? null,
        ownerId: d?.ownerId ?? d?.owner?.id,
        ownerName: d?.owner?.name,
        aeIds: d?.aeIds ?? [],
        bdrIds: d?.bdrIds ?? [],
        contributorIds: d?.contributorIds ?? [],
        updatedAt: d?.updatedAt,
        isAlreadyAssigned: (d?.aeIds?.length ?? 0) > 0 || (d?.bdrIds?.length ?? 0) > 0,
      };
    });

    dispatch(setBulkAssignSelection(rowsToAssign));
    router.push(`/leads/bulk-assign`);
  }, [
    canBulkAssignLead,
    selectedDealIds,
    deals,
    leadsBoardCachedDeals,
    sortedDeals,
    activeBoardId,
    activeBoard.stages,
    pipelines,
    dispatch,
    router,
  ]);

  const reduxHeaderState = useAppSelector(selectDealsHeader);
  /** Tracks whether Redux has ever had a date filter — tells "clear" from "deep link URL only". */
  const everHadReduxDateFilterRef = React.useRef(false);

  // Sync Global Config
  React.useEffect(() => {
    dispatch(setHeaderStaticConfig({
      headerCountBadgeSuffix: pipelineUi.headerCountBadgeSuffix,
      newRecordButtonLabel: pipelineUi.newRecordButtonLabel,
      searchPlaceholder: pipelineUi.searchPlaceholder,
      searchAriaLabel: pipelineUi.searchAriaLabel,
    }));
  }, [dispatch, pipelineUi]);

  // Search: header owns draft input and debounces commits into Redux `search` (list API + filters).

  // View is driven header → Redux → effect below → local `viewParam`. Do not push local `view` back
  // into Redux here: on header click Redux updates first while local is still stale, and dispatching
  // Pushing local `view` into Redux here would overwrite the header's choice and thrash re-renders.
  React.useEffect(() => {
    if (reduxHeaderState.teamFilterDisplay !== teamFilterDisplay) {
      dispatch(setTeamFilterDisplay(teamFilterDisplay));
    }
  }, [dispatch, teamFilterDisplay, reduxHeaderState.teamFilterDisplay]);

  React.useEffect(() => {
    if (JSON.stringify(reduxHeaderState.availableTeams) !== JSON.stringify(availableTeams)) {
      dispatch(setAvailableTeams(availableTeams));
    }
  }, [dispatch, availableTeams, reduxHeaderState.availableTeams]);

  React.useEffect(() => {
    if (reduxHeaderState.isAdmin !== isAdmin) {
      dispatch(setIsAdmin(isAdmin));
    }
  }, [dispatch, isAdmin, reduxHeaderState.isAdmin]);

  React.useEffect(() => {
    if (reduxHeaderState.showLeadOwnerFilter !== showLeadOwnerFilter) {
      dispatch(setShowLeadOwnerFilter(showLeadOwnerFilter));
    }
  }, [dispatch, showLeadOwnerFilter, reduxHeaderState.showLeadOwnerFilter]);

  React.useEffect(() => {
    if (reduxHeaderState.isCreatingDeal !== isCreatingDeal) {
      dispatch(setIsCreatingDeal(isCreatingDeal));
    }
  }, [dispatch, isCreatingDeal, reduxHeaderState.isCreatingDeal]);

  React.useEffect(() => {
    const disabled = !activeBoardId || !canCreateDeal;
    if (reduxHeaderState.addDealDisabled !== disabled) {
      dispatch(setAddDealDisabled(disabled));
    }
  }, [dispatch, activeBoardId, canCreateDeal, reduxHeaderState.addDealDisabled]);

  React.useEffect(() => {
    if (reduxHeaderState.view !== view) setView(reduxHeaderState.view);
  }, [reduxHeaderState.view, view, setView]);

  // Date range: On table view, Redux ↔ URL stay aligned for server list queries. On kanban, date lives in Redux only
  // (no `from`/`to` in URL) so changing the range does not refetch or wipe paged column caches.
  React.useEffect(() => {
    const r = reduxHeaderState.dateRange;
    const hasR = Boolean(r?.from);

    const reduxYFrom = ymdFromIsoOrNull(r?.from);
    const reduxYTo = ymdFromIsoOrNull(r?.to);
    const urlYFrom = dateFrom ?? null;
    const urlYTo = dateTo ?? null;

    if (hasR) everHadReduxDateFilterRef.current = true;

    if (reduxYFrom === urlYFrom && reduxYTo === urlYTo) return;

    if (view === "kanban") {
      if ((dateFrom || dateTo) && !everHadReduxDateFilterRef.current) {
        dispatch(
          setReduxDateRange({
            from: new Date(`${dateFrom}T00:00:00`).toISOString(),
            to: dateTo ? new Date(`${dateTo}T00:00:00`).toISOString() : undefined,
          }),
        );
        everHadReduxDateFilterRef.current = true;
        return;
      }
      if ((dateFrom || dateTo) && !hasR && everHadReduxDateFilterRef.current) {
        setDateRange(undefined);
        everHadReduxDateFilterRef.current = false;
      }
      return;
    }

    if (hasR) {
      setDateRange({
        from: new Date(r!.from as string),
        to: r!.to ? new Date(r.to) : undefined,
      });
      return;
    }

    if ((dateFrom || dateTo) && everHadReduxDateFilterRef.current) {
      setDateRange(undefined);
      everHadReduxDateFilterRef.current = false;
      return;
    }

    if ((dateFrom || dateTo) && !everHadReduxDateFilterRef.current) {
      dispatch(
        setReduxDateRange({
          from: new Date(`${dateFrom}T00:00:00`).toISOString(),
          to: dateTo ? new Date(`${dateTo}T00:00:00`).toISOString() : undefined,
        }),
      );
      everHadReduxDateFilterRef.current = true;
    }
  }, [
    reduxHeaderState.dateRange,
    dateFrom,
    dateTo,
    setDateRange,
    dispatch,
    view,
  ]);

  const prevContentStateRef = React.useRef<any>(null);

  React.useEffect(() => {
    const nextState = {
      pipelinesLoading,
      dealsLoading,
      dealsFetching,
      pipelineCount: pipelines?.length ?? 0,
      convertedDealIdList,
      isAdmin,
      isAddingStage,
      isMovingDeal,
      isReorderingDeal,
      sortCol,
      sortDir,
      pipelineEmptyDescription: pipelineUi.pipelineEmptyDescription,
      kanbanBoardCopyPartial: pipelineUi.kanbanBoardCopy ?? {},
      kanbanEmptyBoardTitle: pipelineUi.kanbanEmptyBoardTitle ?? "",
      kanbanEmptyBoardDescription: pipelineUi.kanbanEmptyBoardDescription ?? "",
      listSelectAllAriaLabel: pipelineUi.listSelectAllAriaLabel ?? "",
      listEmptyTitle: pipelineUi.listEmptyTitle ?? "",
      listEmptySubtitle: pipelineUi.listEmptySubtitle ?? "",
      boardPagingEnabled: leadsBoardPaging || dealBoardPaging,
      boardPagingEntity: dealBoardPaging ? ("business" as const) : ("leads" as const),
      businessPipelineId: dealBoardPaging
        ? (activeBoardId ?? undefined)
        : undefined,
      kanbanClientSearchQuery:
        leadsBoardPaging || dealBoardPaging ? dealsHeaderSearch : "",
      selectedDealIdList,
      listInfiniteHasMore: Boolean(leadsListInfiniteLoadConfig?.hasMore),
      listInfiniteIsLoadingMore: Boolean(
        leadsListInfiniteLoadConfig?.isLoadingMore,
      ),
      assignTaskEnabled: !isBusinessDealBoard,
    };

    if (!equal(prevContentStateRef.current, nextState)) {
      prevContentStateRef.current = nextState;
      dispatch(replaceDealsPipelineContentState(nextState));
    }
  }, [
    dispatch,
    pipelinesLoading,
    dealsLoading,
    dealsFetching,
    pipelines?.length,
    convertedDealIdList,
    isAdmin,
    isAddingStage,
    isMovingDeal,
    isReorderingDeal,
    sortCol,
    sortDir,
    pipelineUi,
    leadsBoardPaging,
    dealBoardPaging,
    activeBoardId,
    dealsHeaderSearch,
    selectedDealIdList,
    leadsListInfiniteLoadConfig?.hasMore,
    leadsListInfiniteLoadConfig?.isLoadingMore,
    isBusinessDealBoard,
  ]);

  React.useEffect(() => {
    setDealsContentHandlers({
      onCreatePipeline: handleOpenCreatePipelineModal,
      onOpenDeal: handleOpenDeal,
      onEditDeal: handleOpenEditDeal,
      onDeleteDeal: handleDeleteDeal,
      onMoveDeal: handleMoveDeal,
      onPagedLeadMoveOptimistic: isBusinessDealBoard
        ? undefined
        : handlePagedLeadMoveOptimistic,
      onInterceptLostMove: isBusinessDealBoard
        ? undefined
        : handleInterceptLostKanbanMove,
      onInterceptDiscoveryMeetingMove: isBusinessDealBoard
        ? undefined
        : handleInterceptDiscoveryMeetingKanbanMove,
      onReorderInStage: handleReorderInStage,
      onAddStage: handleAddStage,
      onReorderStages: handleReorderStages,
      onAssignTask: isBusinessDealBoard ? undefined : handleAssignTask,
      onSort: handleListSort,
      onSelectId: handleSelectDealId,
      onSelectAll: handleSelectAllDeals,
      onColumnDealsChange: handleColumnDeals,
      onListLoadMore: leadsListInfiniteLoadConfig?.onLoadMore,
    });
    return () => {
      clearDealsContentHandlers();
    };
  }, [
    handleOpenCreatePipelineModal,
    handleOpenDeal,
    handleOpenEditDeal,
    handleDeleteDeal,
    handleMoveDeal,
    handlePagedLeadMoveOptimistic,
    handleInterceptLostKanbanMove,
    handleInterceptDiscoveryMeetingKanbanMove,
    handleReorderInStage,
    handleAddStage,
    handleReorderStages,
    handleAssignTask,
    handleListSort,
    handleSelectDealId,
    handleSelectAllDeals,
    handleColumnDeals,
    leadsListInfiniteLoadConfig?.onLoadMore,
    isBusinessDealBoard,
  ]);

  // Global Triggers
  React.useEffect(() => {
    const handleAddDeal = () => handleOpenAddModal();
    const handleOpenBiz = () => handleOpenBusinessDeal();
    const handlePipelineChangeTrigger = (e: any) => handlePipelineChange(e.detail);

    window.addEventListener("trigger-add-deal", handleAddDeal);
    window.addEventListener("trigger-open-business-deal", handleOpenBiz);
    window.addEventListener("trigger-pipeline-change", handlePipelineChangeTrigger);

    return () => {
      window.removeEventListener("trigger-add-deal", handleAddDeal);
      window.removeEventListener("trigger-open-business-deal", handleOpenBiz);
      window.removeEventListener("trigger-pipeline-change", handlePipelineChangeTrigger);
    };
  }, [handleOpenAddModal, handleOpenBusinessDeal, handlePipelineChange]);

  return (
    <div className="flex flex-col h-full gap-4.5 p-1 sm:p-2 md:p-3 animate-in fade-in duration-500 overflow-hidden ">
      <DealsPageHeader
        pageTitle={pageTitle}
        filteredDealsCount={headerDealsCount}
        pipelines={pipelines}
        stages={activeBoard.stages}
        activeBoardId={activeBoardId}
        canSubject={isBusinessDealBoard ? "deal" : "lead"}
        onClearDealUrlDates={
          isBusinessDealBoard ? handleClearDealUrlDates : undefined
        }
      />

      <Suspense fallback={<DealsSkeleton />}>
        <div className="flex flex-1 min-h-0 flex-col overflow-hidden">
          {view === "list" ? (
            <DealsPipelineBulkSelectionBar
              selectedCount={selectedDealIds.size}
              pipelineUi={pipelineUi}
              canUpdateDeal={canUpdateDeal}
              canBulkAssignLead={!isBusinessDealBoard && canBulkAssignLead}
              canBulkEditLead={!isBusinessDealBoard && canBulkEditLead}
              onClear={clearBulkSelection}
              onOpenBulkStageTransfer={openBulkStageTransfer}
              onOpenBulkEdit={
                !isBusinessDealBoard && canBulkEditLead
                  ? onOpenBulkEdit
                  : undefined
              }
              onGoToBulkAssignPage={
                !isBusinessDealBoard && canBulkAssignLead
                  ? onGoToBulkAssignPage
                  : undefined
              }
            />
          ) : null}
          <DealsContent
            sortedDeals={sortedDeals}
            activeBoard={activeBoard}
            boardUiKey={boardUiKey}
            boardListParams={kanbanBoardFetchParams}
            kanbanClientBoardFilters={kanbanClientBoardFilters}
            canSubject={isBusinessDealBoard ? "deal" : "lead"}
            closedWonCustomerPaymentKpis={
              isBusinessDealBoard ? (paymentKpis ?? null) : undefined
            }
            closedWonCustomerPaymentKpisLoading={
              Boolean(isBusinessDealBoard && paymentKpisLoading)
            }
            teams={availableTeams}
          />
        </div>
      </Suspense>

      <Suspense fallback={null}>
        <DealsModals
          stages={activeBoard.stages}
          pipelineId={activeBoardId}
          boardUiKey={boardUiKey}
        />
      </Suspense>

      <Suspense fallback={null}>
        <BulkStageTransferModal
          open={isStageTransferOpen ?? false}
          onClose={() => setIsStageTransferOpen(false)}
          extraStages={activeBoard.stages}
          selectedCount={selectedDealIds.size}
          entityLabel={pipelineUi.entityLabel}
          currentStageId={sharedStageId}
          items={bulkTransferItems}
          onTransfer={handleBulkDealStageTransfer}
        />
        {!isBusinessDealBoard ? (
          <>
            <BulkEditLeadsModal
              open={isBulkEditOpen}
              onClose={() => setIsBulkEditOpen(false)}
              leadIds={selectedDealIdList}
              onApplied={() => {
                clearBulkSelection();
              }}
            />
          </>
        ) : null}
        <DeleteConfirmationAlert
          open={!!dealToDeleteId}
          onOpenChange={(open) => !open && setDealToDeleteId(null)}
          onConfirm={confirmDeleteDeal}
          isLoading={isDeletingDeal || isDeletingBusinessDeal}
          title={pipelineUi.deleteTitle}
          description={pipelineUi.deleteDescription}
          confirmText={pipelineUi.deleteConfirm}
        />
      </Suspense>
    </div>
  );
}

export default function DealsPage() {
  return (
    <DealsPipelinePage
      pageTitle="Leads Pipeline"
      boardStageType="Lead"
      basePath="/leads"
    />
  );
}

export function SalesDealsPipelinePage() {
  return (
    <DealsPipelinePage
      pageTitle="Deal Pipeline"
      boardStageType="Deal"
      basePath="/sales-deals"
    />
  );
}
