"use client";

import { createPortal } from "react-dom";
import { useState, useEffect, useCallback, useMemo, useRef, type ReactNode } from "react";
import Link from "next/link";
import type { Resource, SkillEntry } from "@capakraken/shared";
import { RESOURCE_COLUMNS } from "@capakraken/shared";
import { BlueprintTarget, ResourceType } from "@capakraken/shared";
import { trpc } from "~/lib/trpc/client.js";
import { formatMoney } from "~/lib/format.js";
import { generateCsv, downloadCsv } from "~/lib/csv-export.js";
import dynamic from "next/dynamic";
import { ResourceModal } from "~/components/resources/ResourceModal.js";
import { BulkEditModal } from "~/components/resources/BulkEditModal.js";

const ImportModal = dynamic(
  () => import("~/components/resources/ImportModal.js").then((mod) => mod.ImportModal),
  { ssr: false },
);
import { useSelection } from "~/hooks/useSelection.js";
import { BatchActionBar } from "~/components/ui/BatchActionBar.js";
import { ConfirmDialog } from "~/components/ui/ConfirmDialog.js";
import { FilterBar } from "~/components/ui/FilterBar.js";
import { FilterChips } from "~/components/ui/FilterChips.js";
import { CustomFieldFilterBar } from "~/components/ui/CustomFieldFilterBar.js";
import { useFilters } from "~/hooks/useFilters.js";
import { SortableColumnHeader } from "~/components/ui/SortableColumnHeader.js";
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
import { ColumnTogglePanel } from "~/components/ui/ColumnTogglePanel.js";
import { InfiniteScrollSentinel } from "~/components/ui/InfiniteScrollSentinel.js";
import { useTableSort } from "~/hooks/useTableSort.js";
import { usePermissions } from "~/hooks/usePermissions.js";
import { useColumnConfig } from "~/hooks/useColumnConfig.js";
import { useViewPrefs } from "~/hooks/useViewPrefs.js";
import { useRowOrder } from "~/hooks/useRowOrder.js";
import { useAnchoredOverlay } from "~/hooks/useAnchoredOverlay.js";
import { DraggableTableRow } from "~/components/ui/DraggableTableRow.js";

type ModalState =
  | { type: "closed" }
  | { type: "create" }
  | { type: "edit"; resource: Resource }
  | { type: "import" }
  | { type: "bulkEdit" };

type ConfirmState =
  | { type: "closed" }
  | { type: "batchDeactivate"; ids: string[] }
  | { type: "deactivate"; resource: Resource };

type ActiveFilter = "active" | "inactive" | "all";
type BooleanFilter = "all" | "yes" | "no";
type ResourceListPage = {
  resources: Resource[];
  total: number;
  nextCursor?: string | null;
};

type CountryOption = {
  id: string;
  name: string;
};

const DEFAULT_HIDDEN_RESOURCE_TYPES = [ResourceType.FREELANCER] as const;
const DEFAULT_BOOLEAN_FILTER: BooleanFilter = "no";

const RESOURCE_TYPE_LABELS: Record<ResourceType, string> = {
  [ResourceType.EMPLOYEE]: "Employee",
  [ResourceType.FREELANCER]: "Freelancer",
  [ResourceType.APPRENTICE]: "Apprentice",
  [ResourceType.INTERN]: "Intern",
  [ResourceType.STUDENT]: "Student",
};

function FilterDropdown({
  label,
  children,
  widthClassName = "w-80",
  buttonClassName = "min-w-56",
  tooltipContent,
}: {
  label: string;
  children: ReactNode;
  widthClassName?: string;
  buttonClassName?: string;
  tooltipContent?: ReactNode;
}) {
  const [isOpen, setIsOpen] = useState(false);
  const { triggerRef, panelRef, position, handleOpenChange } = useAnchoredOverlay<HTMLDivElement>({
    open: isOpen,
    onClose: () => setIsOpen(false),
    matchTriggerWidth: true,
  });

  return (
    <div ref={triggerRef} className="relative">
      <div className="flex items-center gap-2">
        <button
          type="button"
          onClick={() => {
            const nextOpen = !isOpen;
            handleOpenChange(nextOpen);
            setIsOpen(nextOpen);
          }}
          className={`inline-flex items-center justify-between gap-3 rounded-xl border border-gray-300 bg-white px-3 py-2 text-sm text-gray-700 shadow-sm transition hover:border-gray-400 hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-900 dark:text-gray-200 dark:hover:bg-gray-800 ${buttonClassName}`}
        >
          <span className="text-left">{label}</span>
          <span className="text-xs text-gray-400 dark:text-gray-500">{isOpen ? "▲" : "▼"}</span>
        </button>
        {tooltipContent ? <InfoTooltip content={tooltipContent} width="w-72" /> : null}
      </div>
      {isOpen &&
        createPortal(
          <div
            ref={panelRef}
            style={{
              position: "fixed",
              top: position.top,
              left: position.left,
              minWidth: position.minWidth,
            }}
            className={`z-[9998] rounded-2xl border border-gray-200 bg-white p-3 shadow-xl dark:border-gray-700 dark:bg-gray-900 ${widthClassName}`}
          >
            {children}
          </div>,
          document.body,
        )}
    </div>
  );
}

function BooleanBadge({ value }: { value: boolean }) {
  return (
    <span
      className={`inline-block rounded-full px-2 py-0.5 text-xs font-medium ${
        value
          ? "bg-amber-100 text-amber-700 dark:bg-amber-950/40 dark:text-amber-300"
          : "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-300"
      }`}
    >
      {value ? "Yes" : "No"}
    </span>
  );
}

