import { useMutation, useQuery } from "@tanstack/react-query";
import { useVirtualizer } from "@tanstack/react-virtual";
import {
  KeyRound,
  RefreshCw,
  RotateCcw,
  Settings2,
  Trash2,
} from "lucide-react";
import * as React from "react";
import { useParams } from "react-router-dom";
import { Badge } from "../../../components/ui/badge";
import { Button } from "../../../components/ui/button";
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "../../../components/ui/card";
import { Checkbox } from "../../../components/ui/checkbox";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "../../../components/ui/dialog";
import { Input } from "../../../components/ui/input";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "../../../components/ui/table";
import { toast } from "../../../components/ui/use-toast";
import {
  deleteWorksmobilePendingJobs,
  enqueueWorksmobileBackfillDryRun,
  enqueueWorksmobileOrgUnitDelete,
  enqueueWorksmobileOrgUnitSync,
  enqueueWorksmobileUserSync,
  fetchMe,
  fetchWorksmobileComparison,
  fetchWorksmobileOverview,
  retryWorksmobileJob,
  type WorksmobileComparisonItem,
  type WorksmobileOutboxItem,
} from "../../../lib/adminApi";
import { t } from "../../../lib/i18n";
import {
  canAccessWorksmobile,
  HANMAC_FAMILY_TENANT_ID,
} from "./worksmobileAccess";
import {
  buildWorksmobilePasswordManageUrl,
  canOpenWorksmobilePasswordManage,
  canSelectWorksmobileRow,
  comparisonFilterOptions,
  filterVisibleWorksmobileComparisonRows,
  filterWorksmobileComparisonRows,
  filterWorksmobileComparisonRowsBySearch,
  formatWorksmobileOrgDetails,
  formatWorksmobilePersonName,
  formatWorksmobileSelectionFailureDescription,
  formatWorksmobileUpdateDetails,
  formatWorksmobileUserMembershipDetails,
  getDefaultGroupComparisonFilters,
  getDefaultUserComparisonFilters,
  getDefaultWorksmobileComparisonColumns,
  getWorksmobileComparisonStatusLabel,
  getWorksmobileRowSelectionKey,
  getWorksmobileSelectedActionIds,
  getWorksmobileSelectedCreateUserIds,
  getWorksmobileSelectedUpdateUserIds,
  getWorksmobileSelectedWorksOnlyOrgUnitIds,
  summarizeWorksmobileComparison,
  type WorksmobileAccountStatusFilter,
  type WorksmobileComparisonColumnKey,
  type WorksmobileComparisonColumnVisibility,
  type WorksmobileComparisonFilter,
  type WorksmobileComparisonSummary,
  worksmobileAccountStatusFilterOptions,
} from "./worksmobileComparison";

function worksmobileJobPayloadString(job: WorksmobileOutboxItem, key: string) {
  const value = job.payload?.[key];
  return typeof value === "string" ? value.trim() : "";
}

function worksmobileJobRequestSummary(job: WorksmobileOutboxItem) {
  const summary = job.payload?.requestSummary;
  if (!summary || typeof summary !== "object" || Array.isArray(summary)) {
    return {};
  }
  return summary as Record<string, unknown>;
}

function worksmobileSummaryString(
  summary: Record<string, unknown>,
  key: string,
) {
  const value = summary[key];
  if (typeof value === "string") {
    return value.trim();
  }
  if (typeof value === "number" && Number.isFinite(value)) {
    return String(value);
  }
  return "";
}

function formatWorksmobileJobTarget(job: WorksmobileOutboxItem) {
  const summary = worksmobileJobRequestSummary(job);
  return (
    worksmobileJobPayloadString(job, "displayName") ||
    worksmobileSummaryString(summary, "displayName") ||
    worksmobileSummaryString(summary, "orgUnitName") ||
    worksmobileJobPayloadString(job, "name") ||
    worksmobileJobPayloadString(job, "loginEmail") ||
    worksmobileJobPayloadString(job, "email") ||
    job.resourceId
  );
}

function formatWorksmobileJobTargetSubtext(job: WorksmobileOutboxItem) {
  const summary = worksmobileJobRequestSummary(job);
  return (
    worksmobileJobPayloadString(job, "loginEmail") ||
    worksmobileSummaryString(summary, "email") ||
    worksmobileJobPayloadString(job, "email") ||
    worksmobileJobPayloadString(job, "externalKey") ||
    worksmobileSummaryString(summary, "orgUnitExternalKey") ||
    job.resourceId
  );
}

function formatWorksmobileJobSummaryParts(job: WorksmobileOutboxItem) {
  const summary = worksmobileJobRequestSummary(job);
  const parts = [
    worksmobileJobPayloadString(job, "primaryLeafOrgName"),
    worksmobileJobPayloadString(job, "matchLocalPart"),
    worksmobileSummaryString(summary, "parentOrgUnitId"),
    worksmobileSummaryString(summary, "employeeNumber"),
    worksmobileSummaryString(summary, "task"),
  ].filter(Boolean);
  return Array.from(new Set(parts));
}

function formatWorksmobileJobPayload(job: WorksmobileOutboxItem) {
  if (!job.payload || Object.keys(job.payload).length === 0) {
    return "";
  }
  return JSON.stringify(
    job.payload,
    (key, value) => {
      if (typeof key === "string" && key.toLowerCase().includes("password")) {
        return "[redacted]";
      }
      return value;
    },
    2,
  );
}

function pageTabClassName(active: boolean) {
  return `relative px-6 py-3 text-sm font-medium transition-colors ${
    active
      ? "border-b-2 border-primary text-primary"
      : "text-muted-foreground hover:text-foreground"
  }`;
}

