import { useInfiniteQuery, useMutation, useQuery } from "@tanstack/react-query";
import {
  observeElementRect,
  type Rect,
  useVirtualizer,
  type Virtualizer,
} from "@tanstack/react-virtual";
import type { AxiosError } from "axios";
import {
  ArrowDown,
  ArrowUp,
  ArrowUpDown,
  ChevronDown,
  FileDown,
  FileSpreadsheet,
  Key,
  LayoutDashboard,
  Plus,
  RefreshCw,
  Search,
  Settings2,
  ShieldCheck,
  Trash2,
  Upload,
  Users,
} from "lucide-react";
import * as React from "react";
import { Link, useNavigate } from "react-router-dom";
import { PageHeader } from "../../../../common/core/components/page";
import {
  SortableTableHead,
  sortableTableHeaderClassName,
} from "../../../../common/core/components/sort";
import {
  type SortConfig,
  type SortResolverMap,
  sortItems,
  toggleSort,
} from "../../../../common/core/utils";
import { SearchFilterBar } from "../../../../common/ui/search-filter-bar";
import {
  commonTableShellClass,
  commonTableViewportClass,
} from "../../../../common/ui/table";
import { Button } from "../../components/ui/button";
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "../../components/ui/card";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "../../components/ui/dialog";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "../../components/ui/dropdown-menu";
import { Input } from "../../components/ui/input";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "../../components/ui/select";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "../../components/ui/table";
import { toast } from "../../components/ui/use-toast";
import {
  bulkDeleteUsers,
  bulkUpdateUsers,
  deleteUser,
  exportUsersCSV,
  fetchAllTenants,
  fetchMe,
  fetchTenant,
  fetchUsers,
  type TenantSummary,
  type UserSummary,
  updateUser,
} from "../../lib/adminApi";
import { t } from "../../lib/i18n";
import { normalizeAdminRole } from "../../lib/roles";
import {
  downloadUserTemplate,
  UserBulkUploadModal,
} from "./components/UserBulkUploadModal";
import { formatUserPolicyMessage } from "./userPolicyMessages";
import {
  normalizeUserStatusValue,
  type UserStatusValue,
  userStatusLabel,
  userStatusValues,
} from "./userStatus";

type UserSchemaField = {
  key: string;
  label: string;
  type: string;
};

type UserSortKey = string;

const USER_ROW_ESTIMATED_HEIGHT = 64;
const USER_ROW_OVERSCAN = 2;
const USER_TABLE_VIEWPORT_ESTIMATED_HEIGHT = 640;
const userFixedColumnWidths = [48, 160, 220, 160, 260, 170, 220] as const;
const userMetadataColumnWidth = 160;
const userCreatedColumnWidth = 150;
type UserRowVirtualizer = Virtualizer<HTMLDivElement, HTMLTableRowElement>;
const userTableHeadClassName =
  "h-9 px-3 py-1 text-xs leading-tight align-middle whitespace-nowrap";
const userTableHeadInteractiveClassName = `${userTableHeadClassName} cursor-pointer transition-colors hover:bg-muted/50`;
const userTableHeadContentClassName = "flex h-full items-center gap-1";
const userSortableTableHeadClassName =
  "!h-9 !px-3 !py-1 leading-tight whitespace-nowrap";
const userSortableTableHeadContentClassName = "h-full items-center";
const userTableStateCellClassName =
  "flex h-24 items-center justify-center p-0 text-center text-sm text-muted-foreground";

type RepresentativeTenantCandidate = {
  id?: string;
  slug?: string;
  name?: string;
  config?: Record<string, unknown>;
};

function stringValue(value: unknown) {
  return typeof value === "string" ? value.trim() : "";
}

function tenantVisibility(tenant?: RepresentativeTenantCandidate) {
  const visibility = tenant?.config?.visibility;
  return typeof visibility === "string" ? visibility.trim() : "";
}

function findTenantCandidate(
  candidate: RepresentativeTenantCandidate,
  tenants: TenantSummary[],
) {
  const id = candidate.id?.toLowerCase() ?? "";
  const slug = candidate.slug?.toLowerCase() ?? "";
  if (!id && !slug) return undefined;
  return tenants.find(
    (tenant) =>
      (id && tenant.id.toLowerCase() === id) ||
      (slug && tenant.slug.toLowerCase() === slug),
  );
}

function isPrivateTenantCandidate(
  candidate: RepresentativeTenantCandidate,
  tenants: TenantSummary[],
) {
  const tenant = findTenantCandidate(candidate, tenants) ?? candidate;
  return tenantVisibility(tenant) === "private";
}

function candidateLabel(candidate: RepresentativeTenantCandidate) {
  return candidate.name || candidate.slug || candidate.id || "";
}

function metadataTenantCandidate(
  metadata: Record<string, unknown> | undefined,
): RepresentativeTenantCandidate | null {
  const id = stringValue(metadata?.primaryTenantId);
  const slug = stringValue(metadata?.primaryTenantSlug);
  const name = stringValue(metadata?.primaryTenantName);
  if (!id && !slug && !name) return null;
  return { id, slug, name };
}