export function ResourcesClient() {
  const [search, setSearch] = useState("");
  const [chapterFilter, setChapterFilter] = useState<string[]>([]);
  const [isActiveFilter, setIsActiveFilter] = useState<ActiveFilter>("active");
  const [includeProposedChargeability, setIncludeProposedChargeability] = useState(false);
  const [hiddenCountryIds, setHiddenCountryIds] = useState<string[]>([]);
  const [includeWithoutCountry, setIncludeWithoutCountry] = useState(true);
  const [hiddenResourceTypes, setHiddenResourceTypes] = useState<ResourceType[]>([
    ...DEFAULT_HIDDEN_RESOURCE_TYPES,
  ]);
  const [includeWithoutResourceType, setIncludeWithoutResourceType] = useState(true);
  const [rolledOffFilter, setRolledOffFilter] = useState<BooleanFilter>(DEFAULT_BOOLEAN_FILTER);
  const [departedFilter, setDepartedFilter] = useState<BooleanFilter>(DEFAULT_BOOLEAN_FILTER);
  const [modal, setModal] = useState<ModalState>({ type: "closed" });
  const [confirm, setConfirm] = useState<ConfirmState>({ type: "closed" });

  const selection = useSelection();
  const utils = trpc.useUtils();
  const { canViewScores, canViewCosts } = usePermissions();
  const {
    customFieldFilters,
    setCustomFieldFilter,
    clearFilters: clearCustomFilters,
  } = useFilters();

  // ─── Custom field columns from global blueprints ──────────────────────────
  const { data: globalFieldDefs } = trpc.blueprint.getGlobalFieldDefs.useQuery(
    { target: BlueprintTarget.RESOURCE },
    { staleTime: 300_000 },
  );
  const customColumns = useMemo(
    () =>
      (globalFieldDefs ?? [])
        .filter((f) => f.showInList)
        .map((f) => ({
          key: `custom_${f.key}`,
          label: f.label,
          defaultVisible: false,
          hideable: true,
          isCustom: true,
          fieldType: f.type as string,
        })),
    [globalFieldDefs],
  );

  const filterableFields = useMemo(
    () => (globalFieldDefs ?? []).filter((f) => f.isFilterable),
    [globalFieldDefs],
  );

  // ─── Column visibility ────────────────────────────────────────────────────
  const { allColumns, visibleColumns, visibleKeys, setVisible } = useColumnConfig(
    "resources",
    RESOURCE_COLUMNS,
    customColumns,
  );
  const defaultKeys = useMemo(
    () => RESOURCE_COLUMNS.filter((c) => c.defaultVisible).map((c) => c.key),
    [],
  );

  // ─── Infinite query (cursor-based) ────────────────────────────────────────
  const {
    data,
    isLoading,
    isFetchingNextPage,
    fetchNextPage,
    hasNextPage,
    // Keep this boundary shallow; the full TRPC inference here trips TS depth limits.
  } = (trpc.resource.listStaff.useInfiniteQuery as any)(
    {
      isActive: isActiveFilter === "all" ? undefined : isActiveFilter === "active",
      search: search || undefined,
      chapters: chapterFilter.length > 0 ? chapterFilter : undefined,
      excludedCountryIds: hiddenCountryIds,
      includeWithoutCountry,
      excludedResourceTypes: hiddenResourceTypes,
      includeWithoutResourceType,
      rolledOff: rolledOffFilter === "all" ? undefined : rolledOffFilter === "yes",
      departed: departedFilter === "all" ? undefined : departedFilter === "yes",
      includeRoles: true,
      limit: 50,
      ...(customFieldFilters.length > 0 ? { customFieldFilters } : {}),
    },
    {
      getNextPageParam: (lastPage: ResourceListPage) => lastPage.nextCursor ?? undefined,
      initialCursor: undefined,
      placeholderData: (prev: { pages: ResourceListPage[] } | undefined) => prev,
      staleTime: 20_000,
    },
  ) as {
    data:
      | {
          pages: ResourceListPage[];
        }
      | undefined;
    isLoading: boolean;
    isFetchingNextPage: boolean;
    fetchNextPage: () => Promise<unknown>;
    hasNextPage: boolean | undefined;
  };

  const resources = useMemo(
    () => (data?.pages.flatMap((p) => p.resources) ?? []) as unknown as Resource[],
    [data],
  );
  const total = data?.pages[0]?.total ?? 0;

  // ─── Sort + row order (per-user persistence) ──────────────────────────────
  const viewPrefs = useViewPrefs("resources");
  const { sorted, sortField, sortDir, toggle, reset } = useTableSort<Resource>(resources, {
    initialField: viewPrefs.savedSort?.field ?? null,
    initialDir: viewPrefs.savedSort?.dir ?? null,
    onSortChange: (field, dir) => {
      viewPrefs.setSavedSort(field && dir ? { field, dir } : null);
    },
  });
  const {
    orderedRows: displayedResources,
    reorder,
    isCustomOrder,
    resetOrder,
  } = useRowOrder(sorted, viewPrefs, sortField, reset);
  const rowDragRef = useRef<string | null>(null);
  const resourceIds: string[] = displayedResources.map((r) => r.id);

  // Performance note: cursor-based infinite scroll (50 rows/page) keeps DOM nodes bounded.
  // True virtualizer is not needed for typical resource counts (<500).

  // ─── Chargeability stats ──────────────────────────────────────────────────
  const { data: chargeabilityData } = trpc.resource.getChargeabilityStats.useQuery(
    { includeProposed: includeProposedChargeability },
    { enabled: canViewCosts, placeholderData: (prev) => prev, staleTime: 60_000 },
  );
  const chargeabilityMap = useMemo(
    () => new Map((chargeabilityData ?? []).map((s) => [s.id, s])),
    [chargeabilityData],
  );

  // ─── Chapters filter ──────────────────────────────────────────────────────
  const { data: chapterData } = trpc.resource.chapters.useQuery(undefined, {
    placeholderData: (prev) => prev,
    staleTime: 60_000,
  });
  const chapters = chapterData ?? [];
  const { data: countriesData } = trpc.country.list.useQuery(undefined, { staleTime: 60_000 });
  const countries = useMemo(
    () =>
      ((countriesData ?? []) as Array<{ id: string; name: string }>).map((country) => ({
        id: country.id,
        name: country.name,
      })),
    [countriesData],
  ) as CountryOption[];
  const allCountryIds = useMemo(() => countries.map((country) => country.id), [countries]);
  const isAllCountriesVisible = hiddenCountryIds.length === 0;
  const allResourceTypes = useMemo(() => Object.values(ResourceType), []);
  const isAllResourceTypesVisible = hiddenResourceTypes.length === 0;
  const isDefaultResourceTypeVisibility =
    hiddenResourceTypes.length === DEFAULT_HIDDEN_RESOURCE_TYPES.length &&
    DEFAULT_HIDDEN_RESOURCE_TYPES.every((resourceType) =>
      hiddenResourceTypes.includes(resourceType),
    );

  // ─── Mutations ────────────────────────────────────────────────────────────
  const deactivateMutation = trpc.resource.deactivate.useMutation({
    onSuccess: () => {
      void utils.resource.directory.invalidate();
      void utils.resource.listStaff.invalidate();
    },
  });
  const batchDeactivateMutation = trpc.resource.batchDeactivate.useMutation({
    onSuccess: () => {
      void utils.resource.directory.invalidate();
      void utils.resource.listStaff.invalidate();
      selection.clear();
    },
  });

  useEffect(() => {
    selection.clear();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [
    search,
    chapterFilter,
    isActiveFilter,
    hiddenCountryIds,
    includeWithoutCountry,
    hiddenResourceTypes,
    includeWithoutResourceType,
    rolledOffFilter,
    departedFilter,
  ]);

  function closeModal() {
    setModal({ type: "closed" });
  }

  function handleConfirm() {
    if (confirm.type === "deactivate") {
      deactivateMutation.mutate({ id: confirm.resource.id });
    } else if (confirm.type === "batchDeactivate") {
      batchDeactivateMutation.mutate({ ids: confirm.ids });
    }
    setConfirm({ type: "closed" });
  }

  function clearAll() {
    setSearch("");
    setChapterFilter([]);
    setIsActiveFilter("active");
    setHiddenCountryIds([]);
    setIncludeWithoutCountry(true);
    setHiddenResourceTypes([...DEFAULT_HIDDEN_RESOURCE_TYPES]);
    setIncludeWithoutResourceType(true);
    setRolledOffFilter(DEFAULT_BOOLEAN_FILTER);
    setDepartedFilter(DEFAULT_BOOLEAN_FILTER);
    clearCustomFilters();
  }

  const handleFetchNext = useCallback(() => {
    if (hasNextPage && !isFetchingNextPage) void fetchNextPage();
  }, [hasNextPage, isFetchingNextPage, fetchNextPage]);

  const toggleCountry = useCallback(
    (countryId: string) => {
      setHiddenCountryIds((current) =>
        current.includes(countryId)
          ? current.filter((id) => id !== countryId)
          : [...current, countryId].sort((left, right) => {
              const leftIndex = allCountryIds.indexOf(left);
              const rightIndex = allCountryIds.indexOf(right);
              return leftIndex - rightIndex;
            }),
      );
    },
    [allCountryIds],
  );

  const toggleAllCountries = useCallback(() => {
    setHiddenCountryIds((current) => (current.length === 0 ? allCountryIds : []));
  }, [allCountryIds]);

  const toggleResourceType = useCallback((resourceType: ResourceType) => {
    setHiddenResourceTypes((current) => {
      if (current.includes(resourceType)) {
        return current.filter((value) => value !== resourceType);
      }
      return [...current, resourceType].sort(
        (left, right) =>
          Object.values(ResourceType).indexOf(left) - Object.values(ResourceType).indexOf(right),
      );
    });
  }, []);

  const toggleAllResourceTypes = useCallback(() => {
    setHiddenResourceTypes((current) => (current.length === 0 ? allResourceTypes : []));
  }, [allResourceTypes]);

  const toggleChapter = useCallback(
    (chapter: string) => {
      setChapterFilter((current) => {
        const currentSelection = current.length === 0 ? [...chapters] : current;
        const next = currentSelection.includes(chapter)
          ? currentSelection.filter((value) => value !== chapter)
          : [...currentSelection, chapter];
        if (next.length === chapters.length) {
          return [];
        }
        return next.sort((left, right) => chapters.indexOf(left) - chapters.indexOf(right));
      });
    },
    [chapters],
  );

  const visibleCountryCount = countries.length - hiddenCountryIds.length;
  const countryFilterLabel = useMemo(() => {
    if (countries.length === 0) return "Countries";
    if (hiddenCountryIds.length === 0 && includeWithoutCountry)
      return `Countries: all (${countries.length})`;
    if (visibleCountryCount === 0 && !includeWithoutCountry) return "Countries: none";
    const suffix = includeWithoutCountry ? " + unspecified" : "";
    return `Countries: ${visibleCountryCount}/${countries.length}${suffix}`;
  }, [countries.length, hiddenCountryIds.length, includeWithoutCountry, visibleCountryCount]);

  const visibleResourceTypeCount = allResourceTypes.length - hiddenResourceTypes.length;
  const resourceTypeFilterLabel = useMemo(() => {
    if (hiddenResourceTypes.length === 0 && includeWithoutResourceType) {
      return `Types: all (${allResourceTypes.length})`;
    }
    if (visibleResourceTypeCount === 0 && !includeWithoutResourceType) {
      return "Types: none";
    }
    const suffix = includeWithoutResourceType ? " + unspecified" : "";
    return `Types: ${visibleResourceTypeCount}/${allResourceTypes.length}${suffix}`;
  }, [
    allResourceTypes.length,
    hiddenResourceTypes.length,
    includeWithoutResourceType,
    visibleResourceTypeCount,
  ]);
  const chapterFilterLabel = useMemo(() => {
    if (chapters.length === 0 || chapterFilter.length === 0) {
      return chapters.length > 0 ? `Chapter: all (${chapters.length})` : "Chapter: all";
    }
    return `Chapter: ${chapterFilter.length}/${chapters.length}`;
  }, [chapterFilter.length, chapters.length]);
  const activeFilterLabel = useMemo(() => {
    if (isActiveFilter === "inactive") return "Status: inactive";
    if (isActiveFilter === "all") return "Status: all";
    return "Status: active";
  }, [isActiveFilter]);
  const rolledOffFilterLabel = useMemo(() => {
    if (rolledOffFilter === "yes") return "Rolled Off: yes";
    if (rolledOffFilter === "all") return "Rolled Off: all";
    return "Rolled Off: no";
  }, [rolledOffFilter]);
  const departedFilterLabel = useMemo(() => {
    if (departedFilter === "yes") return "Departed: yes";
    if (departedFilter === "all") return "Departed: all";
    return "Departed: no";
  }, [departedFilter]);

  const exportSelectedCsv = useCallback(() => {
    const selected = displayedResources.filter((r) => selection.selectedIds.has(r.id));
    if (selected.length === 0) return;
    const csv = generateCsv(selected, [
      { header: "EID", accessor: (r) => r.eid },
      { header: "Name", accessor: (r) => r.displayName },
      { header: "Email", accessor: (r) => r.email },
      { header: "Chapter", accessor: (r) => r.chapter ?? "" },
      { header: "LCR (cents)", accessor: (r) => r.lcrCents },
      { header: "Currency", accessor: (r) => r.currency },
      { header: "Chargeability Target", accessor: (r) => r.chargeabilityTarget },
      { header: "Active", accessor: (r) => r.isActive ? "Yes" : "No" },
    ]);
    downloadCsv(csv, `resources-export-${new Date().toISOString().slice(0, 10)}.csv`);
  }, [displayedResources, selection.selectedIds]);

  const chips = [
    ...(search ? [{ label: `Search: "${search}"`, onRemove: () => setSearch("") }] : []),
    ...(chapterFilter.length > 0
      ? [
          {
            label: `Chapters: ${chapterFilter.length}/${chapters.length}`,
            onRemove: () => setChapterFilter([]),
          },
        ]
      : []),
    ...(isActiveFilter !== "active"
      ? [
          {
            label: isActiveFilter === "all" ? "Showing all" : "Inactive only",
            onRemove: () => setIsActiveFilter("active"),
          },
        ]
      : []),
    ...(hiddenCountryIds.length > 0
      ? [
          {
            label: `Hidden countries: ${hiddenCountryIds.length}`,
            onRemove: () => setHiddenCountryIds([]),
          },
        ]
      : []),
    ...(!includeWithoutCountry
      ? [{ label: "No country hidden", onRemove: () => setIncludeWithoutCountry(true) }]
      : []),
    ...(!isDefaultResourceTypeVisibility
      ? [
          {
            label:
              hiddenResourceTypes.length === 0
                ? "Hidden types: none"
                : `Hidden types: ${hiddenResourceTypes.map((type) => RESOURCE_TYPE_LABELS[type]).join(", ")}`,
            onRemove: () => setHiddenResourceTypes([...DEFAULT_HIDDEN_RESOURCE_TYPES]),
          },
        ]
      : []),
    ...(!includeWithoutResourceType
      ? [{ label: "Unspecified type hidden", onRemove: () => setIncludeWithoutResourceType(true) }]
      : []),
    ...(rolledOffFilter !== DEFAULT_BOOLEAN_FILTER
      ? [
          {
            label: `Rolled Off: ${rolledOffFilter === "yes" ? "Yes" : "No"}`,
            onRemove: () => setRolledOffFilter(DEFAULT_BOOLEAN_FILTER),
          },
        ]
      : []),
    ...(departedFilter !== DEFAULT_BOOLEAN_FILTER
      ? [
          {
            label: `Departed: ${departedFilter === "yes" ? "Yes" : "No"}`,
            onRemove: () => setDepartedFilter(DEFAULT_BOOLEAN_FILTER),
          },
        ]
      : []),
    ...customFieldFilters.map((f) => ({
      label: `${f.key}: ${f.value}`,
      onRemove: () => setCustomFieldFilter(f.key, "", f.type),
    })),
  ];

  return (
    <div className="app-page space-y-5 pb-24">
      <div className="app-page-header gap-4">
        <div>
          <h1 className="app-page-title">Resources</h1>
          {!isLoading && (
            <p className="app-page-subtitle mt-1">
              {total} resource{total !== 1 ? "s" : ""}
            </p>
          )}
        </div>
        <div className="flex items-center gap-2">
          <button
            type="button"
            onClick={() => setModal({ type: "import" })}
            className="inline-flex items-center gap-2 rounded-xl border border-gray-300 bg-white px-4 py-2.5 text-sm font-medium text-gray-700 transition hover:border-gray-400 hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-900 dark:text-gray-200 dark:hover:bg-gray-800"
          >
            <svg
              className="w-4 h-4"
              fill="none"
              viewBox="0 0 24 24"
              stroke="currentColor"
              strokeWidth={2}
            >
              <path
                strokeLinecap="round"
                strokeLinejoin="round"
                d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"
              />
            </svg>
            Import
          </button>
          <button
            type="button"
            onClick={() => setModal({ type: "create" })}
            className="inline-flex items-center gap-2 rounded-xl bg-brand-600 px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-brand-700"
          >
            <svg
              className="w-4 h-4"
              fill="none"
              viewBox="0 0 24 24"
              stroke="currentColor"
              strokeWidth={2}
            >
              <path strokeLinecap="round" strokeLinejoin="round" d="M12 4v16m8-8H4" />
            </svg>
            New Resource
          </button>
        </div>
      </div>

      {/* Filters + Column toggle */}
      <FilterBar>
        <div className="flex items-center gap-2">
          <input
            type="search"
            placeholder="Search by name, EID, email..."
            value={search}
            onChange={(e) => setSearch(e.target.value)}
            className="app-input max-w-xs"
          />
          <InfoTooltip
            content="Searches the loaded resource list by display name, EID, and email. Combine it with the dropdown filters below."
            width="w-72"
          />
        </div>
        {chapters.length > 0 && (
          <FilterDropdown
            label={chapterFilterLabel}
            widthClassName="w-80"
            buttonClassName="min-w-52"
            tooltipContent="Multi-select visibility filter. Checked chapters stay visible. 'All Chapters' resets the filter and shows every chapter."
          >
            <div className="mb-3">
              <h2 className="text-sm font-semibold text-gray-900 dark:text-gray-100">Chapter</h2>
              <p className="text-xs text-gray-500">Checked chapters stay visible.</p>
            </div>
            <div className="rounded-xl border border-gray-200 dark:border-gray-700">
              <label className="flex items-center gap-3 border-b border-gray-200 px-3 py-2 text-sm text-gray-700 dark:border-gray-700 dark:text-gray-200">
                <input
                  type="checkbox"
                  checked={chapterFilter.length === 0}
                  onChange={() => setChapterFilter([])}
                  className="h-4 w-4 rounded border-gray-300 text-brand-600 focus:ring-brand-500 dark:border-gray-600"
                />
                <span className="font-medium">All Chapters</span>
              </label>
              <div className="max-h-64 overflow-auto">
                {chapters.map((chapter) => (
                  <label
                    key={chapter}
                    className="flex items-center gap-3 border-b border-gray-100 px-3 py-2 text-sm text-gray-700 last:border-b-0 hover:bg-gray-50 dark:border-gray-800 dark:text-gray-200 dark:hover:bg-gray-800"
                  >
                    <input
                      type="checkbox"
                      checked={chapterFilter.length === 0 || chapterFilter.includes(chapter)}
                      onChange={() => toggleChapter(chapter)}
                      className="h-4 w-4 rounded border-gray-300 text-brand-600 focus:ring-brand-500 dark:border-gray-600"
                    />
                    <span>{chapter}</span>
                  </label>
                ))}
              </div>
            </div>
          </FilterDropdown>
        )}
        <FilterDropdown
          label={activeFilterLabel}
          widthClassName="w-64"
          buttonClassName="min-w-44"
          tooltipContent="Single-choice filter for the resource record state. 'Active' hides inactive records. 'All' shows both."
        >
          <div className="mb-3">
            <h2 className="text-sm font-semibold text-gray-900 dark:text-gray-100">
              Resource Status
            </h2>
            <p className="text-xs text-gray-500">Select one visibility mode.</p>
          </div>
          <div className="rounded-xl border border-gray-200 dark:border-gray-700">
            {[
              { value: "active", label: "Active only" },
              { value: "inactive", label: "Inactive only" },
              { value: "all", label: "All resources" },
            ].map((option, index) => (
              <label
                key={option.value}
                className={`flex items-center gap-3 px-3 py-2 text-sm text-gray-700 hover:bg-gray-50 dark:text-gray-200 dark:hover:bg-gray-800 ${index < 2 ? "border-b border-gray-100 dark:border-gray-800" : ""}`}
              >
                <input
                  type="checkbox"
                  checked={isActiveFilter === option.value}
                  onChange={() => setIsActiveFilter(option.value as ActiveFilter)}
                  className="h-4 w-4 rounded border-gray-300 text-brand-600 focus:ring-brand-500 dark:border-gray-600"
                />
                <span>{option.label}</span>
              </label>
            ))}
          </div>
        </FilterDropdown>
        <FilterDropdown
          label={rolledOffFilterLabel}
          widthClassName="w-64"
          buttonClassName="min-w-44"
          tooltipContent="Single-choice filter for the explicit rolled-off flag on the resource. Default is 'No', so rolled-off resources are hidden unless you change it."
        >
          <div className="mb-3">
            <h2 className="text-sm font-semibold text-gray-900 dark:text-gray-100">Rolled Off</h2>
            <p className="text-xs text-gray-500">Select one filter value.</p>
          </div>
          <div className="rounded-xl border border-gray-200 dark:border-gray-700">
            {[
              { value: "all", label: "All" },
              { value: "yes", label: "Yes" },
              { value: "no", label: "No" },
            ].map((option, index) => (
              <label
                key={option.value}
                className={`flex items-center gap-3 px-3 py-2 text-sm text-gray-700 hover:bg-gray-50 dark:text-gray-200 dark:hover:bg-gray-800 ${index < 2 ? "border-b border-gray-100 dark:border-gray-800" : ""}`}
              >
                <input
                  type="checkbox"
                  checked={rolledOffFilter === option.value}
                  onChange={() => setRolledOffFilter(option.value as BooleanFilter)}
                  className="h-4 w-4 rounded border-gray-300 text-brand-600 focus:ring-brand-500 dark:border-gray-600"
                />
                <span>{option.label}</span>
              </label>
            ))}
          </div>
        </FilterDropdown>
        <FilterDropdown
          label={departedFilterLabel}
          widthClassName="w-64"
          buttonClassName="min-w-44"
          tooltipContent="Single-choice filter for the departed flag. Default is 'No', so departed resources are hidden from the list until you switch this to 'All' or 'Yes'."
        >
          <div className="mb-3">
            <h2 className="text-sm font-semibold text-gray-900 dark:text-gray-100">Departed</h2>
            <p className="text-xs text-gray-500">Select one filter value.</p>
          </div>
          <div className="rounded-xl border border-gray-200 dark:border-gray-700">
            {[
              { value: "all", label: "All" },
              { value: "yes", label: "Yes" },
              { value: "no", label: "No" },
            ].map((option, index) => (
              <label
                key={option.value}
                className={`flex items-center gap-3 px-3 py-2 text-sm text-gray-700 hover:bg-gray-50 dark:text-gray-200 dark:hover:bg-gray-800 ${index < 2 ? "border-b border-gray-100 dark:border-gray-800" : ""}`}
              >
                <input
                  type="checkbox"
                  checked={departedFilter === option.value}
                  onChange={() => setDepartedFilter(option.value as BooleanFilter)}
                  className="h-4 w-4 rounded border-gray-300 text-brand-600 focus:ring-brand-500 dark:border-gray-600"
                />
                <span>{option.label}</span>
              </label>
            ))}
          </div>
        </FilterDropdown>
        {canViewCosts && (
          <label className="flex items-center gap-2 whitespace-nowrap text-sm text-gray-600 dark:text-gray-300">
            <input
              type="checkbox"
              checked={includeProposedChargeability}
              onChange={(e) => setIncludeProposedChargeability(e.target.checked)}
              className="rounded border-gray-300 dark:border-gray-600"
            />
            Include proposed in chargeability
            <InfoTooltip
              content="Only affects chargeability numbers shown on this page. When enabled, proposed assignments and imported TBD planning are counted alongside confirmed work."
              width="w-72"
            />
          </label>
        )}
        <ColumnTogglePanel
          allColumns={allColumns}
          visibleKeys={visibleKeys}
          onSetVisible={setVisible}
          defaultKeys={defaultKeys}
        />
        {isCustomOrder && (
          <button
            type="button"
            onClick={resetOrder}
            className="whitespace-nowrap text-xs text-gray-500 underline transition hover:text-gray-700 dark:hover:text-gray-200"
            title="Clear manual row order"
          >
            Reset order
          </button>
        )}
      </FilterBar>

      <div className="app-toolbar mb-3 flex flex-wrap items-start gap-3">
        <FilterDropdown
          label={countryFilterLabel}
          widthClassName="w-80"
          tooltipContent="Multi-select visibility filter. Checked countries stay visible. Uncheck a country to hide it while leaving all other countries visible."
        >
          <div className="mb-3 flex items-start justify-between gap-3">
            <div>
              <h2 className="text-sm font-semibold text-gray-900 dark:text-gray-100">Countries</h2>
              <p className="text-xs text-gray-500">Checked countries stay visible.</p>
            </div>
            <div className="flex items-center gap-3 text-xs font-medium">
              <button
                type="button"
                onClick={() => setHiddenCountryIds([])}
                className="text-brand-600 hover:text-brand-800 dark:text-brand-300 dark:hover:text-brand-100"
              >
                Show all
              </button>
              <button
                type="button"
                onClick={toggleAllCountries}
                className="text-gray-500 hover:text-gray-700 dark:hover:text-gray-200"
              >
                {isAllCountriesVisible ? "Hide all" : "Toggle all"}
              </button>
            </div>
          </div>
          <div className="rounded-xl border border-gray-200 dark:border-gray-700">
            <label className="flex items-center gap-3 border-b border-gray-200 px-3 py-2 text-sm text-gray-700 dark:border-gray-700 dark:text-gray-200">
              <input
                type="checkbox"
                checked={includeWithoutCountry}
                onChange={(e) => setIncludeWithoutCountry(e.target.checked)}
                className="h-4 w-4 rounded border-gray-300 text-brand-600 focus:ring-brand-500 dark:border-gray-600"
              />
              <span className="font-medium">Unspecified country</span>
            </label>
            <div className="max-h-64 overflow-auto">
              {countries.map((country) => (
                <label
                  key={country.id}
                  className="flex items-center gap-3 border-b border-gray-100 px-3 py-2 text-sm text-gray-700 last:border-b-0 hover:bg-gray-50 dark:border-gray-800 dark:text-gray-200 dark:hover:bg-gray-800"
                >
                  <input
                    type="checkbox"
                    checked={!hiddenCountryIds.includes(country.id)}
                    onChange={() => toggleCountry(country.id)}
                    className="h-4 w-4 rounded border-gray-300 text-brand-600 focus:ring-brand-500 dark:border-gray-600"
                  />
                  <span>{country.name}</span>
                </label>
              ))}
            </div>
          </div>
        </FilterDropdown>

        <FilterDropdown
          label={resourceTypeFilterLabel}
          widthClassName="w-80"
          tooltipContent="Multi-select visibility filter for stored resource types. Checked types stay visible. Freelancers are hidden by default."
        >
          <div className="mb-3 flex items-start justify-between gap-3">
            <div>
              <h2 className="text-sm font-semibold text-gray-900 dark:text-gray-100">
                Resource Types
              </h2>
              <p className="text-xs text-gray-500">Checked types stay visible.</p>
            </div>
            <div className="flex items-center gap-3 text-xs font-medium">
              <button
                type="button"
                onClick={() => setHiddenResourceTypes([])}
                className="text-brand-600 hover:text-brand-800 dark:text-brand-300 dark:hover:text-brand-100"
              >
                Show all
              </button>
              <button
                type="button"
                onClick={toggleAllResourceTypes}
                className="text-gray-500 hover:text-gray-700 dark:hover:text-gray-200"
              >
                {isAllResourceTypesVisible ? "Hide all" : "Toggle all"}
              </button>
            </div>
          </div>
          <div className="rounded-xl border border-gray-200 dark:border-gray-700">
            <label className="flex items-center gap-3 border-b border-gray-200 px-3 py-2 text-sm text-gray-700 dark:border-gray-700 dark:text-gray-200">
              <input
                type="checkbox"
                checked={includeWithoutResourceType}
                onChange={(e) => setIncludeWithoutResourceType(e.target.checked)}
                className="h-4 w-4 rounded border-gray-300 text-brand-600 focus:ring-brand-500 dark:border-gray-600"
              />
              <span className="font-medium">Unspecified type</span>
            </label>
            <div>
              {allResourceTypes.map((resourceType) => (
                <label
                  key={resourceType}
                  className="flex items-center gap-3 border-b border-gray-100 px-3 py-2 text-sm text-gray-700 last:border-b-0 hover:bg-gray-50 dark:border-gray-800 dark:text-gray-200 dark:hover:bg-gray-800"
                >
                  <input
                    type="checkbox"
                    checked={!hiddenResourceTypes.includes(resourceType)}
                    onChange={() => toggleResourceType(resourceType)}
                    className="h-4 w-4 rounded border-gray-300 text-brand-600 focus:ring-brand-500 dark:border-gray-600"
                  />
                  <span>{RESOURCE_TYPE_LABELS[resourceType]}</span>
                </label>
              ))}
            </div>
          </div>
        </FilterDropdown>
      </div>

      {filterableFields.length > 0 && (
        <div className="mb-2">
          <CustomFieldFilterBar
            filterableFields={filterableFields}
            activeFilters={customFieldFilters}
            onSetFilter={setCustomFieldFilter}
          />
        </div>
      )}

      {chips.length > 0 && (
        <div className="mb-3">
          <FilterChips chips={chips} onClearAll={clearAll} />
        </div>
      )}

      {/* Table */}
      <div className="app-data-table">
        {isLoading && resources.length === 0 ? (
          <div className="p-12 text-center text-sm text-gray-500 shimmer-skeleton">
            Loading resources…
          </div>
        ) : (
          <>
            <table className="w-full">
              <thead className="border-b border-gray-200 dark:border-gray-700">
                <tr>
                  {/* Drag handle column */}
                  <th className="w-8 px-2" />
                  <th className="px-4 py-3 w-10">
                    <input
                      type="checkbox"
                      checked={selection.isAllSelected(resourceIds)}
                      ref={(el) => {
                        if (el) el.indeterminate = selection.isIndeterminate(resourceIds);
                      }}
                      onChange={() => selection.toggleAll(resourceIds)}
                      className="rounded border-gray-300 dark:border-gray-600"
                    />
                  </th>
                  {visibleColumns.map((col) => {
                    if (col.isCustom) {
                      return (
                        <th
                          key={col.key}
                          className="px-3 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
                        >
                          {col.label}
                        </th>
                      );
                    }
                    switch (col.key) {
                      case "eid":
                        return (
                          <SortableColumnHeader
                            key={col.key}
                            label="EID"
                            field="eid"
                            sortField={sortField}
                            sortDir={sortDir}
                            onSort={toggle}
                            tooltip="Unique employee identifier used across all CapaKraken records."
                          />
                        );
                      case "displayName":
                        return (
                          <SortableColumnHeader
                            key={col.key}
                            label="Name / Email"
                            field="displayName"
                            sortField={sortField}
                            sortDir={sortDir}
                            onSort={toggle}
                          />
                        );
                      case "chapter":
                        return (
                          <SortableColumnHeader
                            key={col.key}
                            label="Chapter"
                            field="chapter"
                            sortField={sortField}
                            sortDir={sortDir}
                            onSort={toggle}
                          />
                        );
                      case "lcr":
                        return (
                          <SortableColumnHeader
                            key={col.key}
                            label="LCR (€/h)"
                            field="lcrCents"
                            sortField={sortField}
                            sortDir={sortDir}
                            onSort={toggle}
                            tooltip="Labour Cost Rate — the resource's hourly cost in EUR. Used to calculate project budgets (LCR × hours/day × working days)."
                          />
                        );
                      case "chargeability":
                        return (
                          <SortableColumnHeader
                            key={col.key}
                            label="Chargeability (actual)"
                            field="chargeabilityTarget"
                            sortField={sortField}
                            sortDir={sortDir}
                            onSort={toggle}
                            tooltip="Actual = CONFIRMED+ACTIVE bookings on ACTIVE projects ÷ available hours this month. Enable the page toggle to include PROPOSED work. Expected (in parentheses) includes all non-cancelled bookings."
                            tooltipWidth="w-80"
                          />
                        );
                      case "valueScore":
                        return canViewScores ? (
                          <SortableColumnHeader
                            key={col.key}
                            label="Score"
                            field="valueScore"
                            sortField={sortField}
                            sortDir={sortDir}
                            onSort={toggle}
                            tooltip="Composite price/quality score 0–100. Weights: Skill Depth 30% · Cost Efficiency 25% · Skill Breadth 15% · Chargeability 15% · Experience 15%. Recompute in Admin → Settings."
                            tooltipWidth="w-72"
                          />
                        ) : null;
                      case "roles":
                        return (
                          <th
                            key={col.key}
                            className="px-3 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
                          >
                            Roles{" "}
                            <InfoTooltip content="Primary role (★) and additional roles assigned to this resource. Used for open demand and staffing suggestions." />
                          </th>
                        );
                      case "rolledOff":
                        return (
                          <th
                            key={col.key}
                            className="px-3 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
                          >
                            Rolled Off
                          </th>
                        );
                      case "departed":
                        return (
                          <th
                            key={col.key}
                            className="px-3 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
                          >
                            Departed
                          </th>
                        );
                      case "isActive":
                        return (
                          <th
                            key={col.key}
                            className="px-3 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
                          >
                            Skills{" "}
                            <InfoTooltip content="Skills from the resource's skill matrix. Shows first 3; hover the +N badge for more." />
                          </th>
                        );
                      default:
                        return (
                          <th
                            key={col.key}
                            className="px-3 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
                          >
                            {col.label}
                          </th>
                        );
                    }
                  })}
                  <th className="px-3 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
                    Actions
                  </th>
                </tr>
              </thead>
              <tbody className="divide-y divide-gray-100 dark:divide-gray-800">
                {displayedResources.map((resource, index) => {
                  const skills = resource.skills as unknown as SkillEntry[];
                  const isSelected = selection.selectedIds.has(resource.id);
                  const isDeactivating =
                    deactivateMutation.isPending &&
                    (deactivateMutation.variables as { id: string } | undefined)?.id ===
                      resource.id;
                  const dynFields =
                    (resource as unknown as { dynamicFields?: Record<string, unknown> })
                      .dynamicFields ?? {};

                  return (
                    <DraggableTableRow
                      key={resource.id}
                      id={resource.id}
                      dragRef={rowDragRef}
                      onDrop={(draggedId) => reorder(draggedId, resource.id)}
                      className={`table-row-hover hover-lift hover:bg-gray-50/80 dark:hover:bg-gray-900/60 animate-row-enter ${isSelected ? "bg-brand-50 dark:bg-brand-900/20" : ""}`}
                      style={{ animationDelay: `${Math.min(index * 15, 300)}ms` }}
                    >
                      <td className="px-4 py-3">
                        <input
                          type="checkbox"
                          checked={isSelected}
                          onChange={() => selection.toggle(resource.id)}
                          className="rounded border-gray-300 dark:border-gray-600"
                        />
                      </td>
                      {visibleColumns.map((col) => {
                        if (col.isCustom) {
                          const fieldKey = col.key.replace(/^custom_/, "");
                          const val = dynFields[fieldKey];
                          return (
                            <td
                              key={col.key}
                              className="px-3 py-3 text-sm text-gray-700 dark:text-gray-200"
                            >
                              {val != null ? String(val) : "—"}
                            </td>
                          );
                        }
                        switch (col.key) {
                          case "eid":
                            return (
                              <td
                                key={col.key}
                                className="px-4 py-3 text-sm font-mono text-gray-600 dark:text-gray-300"
                              >
                                {resource.eid}
                              </td>
                            );
                          case "displayName": {
                            const initials = resource.displayName
                              .split(/\s+/)
                              .map((w) => w[0])
                              .filter(Boolean)
                              .slice(0, 2)
                              .join("")
                              .toUpperCase();
                            const rr =
                              (
                                resource as unknown as {
                                  resourceRoles?: {
                                    isPrimary: boolean;
                                    role: { color: string | null };
                                  }[];
                                }
                              ).resourceRoles ?? [];
                            const primaryRole = rr.find((r) => r.isPrimary);
                            const avatarColor =
                              primaryRole?.role.color ??
                              `hsl(${[...resource.displayName].reduce((acc, c) => acc + c.charCodeAt(0), 0) % 360}, 55%, 45%)`;
                            return (
                              <td key={col.key} className="px-4 py-3">
                                <Link
                                  href={`/resources/${resource.id}`}
                                  className="inline-flex items-center gap-2.5 transition-colors hover:text-brand-600 group"
                                >
                                  <span
                                    className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full text-xs font-bold text-white"
                                    style={{ backgroundColor: avatarColor }}
                                  >
                                    {initials}
                                  </span>
                                  <span className="min-w-0">
                                    <span className="block text-sm font-medium text-gray-900 group-hover:text-brand-600 group-hover:underline dark:text-gray-100">
                                      {resource.displayName}
                                    </span>
                                    <span className="block text-xs text-gray-500 dark:text-gray-400">
                                      {resource.email}
                                    </span>
                                  </span>
                                </Link>
                              </td>
                            );
                          }
                          case "chapter":
                            return (
                              <td
                                key={col.key}
                                className="px-4 py-3 text-sm text-gray-600 dark:text-gray-300"
                              >
                                {resource.chapter ?? "—"}
                              </td>
                            );
                          case "lcr":
                            return (
                              <td
                                key={col.key}
                                className="px-4 py-3 text-sm text-gray-900 dark:text-gray-100"
                              >
                                {formatMoney(resource.lcrCents, resource.currency)}
                              </td>
                            );
                          case "chargeability": {
                            if (!canViewCosts)
                              return (
                                <td
                                  key={col.key}
                                  className="px-4 py-3 text-sm text-gray-500 dark:text-gray-400"
                                >
                                  {resource.chargeabilityTarget}%
                                </td>
                              );
                            const stats = chargeabilityMap.get(resource.id);
                            const actual = stats?.actualChargeability;
                            const expected = stats?.expectedChargeability;
                            const target = resource.chargeabilityTarget;
                            const color =
                              actual == null
                                ? "text-gray-400"
                                : actual >= target
                                  ? "text-green-700 dark:text-green-300"
                                  : actual >= target - 20
                                    ? "text-amber-600 dark:text-amber-300"
                                    : "text-red-600 dark:text-red-300";
                            // Bar color based on % of target achieved
                            const barRatio = actual != null && target > 0 ? actual / target : 0;
                            const barColor =
                              actual == null
                                ? "bg-gray-300 dark:bg-gray-600"
                                : barRatio >= 0.8
                                  ? "bg-green-500"
                                  : barRatio >= 0.5
                                    ? "bg-amber-500"
                                    : "bg-red-500";
                            const barWidth = actual != null ? Math.min(actual, 100) : 0;
                            const isOverflow = actual != null && actual > 100;
                            return (
                              <td key={col.key} className="px-4 py-3 text-sm min-w-[120px]">
                                <div>
                                  <span className={`font-medium ${color}`}>
                                    {actual != null ? `${actual}%` : "—"}
                                  </span>
                                  {expected != null && expected !== actual && (
                                    <span className="ml-1 text-xs text-gray-400">
                                      ({expected}% exp.)
                                    </span>
                                  )}
                                  {actual !== target && (
                                    <div className="text-xs text-gray-400">Target: {target}%</div>
                                  )}
                                  {actual != null && (
                                    <div className="mt-1 flex items-center gap-1">
                                      <div className="h-[3px] flex-1 overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700">
                                        <div
                                          className={`h-full rounded-full transition-all duration-700 ease-out ${barColor}`}
                                          style={{ width: `${barWidth}%` }}
                                        />
                                      </div>
                                      {isOverflow && (
                                        <span className="text-[9px] font-bold text-green-600 dark:text-green-400" title={`${actual}% actual`}>+</span>
                                      )}
                                    </div>
                                  )}
                                </div>
                              </td>
                            );
                          }
                          case "valueScore": {
                            if (!canViewScores) return null;
                            const score = (resource as unknown as { valueScore?: number | null })
                              .valueScore;
                            return (
                              <td key={col.key} className="px-4 py-3 text-sm">
                                {score != null ? (
                                  <span
                                    className={`inline-block rounded-full px-2 py-0.5 text-xs font-semibold ${score >= 70 ? "bg-green-100 text-green-700 dark:bg-green-950/30 dark:text-green-300" : score >= 40 ? "bg-amber-100 text-amber-700 dark:bg-amber-950/30 dark:text-amber-300" : "bg-red-100 text-red-700 dark:bg-red-950/30 dark:text-red-300"}`}
                                  >
                                    {score}
                                  </span>
                                ) : (
                                  <span className="text-gray-400 text-xs">—</span>
                                )}
                              </td>
                            );
                          }
                          case "roles": {
                            const rr =
                              (
                                resource as unknown as {
                                  resourceRoles?: {
                                    isPrimary: boolean;
                                    role: { id: string; name: string; color: string | null };
                                  }[];
                                }
                              ).resourceRoles ?? [];
                            return (
                              <td key={col.key} className="px-4 py-3">
                                <div className="flex flex-wrap gap-1">
                                  {rr.map((r) => (
                                    <span
                                      key={r.role.id}
                                      className="inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium"
                                      style={{
                                        backgroundColor: `${r.role.color ?? "#6366f1"}22`,
                                        color: r.role.color ?? "#6366f1",
                                      }}
                                    >
                                      {r.isPrimary && <span className="text-[10px]">★</span>}
                                      {r.role.name}
                                    </span>
                                  ))}
                                  {rr.length === 0 && (
                                    <span className="text-xs text-gray-400">—</span>
                                  )}
                                </div>
                              </td>
                            );
                          }
                          case "rolledOff":
                            return (
                              <td key={col.key} className="px-4 py-3 text-sm">
                                <BooleanBadge
                                  value={
                                    (resource as unknown as { rolledOff?: boolean }).rolledOff ??
                                    false
                                  }
                                />
                              </td>
                            );
                          case "departed":
                            return (
                              <td key={col.key} className="px-4 py-3 text-sm">
                                <BooleanBadge
                                  value={
                                    (resource as unknown as { departed?: boolean }).departed ??
                                    false
                                  }
                                />
                              </td>
                            );
                          case "isActive":
                            return (
                              <td key={col.key} className="px-4 py-3">
                                <div className="flex flex-wrap gap-1">
                                  {skills.slice(0, 3).map((s) => (
                                    <span
                                      key={s.skill}
                                      className="inline-block rounded-full bg-brand-50 px-2 py-0.5 text-xs text-brand-700 dark:bg-brand-900/60 dark:text-brand-100"
                                    >
                                      {s.skill}
                                    </span>
                                  ))}
                                  {skills.length > 3 && (
                                    <span className="text-xs text-gray-400">
                                      +{skills.length - 3}
                                    </span>
                                  )}
                                </div>
                              </td>
                            );
                          default:
                            return (
                              <td
                                key={col.key}
                                className="px-4 py-3 text-sm text-gray-600 dark:text-gray-300"
                              >
                                —
                              </td>
                            );
                        }
                      })}
                      <td className="px-4 py-3 text-right whitespace-nowrap">
                        <button
                          type="button"
                          onClick={() =>
                            setModal({ type: "edit", resource: resource as unknown as Resource })
                          }
                          className="link-hover-underline mr-3 text-xs font-medium text-brand-600 transition-colors hover:text-brand-800 dark:text-brand-300 dark:hover:text-brand-100"
                        >
                          Edit
                        </button>
                        <button
                          type="button"
                          onClick={() =>
                            setConfirm({
                              type: "deactivate",
                              resource: resource as unknown as Resource,
                            })
                          }
                          disabled={isDeactivating}
                          className="text-xs font-medium text-red-600 transition-colors hover:text-red-800 disabled:opacity-50 dark:text-red-300 dark:hover:text-red-200"
                        >
                          {isDeactivating ? "Deactivating…" : "Deactivate"}
                        </button>
                      </td>
                    </DraggableTableRow>
                  );
                })}
              </tbody>
            </table>

            {displayedResources.length === 0 && !isLoading && (
              <div className="py-14 text-center text-sm text-gray-500">No resources found.</div>
            )}

            {/* Infinite scroll trigger */}
            <InfiniteScrollSentinel onVisible={handleFetchNext} isLoading={isFetchingNextPage} />
          </>
        )}
      </div>

      <BatchActionBar
        count={selection.count}
        onClear={selection.clear}
        actions={[
          {
            label: "Export Selected",
            variant: "default" as const,
            onClick: exportSelectedCsv,
          },
          ...(filterableFields.length > 0
            ? [
                {
                  label: "Bulk Edit",
                  variant: "default" as const,
                  onClick: () => setModal({ type: "bulkEdit" }),
                  disabled: false,
                },
              ]
            : []),
          {
            label: `Deactivate ${selection.count > 0 ? `(${selection.count})` : ""}`,
            variant: "danger" as const,
            onClick: () => setConfirm({ type: "batchDeactivate", ids: selection.selectedArray }),
            disabled: batchDeactivateMutation.isPending,
          },
        ]}
      />

      {modal.type === "create" && <ResourceModal mode="create" onClose={closeModal} />}
      {modal.type === "edit" && (
        <ResourceModal mode="edit" resource={modal.resource} onClose={closeModal} />
      )}
      {modal.type === "import" && <ImportModal onClose={closeModal} />}
      {modal.type === "bulkEdit" && (
        <BulkEditModal
          selectedIds={selection.selectedArray}
          fieldDefs={filterableFields}
          onClose={closeModal}
          onSuccess={selection.clear}
        />
      )}

      {confirm.type === "deactivate" && (
        <ConfirmDialog
          title="Deactivate Resource"
          message={`Deactivate "${confirm.resource.displayName}" (${confirm.resource.eid})? This will remove them from the active resource list.`}
          confirmLabel="Deactivate"
          variant="danger"
          onConfirm={handleConfirm}
          onCancel={() => setConfirm({ type: "closed" })}
        />
      )}
      {confirm.type === "batchDeactivate" && (
        <ConfirmDialog
          title="Deactivate Resources"
          message={`Deactivate ${confirm.ids.length} selected resource${confirm.ids.length !== 1 ? "s" : ""}?`}
          confirmLabel="Deactivate All"
          variant="danger"
          onConfirm={handleConfirm}
          onCancel={() => setConfirm({ type: "closed" })}
        />
      )}
    </div>
  );
}