export function TenantWorksmobilePage() {
  const params = useParams<{ tenantId: string }>();
  const tenantId = params.tenantId ?? HANMAC_FAMILY_TENANT_ID;
  const [orgUnitId, setOrgUnitId] = React.useState("");
  const [userId, setUserId] = React.useState("");
  const [activeTab, setActiveTab] = React.useState("users");
  const [userFilters, setUserFilters] = React.useState<
    WorksmobileComparisonFilter[]
  >(getDefaultUserComparisonFilters);
  const [groupFilters, setGroupFilters] = React.useState<
    WorksmobileComparisonFilter[]
  >(getDefaultGroupComparisonFilters);
  const [userAccountStatusFilter, setUserAccountStatusFilter] =
    React.useState<WorksmobileAccountStatusFilter>("all");
  const [includeUserMissingExternalKey, setIncludeUserMissingExternalKey] =
    React.useState(false);
  const [includeGroupMissingExternalKey, setIncludeGroupMissingExternalKey] =
    React.useState(false);
  const [userSearch, setUserSearch] = React.useState("");
  const [groupSearch, setGroupSearch] = React.useState("");
  const [selectedUserRowKeys, setSelectedUserRowKeys] = React.useState<
    string[]
  >([]);
  const [selectedGroupRowKeys, setSelectedGroupRowKeys] = React.useState<
    string[]
  >([]);
  const [userVisibleColumns, setUserVisibleColumns] = React.useState(
    getDefaultWorksmobileComparisonColumns,
  );
  const [groupVisibleColumns, setGroupVisibleColumns] = React.useState(
    getDefaultGroupWorksmobileComparisonColumns,
  );

  const profileQuery = useQuery({
    queryKey: ["me"],
    queryFn: fetchMe,
  });
  const hasWorksmobileAccess = canAccessWorksmobile(profileQuery.data);

  const overviewQuery = useQuery({
    queryKey: ["worksmobile", tenantId],
    queryFn: () => fetchWorksmobileOverview(tenantId),
    enabled: tenantId.length > 0 && hasWorksmobileAccess,
  });

  const comparisonQuery = useQuery({
    queryKey: ["worksmobile-comparison", tenantId],
    queryFn: () => fetchWorksmobileComparison(tenantId, true),
    enabled: tenantId.length > 0 && hasWorksmobileAccess,
  });

  const dryRunMutation = useMutation({
    mutationFn: () => enqueueWorksmobileBackfillDryRun(tenantId),
    onSuccess: () => {
      toast.success("Backfill Dry-run 작업을 등록했습니다.");
      overviewQuery.refetch();
    },
    onError: (error) => {
      toast.error("Backfill Dry-run 실패", {
        description: getErrorMessage(error),
      });
    },
  });

  const retryMutation = useMutation({
    mutationFn: (jobId: string) => retryWorksmobileJob(tenantId, jobId),
    onSuccess: () => {
      toast.success("재시도 작업을 등록했습니다.");
      overviewQuery.refetch();
    },
    onError: (error) => {
      toast.error("재시도 작업 등록 실패", {
        description: getErrorMessage(error),
      });
    },
  });

  const deletePendingJobsMutation = useMutation({
    mutationFn: () => deleteWorksmobilePendingJobs(tenantId),
    onSuccess: (result) => {
      toast.success(`대기중 payload ${result.deletedCount}건을 삭제했습니다.`);
      overviewQuery.refetch();
    },
    onError: (error) => {
      toast.error("대기중 payload 삭제 실패", {
        description: getErrorMessage(error),
      });
    },
  });

  const orgUnitSyncMutation = useMutation({
    mutationFn: () => enqueueWorksmobileOrgUnitSync(tenantId, orgUnitId.trim()),
    onSuccess: () => {
      toast.success("조직 Sync 작업을 등록했습니다.");
      overviewQuery.refetch();
    },
    onError: (error) => {
      toast.error("조직 Sync 작업 등록 실패", {
        description: getErrorMessage(error),
      });
    },
  });

  const userSyncMutation = useMutation({
    mutationFn: () => enqueueWorksmobileUserSync(tenantId, userId.trim()),
    onSuccess: () => {
      toast.success("구성원 Sync 작업을 등록했습니다.");
      overviewQuery.refetch();
    },
    onError: (error) => {
      toast.error("구성원 Sync 작업 등록 실패", {
        description: getErrorMessage(error),
      });
    },
  });

  const createSelectedMutation = useMutation({
    mutationFn: async ({
      resourceKind,
      ids,
      initialPassword,
    }: {
      resourceKind: "users" | "groups";
      ids: string[];
      initialPassword?: string;
    }) => {
      const trimmedInitialPassword = initialPassword?.trim();
      const failures: string[] = [];
      let successCount = 0;
      for (const id of ids) {
        try {
          if (resourceKind === "users") {
            await enqueueWorksmobileUserSync(
              tenantId,
              id,
              undefined,
              trimmedInitialPassword,
            );
          } else {
            await enqueueWorksmobileOrgUnitSync(tenantId, id);
          }
          successCount += 1;
        } catch (error) {
          failures.push(`${id}: ${getErrorMessage(error)}`);
        }
      }

      if (successCount === 0 && failures.length > 0) {
        throw new Error(failures.slice(0, 3).join("\n"));
      }

      return {
        resourceKind,
        count: successCount,
        failures,
        failureCount: failures.length,
      };
    },
    onSuccess: ({ resourceKind, count, failureCount, failures }) => {
      if (resourceKind === "users") {
        setSelectedUserRowKeys([]);
      } else {
        setSelectedGroupRowKeys([]);
      }
      if (failureCount > 0) {
        toast.error("일부 WORKS 생성 작업 등록 실패", {
          description: formatWorksmobileSelectionFailureDescription(
            count,
            failures,
          ),
        });
      } else {
        toast.success("WORKS 생성 작업을 등록했습니다.", {
          description: `${count}건`,
        });
      }
      overviewQuery.refetch();
      comparisonQuery.refetch();
    },
    onError: (error) => {
      toast.error("WORKS 생성 작업 등록 실패", {
        description: getErrorMessage(error),
      });
    },
  });

  const syncSelectedOrgUnitsMutation = useMutation({
    mutationFn: async ({
      baronIds,
      worksmobileIds,
    }: {
      baronIds: string[];
      worksmobileIds: string[];
    }) => {
      for (const id of baronIds) {
        await enqueueWorksmobileOrgUnitSync(tenantId, id);
      }
      for (const id of worksmobileIds) {
        await enqueueWorksmobileOrgUnitDelete(tenantId, id);
      }
      return {
        upsertCount: baronIds.length,
        deleteOrReconcileCount: worksmobileIds.length,
      };
    },
    onSuccess: ({ upsertCount, deleteOrReconcileCount }) => {
      setSelectedGroupRowKeys([]);
      toast.success("선택 조직 동기화 작업을 등록했습니다.", {
        description: `upsert ${upsertCount}건, WORKS-only 정리 ${deleteOrReconcileCount}건`,
      });
      overviewQuery.refetch();
      comparisonQuery.refetch();
    },
    onError: (error) => {
      toast.error("선택 조직 동기화 작업 등록 실패", {
        description: getErrorMessage(error),
      });
    },
  });

  if (!profileQuery.isLoading && !hasWorksmobileAccess) {
    return (
      <div className="rounded-md border border-destructive/30 bg-destructive/5 p-4 text-sm text-destructive">
        Worksmobile 연동은 super admin 또는 한맥가족 admin/owner 이상만 사용할
        수 있습니다.
      </div>
    );
  }

  if (overviewQuery.isError) {
    return (
      <div className="rounded-md border border-destructive/30 bg-destructive/5 p-4 text-sm text-destructive">
        {t(
          "ui.admin.tenants.worksmobile.forbidden",
          "한맥가족 테넌트에서만 Worksmobile 연동을 관리할 수 있습니다.",
        )}
      </div>
    );
  }

  const overview = overviewQuery.data;
  const pendingJobCount = (overview?.recentJobs ?? []).filter(
    (job) => job.status === "pending",
  ).length;
  const comparisonUsers = filterVisibleWorksmobileComparisonRows(
    comparisonQuery.data?.users ?? [],
  );
  const comparisonGroups = comparisonQuery.data?.groups ?? [];
  const filteredComparisonUsers = filterWorksmobileComparisonRowsBySearch(
    filterWorksmobileComparisonRows(
      comparisonUsers,
      userFilters,
      includeUserMissingExternalKey,
      userAccountStatusFilter,
    ),
    userSearch,
  );
  const filteredComparisonGroups = filterWorksmobileComparisonRowsBySearch(
    filterWorksmobileComparisonRows(
      comparisonGroups,
      groupFilters,
      includeGroupMissingExternalKey,
    ),
    groupSearch,
  );
  const userSummary = summarizeWorksmobileComparison(comparisonUsers);
  const groupSummary = summarizeWorksmobileComparison(comparisonGroups);
  const isCreatingUsers =
    createSelectedMutation.isPending &&
    createSelectedMutation.variables?.resourceKind === "users";
  const isSyncingGroups = syncSelectedOrgUnitsMutation.isPending;
  const isRefreshing = overviewQuery.isFetching || comparisonQuery.isFetching;

  return (
    <div className="min-w-0 max-w-full space-y-6">
      <header className="flex flex-wrap items-center justify-between gap-3">
        <div>
          <h3 className="text-xl font-semibold">
            {t("ui.admin.tenants.worksmobile.title", "Worksmobile 연동")}
          </h3>
          <p className="text-sm text-muted-foreground">
            {t(
              "ui.admin.tenants.worksmobile.subtitle",
              "한맥가족 Directory 조직/구성원 동기화 상태를 확인하고 실패 작업을 재시도합니다.",
            )}
          </p>
        </div>
        <div className="flex flex-wrap gap-2">
          <Button
            type="button"
            variant="outline"
            onClick={() => {
              overviewQuery.refetch();
              comparisonQuery.refetch();
            }}
            disabled={isRefreshing}
          >
            <RefreshCw size={16} />
            {t("ui.admin.tenants.worksmobile.refresh", "새로고침")}
          </Button>
          <Button
            type="button"
            onClick={() => dryRunMutation.mutate()}
            disabled={dryRunMutation.isPending}
          >
            <RefreshCw size={16} />
            {t("ui.admin.tenants.worksmobile.dry_run", "Backfill Dry-run")}
          </Button>
        </div>
      </header>

      <div
        className="flex border-b border-border"
        role="tablist"
        aria-label="Worksmobile 탭"
      >
        <button
          type="button"
          role="tab"
          aria-selected={activeTab === "history"}
          className={pageTabClassName(activeTab === "history")}
          onClick={() => setActiveTab("history")}
        >
          이력
        </button>
        <button
          type="button"
          role="tab"
          aria-selected={activeTab === "users"}
          className={pageTabClassName(activeTab === "users")}
          onClick={() => setActiveTab("users")}
        >
          사용자
        </button>
        <button
          type="button"
          role="tab"
          aria-selected={activeTab === "groups"}
          className={pageTabClassName(activeTab === "groups")}
          onClick={() => setActiveTab("groups")}
        >
          조직
        </button>
      </div>

      {activeTab === "history" ? (
        <div className="space-y-4">
          <Card>
            <CardHeader className="flex flex-row items-center justify-between gap-3">
              <div>
                <CardTitle className="text-base">
                  {t("ui.admin.tenants.worksmobile.recent_jobs", "최근 작업")}
                </CardTitle>
                <CardDescription>
                  pending payload {pendingJobCount}건
                </CardDescription>
              </div>
              <Button
                type="button"
                variant="outline"
                size="sm"
                onClick={() => {
                  if (
                    window.confirm(
                      `아직 실행되지 않은 pending payload ${pendingJobCount}건을 삭제할까요?`,
                    )
                  ) {
                    deletePendingJobsMutation.mutate();
                  }
                }}
                disabled={
                  pendingJobCount === 0 || deletePendingJobsMutation.isPending
                }
              >
                <Trash2 size={16} />
                대기중 payload 삭제
              </Button>
            </CardHeader>
            <CardContent>
              <Table>
                <TableHeader>
                  <TableRow>
                    <TableHead>대상</TableHead>
                    <TableHead>작업</TableHead>
                    <TableHead>변경 요약</TableHead>
                    <TableHead>상태</TableHead>
                    <TableHead>retry</TableHead>
                    <TableHead className="w-24" />
                  </TableRow>
                </TableHeader>
                <TableBody>
                  {(overview?.recentJobs ?? []).map((job) => (
                    <TableRow key={job.id}>
                      <TableCell>
                        <div className="font-medium">
                          {formatWorksmobileJobTarget(job)}
                        </div>
                        <div className="text-xs text-muted-foreground">
                          {job.resourceType}:
                          {formatWorksmobileJobTargetSubtext(job)}
                        </div>
                      </TableCell>
                      <TableCell>
                        <Badge variant="outline">{job.action}</Badge>
                      </TableCell>
                      <TableCell>
                        <div className="flex max-w-md flex-wrap gap-1">
                          {formatWorksmobileJobSummaryParts(job).map((part) => (
                            <Badge key={part} variant="secondary">
                              {part}
                            </Badge>
                          ))}
                          {formatWorksmobileJobSummaryParts(job).length ===
                            0 && (
                            <span className="text-xs text-muted-foreground">
                              {job.resourceId}
                            </span>
                          )}
                        </div>
                        {formatWorksmobileJobPayload(job) && (
                          <details className="mt-2 max-w-xl text-xs">
                            <summary className="cursor-pointer text-muted-foreground">
                              payload
                            </summary>
                            <pre className="mt-2 max-h-64 overflow-auto rounded-md border bg-muted/30 p-2 text-[11px] leading-relaxed">
                              {formatWorksmobileJobPayload(job)}
                            </pre>
                          </details>
                        )}
                      </TableCell>
                      <TableCell>{job.status}</TableCell>
                      <TableCell>{job.retryCount}</TableCell>
                      <TableCell>
                        <Button
                          type="button"
                          variant="ghost"
                          size="sm"
                          onClick={() => retryMutation.mutate(job.id)}
                          disabled={retryMutation.isPending}
                        >
                          <RotateCcw size={16} />
                        </Button>
                      </TableCell>
                    </TableRow>
                  ))}
                </TableBody>
              </Table>
            </CardContent>
          </Card>
        </div>
      ) : null}

      {activeTab === "users" ? (
        <div className="space-y-4">
          <ComparisonSummary
            title={t(
              "ui.admin.tenants.worksmobile.compare",
              "Baron / Works 비교",
            )}
            summary={userSummary}
          />
          <ComparisonTable
            title={t("ui.admin.tenants.worksmobile.compare_users", "구성원")}
            rows={filteredComparisonUsers}
            totalRows={comparisonQuery.data?.users.length ?? 0}
            loading={comparisonQuery.isLoading}
            selectedKeys={selectedUserRowKeys}
            onSelectedKeysChange={setSelectedUserRowKeys}
            search={userSearch}
            onSearchChange={(nextSearch) => {
              setUserSearch(nextSearch);
              setSelectedUserRowKeys([]);
            }}
            searchPlaceholder="구성원 이름 또는 UUID 검색"
            filters={userFilters}
            onFiltersChange={(nextFilters) => {
              setUserFilters(nextFilters);
              setSelectedUserRowKeys([]);
            }}
            accountStatusFilter={userAccountStatusFilter}
            onAccountStatusFilterChange={(nextStatus) => {
              setUserAccountStatusFilter(nextStatus);
              setSelectedUserRowKeys([]);
            }}
            baronOrgColumnLabel="대표 Baron 조직"
            includeMissingExternalKey={includeUserMissingExternalKey}
            onIncludeMissingExternalKeyChange={(checked) => {
              setIncludeUserMissingExternalKey(checked);
              setSelectedUserRowKeys([]);
            }}
            visibleColumns={userVisibleColumns}
            onVisibleColumnsChange={setUserVisibleColumns}
            passwordManageTenantId={overview?.config.adminTenantId}
            actionLabel="선택 구성원 WORKS에 생성"
            actionDisabled={isCreatingUsers || createSelectedMutation.isPending}
            updateActionLabel="선택 구성원 업데이트 적용"
            onCreateSelected={(ids, initialPassword) =>
              createSelectedMutation.mutateAsync({
                resourceKind: "users",
                ids,
                initialPassword,
              })
            }
            onUpdateSelected={(ids) =>
              createSelectedMutation.mutate({
                resourceKind: "users",
                ids,
              })
            }
            requireInitialPassword
          />
          <Card data-testid="worksmobile-users-single-sync">
            <CardContent className="flex flex-col gap-3 p-4 md:flex-row md:items-center md:justify-between">
              <div className="min-w-0">
                <CardTitle className="text-base">사용자 단건 동기화</CardTitle>
                <CardDescription className="mt-1">
                  Baron 사용자 UUID 기준으로 구성원 sync 작업을 생성합니다.
                </CardDescription>
              </div>
              <div className="flex w-full gap-2 md:w-auto">
                <Input
                  className="md:w-80"
                  value={userId}
                  onChange={(event) => setUserId(event.target.value)}
                  placeholder="Kratos user UUID"
                />
                <Button
                  type="button"
                  className="shrink-0"
                  onClick={() => userSyncMutation.mutate()}
                  disabled={!userId.trim() || userSyncMutation.isPending}
                >
                  {t("ui.admin.tenants.worksmobile.sync_user", "구성원 Sync")}
                </Button>
              </div>
            </CardContent>
          </Card>
        </div>
      ) : null}

      {activeTab === "groups" ? (
        <div className="space-y-4">
          <ComparisonSummary
            title={t(
              "ui.admin.tenants.worksmobile.compare_groups",
              "조직/그룹",
            )}
            summary={groupSummary}
          />
          <ComparisonTable
            title={t(
              "ui.admin.tenants.worksmobile.compare_groups",
              "조직/그룹",
            )}
            rows={filteredComparisonGroups}
            totalRows={comparisonQuery.data?.groups.length ?? 0}
            loading={comparisonQuery.isLoading}
            selectedKeys={selectedGroupRowKeys}
            onSelectedKeysChange={setSelectedGroupRowKeys}
            search={groupSearch}
            onSearchChange={(nextSearch) => {
              setGroupSearch(nextSearch);
              setSelectedGroupRowKeys([]);
            }}
            searchPlaceholder="조직 이름 또는 UUID 검색"
            filters={groupFilters}
            onFiltersChange={(nextFilters) => {
              setGroupFilters(nextFilters);
              setSelectedGroupRowKeys([]);
            }}
            baronOrgColumnLabel="상위 Baron 조직"
            includeMissingExternalKey={includeGroupMissingExternalKey}
            onIncludeMissingExternalKeyChange={(checked) => {
              setIncludeGroupMissingExternalKey(checked);
              setSelectedGroupRowKeys([]);
            }}
            visibleColumns={groupVisibleColumns}
            onVisibleColumnsChange={setGroupVisibleColumns}
            passwordManageTenantId={undefined}
            showBaronIdColumn={false}
            showManageColumn={false}
            actionLabel="선택 조직 동기화"
            actionDisabled={isSyncingGroups}
            onCreateSelected={(ids) =>
              syncSelectedOrgUnitsMutation.mutate({
                baronIds: ids,
                worksmobileIds: [],
              })
            }
            onRunSelected={(baronIds, worksmobileIds) =>
              syncSelectedOrgUnitsMutation.mutate({
                baronIds,
                worksmobileIds,
              })
            }
          />
          <Card data-testid="worksmobile-groups-single-sync">
            <CardContent className="flex flex-col gap-3 p-4 md:flex-row md:items-center md:justify-between">
              <div className="min-w-0">
                <CardTitle className="text-base">조직 단건 동기화</CardTitle>
                <CardDescription className="mt-1">
                  Baron 조직 UUID 기준으로 조직 sync 작업을 생성합니다.
                </CardDescription>
              </div>
              <div className="flex w-full gap-2 md:w-auto">
                <Input
                  className="md:w-80"
                  value={orgUnitId}
                  onChange={(event) => setOrgUnitId(event.target.value)}
                  placeholder="orgUnit tenant UUID"
                />
                <Button
                  type="button"
                  className="shrink-0"
                  onClick={() => orgUnitSyncMutation.mutate()}
                  disabled={!orgUnitId.trim() || orgUnitSyncMutation.isPending}
                >
                  {t("ui.admin.tenants.worksmobile.sync_orgunit", "조직 Sync")}
                </Button>
              </div>
            </CardContent>
          </Card>
        </div>
      ) : null}
    </div>
  );
}