function appointmentTenantCandidate(
  appointment: unknown,
): RepresentativeTenantCandidate | null {
  if (!appointment || typeof appointment !== "object") return null;
  const value = appointment as Record<string, unknown>;
  const id = stringValue(value.tenantId);
  const slug = stringValue(value.tenantSlug ?? value.slug);
  const name = stringValue(value.tenantName ?? value.name);
  if (!id && !slug && !name) return null;
  return { id, slug, name };
}

function resolveRepresentativeTenantLabel(
  user: UserSummary,
  tenants: TenantSummary[],
) {
  const candidates: RepresentativeTenantCandidate[] = [];
  const knownTenants = [
    ...(user.tenant ? [user.tenant] : []),
    ...(user.joinedTenants ?? []),
    ...tenants,
  ];
  const primaryFromMetadata = metadataTenantCandidate(user.metadata);
  if (primaryFromMetadata) candidates.push(primaryFromMetadata);
  if (user.tenant) candidates.push(user.tenant);

  for (const tenant of user.joinedTenants ?? []) {
    candidates.push(tenant);
  }

  const appointments = user.metadata?.additionalAppointments;
  if (Array.isArray(appointments)) {
    for (const appointment of appointments) {
      if (
        appointment &&
        typeof appointment === "object" &&
        (appointment as Record<string, unknown>).isPrimary !== true
      ) {
        continue;
      }
      const candidate = appointmentTenantCandidate(appointment);
      if (candidate) candidates.push(candidate);
    }
  }
  if (user.tenantSlug) candidates.push({ slug: user.tenantSlug });

  const representative = candidates.find(
    (candidate) =>
      candidateLabel(candidate) &&
      !isPrivateTenantCandidate(candidate, knownTenants),
  );

  return candidateLabel(representative ?? {});
}

function normalizeUserTableRect(rect: Rect, fallbackWidth: number): Rect {
  return {
    width: rect.width > 0 ? rect.width : fallbackWidth,
    height:
      rect.height > 0 ? rect.height : USER_TABLE_VIEWPORT_ESTIMATED_HEIGHT,
  };
}

type UserListSearchControlsProps = {
  initialSearch: string;
  selectedCompany: string;
  tenants: TenantSummary[];
  profileRole?: string | null;
  onSearch: (value: string) => void;
  onCompanyChange: (value: string) => void;
};

const UserListSearchControls = React.memo(function UserListSearchControls({
  initialSearch,
  selectedCompany,
  tenants,
  profileRole,
  onSearch,
  onCompanyChange,
}: UserListSearchControlsProps) {
  const [localSearch, setLocalSearch] = React.useState(initialSearch);

  React.useEffect(() => {
    setLocalSearch(initialSearch);
  }, [initialSearch]);

  React.useEffect(() => {
    const timer = setTimeout(() => {
      if (localSearch !== initialSearch) {
        onSearch(localSearch);
      }
    }, 300);
    return () => clearTimeout(timer);
  }, [localSearch, onSearch, initialSearch]);

  const tenantOptions = React.useMemo(
    () =>
      tenants.map((tenant) => (
        <option key={tenant.id} value={tenant.slug}>
          {tenant.name}
        </option>
      )),
    [tenants],
  );

  return (
    <SearchFilterBar
      primary={
        <>
          <div className="relative w-56">
            <Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
            <Input
              id="user-list-search"
              name="user-list-search"
              placeholder={t(
                "ui.admin.users.list.search_placeholder",
                "이름 또는 이메일 검색",
              )}
              className="h-9 pl-9"
              value={localSearch}
              onChange={(event) => setLocalSearch(event.target.value)}
              onKeyDown={(event) => {
                if (event.key === "Enter") {
                  onSearch(localSearch);
                }
              }}
            />
          </div>

          <select
            id="user-list-tenant-filter"
            name="user-list-tenant-filter"
            className="flex h-9 w-[160px] rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:opacity-50"
            value={selectedCompany}
            onChange={(event) => onCompanyChange(event.target.value)}
            disabled={profileRole !== "super_admin"}
          >
            <option value="">{t("ui.common.all", "전체 테넌트")}</option>
            {tenantOptions}
          </select>

          <Button
            variant="secondary"
            size="sm"
            onClick={() => onSearch(localSearch)}
            className="h-9"
          >
            {t("ui.common.search", "검색")}
          </Button>
        </>
      }
    />
  );
});