const worksmobileComparisonColumnOptions: Array<{
  key: WorksmobileComparisonColumnKey;
  label: string;
}> = [
  { key: "status", label: "상태" },
  { key: "baronId", label: "Baron ID" },
  { key: "baron", label: "Baron" },
  { key: "baronOrg", label: "Baron 조직" },
  { key: "externalKey", label: "external_key" },
  { key: "worksmobileDomain", label: "WORKS 도메인" },
  { key: "worksmobile", label: "WORKS" },
  { key: "worksmobileOrg", label: "WORKS 조직 매칭" },
  { key: "manage", label: "관리" },
];

const WORKSMOBILE_ROW_ESTIMATED_HEIGHT = 88;
const WORKSMOBILE_ROW_OVERSCAN = 8;
const WORKSMOBILE_TABLE_VIEWPORT_ESTIMATED_HEIGHT = 520;
const worksmobileComparisonColumnWidths: Record<
  WorksmobileComparisonColumnKey,
  number
> = {
  status: 160,
  baronId: 176,
  baron: 220,
  baronOrg: 220,
  externalKey: 180,
  worksmobileDomain: 160,
  worksmobileId: 176,
  worksmobile: 220,
  worksmobileOrg: 320,
  manage: 112,
};
const worksmobileComparisonTableHeadClassName =
  "h-12 whitespace-nowrap px-0 align-middle";
const worksmobileComparisonTableHeadContentClassName =
  "flex h-full items-center px-4";
const worksmobileComparisonTableHeadCenterContentClassName = `${worksmobileComparisonTableHeadContentClassName} justify-center`;

function getDefaultGroupWorksmobileComparisonColumns(): WorksmobileComparisonColumnVisibility {
  return {
    ...getDefaultWorksmobileComparisonColumns(),
    manage: false,
  };
}

function getErrorMessage(error: unknown) {
  const responseData = (error as { response?: { data?: unknown } })?.response
    ?.data;
  if (typeof responseData === "string") {
    return responseData;
  }
  if (responseData && typeof responseData === "object") {
    const data = responseData as { error?: unknown; message?: unknown };
    if (typeof data.error === "string") {
      return data.error;
    }
    if (typeof data.message === "string") {
      return data.message;
    }
  }
  if (error instanceof Error) {
    return error.message;
  }
  return String(error);
}

function getWorksmobileComparisonStatusVariant(status: string) {
  if (status === "matched") {
    return "success";
  }
  if (status === "needs_update") {
    return "warning";
  }
  if (status === "missing_external_key") {
    return "warning";
  }
  return "secondary";
}

function ComparisonSummary({
  title,
  summary,
}: {
  title: string;
  summary: WorksmobileComparisonSummary;
}) {
  return (
    <div className="rounded-md border p-3">
      <div className="mb-3 flex items-center justify-between gap-3">
        <span className="text-sm font-medium">{title}</span>
        <Badge variant="outline">{summary.total}</Badge>
      </div>
      <div className="grid grid-cols-2 gap-2 text-xs">
        <div className="flex items-center justify-between">
          <span className="text-muted-foreground">WORKS 없음</span>
          <span className="font-mono">{summary.missingInWorksmobile}</span>
        </div>
        <div className="flex items-center justify-between">
          <span className="text-muted-foreground">Baron 없음</span>
          <span className="font-mono">{summary.missingInBaron}</span>
        </div>
        <div className="flex items-center justify-between">
          <span className="text-muted-foreground">업데이트 필요</span>
          <span className="font-mono">{summary.needsUpdate}</span>
        </div>
        <div className="flex items-center justify-between">
          <span className="text-muted-foreground">ex_key 없음</span>
          <span className="font-mono">{summary.missingExternalKey}</span>
        </div>
        <div className="flex items-center justify-between">
          <span className="text-muted-foreground">일치</span>
          <span className="font-mono">{summary.matched}</span>
        </div>
      </div>
    </div>
  );
}