function UserListPage() {
  const _navigate = useNavigate();
  const [search, setSearch] = React.useState("");
  const [selectedCompany, setSelectedCompany] = React.useState<string>("");
  const [visibleColumns, setVisibleColumns] = React.useState<
    Record<string, boolean>
  >({});
  const [selectedUserIds, setSelectedUserIds] = React.useState<string[]>([]);
  const [selectedBulkStatus, setSelectedBulkStatus] = React.useState<
    UserStatusValue | ""
  >("");
  const [selectedBulkRole, setSelectedBulkRole] = React.useState<
    "super_admin" | "user" | ""
  >("");
  const [sortConfig, setSortConfig] =
    React.useState<SortConfig<UserSortKey> | null>(null);
  const [bulkUploadOpen, setBulkUploadOpen] = React.useState(false);
  const userTableViewportRef = React.useRef<HTMLDivElement | null>(null);

  const { data: profile } = useQuery({
    queryKey: ["me"],
    queryFn: fetchMe,
  });
  const profileRole = normalizeAdminRole(profile?.role);
  const isWritable =
    profileRole === "super_admin" || !!profile?.systemPermissions?.manage_users;

  const { data: tenantsData } = useQuery({
    queryKey: ["tenants", "all"],
    queryFn: () => fetchAllTenants(),
  });
  const tenants = tenantsData?.items ?? [];

  // Lock company for non-super_admin
  React.useEffect(() => {
    if (profileRole !== "super_admin" && profile?.tenantSlug) {
      setSelectedCompany(profile.tenantSlug);
    }
  }, [profile, profileRole]);

  const selectedTenantId = React.useMemo(() => {
    return tenants.find((t) => t.slug === selectedCompany)?.id ?? "";
  }, [tenants, selectedCompany]);

  const { data: tenantDetail } = useQuery({
    queryKey: ["tenant", selectedTenantId],
    queryFn: () => fetchTenant(selectedTenantId),
    enabled: selectedTenantId.length > 0,
  });

  const userSchema: UserSchemaField[] = Array.isArray(
    tenantDetail?.config?.userSchema,
  )
    ? (tenantDetail?.config?.userSchema as UserSchemaField[])
    : [];

  // Initialize visible columns when schema changes
  React.useEffect(() => {
    if (userSchema.length > 0) {
      const initial: Record<string, boolean> = {};
      for (const field of userSchema) {
        initial[field.key] = true;
      }
      setVisibleColumns((prev) => {
        // Only set if not already set for these keys to avoid reset on every render
        const next = { ...initial, ...prev };
        return next;
      });
    }
  }, [userSchema]);

  const toggleColumn = (key: string) => {
    setVisibleColumns((prev) => ({
      ...prev,
      [key]: !prev[key],
    }));
  };

  const query = useInfiniteQuery({
    queryKey: ["users", { search, tenantSlug: selectedCompany }],
    queryFn: ({ pageParam }) =>
      fetchUsers(50, 0, search, selectedCompany, pageParam as string),
    initialPageParam: undefined as string | undefined,
    getNextPageParam: (lastPage) => lastPage.next_cursor || lastPage.nextCursor,
  });

  const deleteMutation = useMutation({
    mutationFn: (userId: string) => deleteUser(userId),
    onSuccess: () => {
      query.refetch();
    },
  });

  const exportMutation = useMutation({
    mutationFn: (includeIds: boolean) =>
      exportUsersCSV(search, selectedCompany, includeIds),
    onSuccess: ({ blob, filename }) => {
      const url = window.URL.createObjectURL(blob);
      const link = document.createElement("a");
      link.href = url;
      link.download = filename;
      document.body.appendChild(link);
      link.click();
      link.remove();
      window.URL.revokeObjectURL(url);
    },
    onError: () => {
      toast.error(
        t("msg.admin.users.export_error", "사용자 내보내기에 실패했습니다."),
      );
    },
  });

  const statusMutation = useMutation({
    mutationFn: ({ userId, status }: { userId: string; status: string }) =>
      updateUser(userId, { status }),
    onSuccess: () => {
      query.refetch();
    },
    onError: () => {
      toast.error(
        t("msg.admin.users.status_error", "사용자 상태 변경에 실패했습니다."),
      );
    },
  });

  const handleSearch = React.useCallback((nextSearch: string) => {
    setSearch(nextSearch);
  }, []);

  const handleCompanyChange = React.useCallback((nextCompany: string) => {
    setSelectedCompany(nextCompany);
  }, []);

  const handleExport = (includeIds = false) => {
    exportMutation.mutate(includeIds);
  };

  const errorMsg = (query.error as AxiosError<{ error?: string }>)?.response
    ?.data?.error;
  const fallbackError =
    !errorMsg && query.isError
      ? t(
          "msg.admin.users.list.fetch_error",
          "사용자 목록 조회에 실패했습니다.",
        )
      : null;

  const serverItems = React.useMemo(
    () => query.data?.pages.flatMap((page) => page.items) ?? [],
    [query.data],
  );
  const rawItems = serverItems;
  const userSortResolvers = React.useMemo<
    SortResolverMap<UserSummary, UserSortKey>
  >(
    () =>
      userSchema.reduce<SortResolverMap<UserSummary, UserSortKey>>(
        (accumulator, field) => {
          accumulator[field.key] = (user) => {
            const value = user.metadata?.[field.key];
            return typeof value === "string" ||
              typeof value === "number" ||
              typeof value === "boolean"
              ? value
              : null;
          };
          return accumulator;
        },
        {
          name_email: (user) =>
            `${user.name ?? ""} ${user.email ?? ""} ${user.phone ?? ""}`,
          tenant_dept: (user) =>
            `${resolveRepresentativeTenantLabel(user, tenants)} ${user.department ?? ""}`,
        },
      ),
    [tenants, userSchema],
  );
  const items = React.useMemo(() => {
    if (!sortConfig) {
      return rawItems;
    }

    return sortItems(rawItems, sortConfig, userSortResolvers);
  }, [rawItems, sortConfig, userSortResolvers]);
  const visibleUserSchemaFields = React.useMemo(
    () => userSchema.filter((field) => visibleColumns[field.key] !== false),
    [userSchema, visibleColumns],
  );
  const userTableColumnWidths = React.useMemo(
    () => [
      ...userFixedColumnWidths,
      ...visibleUserSchemaFields.map(() => userMetadataColumnWidth),
      userCreatedColumnWidth,
    ],
    [visibleUserSchemaFields],
  );
  const userTableGridTemplateColumns = React.useMemo(
    () => userTableColumnWidths.map((width) => `${width}px`).join(" "),
    [userTableColumnWidths],
  );
  const userTableMinWidth = React.useMemo(
    () => userTableColumnWidths.reduce((sum, width) => sum + width, 0),
    [userTableColumnWidths],
  );
  const observeUserTableElementRect = React.useCallback(
    (instance: UserRowVirtualizer, callback: (rect: Rect) => void) =>
      observeElementRect(instance, (rect) => {
        callback(normalizeUserTableRect(rect, userTableMinWidth));
      }),
    [userTableMinWidth],
  );
  const rowVirtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => userTableViewportRef.current,
    estimateSize: () => USER_ROW_ESTIMATED_HEIGHT,
    measureElement: (element) =>
      element.getBoundingClientRect().height || USER_ROW_ESTIMATED_HEIGHT,
    observeElementRect: observeUserTableElementRect,
    overscan: USER_ROW_OVERSCAN,
    initialRect: {
      width: userTableMinWidth,
      height: USER_TABLE_VIEWPORT_ESTIMATED_HEIGHT,
    },
  });
  const virtualRows = rowVirtualizer.getVirtualItems();

  const lastItem = virtualRows[virtualRows.length - 1];
  React.useEffect(() => {
    if (!lastItem) return;
    if (
      lastItem.index >= serverItems.length - 1 &&
      query.hasNextPage &&
      !query.isFetchingNextPage
    ) {
      query.fetchNextPage();
    }
  }, [
    lastItem,
    serverItems.length,
    query.hasNextPage,
    query.isFetchingNextPage,
    query.fetchNextPage,
  ]);

  const shouldVirtualizeRows = !query.isLoading && items.length > 0;
  const tableColumnCount = 8 + visibleUserSchemaFields.length;

  const requestSort = (key: UserSortKey) => {
    setSortConfig((current) => toggleSort(current, key));
  };

  const getSortIcon = (key: UserSortKey) => {
    if (!sortConfig || sortConfig.key !== key) {
      return <ArrowUpDown size={14} className="ml-1 opacity-50" />;
    }
    return sortConfig.direction === "asc" ? (
      <ArrowUp size={14} className="ml-1" />
    ) : (
      <ArrowDown size={14} className="ml-1" />
    );
  };

  const total = query.data?.pages[0]?.total ?? 0;
  const toggleSelectAll = () => {
    if (selectedUserIds.length === items.length) {
      setSelectedUserIds([]);
    } else {
      setSelectedUserIds(items.map((u) => u.id));
    }
  };

  const toggleSelectUser = (id: string) => {
    setSelectedUserIds((prev) =>
      prev.includes(id) ? prev.filter((i) => i !== id) : [...prev, id],
    );
  };

  const bulkDeleteMutation = useMutation({
    mutationFn: bulkDeleteUsers,
    onSuccess: (_, variables) => {
      query.refetch();
      setSelectedUserIds([]);
      toast.success(
        t(
          "msg.admin.users.bulk.delete_success",
          "{{count}}명의 사용자가 삭제되었습니다.",
          { count: variables.length },
        ),
      );
    },
  });

  const bulkUpdateMutation = useMutation({
    mutationFn: bulkUpdateUsers,
    onSuccess: (data) => {
      const failed = data.results?.filter((result) => !result.success) ?? [];
      if (failed.length > 0) {
        toast.error(
          t(
            "msg.admin.users.bulk.update_partial_error",
            "{{count}}명의 사용자 정보 수정에 실패했습니다.",
            { count: failed.length },
          ),
          {
            description: formatUserPolicyMessage(failed[0]?.message),
          },
        );
        return;
      }
      query.refetch();
      setSelectedUserIds([]);
      setSelectedBulkStatus("");
      setSelectedBulkRole("");
      toast.success(
        t(
          "msg.admin.users.bulk.update_success",
          "선택한 사용자들의 정보가 수정되었습니다.",
        ),
      );
    },
  });

  const _handleApplyBulkStatus = () => {
    if (selectedUserIds.length === 0 || !selectedBulkStatus) return;
    bulkUpdateMutation.mutate({
      userIds: selectedUserIds,
      status: selectedBulkStatus,
    });
  };

  const handleBulkDelete = () => {
    if (selectedUserIds.length === 0) return;
    if (
      window.confirm(
        t(
          "msg.admin.users.bulk.delete_confirm",
          "{{count}}명의 사용자를 정말 삭제하시겠습니까?",
          { count: selectedUserIds.length },
        ),
      )
    ) {
      bulkDeleteMutation.mutate(selectedUserIds);
    }
  };

  const _handleDelete = (userId: string, userName: string) => {
    if (
      !window.confirm(
        t(
          "msg.admin.users.list.delete_confirm",
          '사용자 "{{name}}"을(를) 정말 삭제하시겠습니까?',
          { name: userName },
        ),
      )
    ) {
      return;
    }
    deleteMutation.mutate(userId);
  };

  return (
    <div className="space-y-6 flex flex-col h-[calc(100vh-theme(spacing.32))]">
      <PageHeader
        sticky
        titleAs="h2"
        icon={<Users size={20} />}
        title={
          <span data-testid="page-title">
            {t("ui.admin.users.list.title", "사용자 관리")}
          </span>
        }
        description={t(
          "msg.admin.users.list.subtitle",
          "Kratos identity mirror 기준으로 시스템 사용자를 조회하고 관리합니다.",
        )}
        actions={
          <>
            <UserListSearchControls
              initialSearch={search}
              selectedCompany={selectedCompany}
              tenants={tenants}
              profileRole={profileRole}
              onSearch={handleSearch}
              onCompanyChange={handleCompanyChange}
            />

            <Button
              variant="outline"
              size="sm"
              className="h-9"
              onClick={() => query.refetch()}
              disabled={query.isFetching}
            >
              <RefreshCw size={16} />
              {t("ui.common.refresh", "새로고침")}
            </Button>
            <Button asChild variant="outline" size="sm" className="h-9 gap-2">
              <Link to="/users/custom-claims">
                <Key size={16} />
                {t(
                  "ui.admin.users.global_custom_claims.title",
                  "전역 Claim 설정",
                )}
              </Link>
            </Button>
            <DropdownMenu>
              <DropdownMenuTrigger asChild>
                <Button
                  variant="outline"
                  data-testid="user-data-mgmt-btn"
                  className="gap-2 h-9"
                >
                  <LayoutDashboard size={16} />
                  {t("ui.admin.users.data_mgmt", "데이터 관리")}
                  <ChevronDown size={14} className="opacity-50" />
                </Button>
              </DropdownMenuTrigger>
              <DropdownMenuContent align="end" className="w-56">
                <DropdownMenuItem
                  onClick={downloadUserTemplate}
                  data-testid="user-template-menu-item"
                  className="cursor-pointer"
                >
                  <FileSpreadsheet size={16} className="mr-2 opacity-50" />
                  {t("ui.admin.users.csv_template", "템플릿 다운로드")}
                </DropdownMenuItem>
                <DropdownMenuSeparator />
                <DropdownMenuItem
                  onSelect={() => {
                    if (isWritable) setBulkUploadOpen(true);
                  }}
                  disabled={!isWritable}
                  className="cursor-pointer"
                >
                  <Upload size={16} className="mr-2 opacity-50" />
                  {t("ui.admin.users.list.bulk_import", "일괄 등록 (CSV)")}
                </DropdownMenuItem>
                <DropdownMenuSeparator />
                <DropdownMenuItem
                  onClick={() => handleExport(false)}
                  disabled={exportMutation.isPending}
                  data-testid="user-export-menu-item"
                  className="cursor-pointer"
                >
                  <FileDown size={16} className="mr-2 opacity-50" />
                  {t("ui.common.export_without_ids", "UUID 제외 내보내기")}
                </DropdownMenuItem>
                <DropdownMenuItem
                  onClick={() => handleExport(true)}
                  disabled={exportMutation.isPending}
                  data-testid="user-export-with-ids-menu-item"
                  className="cursor-pointer"
                >
                  <FileDown size={16} className="mr-2 opacity-50" />
                  {t("ui.common.export_with_ids", "UUID 포함 내보내기")}
                </DropdownMenuItem>
              </DropdownMenuContent>
            </DropdownMenu>
            <UserBulkUploadModal
              variant="custom"
              open={bulkUploadOpen}
              onOpenChange={setBulkUploadOpen}
              onSuccess={() => query.refetch()}
            />
            <Dialog>
              <DialogTrigger asChild>
                <Button variant="outline" size="icon" className="h-9 w-9">
                  <Settings2 size={16} />
                </Button>
              </DialogTrigger>
              <DialogContent>
                <DialogHeader>
                  <DialogTitle>
                    {t("ui.admin.users.list.columns.title", "표시 컬럼 설정")}
                  </DialogTitle>
                  <DialogDescription>
                    {t(
                      "msg.admin.users.list.columns.description",
                      "사용자 목록에 표시할 커스텀 필드를 선택하세요.",
                    )}
                  </DialogDescription>
                </DialogHeader>
                <div className="grid gap-4 py-4">
                  {userSchema.length === 0 && (
                    <p className="py-4 text-center text-sm text-muted-foreground">
                      {t(
                        "msg.admin.users.list.columns.no_custom",
                        "현재 테넌트에 정의된 커스텀 필드가 없습니다.",
                      )}
                    </p>
                  )}
                  {userSchema.map((field) => (
                    <label
                      key={field.key}
                      className="flex cursor-pointer items-center gap-3 rounded-lg p-2 hover:bg-muted/50"
                    >
                      <input
                        name={`user-list-column-${field.key}`}
                        type="checkbox"
                        className="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
                        checked={visibleColumns[field.key] !== false}
                        onChange={() => toggleColumn(field.key)}
                      />
                      <div className="flex flex-col">
                        <span className="text-sm font-medium">
                          {field.label}
                        </span>
                        <span className="font-mono text-xs text-muted-foreground">
                          {field.key}
                        </span>
                      </div>
                    </label>
                  ))}
                </div>
                <DialogFooter>
                  <DialogTrigger asChild>
                    <Button variant="secondary">
                      {t("ui.common.close", "닫기")}
                    </Button>
                  </DialogTrigger>
                </DialogFooter>
              </DialogContent>
            </Dialog>
            {isWritable ? (
              <Button asChild size="sm" className="h-9">
                <Link to="/users/new">
                  <Plus size={16} />
                  {t("ui.admin.users.list.add", "사용자 추가")}
                </Link>
              </Button>
            ) : (
              <Button size="sm" className="h-9" disabled>
                <Plus size={16} />
                {t("ui.admin.users.list.add", "사용자 추가")}
              </Button>
            )}
          </>
        }
      />

      <Card className="flex-1 flex flex-col min-h-0 bg-[var(--color-panel)] overflow-hidden">
        <CardHeader className="flex flex-row items-center justify-between flex-shrink-0">
          <div>
            <CardTitle className="text-lg font-bold flex items-center gap-2">
              {t("ui.admin.users.list.registry.title", "User Registry")}
            </CardTitle>
            <CardDescription>
              {t(
                "msg.admin.users.list.registry.count",
                "총 {{count}}명의 사용자가 등록되어 있습니다.",
                { count: total },
              )}
            </CardDescription>
          </div>
        </CardHeader>
        <CardContent className="flex-1 flex flex-col min-h-0 pt-0">
          {(errorMsg || fallbackError) && (
            <div className="mb-4 rounded-lg border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive flex-shrink-0">
              {errorMsg ?? fallbackError}
            </div>
          )}

          <div className={commonTableShellClass}>
            <div
              ref={userTableViewportRef}
              data-testid="user-table-viewport"
              className={commonTableViewportClass}
            >
              <Table style={{ display: "grid", minWidth: userTableMinWidth }}>
                <TableHeader className={sortableTableHeaderClassName}>
                  <TableRow
                    style={{
                      display: "grid",
                      gridTemplateColumns: userTableGridTemplateColumns,
                      minWidth: userTableMinWidth,
                    }}
                  >
                    <TableHead className={`${userTableHeadClassName} w-12`}>
                      <div className="flex h-full items-center justify-center">
                        <input
                          name="user-list-select-all"
                          type="checkbox"
                          className="w-4 h-4 rounded border-gray-300 text-primary focus:ring-primary cursor-pointer"
                          checked={
                            items.length > 0 &&
                            selectedUserIds.length === items.length
                          }
                          onChange={toggleSelectAll}
                        />
                      </div>
                    </TableHead>
                    <TableHead
                      className={`${userTableHeadInteractiveClassName} min-w-[120px]`}
                      onClick={() => requestSort("name")}
                    >
                      <div className={userTableHeadContentClassName}>
                        {t("ui.admin.users.list.table.name", "이름")}
                        {getSortIcon("name")}
                      </div>
                    </TableHead>
                    <TableHead
                      className={`${userTableHeadInteractiveClassName} min-w-[180px]`}
                      onClick={() => requestSort("email")}
                    >
                      <div className={userTableHeadContentClassName}>
                        {t("ui.admin.users.list.table.email", "이메일")}
                        {getSortIcon("email")}
                      </div>
                    </TableHead>
                    <TableHead
                      className={`${userTableHeadInteractiveClassName} min-w-[140px]`}
                      onClick={() => requestSort("phone")}
                    >
                      <div className={userTableHeadContentClassName}>
                        {t("ui.admin.users.list.table.phone", "전화번호")}
                        {getSortIcon("phone")}
                      </div>
                    </TableHead>
                    <TableHead
                      className={`${userTableHeadInteractiveClassName} min-w-[220px]`}
                      onClick={() => requestSort("id")}
                    >
                      <div className={userTableHeadContentClassName}>
                        {t("ui.admin.users.list.table.id", "ID")}
                        {getSortIcon("id")}
                      </div>
                    </TableHead>
                    <TableHead
                      className={userTableHeadInteractiveClassName}
                      onClick={() => requestSort("status")}
                    >
                      <div className={userTableHeadContentClassName}>
                        {t("ui.admin.users.list.table.status", "STATUS")}
                        {getSortIcon("status")}
                      </div>
                    </TableHead>
                    <TableHead
                      className={userTableHeadInteractiveClassName}
                      onClick={() => requestSort("tenant_dept")}
                    >
                      <div className={userTableHeadContentClassName}>
                        {t(
                          "ui.admin.users.list.table.tenant_dept",
                          "TENANT / DEPT",
                        )}
                        {getSortIcon("tenant_dept")}
                      </div>
                    </TableHead>
                    {/* Dynamic Columns from Schema */}
                    {visibleUserSchemaFields.map((field) => (
                      <SortableTableHead
                        key={field.key}
                        className={userSortableTableHeadClassName}
                        contentClassName={userSortableTableHeadContentClassName}
                        label={field.label}
                        onSort={requestSort}
                        sortConfig={sortConfig}
                        sortKey={field.key}
                      />
                    ))}
                    <SortableTableHead
                      className={userSortableTableHeadClassName}
                      contentClassName={userSortableTableHeadContentClassName}
                      label={t("ui.admin.users.list.table.created", "CREATED")}
                      onSort={requestSort}
                      sortConfig={sortConfig}
                      sortKey="createdAt"
                    />
                  </TableRow>
                </TableHeader>
                <TableBody
                  style={
                    shouldVirtualizeRows
                      ? {
                          display: "grid",
                          height: `${rowVirtualizer.getTotalSize()}px`,
                          minWidth: userTableMinWidth,
                          position: "relative",
                        }
                      : undefined
                  }
                >
                  {query.isLoading && (
                    <TableRow
                      data-testid="user-table-loading-row"
                      style={{
                        display: "grid",
                        gridTemplateColumns: userTableGridTemplateColumns,
                        minWidth: userTableMinWidth,
                      }}
                    >
                      <TableCell
                        colSpan={tableColumnCount}
                        data-testid="user-table-loading-cell"
                        className={userTableStateCellClassName}
                        style={{ gridColumn: "1 / -1" }}
                      >
                        {t("msg.common.loading", "로딩 중...")}
                      </TableCell>
                    </TableRow>
                  )}
                  {!query.isLoading && items.length === 0 && (
                    <TableRow
                      data-testid="user-table-empty-row"
                      style={{
                        display: "grid",
                        gridTemplateColumns: userTableGridTemplateColumns,
                        minWidth: userTableMinWidth,
                      }}
                    >
                      <TableCell
                        colSpan={tableColumnCount}
                        data-testid="user-table-empty-cell"
                        className={userTableStateCellClassName}
                        style={{ gridColumn: "1 / -1" }}
                      >
                        {t(
                          "msg.admin.users.list.empty",
                          "검색 결과가 없습니다.",
                        )}
                      </TableCell>
                    </TableRow>
                  )}
                  {shouldVirtualizeRows &&
                    virtualRows.map((virtualRow) => {
                      const user = items[virtualRow.index];
                      if (!user) return null;
                      const representativeTenantLabel =
                        resolveRepresentativeTenantLabel(user, tenants) ||
                        t("ui.common.unassigned", "미배정");

                      return (
                        <TableRow
                          key={user.id}
                          data-index={virtualRow.index}
                          ref={rowVirtualizer.measureElement}
                          className={
                            selectedUserIds.includes(user.id)
                              ? "bg-primary/5"
                              : ""
                          }
                          style={{
                            display: "grid",
                            gridTemplateColumns: userTableGridTemplateColumns,
                            height: `${virtualRow.size}px`,
                            minWidth: userTableMinWidth,
                            position: "absolute",
                            transform: `translateY(${virtualRow.start}px)`,
                            width: "100%",
                          }}
                        >
                          <TableCell>
                            <input
                              name={`user-list-select-${user.id}`}
                              type="checkbox"
                              className="w-4 h-4 rounded border-gray-300 text-primary focus:ring-primary cursor-pointer disabled:opacity-30 disabled:cursor-not-allowed"
                              checked={selectedUserIds.includes(user.id)}
                              onChange={() => toggleSelectUser(user.id)}
                              disabled={user.id === profile?.id}
                              title={
                                user.id === profile?.id
                                  ? t(
                                      "msg.admin.users.self_delete_blocked",
                                      "본인 계정은 삭제할 수 없습니다.",
                                    )
                                  : undefined
                              }
                            />
                          </TableCell>
                          <TableCell>
                            <Link
                              to={`/users/${user.id}`}
                              className="block max-w-[150px] truncate font-medium text-foreground transition-colors hover:text-primary hover:underline"
                              title={user.name}
                            >
                              {user.name}
                            </Link>
                          </TableCell>
                          <TableCell
                            className="text-sm text-muted-foreground truncate max-w-[200px]"
                            title={user.email}
                          >
                            {user.email}
                          </TableCell>
                          <TableCell className="text-sm text-muted-foreground whitespace-nowrap">
                            {user.phone || "-"}
                          </TableCell>
                          <TableCell
                            className="max-w-[220px] break-all font-mono text-xs text-muted-foreground"
                            data-testid={`user-internal-id-${user.id}`}
                          >
                            {user.id}
                          </TableCell>
                          <TableCell>
                            <Select
                              value={normalizeUserStatusValue(user.status)}
                              onValueChange={(status) =>
                                statusMutation.mutate({
                                  userId: user.id,
                                  status,
                                })
                              }
                              disabled={
                                statusMutation.isPending ||
                                user.id === profile?.id ||
                                !isWritable
                              }
                            >
                              <SelectTrigger
                                className="h-8 w-[150px] border-none bg-transparent hover:bg-muted/50 transition-colors px-0 font-medium"
                                aria-label={t(
                                  "ui.admin.users.list.change_status",
                                  "{{name}} 상태 변경",
                                  { name: user.name },
                                )}
                                data-testid={`user-status-select-${user.id}`}
                              >
                                <SelectValue />
                              </SelectTrigger>
                              <SelectContent>
                                {userStatusValues.map((status) => (
                                  <SelectItem key={status} value={status}>
                                    {userStatusLabel(status)}
                                  </SelectItem>
                                ))}
                              </SelectContent>
                            </Select>
                          </TableCell>
                          <TableCell>
                            <div className="flex flex-col gap-1">
                              <span className="text-sm font-medium">
                                {representativeTenantLabel}
                              </span>
                              {user.department && (
                                <span className="text-xs text-muted-foreground">
                                  {user.department}
                                </span>
                              )}
                            </div>
                          </TableCell>
                          {/* Dynamic Metadata Cells */}
                          {visibleUserSchemaFields.map((field) => (
                            <TableCell key={field.key} className="text-sm">
                              {String(user.metadata?.[field.key] ?? "-")}
                            </TableCell>
                          ))}
                          <TableCell className="text-sm text-muted-foreground">
                            {new Date(user.createdAt).toLocaleDateString()}
                          </TableCell>
                        </TableRow>
                      );
                    })}
                </TableBody>
              </Table>
            </div>
          </div>

          {/* Bulk Action Bar */}
          {selectedUserIds.length > 0 && (
            <div
              className="fixed bottom-8 left-1/2 -translate-x-1/2 z-50 flex items-center gap-4 px-6 py-3 rounded-2xl bg-foreground text-background shadow-2xl animate-in slide-in-from-bottom-4 duration-300"
              data-testid="bulk-action-bar"
            >
              <span className="text-sm font-medium border-r border-background/20 pr-4 mr-2">
                {t("ui.admin.users.bulk.selected_count", "{{count}}명 선택됨", {
                  count: selectedUserIds.length,
                })}
              </span>
              <div className="flex items-center gap-2">
                <Select
                  value={selectedBulkStatus}
                  onValueChange={(value) =>
                    setSelectedBulkStatus(value as UserStatusValue)
                  }
                >
                  <SelectTrigger
                    className="h-8 w-[120px] bg-transparent border-background/20 text-background text-xs"
                    data-testid="bulk-status-select"
                  >
                    <SelectValue
                      placeholder={t(
                        "ui.admin.users.bulk.status_placeholder",
                        "상태 선택",
                      )}
                    />
                  </SelectTrigger>
                  <SelectContent>
                    {userStatusValues.map((status) => (
                      <SelectItem key={status} value={status}>
                        {userStatusLabel(status)}
                      </SelectItem>
                    ))}
                  </SelectContent>
                </Select>
                <Button
                  variant="ghost"
                  size="sm"
                  className="text-background hover:bg-background/10 h-8 gap-1.5"
                  onClick={() => {
                    const payload: {
                      userIds: string[];
                      status?: UserStatusValue;
                      role?: "super_admin" | "user";
                    } = { userIds: selectedUserIds };
                    let hasChanges = false;
                    if (selectedBulkStatus) {
                      payload.status = selectedBulkStatus;
                      hasChanges = true;
                    }
                    if (selectedBulkRole) {
                      payload.role = selectedBulkRole;
                      hasChanges = true;
                    }
                    if (hasChanges) {
                      bulkUpdateMutation.mutate(payload);
                    }
                  }}
                  disabled={
                    (!selectedBulkStatus && !selectedBulkRole) ||
                    bulkUpdateMutation.isPending ||
                    !isWritable
                  }
                  data-testid="bulk-apply-btn"
                >
                  <ShieldCheck size={14} />
                  {t("ui.common.apply", "적용")}
                </Button>
                <Select
                  value={selectedBulkRole}
                  onValueChange={(value) =>
                    setSelectedBulkRole(value as "super_admin" | "user")
                  }
                >
                  <SelectTrigger
                    className="h-8 w-[150px] bg-transparent border-background/20 text-background text-xs"
                    data-testid="bulk-permission-select"
                  >
                    <SelectValue
                      placeholder={t(
                        "ui.admin.users.bulk.permission_placeholder",
                        "권한 선택",
                      )}
                    />
                  </SelectTrigger>
                  <SelectContent>
                    <SelectItem value="super_admin">
                      {t(
                        "ui.admin.users.detail.form.role_super_admin",
                        "시스템 관리자",
                      )}
                    </SelectItem>
                    <SelectItem value="user">
                      {t("ui.admin.users.detail.form.role_user", "일반 사용자")}
                    </SelectItem>
                  </SelectContent>
                </Select>
                <div className="w-px h-4 bg-background/20 mx-1" />
                <Button
                  variant="ghost"
                  size="sm"
                  className="text-destructive-foreground hover:bg-destructive/20 h-8 gap-1.5"
                  onClick={handleBulkDelete}
                  disabled={!isWritable}
                  data-testid="bulk-delete-btn"
                >
                  <Trash2 size={14} />
                  {t("ui.common.delete", "삭제")}
                </Button>
              </div>
              <Button
                variant="ghost"
                size="icon"
                className="text-background/50 hover:text-background h-8 w-8 ml-2"
                onClick={() => setSelectedUserIds([])}
                aria-label={t("ui.common.close", "닫기")}
                data-testid="bulk-close-btn"
              >
                <Plus size={16} className="rotate-45" />
              </Button>
            </div>
          )}
        </CardContent>
      </Card>
    </div>
  );
}

export default UserListPage;