function ComparisonFilterButtons<T extends string>({
  options,
  selected,
  onToggle,
  detailFor,
  detail,
}: {
  options: Array<{ value: T; label: string }>;
  selected?: T[];
  onToggle: (value: T) => void;
  detailFor?: T;
  detail?: React.ReactNode;
}) {
  const [openDetailFor, setOpenDetailFor] = React.useState<T | null>(null);
  const rootRef = React.useRef<HTMLDivElement | null>(null);

  React.useEffect(() => {
    if (!openDetailFor) {
      return;
    }
    const onPointerDown = (event: PointerEvent) => {
      if (!rootRef.current?.contains(event.target as Node)) {
        setOpenDetailFor(null);
      }
    };
    document.addEventListener("pointerdown", onPointerDown);
    return () => document.removeEventListener("pointerdown", onPointerDown);
  }, [openDetailFor]);

  if (!selected) {
    return null;
  }
  return (
    <div ref={rootRef} className="flex flex-wrap items-center gap-2">
      {options.map((option) => {
        const isSelected = selected.includes(option.value);
        const hasDetail = detailFor === option.value && Boolean(detail);
        return (
          <div key={option.value} className="relative">
            <Button
              type="button"
              size="sm"
              variant={isSelected ? "default" : "outline"}
              aria-pressed={isSelected}
              aria-expanded={hasDetail && openDetailFor === option.value}
              onClick={() => {
                if (hasDetail && isSelected && openDetailFor !== option.value) {
                  setOpenDetailFor(option.value);
                  return;
                }
                onToggle(option.value);
                if (hasDetail) {
                  setOpenDetailFor(isSelected ? null : option.value);
                }
              }}
            >
              {option.label}
            </Button>
            {hasDetail && isSelected && openDetailFor === option.value && (
              <div className="absolute left-0 top-[calc(100%+0.25rem)] z-30 w-52 rounded-md border bg-background p-3 shadow-lg">
                {detail}
              </div>
            )}
          </div>
        );
      })}
    </div>
  );
}

function ComparisonTable({
  title,
  rows,
  totalRows,
  loading,
  selectedKeys,
  onSelectedKeysChange,
  search,
  onSearchChange,
  searchPlaceholder = "이름 또는 UUID 검색",
  filters,
  onFiltersChange,
  accountStatusFilter,
  onAccountStatusFilterChange,
  baronOrgColumnLabel = "Baron 조직",
  includeMissingExternalKey,
  onIncludeMissingExternalKeyChange,
  visibleColumns,
  onVisibleColumnsChange,
  passwordManageTenantId,
  showBaronIdColumn = true,
  showManageColumn = true,
  actionLabel,
  updateActionLabel,
  actionDisabled,
  onCreateSelected,
  onUpdateSelected,
  onRunSelected,
  deleteActionLabel,
  deleteActionDisabled = false,
  onDeleteSelected,
  requireInitialPassword = false,
}: {
  title: string;
  rows: WorksmobileComparisonItem[];
  totalRows: number;
  loading: boolean;
  selectedKeys: string[];
  onSelectedKeysChange: (ids: string[]) => void;
  search: string;
  onSearchChange: (value: string) => void;
  searchPlaceholder?: string;
  filters?: WorksmobileComparisonFilter[];
  onFiltersChange?: (filters: WorksmobileComparisonFilter[]) => void;
  accountStatusFilter?: WorksmobileAccountStatusFilter;
  onAccountStatusFilterChange?: (
    status: WorksmobileAccountStatusFilter,
  ) => void;
  baronOrgColumnLabel?: string;
  includeMissingExternalKey?: boolean;
  onIncludeMissingExternalKeyChange?: (checked: boolean) => void;
  visibleColumns: WorksmobileComparisonColumnVisibility;
  onVisibleColumnsChange: React.Dispatch<
    React.SetStateAction<WorksmobileComparisonColumnVisibility>
  >;
  passwordManageTenantId?: string;
  showBaronIdColumn?: boolean;
  showManageColumn?: boolean;
  actionLabel: string;
  updateActionLabel?: string;
  actionDisabled: boolean;
  onCreateSelected: (ids: string[], initialPassword?: string) => unknown;
  onUpdateSelected?: (ids: string[]) => void;
  onRunSelected?: (actionIds: string[], deleteIds: string[]) => void;
  deleteActionLabel?: string;
  deleteActionDisabled?: boolean;
  onDeleteSelected?: (ids: string[]) => void;
  requireInitialPassword?: boolean;
}) {
  const [columnSettingsOpen, setColumnSettingsOpen] = React.useState(false);
  const [initialPasswordOpen, setInitialPasswordOpen] = React.useState(false);
  const [initialPassword, setInitialPassword] = React.useState("");
  const [pendingInitialPasswordIds, setPendingInitialPasswordIds] =
    React.useState<string[]>([]);
  const tableViewportRef = React.useRef<HTMLDivElement>(null);
  const selectableKeys = rows
    .filter(canSelectWorksmobileRow)
    .map(getWorksmobileRowSelectionKey)
    .filter(Boolean);
  const selectedActionIds = getWorksmobileSelectedActionIds(rows, selectedKeys);
  const selectedCreateUserIds = getWorksmobileSelectedCreateUserIds(
    rows,
    selectedKeys,
  );
  const selectedUpdateUserIds = getWorksmobileSelectedUpdateUserIds(
    rows,
    selectedKeys,
  );
  const selectedDeleteIds = getWorksmobileSelectedWorksOnlyOrgUnitIds(
    rows,
    selectedKeys,
  );
  const canRunDeleteAction = Boolean(onDeleteSelected && deleteActionLabel);
  const canRunCombinedAction = Boolean(onRunSelected);
  const shouldRunDeleteAction =
    selectedActionIds.length === 0 &&
    selectedDeleteIds.length > 0 &&
    canRunDeleteAction;
  const canRunUserUpdateAction = Boolean(onUpdateSelected);
  const selectedActionLabel = shouldRunDeleteAction
    ? deleteActionLabel
    : actionLabel;
  const selectedActionVariant = shouldRunDeleteAction
    ? "destructive"
    : "default";
  const selectedActionDisabled =
    (canRunCombinedAction
      ? selectedActionIds.length === 0 && selectedDeleteIds.length === 0
      : shouldRunDeleteAction
        ? selectedDeleteIds.length === 0 || deleteActionDisabled
        : requireInitialPassword
          ? selectedCreateUserIds.length === 0
          : selectedActionIds.length === 0) || actionDisabled;
  const updateActionDisabled =
    selectedUpdateUserIds.length === 0 || actionDisabled;
  const allSelectableSelected =
    selectableKeys.length > 0 &&
    selectableKeys.every((key) => selectedKeys.includes(key));
  const columnOptions = worksmobileComparisonColumnOptions.filter(
    (column) =>
      (showManageColumn || column.key !== "manage") &&
      (showBaronIdColumn || column.key !== "baronId"),
  );
  const visibleColumnCount = columnOptions.filter(
    (column) => visibleColumns[column.key] !== false,
  ).length;
  const tableColSpan = visibleColumnCount + 1;
  const tableColumnWidths = React.useMemo(() => {
    const widths = [40];
    for (const column of columnOptions) {
      if (visibleColumns[column.key] !== false) {
        widths.push(worksmobileComparisonColumnWidths[column.key]);
      }
    }
    return widths;
  }, [columnOptions, visibleColumns]);
  const tableGridTemplateColumns = React.useMemo(
    () => tableColumnWidths.map((width) => `${width}px`).join(" "),
    [tableColumnWidths],
  );
  const tableMinWidth = React.useMemo(
    () => tableColumnWidths.reduce((sum, width) => sum + width, 0),
    [tableColumnWidths],
  );
  const rowVirtualizer = useVirtualizer({
    count: rows.length,
    getScrollElement: () => tableViewportRef.current,
    estimateSize: () => WORKSMOBILE_ROW_ESTIMATED_HEIGHT,
    measureElement: (element) =>
      element.getBoundingClientRect().height ||
      WORKSMOBILE_ROW_ESTIMATED_HEIGHT,
    overscan: WORKSMOBILE_ROW_OVERSCAN,
    initialRect: {
      width: tableMinWidth,
      height: WORKSMOBILE_TABLE_VIEWPORT_ESTIMATED_HEIGHT,
    },
  });
  const isTestEnv =
    typeof process !== "undefined" && process.env.NODE_ENV === "test";

  const virtualRows = isTestEnv
    ? rows.map((_, index) => ({
        index,
        start: index * WORKSMOBILE_ROW_ESTIMATED_HEIGHT,
        size: WORKSMOBILE_ROW_ESTIMATED_HEIGHT,
        key: index,
        lanes: 0,
      }))
    : rowVirtualizer.getVirtualItems();
  const shouldVirtualizeRows = !loading && rows.length > 0;

  const toggleAll = (checked: boolean | "indeterminate") => {
    onSelectedKeysChange(checked === true ? selectableKeys : []);
  };

  const toggleRow = (
    row: WorksmobileComparisonItem,
    checked: boolean | "indeterminate",
  ) => {
    const key = getWorksmobileRowSelectionKey(row);
    if (!key) {
      return;
    }
    if (checked === true) {
      onSelectedKeysChange([...new Set([...selectedKeys, key])]);
      return;
    }
    onSelectedKeysChange(
      selectedKeys.filter((selectedKey) => selectedKey !== key),
    );
  };

  const openPasswordManage = (row: WorksmobileComparisonItem) => {
    const url = buildWorksmobilePasswordManageUrl({
      tenantId: passwordManageTenantId,
      domainId: row.worksmobileDomainId,
      userIdNo: row.worksmobileId,
    });
    if (!url) return;
    window.open(url, "_blank", "noopener,noreferrer");
  };

  const toggleColumn = (key: WorksmobileComparisonColumnKey) => {
    onVisibleColumnsChange((current) => ({
      ...current,
      [key]: current[key] === false,
    }));
  };

  const isColumnVisible = (key: WorksmobileComparisonColumnKey) =>
    visibleColumns[key] !== false;
  const columnLabel = (column: {
    key: WorksmobileComparisonColumnKey;
    label: string;
  }) => (column.key === "baronOrg" ? baronOrgColumnLabel : column.label);

  const toggleFilter = (filter: WorksmobileComparisonFilter) => {
    if (!filters || !onFiltersChange) {
      return;
    }
    onFiltersChange(
      filters.includes(filter)
        ? filters.filter((value) => value !== filter)
        : [...filters, filter],
    );
  };

  const runSelectedAction = () => {
    if (onRunSelected) {
      onRunSelected(selectedActionIds, selectedDeleteIds);
      return;
    }
    if (shouldRunDeleteAction && onDeleteSelected) {
      onDeleteSelected(selectedDeleteIds);
      return;
    }
    if (requireInitialPassword) {
      setPendingInitialPasswordIds(selectedCreateUserIds);
      setInitialPassword("");
      setInitialPasswordOpen(true);
      return;
    }
    onCreateSelected(selectedActionIds);
  };

  const runUpdateAction = () => {
    if (!onUpdateSelected || selectedUpdateUserIds.length === 0) {
      return;
    }
    onUpdateSelected(selectedUpdateUserIds);
  };

  const confirmInitialPassword = async () => {
    const password = initialPassword.trim();
    if (!password) {
      toast.error("WORKS 초기 비밀번호를 입력해 주세요.");
      return;
    }
    try {
      await onCreateSelected(pendingInitialPasswordIds, password);
    } catch {
      return;
    }
    setInitialPasswordOpen(false);
    setInitialPassword("");
    setPendingInitialPasswordIds([]);
  };

  return (
    <div className="min-w-0 space-y-2">
      <div className="flex flex-col gap-3 xl:flex-row xl:items-center xl:justify-between">
        <div className="flex min-w-0 flex-1 flex-wrap items-center gap-3">
          <h4 className="text-lg font-semibold leading-none">{title}</h4>
          <Badge
            variant="outline"
            data-testid={`worksmobile-${title}-row-count`}
            className="font-mono"
          >
            표시 {rows.length} / 전체 {totalRows}
          </Badge>
          <Input
            type="search"
            value={search}
            onChange={(event) => onSearchChange(event.target.value)}
            placeholder={searchPlaceholder}
            aria-label={`${title} 검색`}
            className="h-9 w-56"
          />
          <ComparisonFilterButtons
            options={comparisonFilterOptions}
            selected={filters}
            onToggle={toggleFilter}
            detailFor={"works_only"}
            detail={
              onIncludeMissingExternalKeyChange ? (
                <label className="flex cursor-pointer items-center gap-2 text-sm text-muted-foreground">
                  <Checkbox
                    checked={includeMissingExternalKey === true}
                    onCheckedChange={(checked) =>
                      onIncludeMissingExternalKeyChange?.(checked === true)
                    }
                  />
                  <span>ex_key 없음만 보기</span>
                </label>
              ) : null
            }
          />
          {accountStatusFilter && onAccountStatusFilterChange ? (
            <div
              className="flex flex-wrap items-center gap-2"
              role="tablist"
              aria-label="WORKS 계정 상태"
            >
              {worksmobileAccountStatusFilterOptions.map((option) => (
                <Button
                  key={option.value}
                  type="button"
                  role="tab"
                  size="sm"
                  variant={
                    accountStatusFilter === option.value ? "default" : "outline"
                  }
                  aria-selected={accountStatusFilter === option.value}
                  onClick={() => onAccountStatusFilterChange(option.value)}
                >
                  {option.label}
                </Button>
              ))}
            </div>
          ) : null}
        </div>
        <div className="flex shrink-0 flex-wrap items-center justify-start gap-2 xl:justify-end">
          <Dialog
            open={columnSettingsOpen}
            onOpenChange={setColumnSettingsOpen}
          >
            <DialogTrigger asChild>
              <Button type="button" variant="outline" size="sm">
                <Settings2 size={16} />
                컬럼 설정
              </Button>
            </DialogTrigger>
            <DialogContent className="max-w-md">
              <DialogHeader>
                <DialogTitle>{title} 컬럼 설정</DialogTitle>
                <DialogDescription>
                  이 테이블에 표시할 비교 컬럼을 선택하세요.
                </DialogDescription>
              </DialogHeader>
              <div className="grid gap-2 py-2">
                {columnOptions.map((column) => (
                  <label
                    key={column.key}
                    className="flex cursor-pointer items-center gap-3 rounded-md p-2 hover:bg-muted/50"
                  >
                    <input
                      name={`worksmobile-column-${column.key}`}
                      type="checkbox"
                      className="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
                      checked={isColumnVisible(column.key)}
                      onChange={() => toggleColumn(column.key)}
                    />
                    <span className="text-sm font-medium">
                      {columnLabel(column)}
                    </span>
                  </label>
                ))}
              </div>
              <DialogFooter>
                <Button
                  type="button"
                  variant="secondary"
                  onClick={() => setColumnSettingsOpen(false)}
                >
                  닫기
                </Button>
              </DialogFooter>
            </DialogContent>
          </Dialog>
          <Button
            type="button"
            size="sm"
            variant={selectedActionVariant}
            onClick={runSelectedAction}
            disabled={selectedActionDisabled}
          >
            {selectedActionLabel}
          </Button>
          {canRunUserUpdateAction && (
            <Button
              type="button"
              size="sm"
              variant="outline"
              onClick={runUpdateAction}
              disabled={updateActionDisabled}
            >
              {updateActionLabel || "선택 구성원 업데이트 적용"}
            </Button>
          )}
          <Dialog
            open={initialPasswordOpen}
            onOpenChange={(open) => {
              setInitialPasswordOpen(open);
              if (!open) {
                setInitialPassword("");
                setPendingInitialPasswordIds([]);
              }
            }}
          >
            <DialogContent className="max-w-md">
              <DialogHeader>
                <DialogTitle>WORKS 초기 비밀번호</DialogTitle>
                <DialogDescription>
                  선택한 구성원을 WORKS에 신규 생성할 때 사용할 공통 초기
                  비밀번호를 입력하세요.
                </DialogDescription>
              </DialogHeader>
              <div className="space-y-2 py-2">
                <label
                  className="text-sm font-medium"
                  htmlFor="worksmobile-initial-password"
                >
                  초기 비밀번호
                </label>
                <Input
                  id="worksmobile-initial-password"
                  type="password"
                  value={initialPassword}
                  onChange={(event) => setInitialPassword(event.target.value)}
                  autoComplete="new-password"
                />
              </div>
              <DialogFooter>
                <Button
                  type="button"
                  variant="secondary"
                  onClick={() => setInitialPasswordOpen(false)}
                >
                  취소
                </Button>
                <Button
                  type="button"
                  onClick={confirmInitialPassword}
                  disabled={actionDisabled}
                >
                  생성 작업 등록
                </Button>
              </DialogFooter>
            </DialogContent>
          </Dialog>
        </div>
      </div>
      <div
        ref={tableViewportRef}
        className="max-h-[560px] w-full max-w-full overflow-auto rounded-md border"
        data-testid={`worksmobile-${title}-virtual-viewport`}
      >
        <Table className="min-w-max" style={{ minWidth: tableMinWidth }}>
          <TableHeader>
            <TableRow
              style={{
                display: "grid",
                gridTemplateColumns: tableGridTemplateColumns,
                minWidth: tableMinWidth,
              }}
            >
              <TableHead className={worksmobileComparisonTableHeadClassName}>
                <div
                  className={
                    worksmobileComparisonTableHeadCenterContentClassName
                  }
                >
                  <Checkbox
                    aria-label={`${title} 전체 선택`}
                    checked={allSelectableSelected}
                    disabled={selectableKeys.length === 0}
                    onCheckedChange={toggleAll}
                  />
                </div>
              </TableHead>
              {isColumnVisible("status") && (
                <TableHead className={worksmobileComparisonTableHeadClassName}>
                  <div
                    className={worksmobileComparisonTableHeadContentClassName}
                  >
                    상태
                  </div>
                </TableHead>
              )}
              {showBaronIdColumn && isColumnVisible("baronId") && (
                <TableHead className={worksmobileComparisonTableHeadClassName}>
                  <div
                    className={worksmobileComparisonTableHeadContentClassName}
                  >
                    Baron ID
                  </div>
                </TableHead>
              )}
              {isColumnVisible("baron") && (
                <TableHead className={worksmobileComparisonTableHeadClassName}>
                  <div
                    className={worksmobileComparisonTableHeadContentClassName}
                  >
                    Baron
                  </div>
                </TableHead>
              )}
              {isColumnVisible("baronOrg") && (
                <TableHead className={worksmobileComparisonTableHeadClassName}>
                  <div
                    className={worksmobileComparisonTableHeadContentClassName}
                  >
                    {baronOrgColumnLabel}
                  </div>
                </TableHead>
              )}
              {isColumnVisible("externalKey") && (
                <TableHead className={worksmobileComparisonTableHeadClassName}>
                  <div
                    className={worksmobileComparisonTableHeadContentClassName}
                  >
                    external_key
                  </div>
                </TableHead>
              )}
              {isColumnVisible("worksmobileDomain") && (
                <TableHead className={worksmobileComparisonTableHeadClassName}>
                  <div
                    className={worksmobileComparisonTableHeadContentClassName}
                  >
                    WORKS 도메인
                  </div>
                </TableHead>
              )}
              {isColumnVisible("worksmobile") && (
                <TableHead className={worksmobileComparisonTableHeadClassName}>
                  <div
                    className={worksmobileComparisonTableHeadContentClassName}
                  >
                    WORKS
                  </div>
                </TableHead>
              )}
              {isColumnVisible("worksmobileOrg") && (
                <TableHead className={worksmobileComparisonTableHeadClassName}>
                  <div
                    className={worksmobileComparisonTableHeadContentClassName}
                  >
                    WORKS 조직 매칭
                  </div>
                </TableHead>
              )}
              {showManageColumn && isColumnVisible("manage") && (
                <TableHead className={worksmobileComparisonTableHeadClassName}>
                  <div
                    className={worksmobileComparisonTableHeadContentClassName}
                  >
                    관리
                  </div>
                </TableHead>
              )}
            </TableRow>
          </TableHeader>
          <TableBody
            data-testid={`worksmobile-${title}-virtual-body`}
            style={
              shouldVirtualizeRows
                ? {
                    display: "grid",
                    height: `${
                      isTestEnv
                        ? rows.length * WORKSMOBILE_ROW_ESTIMATED_HEIGHT
                        : rowVirtualizer.getTotalSize()
                    }px`,
                    minWidth: tableMinWidth,
                    position: "relative",
                  }
                : undefined
            }
          >
            {loading && (
              <TableRow
                style={{
                  display: "grid",
                  gridTemplateColumns: tableGridTemplateColumns,
                  minWidth: tableMinWidth,
                }}
              >
                <TableCell
                  colSpan={tableColSpan}
                  className="text-muted-foreground"
                  style={{ gridColumn: "1 / -1" }}
                >
                  불러오는 중...
                </TableCell>
              </TableRow>
            )}
            {!loading && rows.length === 0 && (
              <TableRow
                style={{
                  display: "grid",
                  gridTemplateColumns: tableGridTemplateColumns,
                  minWidth: tableMinWidth,
                }}
              >
                <TableCell
                  colSpan={tableColSpan}
                  className="text-muted-foreground"
                  style={{ gridColumn: "1 / -1" }}
                >
                  표시할 차이가 없습니다.
                </TableCell>
              </TableRow>
            )}
            {shouldVirtualizeRows &&
              virtualRows.map((virtualRow) => {
                const row = rows[virtualRow.index];
                if (!row) {
                  return null;
                }
                const rowKey = `${row.status}:${row.baronId ?? row.worksmobileId ?? row.externalKey}`;

                return (
                  <TableRow
                    key={rowKey}
                    data-index={virtualRow.index}
                    ref={rowVirtualizer.measureElement}
                    style={{
                      display: "grid",
                      gridTemplateColumns: tableGridTemplateColumns,
                      minWidth: tableMinWidth,
                      position: "absolute",
                      transform: `translateY(${virtualRow.start}px)`,
                      width: "100%",
                    }}
                  >
                    <TableCell className="whitespace-nowrap">
                      <Checkbox
                        aria-label={`${row.baronName ?? row.baronId ?? row.worksmobileName ?? row.worksmobileId ?? "row"} 선택`}
                        checked={selectedKeys.includes(
                          getWorksmobileRowSelectionKey(row),
                        )}
                        disabled={!canSelectWorksmobileRow(row)}
                        onCheckedChange={(checked) => toggleRow(row, checked)}
                      />
                    </TableCell>
                    {isColumnVisible("status") && (
                      <TableCell className="whitespace-nowrap">
                        <Badge
                          className="whitespace-nowrap"
                          variant={getWorksmobileComparisonStatusVariant(
                            row.status,
                          )}
                        >
                          {getWorksmobileComparisonStatusLabel(row.status)}
                        </Badge>
                        {row.worksmobileAccountStatus && (
                          <div className="mt-1">
                            <Badge variant="outline">
                              WORKS {row.worksmobileAccountStatus}
                            </Badge>
                          </div>
                        )}
                        {formatWorksmobileUpdateDetails(row).map((detail) => (
                          <div
                            key={detail}
                            className="mt-1 max-w-56 whitespace-normal text-xs text-muted-foreground"
                          >
                            {detail}
                          </div>
                        ))}
                      </TableCell>
                    )}
                    {showBaronIdColumn && isColumnVisible("baronId") && (
                      <TableCell className="font-mono text-xs">
                        {row.baronId ?? "-"}
                      </TableCell>
                    )}
                    {isColumnVisible("baron") && (
                      <TableCell>
                        <ComparisonBaronCell
                          name={row.baronName}
                          email={row.baronEmail}
                          slug={row.baronSlug}
                          id={row.baronId}
                        />
                      </TableCell>
                    )}
                    {isColumnVisible("baronOrg") && (
                      <TableCell>
                        <ComparisonOrgCell
                          name={
                            row.resourceType === "GROUP"
                              ? row.baronParentName
                              : row.baronPrimaryOrgName
                          }
                          id={
                            row.resourceType === "GROUP"
                              ? row.baronParentId
                              : row.baronPrimaryOrgId
                          }
                          slug={
                            row.resourceType === "GROUP"
                              ? row.baronParentSlug
                              : row.baronPrimaryOrgSlug
                          }
                        />
                      </TableCell>
                    )}
                    {isColumnVisible("externalKey") && (
                      <TableCell className="font-mono text-xs">
                        {row.externalKey ?? "-"}
                      </TableCell>
                    )}
                    {isColumnVisible("worksmobileDomain") && (
                      <TableCell>
                        <ComparisonDomainCell
                          name={row.worksmobileDomainName}
                          id={row.worksmobileDomainId}
                        />
                      </TableCell>
                    )}
                    {isColumnVisible("worksmobile") && (
                      <TableCell>
                        <ComparisonWorksmobileCell
                          name={formatWorksmobilePersonName(row)}
                          email={row.worksmobileEmail}
                          id={row.worksmobileId}
                        />
                      </TableCell>
                    )}
                    {isColumnVisible("worksmobileOrg") && (
                      <TableCell>
                        {row.resourceType === "USER" ? (
                          <ComparisonUserMembershipCell row={row} />
                        ) : (
                          <ComparisonOrgCell
                            name={getWorksmobileParentName(row)}
                            email={getWorksmobileParentEmail(row)}
                            id={row.worksmobileParentId}
                            details={formatWorksmobileParentOrgDetails(row)}
                            missingLabel="상위 Works 조직 정보 없음"
                          />
                        )}
                      </TableCell>
                    )}
                    {showManageColumn && isColumnVisible("manage") && (
                      <TableCell className="whitespace-nowrap">
                        {row.resourceType === "USER" && (
                          <div className="flex items-center gap-1">
                            <Button
                              type="button"
                              variant="ghost"
                              size="sm"
                              aria-label={`${row.worksmobileName ?? row.baronName ?? row.worksmobileId ?? "WORKS user"} 비밀번호 관리`}
                              disabled={
                                !canOpenWorksmobilePasswordManage(
                                  row,
                                  passwordManageTenantId,
                                )
                              }
                              onClick={() => openPasswordManage(row)}
                            >
                              <KeyRound size={16} />
                            </Button>
                          </div>
                        )}
                      </TableCell>
                    )}
                  </TableRow>
                );
              })}
          </TableBody>
        </Table>
      </div>
    </div>
  );
}

function ComparisonDomainCell({ name, id }: { name?: string; id?: number }) {
  if (!name && !id) {
    return <span className="text-muted-foreground">-</span>;
  }
  return (
    <div className="space-y-1">
      <div>{name ?? "-"}</div>
      <div className="font-mono text-xs text-muted-foreground">{id ?? ""}</div>
    </div>
  );
}

function ComparisonBaronCell({
  name,
  email,
  slug,
  id,
}: {
  name?: string;
  email?: string;
  slug?: string;
  id?: string;
}) {
  if (!name && !email && !slug && !id) {
    return <span className="text-muted-foreground">-</span>;
  }
  return (
    <div className="space-y-1">
      <div>{name ?? "-"}</div>
      {email && <div className="text-xs text-muted-foreground">{email}</div>}
      {slug && (
        <div className="font-mono text-xs text-muted-foreground">{slug}</div>
      )}
      {id && (
        <div className="font-mono text-xs text-muted-foreground">{id}</div>
      )}
    </div>
  );
}

function ComparisonWorksmobileCell({
  name,
  email,
  id,
}: {
  name?: string;
  email?: string;
  id?: string;
}) {
  if (!name && !email && !id) {
    return <span className="text-muted-foreground">-</span>;
  }
  return (
    <div className="space-y-1">
      <div>{name ?? "-"}</div>
      {email && <div className="text-xs text-muted-foreground">{email}</div>}
      {id && (
        <div className="font-mono text-xs text-muted-foreground">{id}</div>
      )}
    </div>
  );
}

function getWorksmobileParentName(row: WorksmobileComparisonItem) {
  if (row.worksmobileParentName) {
    return row.worksmobileParentName;
  }
  if (
    row.worksmobileParentId &&
    row.worksmobileParentId === row.baronParentWorksmobileId
  ) {
    return row.baronParentWorksmobileName;
  }
  return undefined;
}

function getWorksmobileParentEmail(row: WorksmobileComparisonItem) {
  if (row.worksmobileParentEmail) {
    return row.worksmobileParentEmail;
  }
  if (
    row.worksmobileParentId &&
    row.worksmobileParentId === row.baronParentWorksmobileId
  ) {
    return row.baronParentWorksmobileEmail;
  }
  return undefined;
}

function formatWorksmobileParentOrgDetails(row: WorksmobileComparisonItem) {
  const details: string[] = [];
  if (
    row.status === "matched" &&
    row.baronParentId &&
    row.baronParentWorksmobileId
  ) {
    if (!row.worksmobileParentId) {
      details.push("Baron 상위는 Works에 연결됨, 현재 Works 상위 없음");
    } else if (row.worksmobileParentId !== row.baronParentWorksmobileId) {
      details.push(
        `Baron 상위와 연결 불일치: ${row.baronParentWorksmobileName ?? row.baronParentWorksmobileId}`,
      );
    }
  }
  return details;
}

function ComparisonUserMembershipCell({
  row,
}: {
  row: WorksmobileComparisonItem;
}) {
  const membershipDetails = formatWorksmobileUserMembershipDetails(row);
  if (membershipDetails.length > 0) {
    return (
      <div className="space-y-1">
        {membershipDetails.map((detail) => (
          <div key={detail} className="text-xs leading-relaxed">
            {detail}
          </div>
        ))}
      </div>
    );
  }

  return (
    <ComparisonOrgCell
      name={row.worksmobilePrimaryOrgName}
      id={row.worksmobilePrimaryOrgId}
      details={formatWorksmobileOrgDetails(row)}
    />
  );
}

function ComparisonOrgCell({
  name,
  email,
  id,
  slug,
  details = [],
  missingLabel = "-",
}: {
  name?: string;
  email?: string;
  id?: string;
  slug?: string;
  details?: string[];
  missingLabel?: string;
}) {
  if (!name && !email && !id && !slug && details.length === 0) {
    return <span className="text-muted-foreground">{missingLabel}</span>;
  }
  return (
    <div className="space-y-1">
      <div>{name ?? "-"}</div>
      {email && <div className="text-xs text-muted-foreground">{email}</div>}
      {slug && (
        <div className="font-mono text-xs text-muted-foreground">{slug}</div>
      )}
      <div className="font-mono text-xs text-muted-foreground">{id ?? ""}</div>
      {details.length > 0 && (
        <div className="text-xs text-muted-foreground">
          {details.join(" · ")}
        </div>
      )}
    </div>
  );
}
