import { useState, useEffect, useRef, useMemo, type ReactNode } from "react";
import { useTranslation } from "react-i18next";
import { useLocation } from "wouter";
import {
  useCreateApp,
  useUpdateApp,
  useDeleteApp,
  useListAppPermissions,
  getListAppPermissionsQueryKey,
  useAddAppPermission,
  useRemoveAppPermission,
  usePreviewAppPermissionsImpact,
  useListServices,
  getListServicesQueryKey,
  useCreateService,
  useUpdateService,
  useDeleteService,
  useListUsers,
  getListUsersQueryKey,
  useCreateUser,
  useUpdateUser,
  useDeleteUser,
  useAddUserRole,
  useRemoveUserRole,
  useGetAppSettings,
  getGetAppSettingsQueryKey,
  useUpdateAppSettings,
  useGetAdminStats,
  getGetAdminStatsQueryKey,
  useGetAdminAppOpensByApp,
  getGetAdminAppOpensByAppQueryKey,
  getAdminAppOpensByApp,
  getRolePermissionAudit,
  useGetAdminAppOpensByUser,
  getGetAdminAppOpensByUserQueryKey,
  getAdminAppOpensByUser,
  useAdminIssueResetLink,
  useListGroups,
  getListGroupsQueryKey,
  useGetGroup,
  getGetGroupQueryKey,
  useCreateGroup,
  useUpdateGroup,
  useDeleteGroup,
  useListRoles,
  getListRolesQueryKey,
  useCreateRole,
  useUpdateRole,
  useDeleteRole,
  useListPermissions,
  getListPermissionsQueryKey,
  useGetRolePermissions,
  getGetRolePermissionsQueryKey,
  useGetRoleUsage,
  getGetRoleUsageQueryKey,
  useReplaceRolePermissions,
  usePreviewRolePermissionsImpact,
  useGetRolePermissionAudit,
  getGetRolePermissionAuditQueryKey,
  useGetUserPermissionAudit,
  getGetUserPermissionAuditQueryKey,
  getUserPermissionAudit,
  useGetGroupPermissionAudit,
  getGetGroupPermissionAuditQueryKey,
  getGroupPermissionAudit,
  useGetAppPermissionAudit,
  getGetAppPermissionAuditQueryKey,
  getAppPermissionAudit,
  useListAuditLogs,
  getListAuditLogsQueryKey,
  getExportAuditLogsCsvUrl,
  ApiError,
} from "@workspace/api-client-react";
import type {
  GroupDeletionConflict,
  AppDeletionConflict,
  ServiceDeletionConflict,
  UserDeletionConflict,
  App,
  Service,
  UserProfile,
  AppSettings,
  AdminStats,
  AdminAppOpensByApp,
  AdminAppOpensByUser,
  GetAdminStatsQueryError,
  GetAdminStatsParams,
  Group,
  ListUsersParams,
  ListAuditLogsParams,
  AuditLogEntry,
  RolePermissionsImpact,
  AppPermissionsImpact,
  GetRolePermissionAuditParams,
  RolePermissionAuditEntry,
  GetUserPermissionAuditParams,
  GetGroupPermissionAuditParams,
  GetAppPermissionAuditParams,
  PermissionAuditEntry,
  PermissionAuditEntryChangeKind,
  Permission,
} from "@workspace/api-client-react";
import { ListUsersStatus } from "@workspace/api-client-react";
import { useQueryClient, useQuery } from "@tanstack/react-query";
import { useAuth } from "@/contexts/AuthContext";
import { useUpload, type UploadResponse } from "@workspace/object-storage-web";
import { resolveServiceImageUrl } from "@/lib/image-url";
import { ArrowRight, ArrowLeft, Settings, Plus, Pencil, Trash2, Shield, Upload, Loader2, LayoutDashboard, Grid2X2, Coffee, Users as UsersIcon, Menu as MenuIcon, TrendingUp, TrendingDown, UserPlus, Activity, KeyRound, Copy, ChevronDown, ScrollText, Download } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { useToast } from "@/hooks/use-toast";
import { cn } from "@/lib/utils";
import { formatDate as formatDateUtil, formatWeekday as formatWeekdayUtil } from "@/lib/i18n-format";

const ADMIN_APPS_KEY = ["/api/admin/apps"] as const;

async function fetchAdminApps(): Promise<App[]> {
  const res = await fetch("/api/admin/apps", { credentials: "include" });
  if (!res.ok) throw new Error("Failed to fetch admin apps");
  return res.json();
}

type AdminSection = "dashboard" | "apps" | "services" | "users" | "groups" | "roles" | "audit-log" | "settings";

function LeaderboardAvatar({
  src,
  initial,
  alt,
}: {
  src: string | null;
  initial: string;
  alt: string;
}) {
  const [errored, setErrored] = useState(false);
  useEffect(() => {
    setErrored(false);
  }, [src]);
  const showImage = src && !errored;
  return (
    <div className="w-8 h-8 rounded-full shrink-0 bg-emerald-100/70 text-emerald-700 flex items-center justify-center text-xs font-semibold overflow-hidden">
      {showImage ? (
        <img
          src={src}
          alt={alt}
          onError={() => setErrored(true)}
          className="w-full h-full object-cover"
        />
      ) : (
        initial
      )}
    </div>
  );
}

type AppForm = {
  nameAr: string;
  nameEn: string;
  slug: string;
  iconName: string;
  route: string;
  color: string;
  sortOrder: number;
  // Only used when creating a new app — admins can pre-set the required
  // permissions so the app is never visible to everyone in the brief
  // window between insert and the follow-up gate. Existing apps keep
  // editing their gate through the AppPermissionsEditor below.
  permissionIds: number[];
};

type ServiceForm = {
  nameAr: string;
  nameEn: string;
  descriptionAr: string;
  descriptionEn: string;
  price: string;
  imageUrl: string;
  isAvailable: boolean;
};

function DeletionWarningDialog({
  title,
  items,
  warning,
  forceHint,
  emptyBody,
  confirmLabel,
  anywayLabel,
  showWarning,
  isPending,
  onCancel,
  onConfirm,
  testId,
  confirmTestId,
}: {
  title: string;
  items: string[];
  warning: string;
  forceHint: string;
  emptyBody: string;
  confirmLabel: string;
  anywayLabel: string;
  showWarning: boolean;
  isPending: boolean;
  onCancel: () => void;
  onConfirm: () => void;
  testId?: string;
  confirmTestId?: string;
}) {
  const { t } = useTranslation();
  return (
    <div className="fixed inset-0 bg-slate-900/30 backdrop-blur-sm z-50 flex items-center justify-center p-4">
      <div
        className="glass-panel rounded-3xl p-6 w-full max-w-sm space-y-3"
        data-testid={testId}
      >
        <h2 className="font-semibold text-foreground">{title}</h2>
        {showWarning ? (
          <>
            <p className="text-sm text-destructive">{warning}</p>
            {items.length > 0 && (
              <ul className="text-sm text-foreground space-y-1 ps-4 list-disc">
                {items.map((item, idx) => (
                  <li key={idx}>{item}</li>
                ))}
              </ul>
            )}
            <p className="text-xs text-muted-foreground">{forceHint}</p>
          </>
        ) : (
          <p className="text-sm text-muted-foreground">{emptyBody}</p>
        )}
        <div className="flex gap-2 pt-2">
          <Button
            variant="outline"
            className="flex-1"
            onClick={onCancel}
            disabled={isPending}
          >
            {t("common.cancel")}
          </Button>
          <Button
            className="flex-1"
            variant="destructive"
            disabled={isPending}
            onClick={onConfirm}
            data-testid={confirmTestId}
          >
            {showWarning ? anywayLabel : confirmLabel}
          </Button>
        </div>
      </div>
    </div>
  );
}

// Local-state permission picker used by the "Add app" dialog. The full
// AppPermissionsEditor below talks to /apps/:id/permissions, which can't
// run before the app exists. This picker just collects ids into the form
// state; handleSaveApp sends them inside the same POST /apps payload so
// the new app never appears unrestricted (the goal of the feature). No
// impact preview is shown here because a brand-new app starts with zero
// users seeing it, so adding permissions can't hide it from anyone.
function NewAppPermissionsPicker({
  selectedIds,
  onChange,
}: {
  selectedIds: number[];
  onChange: (next: number[]) => void;
}) {
  const { t } = useTranslation();
  const { data: allPermissions } = useListPermissions({
    query: { queryKey: getListPermissionsQueryKey() },
  });
  const [pendingId, setPendingId] = useState<number | "">("");

  const selectedSet = useMemo(() => new Set(selectedIds), [selectedIds]);
  const selected = useMemo(
    () => (allPermissions ?? []).filter((p) => selectedSet.has(p.id)),
    [allPermissions, selectedSet],
  );
  const available = useMemo(
    () => (allPermissions ?? []).filter((p) => !selectedSet.has(p.id)),
    [allPermissions, selectedSet],
  );

  const onAdd = () => {
    if (pendingId === "" || typeof pendingId !== "number") return;
    if (!selectedSet.has(pendingId)) onChange([...selectedIds, pendingId]);
    setPendingId("");
  };
  const onRemove = (id: number) => {
    onChange(selectedIds.filter((x) => x !== id));
  };

  return (
    <div
      className="space-y-2 border-t border-slate-200/70 pt-3"
      data-testid="app-permissions-picker"
    >
      <Label>{t("admin.appPermissions.title")}</Label>
      <p className="text-xs text-muted-foreground">
        {t("admin.appPermissions.help")}
      </p>
      {selected.length === 0 ? (
        <p className="text-xs text-muted-foreground">
          {t("admin.appPermissions.none")}
        </p>
      ) : (
        <ul className="space-y-1" data-testid="app-permissions-picker-list">
          {selected.map((p) => (
            <li
              key={p.id}
              className="flex items-center justify-between gap-2 rounded-xl bg-white/70 border border-slate-200 px-3 py-1.5"
            >
              <span className="text-sm text-foreground truncate">{p.name}</span>
              <button
                type="button"
                aria-label={t("admin.appPermissions.removeAria", { name: p.name })}
                className="text-muted-foreground hover:text-destructive transition-colors"
                onClick={() => onRemove(p.id)}
                data-testid={`app-permission-picker-remove-${p.id}`}
              >
                <Trash2 size={14} />
              </button>
            </li>
          ))}
        </ul>
      )}
      <div className="flex items-center gap-2">
        <select
          className="flex-1 rounded-xl bg-white/70 border border-slate-200 px-3 py-2 text-sm"
          value={pendingId === "" ? "" : String(pendingId)}
          onChange={(e) =>
            setPendingId(e.target.value === "" ? "" : Number(e.target.value))
          }
          disabled={available.length === 0}
          data-testid="app-permission-picker-select"
        >
          <option value="">{t("admin.appPermissions.selectPlaceholder")}</option>
          {available.map((p) => (
            <option key={p.id} value={p.id}>
              {p.name}
            </option>
          ))}
        </select>
        <Button
          size="sm"
          onClick={onAdd}
          disabled={pendingId === "" || available.length === 0}
          data-testid="app-permission-picker-add"
        >
          {t("admin.appPermissions.add")}
        </Button>
      </div>
    </div>
  );
}

function AppPermissionsEditor({ appId }: { appId: number }) {
  const { t } = useTranslation();
  const queryClient = useQueryClient();
  const { toast } = useToast();
  const { data: assigned, isLoading } = useListAppPermissions(appId, {
    query: { queryKey: getListAppPermissionsQueryKey(appId) },
  });
  const { data: allPermissions } = useListPermissions({
    query: { queryKey: getListPermissionsQueryKey() },
  });
  const addPermission = useAddAppPermission();
  const removePermission = useRemoveAppPermission();
  const previewImpact = usePreviewAppPermissionsImpact();
  const [pendingId, setPendingId] = useState<number | "">("");
  const [impact, setImpact] = useState<AppPermissionsImpact | null>(null);
  const [impactLoading, setImpactLoading] = useState(false);
  const [impactError, setImpactError] = useState(false);
  const [confirmAdd, setConfirmAdd] = useState(false);
  const impactRequestSeq = useRef(0);

  const assignedIds = useMemo(
    () => new Set((assigned ?? []).map((p) => p.id)),
    [assigned],
  );
  const available = useMemo(
    () => (allPermissions ?? []).filter((p) => !assignedIds.has(p.id)),
    [allPermissions, assignedIds],
  );

  const refresh = () => {
    queryClient.invalidateQueries({
      queryKey: getListAppPermissionsQueryKey(appId),
    });
    queryClient.invalidateQueries({ queryKey: ADMIN_APPS_KEY });
  };

  // Debounced, cancel-safe impact preview for the candidate "add this
  // permission" change. Mirrors the RolesPanel UX so admins see the same
  // kind of warning before they tighten an app's gate. Stale responses
  // are dropped via impactRequestSeq.
  useEffect(() => {
    if (pendingId === "" || typeof pendingId !== "number") {
      setImpact(null);
      setImpactLoading(false);
      setImpactError(false);
      return;
    }
    const candidate = Array.from(assignedIds);
    if (!candidate.includes(pendingId)) candidate.push(pendingId);
    setImpactLoading(true);
    setImpactError(false);
    const requestId = ++impactRequestSeq.current;
    const handle = setTimeout(() => {
      previewImpact.mutate(
        { id: appId, data: { permissionIds: candidate } },
        {
          onSuccess: (data) => {
            if (impactRequestSeq.current !== requestId) return;
            setImpact(data);
            setImpactError(false);
            setImpactLoading(false);
          },
          onError: () => {
            if (impactRequestSeq.current !== requestId) return;
            setImpact(null);
            setImpactError(true);
            setImpactLoading(false);
          },
        },
      );
    }, 350);
    return () => clearTimeout(handle);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [appId, pendingId, assignedIds]);

  const requiresConfirmation =
    !impactLoading &&
    !impactError &&
    impact != null &&
    impact.affectedUserCount > 0;

  const performAdd = () => {
    if (pendingId === "" || typeof pendingId !== "number") return;
    addPermission.mutate(
      { id: appId, data: { permissionId: pendingId } },
      {
        onSuccess: () => {
          setPendingId("");
          setImpact(null);
          setConfirmAdd(false);
          impactRequestSeq.current += 1;
          refresh();
        },
        onError: () => {
          toast({ title: t("common.error"), variant: "destructive" });
        },
      },
    );
  };

  const onAdd = () => {
    if (pendingId === "" || typeof pendingId !== "number") return;
    if (requiresConfirmation && !confirmAdd) {
      setConfirmAdd(true);
      return;
    }
    performAdd();
  };

  const onRemove = (permissionId: number) => {
    removePermission.mutate(
      { id: appId, permissionId },
      {
        onSuccess: refresh,
        onError: () => {
          toast({ title: t("common.error"), variant: "destructive" });
        },
      },
    );
  };

  return (
    <div
      className="space-y-2 border-t border-slate-200/70 pt-3"
      data-testid="app-permissions-editor"
    >
      <Label>{t("admin.appPermissions.title")}</Label>
      <p className="text-xs text-muted-foreground">
        {t("admin.appPermissions.help")}
      </p>
      {isLoading ? (
        <p className="text-xs text-muted-foreground">{t("common.loading")}</p>
      ) : (assigned?.length ?? 0) === 0 ? (
        <p className="text-xs text-muted-foreground">
          {t("admin.appPermissions.none")}
        </p>
      ) : (
        <ul className="space-y-1" data-testid="app-permissions-list">
          {assigned!.map((p) => (
            <li
              key={p.id}
              className="flex items-center justify-between gap-2 rounded-xl bg-white/70 border border-slate-200 px-3 py-1.5"
            >
              <span className="text-sm text-foreground truncate">{p.name}</span>
              <button
                type="button"
                aria-label={t("admin.appPermissions.removeAria", {
                  name: p.name,
                })}
                className="text-muted-foreground hover:text-destructive transition-colors"
                disabled={removePermission.isPending}
                onClick={() => onRemove(p.id)}
                data-testid={`app-permission-remove-${p.id}`}
              >
                <Trash2 size={14} />
              </button>
            </li>
          ))}
        </ul>
      )}
      <div className="flex items-center gap-2">
        <select
          className="flex-1 rounded-xl bg-white/70 border border-slate-200 px-3 py-2 text-sm"
          value={pendingId === "" ? "" : String(pendingId)}
          onChange={(e) =>
            setPendingId(e.target.value === "" ? "" : Number(e.target.value))
          }
          disabled={available.length === 0 || addPermission.isPending}
          data-testid="app-permission-select"
        >
          <option value="">{t("admin.appPermissions.selectPlaceholder")}</option>
          {available.map((p) => (
            <option key={p.id} value={p.id}>
              {p.name}
            </option>
          ))}
        </select>
        <Button
          size="sm"
          onClick={onAdd}
          disabled={
            pendingId === "" ||
            addPermission.isPending ||
            available.length === 0 ||
            impactLoading ||
            impactError
          }
          data-testid="app-permission-add"
        >
          {addPermission.isPending ? (
            <Loader2 size={14} className="animate-spin" />
          ) : (
            t("admin.appPermissions.add")
          )}
        </Button>
      </div>
      {pendingId !== "" && (
        <div
          className="mt-2 rounded-xl border border-amber-300 bg-amber-50 p-3 text-xs text-amber-900 space-y-1"
          data-testid="app-perm-impact"
          role="status"
          aria-live="polite"
        >
          <p className="font-semibold">{t("admin.appPermissions.impactTitle")}</p>
          {impactLoading ? (
            <div className="flex items-center gap-2 text-amber-800">
              <Loader2 size={12} className="animate-spin" />
              <span>{t("admin.appPermissions.impactLoading")}</span>
            </div>
          ) : impactError || !impact ? (
            <p
              className="text-amber-900"
              data-testid="app-perm-impact-error"
            >
              {t("admin.appPermissions.impactError")}
            </p>
          ) : impact.affectedUserCount === 0 ? (
            <p data-testid="app-perm-impact-none">
              {t("admin.appPermissions.impactNone", {
                visible: impact.currentlyVisibleUserCount,
              })}
            </p>
          ) : (
            <>
              <p data-testid="app-perm-impact-summary">
                {t("admin.appPermissions.impactSummary", {
                  affected: impact.affectedUserCount,
                  visible: impact.currentlyVisibleUserCount,
                })}
              </p>
              {impact.groups.length > 0 && (
                <p
                  className="text-amber-800"
                  data-testid="app-perm-impact-groups"
                >
                  {t("admin.appPermissions.impactViaGroups", {
                    groups: impact.groups.map((g) => g.name).join(", "),
                  })}
                </p>
              )}
            </>
          )}
        </div>
      )}
      {confirmAdd && impact && impact.affectedUserCount > 0 && (
        <div className="fixed inset-0 bg-slate-900/40 backdrop-blur-sm z-[60] flex items-center justify-center p-4">
          <div
            className="glass-panel rounded-3xl p-6 w-full max-w-md space-y-3"
            data-testid="app-perm-confirm-dialog"
            role="alertdialog"
            aria-modal="true"
          >
            <h2 className="font-semibold text-foreground">
              {t("admin.appPermissions.confirmTitle")}
            </h2>
            <p className="text-sm text-foreground">
              {t("admin.appPermissions.confirmBody", {
                affected: impact.affectedUserCount,
                visible: impact.currentlyVisibleUserCount,
              })}
            </p>
            {impact.groups.length > 0 && (
              <p className="text-xs text-muted-foreground">
                {t("admin.appPermissions.impactViaGroups", {
                  groups: impact.groups.map((g) => g.name).join(", "),
                })}
              </p>
            )}
            <div className="flex gap-2 pt-2">
              <Button
                variant="outline"
                className="flex-1"
                onClick={() => setConfirmAdd(false)}
                data-testid="app-perm-confirm-cancel"
              >
                {t("common.cancel")}
              </Button>
              <Button
                variant="destructive"
                className="flex-1"
                disabled={addPermission.isPending}
                onClick={performAdd}
                data-testid="app-perm-confirm-submit"
              >
                {addPermission.isPending ? (
                  <Loader2 size={14} className="animate-spin" />
                ) : (
                  t("admin.appPermissions.confirmAction")
                )}
              </Button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

export default function AdminPage() {
  const { t, i18n } = useTranslation();
  const [, setLocation] = useLocation();
  const { user } = useAuth();
  const lang = i18n.language;
  const isRtl = lang === "ar";
  const queryClient = useQueryClient();
  const { toast } = useToast();
  const BackIcon = isRtl ? ArrowRight : ArrowLeft;
  const isAdmin = user?.roles?.includes("admin") ?? false;

  // Redirect non-admins using useEffect to avoid violating rules of hooks
  useEffect(() => {
    if (user && !isAdmin) {
      setLocation("/");
    }
  }, [user, isAdmin, setLocation]);

  const { data: apps } = useQuery({ queryKey: ADMIN_APPS_KEY, queryFn: fetchAdminApps, enabled: isAdmin });
  const { data: services } = useListServices({ query: { queryKey: getListServicesQueryKey() } });
  const { data: users } = useListUsers(undefined, { query: { queryKey: getListUsersQueryKey() } });
  const { data: rolesList } = useListRoles({ query: { queryKey: getListRolesQueryKey(), enabled: isAdmin } });
  const { data: permissionsList } = useListPermissions({ query: { queryKey: getListPermissionsQueryKey(), enabled: isAdmin } });
  const { data: appSettings } = useGetAppSettings({ query: { queryKey: getGetAppSettingsQueryKey() } });
  const statsRangeStorageKey = user?.id ? `admin.statsRange.${user.id}` : null;
  const [statsRange, setStatsRange] = useState<"7d" | "30d" | "90d" | "custom">("7d");
  const [statsRangeHydrated, setStatsRangeHydrated] = useState(false);
  useEffect(() => {
    setStatsRangeHydrated(false);
    if (typeof window === "undefined" || !statsRangeStorageKey) return;
    const stored = window.localStorage.getItem(statsRangeStorageKey);
    if (stored === "7d" || stored === "30d" || stored === "90d") {
      setStatsRange(stored);
    }
    setStatsRangeHydrated(true);
  }, [statsRangeStorageKey]);
  useEffect(() => {
    if (typeof window === "undefined" || !statsRangeStorageKey || !statsRangeHydrated) return;
    if (statsRange === "7d" || statsRange === "30d" || statsRange === "90d") {
      window.localStorage.setItem(statsRangeStorageKey, statsRange);
    }
  }, [statsRange, statsRangeStorageKey, statsRangeHydrated]);
  const todayIso = new Date().toISOString().slice(0, 10);
  const sevenAgoIso = new Date(Date.now() - 6 * 24 * 60 * 60 * 1000)
    .toISOString()
    .slice(0, 10);
  const [customFrom, setCustomFrom] = useState<string>(sevenAgoIso);
  const [customTo, setCustomTo] = useState<string>(todayIso);
  const [appliedCustom, setAppliedCustom] = useState<{ from: string; to: string }>({
    from: sevenAgoIso,
    to: todayIso,
  });
  const isCustomValid =
    /^\d{4}-\d{2}-\d{2}$/.test(appliedCustom.from) &&
    /^\d{4}-\d{2}-\d{2}$/.test(appliedCustom.to) &&
    appliedCustom.from <= appliedCustom.to;
  const statsQueryParams =
    statsRange === "custom"
      ? { range: "custom" as const, from: appliedCustom.from, to: appliedCustom.to }
      : { range: statsRange };
  const { data: adminStats, error: adminStatsError } = useGetAdminStats(statsQueryParams, {
    query: {
      queryKey: getGetAdminStatsQueryKey(statsQueryParams),
      enabled: isAdmin && (statsRange !== "custom" || isCustomValid),
      retry: false,
    },
  });
  const adminStatsErrorMessage: string | null = (() => {
    if (!adminStatsError) return null;
    const err = adminStatsError as GetAdminStatsQueryError;
    return err.data?.error ?? err.message ?? null;
  })();

  const createApp = useCreateApp();
  const updateApp = useUpdateApp();
  const deleteApp = useDeleteApp();
  const createService = useCreateService();
  const updateService = useUpdateService();
  const deleteService = useDeleteService();
  const createUser = useCreateUser();
  const updateUser = useUpdateUser();
  const deleteUser = useDeleteUser();
  const addUserRole = useAddUserRole();
  const removeUserRole = useRemoveUserRole();
  const issueResetLink = useAdminIssueResetLink();
  const [resetLinkUser, setResetLinkUser] = useState<{ username: string; url: string; expiresAt: string } | null>(null);

  const [editingApp, setEditingApp] = useState<{ id?: number; form: AppForm } | null>(null);
  const [editingService, setEditingService] = useState<{ id?: number; form: ServiceForm } | null>(null);
  const [appDeleteTarget, setAppDeleteTarget] = useState<App | null>(null);
  const [appDeleteConflict, setAppDeleteConflict] = useState<AppDeletionConflict | null>(null);
  const [serviceDeleteTarget, setServiceDeleteTarget] = useState<Service | null>(null);
  const [serviceDeleteConflict, setServiceDeleteConflict] = useState<ServiceDeletionConflict | null>(null);

  const closeAppDeleteDialog = () => {
    setAppDeleteTarget(null);
    setAppDeleteConflict(null);
  };
  const closeServiceDeleteDialog = () => {
    setServiceDeleteTarget(null);
    setServiceDeleteConflict(null);
  };

  const performAppDelete = (app: App, force: boolean) => {
    deleteApp.mutate(
      { id: app.id, params: force ? { force: true } : undefined },
      {
        onSuccess: () => {
          queryClient.invalidateQueries({ queryKey: ADMIN_APPS_KEY });
          toast({ title: t("common.success") });
          closeAppDeleteDialog();
        },
        onError: (err: unknown) => {
          if (err instanceof ApiError && err.status === 409 && err.data) {
            setAppDeleteConflict(err.data as AppDeletionConflict);
            return;
          }
          toast({ title: t("common.error"), variant: "destructive" });
        },
      },
    );
  };

  const performServiceDelete = (service: Service, force: boolean) => {
    deleteService.mutate(
      { id: service.id, params: force ? { force: true } : undefined },
      {
        onSuccess: () => {
          queryClient.invalidateQueries({ queryKey: getListServicesQueryKey() });
          toast({ title: t("common.success") });
          closeServiceDeleteDialog();
        },
        onError: (err: unknown) => {
          if (err instanceof ApiError && err.status === 409 && err.data) {
            setServiceDeleteConflict(err.data as ServiceDeletionConflict);
            return;
          }
          toast({ title: t("common.error"), variant: "destructive" });
        },
      },
    );
  };
  const [newUserForm, setNewUserForm] = useState<{ username: string; email: string; password: string } | null>(null);
  const [section, setSection] = useState<AdminSection>("dashboard");
  const [navOpenGroups, setNavOpenGroups] = useState<Record<string, boolean>>({});
  const [mobileNavOpen, setMobileNavOpen] = useState(false);
  const [highlightedUserId, setHighlightedUserId] = useState<number | null>(null);
  const userRowRefs = useRef<Map<number, HTMLDivElement>>(new Map());

  useEffect(() => {
    if (section !== "users" || highlightedUserId == null) return;
    const node = userRowRefs.current.get(highlightedUserId);
    if (node) {
      node.scrollIntoView({ behavior: "smooth", block: "center" });
    }
    const timer = window.setTimeout(() => setHighlightedUserId(null), 2500);
    return () => window.clearTimeout(timer);
  }, [section, highlightedUserId, users]);

  const handleSelectAppFromDashboard = (appId: number) => {
    const app = apps?.find((a) => a.id === appId);
    setSection("apps");
    if (app) {
      setEditingApp({
        id: app.id,
        form: {
          nameAr: app.nameAr ?? "",
          nameEn: app.nameEn ?? "",
          slug: app.slug ?? "",
          iconName: app.iconName ?? "Grid2X2",
          route: app.route ?? "/",
          color: app.color ?? "#6366f1",
          sortOrder: app.sortOrder ?? 0,
          permissionIds: [],
        },
      });
    }
  };

  const handleSelectUserFromDashboard = (userId: number) => {
    setSection("users");
    setHighlightedUserId(userId);
  };

  type NavLeaf = { key: AdminSection; label: string; Icon: typeof LayoutDashboard };
  type NavGroup = { groupKey: string; label: string; Icon: typeof LayoutDashboard; children: NavLeaf[] };
  type NavEntry = NavLeaf | NavGroup;
  const navItems: NavEntry[] = [
    { key: "dashboard", label: t("admin.nav.dashboard"), Icon: LayoutDashboard },
    { key: "apps", label: t("admin.nav.apps"), Icon: Grid2X2 },
    { key: "services", label: t("admin.nav.services"), Icon: Coffee },
    {
      groupKey: "user-management",
      label: t("admin.nav.userManagement"),
      Icon: UsersIcon,
      children: [
        { key: "users", label: t("admin.nav.users"), Icon: UsersIcon },
        { key: "groups", label: t("admin.nav.groups"), Icon: Shield },
        { key: "roles", label: t("admin.nav.roles"), Icon: KeyRound },
        { key: "audit-log", label: t("admin.nav.auditLog"), Icon: ScrollText },
      ],
    },
    { key: "settings", label: t("admin.nav.settings"), Icon: Settings },
  ];

  // Sync section to URL hash so deep links work
  useEffect(() => {
    if (typeof window === "undefined") return;
    const hash = window.location.hash.replace(/^#/, "");
    const sectionMatch = hash.match(/section=([a-z-]+)/);
    if (sectionMatch) {
      const s = sectionMatch[1] as AdminSection;
      if (["dashboard", "apps", "services", "users", "groups", "roles", "audit-log", "settings"].includes(s)) {
        setSection(s);
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);
  // Skip the very first render so we don't clobber a deep-linked hash like
  // `#section=audit-log&targetType=group&targetId=42` before the mount-time
  // parser above has had a chance to apply it.
  const skipNextSectionSync = useRef<boolean>(true);
  useEffect(() => {
    if (typeof window === "undefined") return;
    if (skipNextSectionSync.current) {
      skipNextSectionSync.current = false;
      return;
    }
    const raw = window.location.hash.replace(/^#/, "");
    const params = new URLSearchParams(raw);
    const previousSection = params.get("section");
    params.set("section", section);
    // Section-scoped params (the audit log's targetType / targetId deep
    // links) only make sense within their owning section. Switching to a
    // different section drops them so the next visit starts fresh.
    if (previousSection !== section) {
      params.delete("targetType");
      params.delete("targetId");
    }
    const newHash = `#${params.toString()}`;
    if (window.location.hash !== newHash) {
      window.history.replaceState(null, "", newHash);
    }
  }, [section]);

  const renderNavList = (onSelect?: () => void) => {
    const renderLeaf = (leaf: NavLeaf, indent = false) => {
      const active = section === leaf.key;
      const Icon = leaf.Icon;
      return (
        <button
          key={leaf.key}
          onClick={() => {
            setSection(leaf.key);
            onSelect?.();
          }}
          className={cn(
            "flex items-center gap-3 w-full py-2.5 rounded-xl text-sm transition-all text-start relative",
            indent ? "ps-9 pe-3" : "px-3",
            active
              ? "bg-primary text-primary-foreground font-semibold shadow-md shadow-primary/20"
              : "text-foreground/70 hover:bg-slate-100 hover:text-foreground",
          )}
          data-testid={`nav-${leaf.key}`}
        >
          <Icon size={16} className="shrink-0" />
          <span className="truncate">{leaf.label}</span>
        </button>
      );
    };
    return (
      <nav className="flex flex-col gap-1">
        {navItems.map((entry) => {
          if ("children" in entry) {
            const Icon = entry.Icon;
            const anyChildActive = entry.children.some((c) => c.key === section);
            const expanded = navOpenGroups[entry.groupKey] ?? anyChildActive;
            return (
              <div key={entry.groupKey} className="flex flex-col gap-0.5">
                <button
                  type="button"
                  onClick={() =>
                    setNavOpenGroups((prev) => ({
                      ...prev,
                      [entry.groupKey]: !(prev[entry.groupKey] ?? anyChildActive),
                    }))
                  }
                  aria-expanded={expanded}
                  className={cn(
                    "flex items-center gap-2 w-full px-3 py-2 rounded-xl text-xs uppercase tracking-wider transition-colors text-start",
                    anyChildActive ? "text-primary" : "text-muted-foreground hover:text-foreground",
                  )}
                  data-testid={`nav-group-${entry.groupKey}`}
                >
                  <Icon size={14} className="shrink-0" />
                  <span className="truncate flex-1">{entry.label}</span>
                  <ChevronDown
                    size={14}
                    className={cn(
                      "shrink-0 transition-transform",
                      expanded ? "rotate-0" : "-rotate-90",
                    )}
                  />
                </button>
                {expanded && entry.children.map((leaf) => renderLeaf(leaf, true))}
              </div>
            );
          }
          return renderLeaf(entry, false);
        })}
      </nav>
    );
  };

  const emptyAppForm: AppForm = { nameAr: "", nameEn: "", slug: "", iconName: "Grid2X2", route: "/", color: "#6366f1", sortOrder: 0, permissionIds: [] };
  const emptyServiceForm: ServiceForm = { nameAr: "", nameEn: "", descriptionAr: "", descriptionEn: "", price: "0.00", imageUrl: "", isAvailable: true };

  const handleSaveApp = () => {
    if (!editingApp) return;
    if (editingApp.id) {
      // Edit path: permissionIds aren't part of the update payload — the
      // existing AppPermissionsEditor manages them with its own impact
      // preview + audit, so strip the field before sending.
      const { permissionIds: _ignored, ...updateData } = editingApp.form;
      void _ignored;
      updateApp.mutate(
        { id: editingApp.id, data: updateData },
        {
          onSuccess: () => {
            queryClient.invalidateQueries({ queryKey: ADMIN_APPS_KEY });
            setEditingApp(null);
            toast({ title: t("common.success") });
          },
        },
      );
    } else {
      const { permissionIds, ...rest } = editingApp.form;
      createApp.mutate(
        {
          // Only include permissionIds if the admin actually picked some
          // so the request body stays minimal and the server's "no
          // restriction" path is unchanged.
          data:
            permissionIds.length > 0
              ? { ...rest, permissionIds }
              : rest,
        },
        {
          onSuccess: () => {
            queryClient.invalidateQueries({ queryKey: ADMIN_APPS_KEY });
            setEditingApp(null);
            toast({ title: t("common.success") });
          },
        },
      );
    }
  };

  const handleSaveService = () => {
    if (!editingService) return;
    if (editingService.id) {
      updateService.mutate(
        { id: editingService.id, data: editingService.form },
        {
          onSuccess: () => {
            queryClient.invalidateQueries({ queryKey: getListServicesQueryKey() });
            setEditingService(null);
            toast({ title: t("common.success") });
          },
        },
      );
    } else {
      createService.mutate(
        { data: editingService.form },
        {
          onSuccess: () => {
            queryClient.invalidateQueries({ queryKey: getListServicesQueryKey() });
            setEditingService(null);
            toast({ title: t("common.success") });
          },
        },
      );
    }
  };

  const handleCreateUser = () => {
    if (!newUserForm) return;
    createUser.mutate(
      { data: { username: newUserForm.username, email: newUserForm.email, password: newUserForm.password } },
      {
        onSuccess: () => {
          queryClient.invalidateQueries({ queryKey: getListUsersQueryKey() });
          setNewUserForm(null);
          toast({ title: t("common.success") });
        },
      },
    );
  };

  if (!user || !isAdmin) return null;

  return (
    <div className="min-h-screen os-bg flex flex-col">
      {/* Header */}
      <div className="glass-panel border-b border-slate-200/70 px-4 py-4 flex items-center gap-3 sticky top-0 z-10">
        <button
          onClick={() => setLocation("/")}
          className="p-2 rounded-xl hover:bg-slate-100 text-muted-foreground hover:text-foreground transition-colors"
        >
          <BackIcon size={20} />
        </button>
        <div className="flex items-center gap-2 flex-1 min-w-0">
          <Settings size={20} className="text-red-400 shrink-0" />
          <h1 className="text-lg font-semibold text-foreground truncate">{t("admin.title")}</h1>
        </div>
        <Sheet open={mobileNavOpen} onOpenChange={setMobileNavOpen}>
          <SheetTrigger asChild>
            <button
              className="md:hidden p-2 rounded-xl hover:bg-slate-100 text-muted-foreground hover:text-foreground transition-colors"
              aria-label={t("admin.nav.menu")}
            >
              <MenuIcon size={20} />
            </button>
          </SheetTrigger>
          <SheetContent side={isRtl ? "right" : "left"} className="w-full sm:w-72 p-4">
            <div className="flex items-center gap-2 mb-4 mt-2">
              <Settings size={18} className="text-red-400" />
              <h2 className="font-semibold text-foreground">{t("admin.title")}</h2>
            </div>
            {renderNavList(() => setMobileNavOpen(false))}
          </SheetContent>
        </Sheet>
      </div>

      {/* Edit App Modal */}
      {editingApp && (
        <div className="fixed inset-0 bg-slate-900/30 backdrop-blur-sm z-50 flex items-center justify-center p-4">
          <div className="glass-panel rounded-3xl p-6 w-full max-w-sm space-y-4 max-h-[90vh] overflow-y-auto">
            <h2 className="font-semibold text-foreground">
              {editingApp.id ? t("admin.editApp") : t("admin.addApp")}
            </h2>
            {(
              [
                { field: "nameAr", label: t("admin.appNameAr") },
                { field: "nameEn", label: t("admin.appNameEn") },
                { field: "slug", label: t("admin.appSlug") },
                { field: "iconName", label: t("admin.appIcon") },
                { field: "route", label: t("admin.appRoute") },
                { field: "color", label: t("admin.appColor") },
              ] as { field: keyof AppForm; label: string }[]
            ).map(({ field, label }) => (
              <div key={field} className="space-y-1">
                <Label>{label}</Label>
                <Input
                  value={String(editingApp.form[field])}
                  onChange={(e) => setEditingApp({ ...editingApp, form: { ...editingApp.form, [field]: e.target.value } })}
                  className="bg-white/70 border-slate-200"
                />
              </div>
            ))}
            {editingApp.id !== undefined ? (
              <AppPermissionsEditor appId={editingApp.id} />
            ) : (
              <NewAppPermissionsPicker
                selectedIds={editingApp.form.permissionIds}
                onChange={(next) =>
                  setEditingApp({
                    ...editingApp,
                    form: { ...editingApp.form, permissionIds: next },
                  })
                }
              />
            )}
            {editingApp.id !== undefined && (
              <PermissionAuditHistory
                targetKind="app"
                targetId={editingApp.id}
                enabled={true}
                language={lang}
                scopeI18nKey="admin.apps.history"
                changeKindLabel={null}
                labelResolver={(_ck, id) => {
                  const p = (permissionsList ?? []).find((x) => x.id === id);
                  return p ? p.name : `#${id}`;
                }}
              />
            )}
            <div className="flex gap-2">
              <Button variant="outline" className="flex-1" onClick={() => setEditingApp(null)}>{t("common.cancel")}</Button>
              <Button className="flex-1" onClick={handleSaveApp}>{t("common.save")}</Button>
            </div>
          </div>
        </div>
      )}

      {/* Edit Service Modal */}
      {editingService && (
        <div className="fixed inset-0 bg-slate-900/30 backdrop-blur-sm z-50 flex items-center justify-center p-4">
          <div className="glass-panel rounded-3xl p-6 w-full max-w-sm space-y-4 max-h-[90vh] overflow-y-auto">
            <h2 className="font-semibold text-foreground">
              {editingService.id ? t("admin.editService") : t("admin.addService")}
            </h2>
            {(
              [
                { field: "nameAr", label: t("admin.serviceNameAr") },
                { field: "nameEn", label: t("admin.serviceNameEn") },
                { field: "price", label: t("admin.servicePrice") },
              ] as { field: keyof ServiceForm; label: string }[]
            ).map(({ field, label }) => (
              <div key={field} className="space-y-1">
                <Label>{label}</Label>
                <Input
                  value={String(editingService.form[field])}
                  onChange={(e) => setEditingService({ ...editingService, form: { ...editingService.form, [field]: e.target.value } })}
                  className="bg-white/70 border-slate-200"
                />
              </div>
            ))}

            <div className="space-y-2">
              <Label>{t("admin.serviceImage")}</Label>
              <ServiceImageUploader
                value={editingService.form.imageUrl}
                onChange={(url) =>
                  setEditingService({
                    ...editingService,
                    form: { ...editingService.form, imageUrl: url },
                  })
                }
              />
            </div>
            <div className="flex items-center justify-between">
              <Label>{t("admin.serviceAvailable")}</Label>
              <Switch
                checked={editingService.form.isAvailable}
                onCheckedChange={(v) => setEditingService({ ...editingService, form: { ...editingService.form, isAvailable: v } })}
              />
            </div>
            <div className="flex gap-2">
              <Button variant="outline" className="flex-1" onClick={() => setEditingService(null)}>{t("common.cancel")}</Button>
              <Button className="flex-1" onClick={handleSaveService}>{t("common.save")}</Button>
            </div>
          </div>
        </div>
      )}

      {/* Create User Modal */}
      {newUserForm && (
        <div className="fixed inset-0 bg-slate-900/30 backdrop-blur-sm z-50 flex items-center justify-center p-4">
          <div className="glass-panel rounded-3xl p-6 w-full max-w-sm space-y-4">
            <h2 className="font-semibold text-foreground">{t("admin.addUser")}</h2>
            <div className="space-y-1">
              <Label>{t("admin.username")}</Label>
              <Input
                value={newUserForm.username}
                onChange={(e) => setNewUserForm({ ...newUserForm, username: e.target.value })}
                className="bg-white/70 border-slate-200"
              />
            </div>
            <div className="space-y-1">
              <Label>{t("admin.email")}</Label>
              <Input
                type="email"
                value={newUserForm.email}
                onChange={(e) => setNewUserForm({ ...newUserForm, email: e.target.value })}
                className="bg-white/70 border-slate-200"
              />
            </div>
            <div className="space-y-1">
              <Label>{t("admin.password")}</Label>
              <Input
                type="password"
                value={newUserForm.password}
                onChange={(e) => setNewUserForm({ ...newUserForm, password: e.target.value })}
                className="bg-white/70 border-slate-200"
              />
            </div>
            <div className="flex gap-2">
              <Button variant="outline" className="flex-1" onClick={() => setNewUserForm(null)}>{t("common.cancel")}</Button>
              <Button className="flex-1" onClick={handleCreateUser}>{t("common.save")}</Button>
            </div>
          </div>
        </div>
      )}

      <div className="flex-1 flex w-full">
        {/* Sidebar (desktop) */}
        <aside className="hidden md:flex flex-col w-64 shrink-0 border-e border-slate-200/70 bg-white/70 backdrop-blur-md">
          <div className="sticky top-[73px] p-4 space-y-4">
            <div className="flex items-center gap-2 px-2 pb-3 border-b border-slate-200/70">
              <Shield size={18} className="text-primary" />
              <span className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
                {t("admin.nav.menu")}
              </span>
            </div>
            {renderNavList()}
          </div>
        </aside>

        {/* Main content */}
        <div className="flex-1 min-w-0 p-4 pb-8">
          {section === "dashboard" && (
            <DashboardSection
              apps={apps}
              services={services}
              users={users}
              appSettings={appSettings}
              adminStats={adminStats}
              adminStatsErrorMessage={adminStatsErrorMessage}
              lang={lang}
              statsRange={statsRange}
              onStatsRangeChange={setStatsRange}
              customFrom={customFrom}
              customTo={customTo}
              onCustomFromChange={setCustomFrom}
              onCustomToChange={setCustomTo}
              onApplyCustom={() => setAppliedCustom({ from: customFrom, to: customTo })}
              statsQueryParams={statsQueryParams}
              onSelectApp={handleSelectAppFromDashboard}
              onSelectUser={handleSelectUserFromDashboard}
            />
          )}

          {section === "apps" && (
          <div className="space-y-3">
            <div className="flex justify-end">
              <Button size="sm" onClick={() => setEditingApp({ form: emptyAppForm })}>
                <Plus size={14} className="me-1" />
                {t("admin.addApp")}
              </Button>
            </div>
            {apps?.map((app) => (
              <div key={app.id} className={cn("rounded-2xl p-4 flex items-center justify-between gap-3 border", app.isActive ? "glass-panel border-transparent" : "bg-slate-100/80 border-slate-200")}>
                <div className="flex items-center gap-3 min-w-0">
                  <div
                    className={cn("w-8 h-8 rounded-lg shrink-0", !app.isActive && "grayscale")}
                    style={{ backgroundColor: `${app.color}40` }}
                  />
                  <div className="min-w-0">
                    <div className="flex items-center gap-2">
                      <div className={cn("font-medium text-sm truncate", app.isActive ? "text-foreground" : "text-slate-400")}>
                        {lang === "ar" ? app.nameAr : app.nameEn}
                      </div>
                      {!app.isActive && (
                        <span className="text-[10px] font-medium bg-slate-300 text-slate-600 rounded px-1.5 py-0.5 shrink-0">
                          {t("admin.appDisabled")}
                        </span>
                      )}
                    </div>
                    <div className="text-xs text-muted-foreground">{app.route}</div>
                    {(() => {
                      const parts: string[] = [];
                      if ((app.groupCount ?? 0) > 0) parts.push(t("admin.apps.counts.groups", { count: app.groupCount }));
                      if ((app.restrictionCount ?? 0) > 0) parts.push(t("admin.apps.counts.restrictions", { count: app.restrictionCount }));
                      if ((app.openCount ?? 0) > 0) parts.push(t("admin.apps.counts.opens", { count: app.openCount }));
                      if (parts.length === 0) return null;
                      return (
                        <div
                          className="flex flex-wrap items-center gap-x-2 gap-y-0.5 text-[11px] text-muted-foreground pt-0.5"
                          data-testid={`app-counts-${app.id}`}
                        >
                          {parts.map((p, i) => (
                            <span key={i} className="flex items-center gap-2">
                              {i > 0 && <span aria-hidden="true">•</span>}
                              <span>{p}</span>
                            </span>
                          ))}
                        </div>
                      );
                    })()}
                  </div>
                </div>
                <div className="flex items-center gap-2 shrink-0">
                  <Switch
                    checked={app.isActive}
                    onCheckedChange={(v) => {
                      updateApp.mutate(
                        { id: app.id, data: { isActive: v } },
                        { onSuccess: () => queryClient.invalidateQueries({ queryKey: ADMIN_APPS_KEY }) },
                      );
                    }}
                  />
                  <button
                    onClick={() => setEditingApp({
                      id: app.id,
                      form: {
                        nameAr: app.nameAr ?? "",
                        nameEn: app.nameEn ?? "",
                        slug: app.slug ?? "",
                        iconName: app.iconName ?? "Grid2X2",
                        route: app.route ?? "/",
                        color: app.color ?? "#6366f1",
                        sortOrder: app.sortOrder ?? 0,
                        permissionIds: [],
                      },
                    })}
                    className="p-1.5 hover:bg-slate-100 rounded-lg text-muted-foreground hover:text-foreground"
                  >
                    <Pencil size={14} />
                  </button>
                  <button
                    onClick={() => {
                      // Pre-populate conflict from list-row counts so the
                      // warning dialog shows on the FIRST click. Falls back
                      // to the lazy 409 round-trip if counts are missing.
                      const groupCount = app.groupCount ?? 0;
                      const restrictionCount = app.restrictionCount ?? 0;
                      const openCount = app.openCount ?? 0;
                      const hasDeps =
                        groupCount > 0 || restrictionCount > 0 || openCount > 0;
                      setAppDeleteConflict(
                        hasDeps
                          ? {
                              error: "App has dependent records",
                              groupCount,
                              restrictionCount,
                              openCount,
                            }
                          : null,
                      );
                      setAppDeleteTarget(app);
                    }}
                    className="p-1.5 hover:bg-destructive/20 rounded-lg text-muted-foreground hover:text-destructive"
                    data-testid={`app-delete-${app.id}`}
                  >
                    <Trash2 size={14} />
                  </button>
                </div>
              </div>
            ))}
          </div>
          )}

          {section === "services" && (
          <div className="space-y-3">
            <div className="flex justify-end">
              <Button size="sm" onClick={() => setEditingService({ form: emptyServiceForm })}>
                <Plus size={14} className="me-1" />
                {t("admin.addService")}
              </Button>
            </div>
            {services?.map((service) => (
              <div key={service.id} className="glass-panel rounded-2xl p-4 flex items-center justify-between gap-3">
                <div className="min-w-0 flex-1">
                  <div className="font-medium text-sm text-foreground truncate">
                    {lang === "ar" ? service.nameAr : service.nameEn}
                  </div>
                  <div className="text-xs text-muted-foreground">{service.price}</div>
                  {(service.orderCount ?? 0) > 0 && (
                    <div
                      className="text-[11px] text-muted-foreground pt-0.5"
                      data-testid={`service-counts-${service.id}`}
                    >
                      {t("admin.services.counts.orders", { count: service.orderCount })}
                    </div>
                  )}
                </div>
                <div className="flex items-center gap-2 shrink-0">
                  <Switch
                    checked={service.isAvailable}
                    onCheckedChange={(v) => {
                      updateService.mutate(
                        { id: service.id, data: { isAvailable: v } },
                        { onSuccess: () => queryClient.invalidateQueries({ queryKey: getListServicesQueryKey() }) },
                      );
                    }}
                  />
                  <button
                    onClick={() => setEditingService({
                      id: service.id,
                      form: {
                        nameAr: service.nameAr ?? "",
                        nameEn: service.nameEn ?? "",
                        descriptionAr: service.descriptionAr ?? "",
                        descriptionEn: service.descriptionEn ?? "",
                        price: service.price ?? "0.00",
                        imageUrl: service.imageUrl ?? "",
                        isAvailable: service.isAvailable ?? true,
                      },
                    })}
                    className="p-1.5 hover:bg-slate-100 rounded-lg text-muted-foreground hover:text-foreground"
                  >
                    <Pencil size={14} />
                  </button>
                  <button
                    onClick={() => {
                      // Pre-populate conflict from list-row orderCount so
                      // the warning dialog shows on the FIRST click. Falls
                      // back to the lazy 409 round-trip if count is missing.
                      const orderCount = service.orderCount ?? 0;
                      setServiceDeleteConflict(
                        orderCount > 0
                          ? {
                              error: "Service has dependent records",
                              orderCount,
                            }
                          : null,
                      );
                      setServiceDeleteTarget(service);
                    }}
                    className="p-1.5 hover:bg-destructive/20 rounded-lg text-muted-foreground hover:text-destructive"
                    data-testid={`service-delete-${service.id}`}
                  >
                    <Trash2 size={14} />
                  </button>
                </div>
              </div>
            ))}
          </div>
          )}

          {section === "users" && (
            <UsersPanel
              currentUserId={user.id}
              highlightedUserId={highlightedUserId}
              userRowRefs={userRowRefs}
              onIssueResetLink={(uid, username) =>
                issueResetLink.mutate(
                  { id: uid },
                  {
                    onSuccess: (resp) =>
                      setResetLinkUser({
                        username,
                        url: resp.resetUrl,
                        expiresAt:
                          typeof resp.expiresAt === "string"
                            ? resp.expiresAt
                            : new Date(resp.expiresAt).toISOString(),
                      }),
                    onError: () =>
                      toast({ title: t("common.error"), variant: "destructive" }),
                  },
                )
              }
            />
          )}
          {section === "groups" && <GroupsPanel />}
          {section === "roles" && <RolesPanel />}
          {section === "audit-log" && <AuditLogPanel />}

          {section === "settings" && (
            <div className="space-y-3">
              <SiteSettingsPanel />
            </div>
          )}
        </div>
      </div>

      {appDeleteTarget && (() => {
        const target = appDeleteTarget;
        const items: string[] = [];
        if (appDeleteConflict) {
          if (appDeleteConflict.groupCount > 0)
            items.push(t("admin.deleteApp.groupCount", { count: appDeleteConflict.groupCount }));
          if (appDeleteConflict.restrictionCount > 0)
            items.push(t("admin.deleteApp.restrictionCount", { count: appDeleteConflict.restrictionCount }));
          if (appDeleteConflict.openCount > 0)
            items.push(t("admin.deleteApp.openCount", { count: appDeleteConflict.openCount }));
        }
        return (
          <DeletionWarningDialog
            title={t("admin.deleteApp.title", { name: lang === "ar" ? target.nameAr : target.nameEn })}
            warning={t("admin.deleteApp.warning")}
            forceHint={t("admin.deleteApp.forceHint")}
            emptyBody={t("admin.deleteApp.emptyBody")}
            confirmLabel={t("admin.deleteApp.confirm")}
            anywayLabel={t("admin.deleteApp.anyway")}
            items={items}
            showWarning={appDeleteConflict !== null}
            isPending={deleteApp.isPending}
            onCancel={closeAppDeleteDialog}
            onConfirm={() => performAppDelete(target, appDeleteConflict !== null)}
            testId="app-delete-dialog"
            confirmTestId="app-delete-confirm"
          />
        );
      })()}

      {serviceDeleteTarget && (() => {
        const target = serviceDeleteTarget;
        const items: string[] = [];
        if (serviceDeleteConflict && serviceDeleteConflict.orderCount > 0) {
          items.push(t("admin.deleteService.orderCount", { count: serviceDeleteConflict.orderCount }));
        }
        return (
          <DeletionWarningDialog
            title={t("admin.deleteService.title", { name: lang === "ar" ? target.nameAr : target.nameEn })}
            warning={t("admin.deleteService.warning")}
            forceHint={t("admin.deleteService.forceHint")}
            emptyBody={t("admin.deleteService.emptyBody")}
            confirmLabel={t("admin.deleteService.confirm")}
            anywayLabel={t("admin.deleteService.anyway")}
            items={items}
            showWarning={serviceDeleteConflict !== null}
            isPending={deleteService.isPending}
            onCancel={closeServiceDeleteDialog}
            onConfirm={() => performServiceDelete(target, serviceDeleteConflict !== null)}
            testId="service-delete-dialog"
            confirmTestId="service-delete-confirm"
          />
        );
      })()}

      {resetLinkUser && (
        <div
          className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
          onClick={() => setResetLinkUser(null)}
        >
          <div
            className="glass-panel rounded-2xl p-6 max-w-md w-full space-y-4"
            onClick={(e) => e.stopPropagation()}
          >
            <h3 className="text-lg font-bold">
              {t("admin.resetLinkModal.title", { username: resetLinkUser.username })}
            </h3>
            <p className="text-sm text-muted-foreground">
              {t("admin.resetLinkModal.description")}
            </p>
            <div className="rounded-md border border-border bg-background/50 p-3">
              <code className="text-xs break-all block font-mono">
                {resetLinkUser.url}
              </code>
            </div>
            <p className="text-xs text-muted-foreground">
              {t("admin.resetLinkModal.expires", {
                time: new Date(resetLinkUser.expiresAt).toLocaleString(),
              })}
            </p>
            <div className="flex gap-2">
              <Button
                variant="outline"
                onClick={() => {
                  navigator.clipboard
                    .writeText(resetLinkUser.url)
                    .then(() =>
                      toast({ title: t("admin.resetLinkModal.copied") }),
                    )
                    .catch(() => {});
                }}
                className="flex-1"
              >
                <Copy size={14} />
                <span className="ml-2">{t("admin.resetLinkModal.copy")}</span>
              </Button>
              <Button
                onClick={() => setResetLinkUser(null)}
                className="flex-1"
              >
                {t("common.close")}
              </Button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

function SiteSettingsPanel() {
  const { t } = useTranslation();
  const { toast } = useToast();
  const queryClient = useQueryClient();
  const { data } = useGetAppSettings({ query: { queryKey: getGetAppSettingsQueryKey() } });
  const update = useUpdateAppSettings();
  const [form, setForm] = useState({
    siteNameAr: "",
    siteNameEn: "",
    registrationOpen: true,
    footerTextAr: "",
    footerTextEn: "",
  });

  useEffect(() => {
    if (data) setForm({
      siteNameAr: data.siteNameAr,
      siteNameEn: data.siteNameEn,
      registrationOpen: data.registrationOpen,
      footerTextAr: data.footerTextAr,
      footerTextEn: data.footerTextEn,
    });
  }, [data]);

  const handleSave = () => {
    update.mutate(
      { data: form },
      {
        onSuccess: () => {
          queryClient.invalidateQueries({ queryKey: getGetAppSettingsQueryKey() });
          toast({ title: t("common.success") });
        },
        onError: () => toast({ title: t("common.error"), variant: "destructive" }),
      },
    );
  };

  return (
    <div className="glass-panel rounded-2xl p-4 space-y-4">
      <div className="space-y-1">
        <Label>{t("admin.siteNameAr")}</Label>
        <Input
          value={form.siteNameAr}
          onChange={(e) => setForm({ ...form, siteNameAr: e.target.value })}
          className="bg-white/70 border-slate-200"
          dir="rtl"
        />
      </div>
      <div className="space-y-1">
        <Label>{t("admin.siteNameEn")}</Label>
        <Input
          value={form.siteNameEn}
          onChange={(e) => setForm({ ...form, siteNameEn: e.target.value })}
          className="bg-white/70 border-slate-200"
          dir="ltr"
        />
      </div>
      <div className="space-y-1">
        <Label>{t("admin.footerTextAr")}</Label>
        <Input
          value={form.footerTextAr}
          onChange={(e) => setForm({ ...form, footerTextAr: e.target.value })}
          className="bg-white/70 border-slate-200"
          dir="rtl"
        />
      </div>
      <div className="space-y-1">
        <Label>{t("admin.footerTextEn")}</Label>
        <Input
          value={form.footerTextEn}
          onChange={(e) => setForm({ ...form, footerTextEn: e.target.value })}
          className="bg-white/70 border-slate-200"
          dir="ltr"
        />
      </div>
      <div className="flex items-center justify-between pt-2">
        <div>
          <Label className="font-medium">{t("admin.registrationOpen")}</Label>
          <p className="text-xs text-muted-foreground">{t("admin.registrationOpenHint")}</p>
        </div>
        <Switch
          checked={form.registrationOpen}
          onCheckedChange={(v) => setForm({ ...form, registrationOpen: v })}
        />
      </div>
      <Button onClick={handleSave} disabled={update.isPending} className="w-full">
        {update.isPending ? t("common.loading") : t("common.save")}
      </Button>
    </div>
  );
}

function ServiceImageUploader({
  value,
  onChange,
}: {
  value: string;
  onChange: (url: string) => void;
}) {
  const { t } = useTranslation();
  const { toast } = useToast();
  const { uploadFile, isUploading, progress } = useUpload({
    onSuccess: (resp: UploadResponse) => {
      onChange(resp.objectPath);
    },
    onError: (err: Error) => {
      toast({ title: t("admin.uploadFailed"), description: err.message, variant: "destructive" });
    },
  });

  return (
    <div className="space-y-2">
      <div className="flex items-center gap-2">
        <label className="cursor-pointer">
          <input
            type="file"
            accept="image/*"
            disabled={isUploading}
            className="hidden"
            onChange={(e) => {
              const file = e.target.files?.[0];
              if (file) {
                void uploadFile(file);
              }
              e.currentTarget.value = "";
            }}
          />
          <span className="inline-flex items-center gap-2 px-3 py-2 rounded-lg border border-slate-200 bg-white text-sm text-slate-700 hover:bg-slate-50 transition">
            {isUploading ? <Loader2 size={16} className="animate-spin" /> : <Upload size={16} />}
            {isUploading
              ? `${t("admin.uploading")} ${progress}%`
              : value
                ? t("admin.replaceImage")
                : t("admin.uploadImage")}
          </span>
        </label>
        {value && !isUploading && (
          <button
            type="button"
            onClick={() => onChange("")}
            className="px-3 py-2 rounded-lg border border-slate-200 bg-white text-sm text-slate-500 hover:text-destructive hover:border-destructive/40"
          >
            <Trash2 size={14} />
          </button>
        )}
      </div>
      {value && (
        <div className="rounded-xl overflow-hidden border border-slate-200 bg-slate-50">
          <img
            src={resolveServiceImageUrl(value) ?? ""}
            alt=""
            className="w-full h-32 object-cover"
            onError={(e) => { (e.currentTarget as HTMLImageElement).style.display = "none"; }}
          />
        </div>
      )}
    </div>
  );
}

function DashboardSection({
  apps,
  services,
  users,
  appSettings,
  adminStats,
  adminStatsErrorMessage,
  lang,
  statsRange,
  onStatsRangeChange,
  customFrom,
  customTo,
  onCustomFromChange,
  onCustomToChange,
  onApplyCustom,
  statsQueryParams,
  onSelectApp,
  onSelectUser,
}: {
  apps: App[] | undefined;
  services: Service[] | undefined;
  users: UserProfile[] | undefined;
  appSettings: AppSettings | undefined;
  adminStats: AdminStats | undefined;
  adminStatsErrorMessage: string | null;
  lang: string;
  statsRange: "7d" | "30d" | "90d" | "custom";
  onStatsRangeChange: (range: "7d" | "30d" | "90d" | "custom") => void;
  customFrom: string;
  customTo: string;
  onCustomFromChange: (v: string) => void;
  onCustomToChange: (v: string) => void;
  onApplyCustom: () => void;
  statsQueryParams: GetAdminStatsParams;
  onSelectApp: (appId: number) => void;
  onSelectUser: (userId: number) => void;
}) {
  const { t } = useTranslation();
  const [drillIn, setDrillIn] = useState<
    | { kind: "app"; appId: number }
    | { kind: "user"; userId: number }
    | null
  >(null);

  const totalApps = apps?.length ?? 0;
  const totalServices = services?.length ?? 0;
  const totalUsers = users?.length ?? 0;
  const registrationOpen = appSettings?.registrationOpen ?? false;

  const latestUser = users
    ? [...users].sort((a, b) => (b.createdAt ?? "").localeCompare(a.createdAt ?? ""))[0]
    : undefined;
  const latestApp = apps
    ? [...apps].sort((a, b) => (b.createdAt ?? "").localeCompare(a.createdAt ?? ""))[0]
    : undefined;

  const formatDate = (iso?: string) => {
    if (!iso) return "";
    try {
      return formatDateUtil(iso, lang, { year: "numeric", month: "short", day: "numeric" });
    } catch {
      return "";
    }
  };

  const stats: { label: string; value: string; Icon: typeof Grid2X2; tint: string }[] = [
    { label: t("admin.dashboard.totalApps"), value: String(totalApps), Icon: Grid2X2, tint: "text-indigo-500 bg-indigo-100/70" },
    { label: t("admin.dashboard.totalServices"), value: String(totalServices), Icon: Coffee, tint: "text-amber-600 bg-amber-100/70" },
    { label: t("admin.dashboard.totalUsers"), value: String(totalUsers), Icon: UsersIcon, tint: "text-emerald-600 bg-emerald-100/70" },
    {
      label: t("admin.dashboard.registration"),
      value: registrationOpen ? t("admin.dashboard.open") : t("admin.dashboard.closed"),
      Icon: Shield,
      tint: registrationOpen ? "text-green-600 bg-green-100/70" : "text-rose-600 bg-rose-100/70",
    },
  ];

  const newUsers = adminStats?.newUsersInRange ?? 0;
  const prevUsers = adminStats?.newUsersPrevRange ?? 0;
  const userDelta = newUsers - prevUsers;
  const userDeltaPct = prevUsers > 0 ? Math.round((userDelta / prevUsers) * 100) : null;
  const activeServices = adminStats?.activeServices ?? 0;
  const inactiveServices = adminStats?.inactiveServices ?? 0;
  const signups = adminStats?.signupsByDay ?? [];
  const maxSignup = Math.max(1, ...signups.map((s) => s.count));
  const opens = adminStats?.appOpensByDay ?? [];
  const maxOpens = Math.max(1, ...opens.map((s) => s.count));
  const opensTotal = adminStats?.appOpensInRange ?? 0;
  const opensPrev = adminStats?.appOpensPrevRange ?? 0;
  const opensDelta = opensTotal - opensPrev;
  const opensDeltaPct = opensPrev > 0 ? Math.round((opensDelta / opensPrev) * 100) : null;
  const servicesCreated = adminStats?.servicesCreatedByDay ?? [];
  const maxServicesCreated = Math.max(1, ...servicesCreated.map((s) => s.count));
  const servicesCreatedTotal = adminStats?.servicesCreatedInRange ?? 0;
  const topApps = adminStats?.topApps ?? [];
  const maxTopAppCount = Math.max(1, ...topApps.map((a) => a.count));
  const mostActiveUsers = adminStats?.mostActiveUsers ?? [];
  const maxActiveUserCount = Math.max(1, ...mostActiveUsers.map((u) => u.count));
  const rangeDays = adminStats?.rangeDays ?? (statsRange === "90d" ? 90 : statsRange === "30d" ? 30 : statsRange === "custom" ? 7 : 7);
  const formatRangeDate = (iso: string) => {
    try {
      return formatDateUtil(iso + "T00:00:00Z", lang, { month: "short", day: "numeric" });
    } catch {
      return iso;
    }
  };
  const rangeLabel =
    statsRange === "custom" && adminStats?.rangeFrom && adminStats?.rangeTo
      ? t("admin.dashboard.range.customLabel", {
          from: formatRangeDate(adminStats.rangeFrom),
          to: formatRangeDate(adminStats.rangeTo),
        })
      : t(`admin.dashboard.range.${statsRange}`);
  const prevRangeLabel =
    statsRange === "custom"
      ? t("admin.dashboard.prevRange.custom", { count: rangeDays })
      : t(`admin.dashboard.prevRange.${statsRange}`);
  const dayLabel = (iso: string) => {
    try {
      return formatWeekdayUtil(new Date(iso + "T00:00:00Z"), lang, "short");
    } catch {
      return "";
    }
  };

  const showCounts = rangeDays <= 14;
  const labelStride = rangeDays <= 7 ? 1 : rangeDays <= 30 ? 5 : 15;
  const gapClass = rangeDays <= 7 ? "gap-1.5" : rangeDays <= 30 ? "gap-1" : "gap-px";
  const monthDayLabel = (iso: string) => {
    try {
      return formatDateUtil(iso + "T00:00:00Z", lang, { month: "short", day: "numeric" });
    } catch {
      return iso.slice(5);
    }
  };

  const renderChart = (
    title: string,
    series: { date: string; count: number }[],
    max: number,
    accent: string,
    subtitle?: string,
  ) => (
    <div className="glass-panel rounded-2xl p-4 space-y-3">
      <div className="flex items-center justify-between gap-2">
        <div className="text-sm font-semibold text-foreground truncate">{title}</div>
        <div className="text-xs text-muted-foreground shrink-0">{subtitle ?? rangeLabel}</div>
      </div>
      <div className={cn("flex items-end justify-between h-28 px-1", gapClass)} dir="ltr">
        {series.map((s, idx) => {
          const heightPct = (s.count / max) * 100;
          const showLabel =
            rangeDays <= 7
              ? true
              : idx === series.length - 1 || (series.length - 1 - idx) % labelStride === 0;
          return (
            <div key={s.date} className="flex-1 flex flex-col items-center gap-1.5 min-w-0">
              {showCounts && (
                <div className="text-[10px] font-medium text-foreground leading-none">{s.count}</div>
              )}
              <div className="w-full flex-1 flex items-end">
                <div
                  className={cn("w-full rounded-t-md transition-all bg-gradient-to-t", accent)}
                  style={{ height: `${Math.max(heightPct, s.count > 0 ? 6 : 2)}%` }}
                />
              </div>
              <div className="text-[10px] text-muted-foreground truncate w-full text-center">
                {showLabel ? (rangeDays <= 14 ? dayLabel(s.date) : monthDayLabel(s.date)) : ""}
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );

  const rangeOptions: { value: "7d" | "30d" | "90d" | "custom"; label: string }[] = [
    { value: "7d", label: t("admin.dashboard.range.7d") },
    { value: "30d", label: t("admin.dashboard.range.30d") },
    { value: "90d", label: t("admin.dashboard.range.90d") },
    { value: "custom", label: t("admin.dashboard.range.custom") },
  ];

  const customInputsValid =
    /^\d{4}-\d{2}-\d{2}$/.test(customFrom) &&
    /^\d{4}-\d{2}-\d{2}$/.test(customTo) &&
    customFrom <= customTo;

  return (
    <div className="space-y-4">
      <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
        {stats.map(({ label, value, Icon, tint }) => (
          <div key={label} className="glass-panel rounded-2xl p-4 flex items-center gap-3">
            <div className={cn("w-10 h-10 rounded-xl flex items-center justify-center shrink-0", tint)}>
              <Icon size={20} />
            </div>
            <div className="min-w-0">
              <div className="text-xs text-muted-foreground truncate">{label}</div>
              <div className="text-xl font-semibold text-foreground truncate">{value}</div>
            </div>
          </div>
        ))}
      </div>

      <div className="glass-panel rounded-2xl p-3 space-y-3">
        <div className="flex items-center justify-between gap-3 flex-wrap">
          <div className="text-sm font-semibold text-foreground">
            {t("admin.dashboard.trends")}
          </div>
          <div className="inline-flex rounded-xl bg-slate-100/80 p-1" role="group" aria-label={t("admin.dashboard.rangeSelector")}>
            {rangeOptions.map((opt) => {
              const active = statsRange === opt.value;
              return (
                <button
                  key={opt.value}
                  type="button"
                  onClick={() => onStatsRangeChange(opt.value)}
                  aria-pressed={active}
                  className={cn(
                    "px-3 py-1.5 text-xs rounded-lg transition-all font-medium",
                    active
                      ? "bg-white text-foreground shadow-sm"
                      : "text-muted-foreground hover:text-foreground",
                  )}
                >
                  {opt.label}
                </button>
              );
            })}
          </div>
        </div>
        {statsRange === "custom" && (
          <div className="flex items-end gap-2 flex-wrap" dir="ltr">
            <div className="space-y-1">
              <Label className="text-xs text-muted-foreground">
                {t("admin.dashboard.customRange.from")}
              </Label>
              <Input
                type="date"
                value={customFrom}
                max={customTo || undefined}
                onChange={(e) => onCustomFromChange(e.target.value)}
                className="bg-white/70 border-slate-200 h-9 text-sm"
              />
            </div>
            <div className="space-y-1">
              <Label className="text-xs text-muted-foreground">
                {t("admin.dashboard.customRange.to")}
              </Label>
              <Input
                type="date"
                value={customTo}
                min={customFrom || undefined}
                onChange={(e) => onCustomToChange(e.target.value)}
                className="bg-white/70 border-slate-200 h-9 text-sm"
              />
            </div>
            <Button
              size="sm"
              onClick={onApplyCustom}
              disabled={!customInputsValid}
              className="h-9"
            >
              {t("admin.dashboard.customRange.apply")}
            </Button>
            {!customInputsValid && (
              <div className="text-xs text-destructive">
                {t("admin.dashboard.customRange.invalid")}
              </div>
            )}
          </div>
        )}
        {adminStatsErrorMessage && (
          <div
            role="alert"
            className="text-xs text-destructive bg-destructive/10 border border-destructive/30 rounded-lg px-3 py-2"
          >
            {t("admin.dashboard.customRange.loadError")}
            <div className="mt-0.5 font-mono text-[11px] opacity-80 break-words">
              {adminStatsErrorMessage}
            </div>
          </div>
        )}
      </div>

      <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
        <div className="glass-panel rounded-2xl p-4 flex items-center gap-3">
          <div className="w-10 h-10 rounded-xl flex items-center justify-center shrink-0 text-sky-600 bg-sky-100/70">
            <UserPlus size={20} />
          </div>
          <div className="min-w-0 flex-1">
            <div className="text-xs text-muted-foreground truncate">
              {t("admin.dashboard.newUsersInRange", { range: rangeLabel })}
            </div>
            <div className="flex items-baseline gap-2">
              <div className="text-xl font-semibold text-foreground">{newUsers}</div>
              {userDelta !== 0 && (
                <div
                  className={cn(
                    "text-xs flex items-center gap-0.5 font-medium",
                    userDelta > 0 ? "text-emerald-600" : "text-rose-600",
                  )}
                >
                  {userDelta > 0 ? <TrendingUp size={12} /> : <TrendingDown size={12} />}
                  {userDeltaPct !== null
                    ? `${userDelta > 0 ? "+" : ""}${userDeltaPct}%`
                    : `${userDelta > 0 ? "+" : ""}${userDelta}`}
                </div>
              )}
            </div>
            <div className="text-[11px] text-muted-foreground mt-0.5">
              {t("admin.dashboard.vsPrevRange", { range: prevRangeLabel, count: prevUsers })}
            </div>
          </div>
        </div>

        <div className="glass-panel rounded-2xl p-4 flex items-center gap-3">
          <div className="w-10 h-10 rounded-xl flex items-center justify-center shrink-0 text-amber-600 bg-amber-100/70">
            <Activity size={20} />
          </div>
          <div className="min-w-0 flex-1">
            <div className="text-xs text-muted-foreground truncate">{t("admin.dashboard.activeServices")}</div>
            <div className="flex items-baseline gap-2">
              <div className="text-xl font-semibold text-foreground">{activeServices}</div>
              <div className="text-xs text-muted-foreground">
                / {activeServices + inactiveServices}
              </div>
            </div>
            <div className="text-[11px] text-muted-foreground mt-0.5">
              {t("admin.dashboard.inactiveServices", { count: inactiveServices })}
            </div>
          </div>
        </div>
      </div>

      {renderChart(
        t("admin.dashboard.signupsTrend"),
        signups,
        maxSignup,
        "from-primary/40 to-primary",
      )}

      <div className="grid grid-cols-1 lg:grid-cols-2 gap-3">
        {renderChart(
          t("admin.dashboard.appOpensTrend"),
          opens,
          maxOpens,
          "from-sky-300/40 to-sky-500",
          t("admin.dashboard.appOpensSummaryRanged", {
            count: opensTotal,
            range: rangeLabel,
            delta:
              opensDelta === 0
                ? "±0"
                : opensDeltaPct !== null
                  ? `${opensDelta > 0 ? "+" : ""}${opensDeltaPct}%`
                  : `${opensDelta > 0 ? "+" : ""}${opensDelta}`,
          }),
        )}
        {renderChart(
          t("admin.dashboard.servicesCreatedTrend"),
          servicesCreated,
          maxServicesCreated,
          "from-amber-300/40 to-amber-500",
          t("admin.dashboard.servicesCreatedSummaryRanged", { count: servicesCreatedTotal, range: rangeLabel }),
        )}
      </div>

      <div className="grid grid-cols-1 lg:grid-cols-2 gap-3">
        <div className="glass-panel rounded-2xl p-4 space-y-3">
          <div className="flex items-center justify-between gap-2">
            <div className="text-sm font-semibold text-foreground truncate">
              {t("admin.dashboard.topApps")}
            </div>
            <div className="text-xs text-muted-foreground shrink-0">
              {t("admin.dashboard.topAppsSubtitle", { range: rangeLabel })}
            </div>
          </div>
          {topApps.length === 0 ? (
            <div className="text-xs text-muted-foreground py-4 text-center">
              {t("admin.dashboard.leaderboardEmpty")}
            </div>
          ) : (
            <ol className="space-y-2">
              {topApps.map((app, idx) => {
                const widthPct = (app.count / maxTopAppCount) * 100;
                const appName = lang === "ar" ? app.nameAr : app.nameEn;
                return (
                  <li key={app.appId}>
                    <button
                      type="button"
                      onClick={() => setDrillIn({ kind: "app", appId: app.appId })}
                      aria-label={t("admin.dashboard.viewAppDetails", { name: appName })}
                      className="w-full text-start flex items-center gap-3 p-2 rounded-xl bg-white/60 border border-slate-200/60 hover:bg-white hover:border-slate-300 hover:shadow-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-primary transition-all cursor-pointer"
                    >
                      <div className="text-xs font-semibold text-muted-foreground w-5 text-center shrink-0">
                        {idx + 1}
                      </div>
                      <div
                        className="w-8 h-8 rounded-lg shrink-0"
                        style={{ backgroundColor: `${app.color}40` }}
                      />
                      <div className="min-w-0 flex-1">
                        <div className="flex items-center justify-between gap-2">
                          <div className="text-sm font-medium text-foreground truncate">
                            {appName}
                          </div>
                          <div className="text-xs font-semibold text-foreground shrink-0 tabular-nums">
                            {app.count}
                          </div>
                        </div>
                        <div className="mt-1 h-1.5 rounded-full bg-slate-100 overflow-hidden" dir="ltr">
                          <div
                            className="h-full rounded-full bg-gradient-to-r from-sky-300 to-sky-500"
                            style={{ width: `${widthPct}%` }}
                          />
                        </div>
                      </div>
                    </button>
                  </li>
                );
              })}
            </ol>
          )}
        </div>

        <div className="glass-panel rounded-2xl p-4 space-y-3">
          <div className="flex items-center justify-between gap-2">
            <div className="text-sm font-semibold text-foreground truncate">
              {t("admin.dashboard.mostActiveUsers")}
            </div>
            <div className="text-xs text-muted-foreground shrink-0">
              {t("admin.dashboard.mostActiveUsersSubtitle", { range: rangeLabel })}
            </div>
          </div>
          {mostActiveUsers.length === 0 ? (
            <div className="text-xs text-muted-foreground py-4 text-center">
              {t("admin.dashboard.leaderboardEmpty")}
            </div>
          ) : (
            <ol className="space-y-2">
              {mostActiveUsers.map((u, idx) => {
                const widthPct = (u.count / maxActiveUserCount) * 100;
                const displayName =
                  (lang === "ar" ? u.displayNameAr : u.displayNameEn) ?? u.username;
                const initial = (displayName || u.username || "?").trim().charAt(0).toUpperCase();
                const avatarSrc = resolveServiceImageUrl(u.avatarUrl);
                return (
                  <li key={u.userId}>
                    <button
                      type="button"
                      onClick={() => setDrillIn({ kind: "user", userId: u.userId })}
                      aria-label={t("admin.dashboard.viewUserDetails", { name: displayName })}
                      className="w-full text-start flex items-center gap-3 p-2 rounded-xl bg-white/60 border border-slate-200/60 hover:bg-white hover:border-slate-300 hover:shadow-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-primary transition-all cursor-pointer"
                    >
                      <div className="text-xs font-semibold text-muted-foreground w-5 text-center shrink-0">
                        {idx + 1}
                      </div>
                      <LeaderboardAvatar src={avatarSrc} initial={initial} alt={displayName} />
                      <div className="min-w-0 flex-1">
                        <div className="flex items-center justify-between gap-2">
                          <div className="min-w-0">
                            <div className="text-sm font-medium text-foreground truncate">
                              {displayName}
                            </div>
                            {displayName !== u.username && (
                              <div className="text-[11px] text-muted-foreground truncate">
                                @{u.username}
                              </div>
                            )}
                          </div>
                          <div className="text-xs font-semibold text-foreground shrink-0 tabular-nums">
                            {u.count}
                          </div>
                        </div>
                        <div className="mt-1 h-1.5 rounded-full bg-slate-100 overflow-hidden" dir="ltr">
                          <div
                            className="h-full rounded-full bg-gradient-to-r from-emerald-300 to-emerald-500"
                            style={{ width: `${widthPct}%` }}
                          />
                        </div>
                      </div>
                    </button>
                  </li>
                );
              })}
            </ol>
          )}
        </div>
      </div>

      <div className="glass-panel rounded-2xl p-4 space-y-3">
        <div className="text-sm font-semibold text-foreground">
          {t("admin.dashboard.recentActivity")}
        </div>
        <div className="space-y-2">
          <div className="flex items-center gap-3 p-3 rounded-xl bg-white/60 border border-slate-200/60">
            <div className="w-9 h-9 rounded-lg flex items-center justify-center bg-emerald-100/70 text-emerald-600 shrink-0">
              <UsersIcon size={16} />
            </div>
            <div className="min-w-0 flex-1">
              <div className="text-xs text-muted-foreground">{t("admin.dashboard.latestUser")}</div>
              <div className="text-sm font-medium text-foreground truncate">
                {latestUser?.username ?? t("admin.dashboard.none")}
              </div>
            </div>
            {latestUser?.createdAt && (
              <div className="text-xs text-muted-foreground shrink-0">{formatDate(latestUser.createdAt)}</div>
            )}
          </div>
          <div className="flex items-center gap-3 p-3 rounded-xl bg-white/60 border border-slate-200/60">
            <div className="w-9 h-9 rounded-lg flex items-center justify-center bg-indigo-100/70 text-indigo-600 shrink-0">
              <Grid2X2 size={16} />
            </div>
            <div className="min-w-0 flex-1">
              <div className="text-xs text-muted-foreground">{t("admin.dashboard.latestApp")}</div>
              <div className="text-sm font-medium text-foreground truncate">
                {latestApp ? (lang === "ar" ? latestApp.nameAr : latestApp.nameEn) : t("admin.dashboard.none")}
              </div>
            </div>
            {latestApp?.createdAt && (
              <div className="text-xs text-muted-foreground shrink-0">{formatDate(latestApp.createdAt)}</div>
            )}
          </div>
        </div>
      </div>

      {drillIn?.kind === "app" && (
        <AppOpensDrillIn
          appId={drillIn.appId}
          lang={lang}
          rangeLabel={rangeLabel}
          statsQueryParams={statsQueryParams}
          onClose={() => setDrillIn(null)}
          onJump={(id) => {
            setDrillIn(null);
            onSelectApp(id);
          }}
          onJumpUser={(id) => {
            setDrillIn(null);
            onSelectUser(id);
          }}
        />
      )}
      {drillIn?.kind === "user" && (
        <UserOpensDrillIn
          userId={drillIn.userId}
          lang={lang}
          rangeLabel={rangeLabel}
          statsQueryParams={statsQueryParams}
          onClose={() => setDrillIn(null)}
          onJump={(id) => {
            setDrillIn(null);
            onSelectUser(id);
          }}
          onJumpApp={(id) => {
            setDrillIn(null);
            onSelectApp(id);
          }}
        />
      )}
    </div>
  );
}

function DrillInShell({
  title,
  subtitle,
  onClose,
  children,
  footer,
}: {
  title: string;
  subtitle: string;
  onClose: () => void;
  children: ReactNode;
  footer?: ReactNode;
}) {
  const { t } = useTranslation();
  useEffect(() => {
    const handler = (e: KeyboardEvent) => {
      if (e.key === "Escape") onClose();
    };
    window.addEventListener("keydown", handler);
    return () => window.removeEventListener("keydown", handler);
  }, [onClose]);
  return (
    <div
      className="fixed inset-0 bg-slate-900/30 backdrop-blur-sm z-50 flex items-center justify-center p-4"
      role="dialog"
      aria-modal="true"
      onClick={onClose}
    >
      <div
        className="glass-panel rounded-3xl p-5 w-full max-w-md max-h-[85vh] flex flex-col gap-3"
        onClick={(e) => e.stopPropagation()}
      >
        <div className="flex items-start justify-between gap-3">
          <div className="min-w-0">
            <div className="text-base font-semibold text-foreground truncate">{title}</div>
            <div className="text-xs text-muted-foreground truncate">{subtitle}</div>
          </div>
          <button
            type="button"
            onClick={onClose}
            className="text-xs text-muted-foreground hover:text-foreground px-2 py-1 rounded-lg hover:bg-slate-100"
            aria-label={t("common.close")}
          >
            {t("common.close")}
          </button>
        </div>
        <div className="flex-1 min-h-0 overflow-y-auto pr-1 -mr-1">{children}</div>
        {footer && <div className="pt-2 border-t border-slate-200/70">{footer}</div>}
      </div>
    </div>
  );
}

function formatOpenTimestamp(iso: string, lang: string): string {
  try {
    return formatDateUtil(iso, lang, {
      month: "short",
      day: "numeric",
      hour: "2-digit",
      minute: "2-digit",
    });
  } catch {
    return iso;
  }
}

function AppOpensDrillIn({
  appId,
  lang,
  rangeLabel,
  statsQueryParams,
  onClose,
  onJump,
  onJumpUser,
}: {
  appId: number;
  lang: string;
  rangeLabel: string;
  statsQueryParams: GetAdminStatsParams;
  onClose: () => void;
  onJump: (appId: number) => void;
  onJumpUser: (userId: number) => void;
}) {
  const { t } = useTranslation();
  const { data, isLoading, error } = useGetAdminAppOpensByApp(appId, statsQueryParams, {
    query: {
      queryKey: getGetAdminAppOpensByAppQueryKey(appId, statsQueryParams),
      retry: false,
    },
  });
  type ExtraOpen = AdminAppOpensByApp["opens"][number];
  const [extraOpens, setExtraOpens] = useState<ExtraOpen[]>([]);
  const [extraNextOffset, setExtraNextOffset] = useState<number | null>(null);
  const [hasExtras, setHasExtras] = useState(false);
  const [isLoadingMore, setIsLoadingMore] = useState(false);
  const [loadMoreError, setLoadMoreError] = useState<string | null>(null);

  const paramsKey = JSON.stringify(statsQueryParams);
  useEffect(() => {
    setExtraOpens([]);
    setExtraNextOffset(null);
    setHasExtras(false);
    setLoadMoreError(null);
    setIsLoadingMore(false);
  }, [appId, paramsKey]);

  const errorMessage = error ? (error as { message?: string }).message ?? null : null;
  const appName = data ? (lang === "ar" ? data.nameAr : data.nameEn) : "";
  const title = appName
    ? t("admin.dashboard.drillIn.appTitle", { name: appName })
    : t("admin.dashboard.drillIn.loading");
  const subtitle = data
    ? t("admin.dashboard.drillIn.subtitle", { count: data.totalCount, range: rangeLabel })
    : rangeLabel;
  const opens = data ? [...data.opens, ...extraOpens] : [];
  const nextOffset = hasExtras ? extraNextOffset : data?.nextOffset ?? null;

  const onLoadMore = async () => {
    if (nextOffset === null || isLoadingMore) return;
    setIsLoadingMore(true);
    setLoadMoreError(null);
    try {
      const next = await getAdminAppOpensByApp(appId, {
        ...statsQueryParams,
        offset: nextOffset,
      });
      setExtraOpens((prev) => [...prev, ...next.opens]);
      setExtraNextOffset(next.nextOffset);
      setHasExtras(true);
    } catch (e) {
      setLoadMoreError((e as { message?: string }).message ?? "Error");
    } finally {
      setIsLoadingMore(false);
    }
  };

  return (
    <DrillInShell
      title={title}
      subtitle={subtitle}
      onClose={onClose}
      footer={
        <div className="flex justify-end">
          <Button size="sm" variant="outline" onClick={() => onJump(appId)}>
            {t("admin.dashboard.drillIn.editApp")}
          </Button>
        </div>
      }
    >
      {isLoading ? (
        <div className="flex items-center justify-center py-8 text-muted-foreground">
          <Loader2 size={18} className="animate-spin" />
        </div>
      ) : errorMessage ? (
        <div className="text-xs text-destructive bg-destructive/10 border border-destructive/30 rounded-lg px-3 py-2">
          {t("admin.dashboard.drillIn.loadError")}
          <div className="mt-0.5 font-mono text-[11px] opacity-80 break-words">{errorMessage}</div>
        </div>
      ) : !data || opens.length === 0 ? (
        <div className="text-xs text-muted-foreground py-8 text-center">
          {t("admin.dashboard.drillIn.empty")}
        </div>
      ) : (
        <>
          <ol className="space-y-1.5">
            {opens.map((o) => {
              const display =
                (lang === "ar" ? o.displayNameAr : o.displayNameEn) ?? o.username;
              const initial = (display || o.username || "?").trim().charAt(0).toUpperCase();
              const avatar = resolveServiceImageUrl(o.avatarUrl);
              return (
                <li key={o.id}>
                  <button
                    type="button"
                    onClick={() => onJumpUser(o.userId)}
                    aria-label={t("admin.dashboard.viewUserDetails", { name: display })}
                    className="flex items-center gap-3 p-2 rounded-xl bg-white/60 border border-slate-200/60 w-full text-start hover:bg-white hover:border-slate-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 transition-colors"
                  >
                    <LeaderboardAvatar src={avatar} initial={initial} alt={display} />
                    <div className="min-w-0 flex-1">
                      <div className="text-sm font-medium text-foreground truncate">{display}</div>
                      {display !== o.username && (
                        <div className="text-[11px] text-muted-foreground truncate">@{o.username}</div>
                      )}
                    </div>
                    <div className="text-[11px] text-muted-foreground shrink-0 tabular-nums">
                      {formatOpenTimestamp(o.createdAt, lang)}
                    </div>
                  </button>
                </li>
              );
            })}
          </ol>
          <LoadMoreSection
            shownCount={opens.length}
            totalCount={data.totalCount}
            nextOffset={nextOffset}
            isLoadingMore={isLoadingMore}
            loadMoreError={loadMoreError}
            onLoadMore={onLoadMore}
          />
        </>
      )}
    </DrillInShell>
  );
}

function LoadMoreSection({
  shownCount,
  totalCount,
  nextOffset,
  isLoadingMore,
  loadMoreError,
  onLoadMore,
}: {
  shownCount: number;
  totalCount: number;
  nextOffset: number | null;
  isLoadingMore: boolean;
  loadMoreError: string | null;
  onLoadMore: () => void;
}) {
  const { t } = useTranslation();
  return (
    <div className="mt-3 flex flex-col items-center gap-1.5">
      <div className="text-[11px] text-muted-foreground tabular-nums">
        {t("admin.dashboard.drillIn.shownOf", { shown: shownCount, total: totalCount })}
      </div>
      {nextOffset !== null && (
        <Button
          size="sm"
          variant="outline"
          onClick={onLoadMore}
          disabled={isLoadingMore}
          className="gap-2"
        >
          {isLoadingMore && <Loader2 size={14} className="animate-spin" />}
          {t("admin.dashboard.drillIn.loadMore")}
        </Button>
      )}
      {loadMoreError && (
        <div className="text-[11px] text-destructive">
          {t("admin.dashboard.drillIn.loadMoreError")}
        </div>
      )}
    </div>
  );
}

function UserOpensDrillIn({
  userId,
  lang,
  rangeLabel,
  statsQueryParams,
  onClose,
  onJump,
  onJumpApp,
}: {
  userId: number;
  lang: string;
  rangeLabel: string;
  statsQueryParams: GetAdminStatsParams;
  onClose: () => void;
  onJump: (userId: number) => void;
  onJumpApp: (appId: number) => void;
}) {
  const { t } = useTranslation();
  const { data, isLoading, error } = useGetAdminAppOpensByUser(userId, statsQueryParams, {
    query: {
      queryKey: getGetAdminAppOpensByUserQueryKey(userId, statsQueryParams),
      retry: false,
    },
  });
  type ExtraOpen = AdminAppOpensByUser["opens"][number];
  const [extraOpens, setExtraOpens] = useState<ExtraOpen[]>([]);
  const [extraNextOffset, setExtraNextOffset] = useState<number | null>(null);
  const [hasExtras, setHasExtras] = useState(false);
  const [isLoadingMore, setIsLoadingMore] = useState(false);
  const [loadMoreError, setLoadMoreError] = useState<string | null>(null);

  const paramsKey = JSON.stringify(statsQueryParams);
  useEffect(() => {
    setExtraOpens([]);
    setExtraNextOffset(null);
    setHasExtras(false);
    setLoadMoreError(null);
    setIsLoadingMore(false);
  }, [userId, paramsKey]);

  const errorMessage = error ? (error as { message?: string }).message ?? null : null;
  const displayName = data
    ? (lang === "ar" ? data.displayNameAr : data.displayNameEn) ?? data.username
    : "";
  const opens = data ? [...data.opens, ...extraOpens] : [];
  const nextOffset = hasExtras ? extraNextOffset : data?.nextOffset ?? null;

  const onLoadMore = async () => {
    if (nextOffset === null || isLoadingMore) return;
    setIsLoadingMore(true);
    setLoadMoreError(null);
    try {
      const next = await getAdminAppOpensByUser(userId, {
        ...statsQueryParams,
        offset: nextOffset,
      });
      setExtraOpens((prev) => [...prev, ...next.opens]);
      setExtraNextOffset(next.nextOffset);
      setHasExtras(true);
    } catch (e) {
      setLoadMoreError((e as { message?: string }).message ?? "Error");
    } finally {
      setIsLoadingMore(false);
    }
  };
  const title = displayName
    ? t("admin.dashboard.drillIn.userTitle", { name: displayName })
    : t("admin.dashboard.drillIn.loading");
  const subtitle = data
    ? t("admin.dashboard.drillIn.subtitle", { count: data.totalCount, range: rangeLabel })
    : rangeLabel;

  return (
    <DrillInShell
      title={title}
      subtitle={subtitle}
      onClose={onClose}
      footer={
        <div className="flex justify-end">
          <Button size="sm" variant="outline" onClick={() => onJump(userId)}>
            {t("admin.dashboard.drillIn.viewUser")}
          </Button>
        </div>
      }
    >
      {isLoading ? (
        <div className="flex items-center justify-center py-8 text-muted-foreground">
          <Loader2 size={18} className="animate-spin" />
        </div>
      ) : errorMessage ? (
        <div className="text-xs text-destructive bg-destructive/10 border border-destructive/30 rounded-lg px-3 py-2">
          {t("admin.dashboard.drillIn.loadError")}
          <div className="mt-0.5 font-mono text-[11px] opacity-80 break-words">{errorMessage}</div>
        </div>
      ) : !data || opens.length === 0 ? (
        <div className="text-xs text-muted-foreground py-8 text-center">
          {t("admin.dashboard.drillIn.empty")}
        </div>
      ) : (
        <>
          <ol className="space-y-1.5">
            {opens.map((o) => {
              const name = lang === "ar" ? o.nameAr : o.nameEn;
              return (
                <li key={o.id}>
                  <button
                    type="button"
                    onClick={() => onJumpApp(o.appId)}
                    aria-label={t("admin.dashboard.viewAppDetails", { name })}
                    className="flex items-center gap-3 p-2 rounded-xl bg-white/60 border border-slate-200/60 w-full text-start hover:bg-white hover:border-slate-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 transition-colors"
                  >
                    <div
                      className="w-8 h-8 rounded-lg shrink-0"
                      style={{ backgroundColor: `${o.color}40` }}
                    />
                    <div className="min-w-0 flex-1">
                      <div className="text-sm font-medium text-foreground truncate">{name}</div>
                    </div>
                    <div className="text-[11px] text-muted-foreground shrink-0 tabular-nums">
                      {formatOpenTimestamp(o.createdAt, lang)}
                    </div>
                  </button>
                </li>
              );
            })}
          </ol>
          <LoadMoreSection
            shownCount={opens.length}
            totalCount={data.totalCount}
            nextOffset={nextOffset}
            isLoadingMore={isLoadingMore}
            loadMoreError={loadMoreError}
            onLoadMore={onLoadMore}
          />
        </>
      )}
    </DrillInShell>
  );
}

// ===== Users Management Panel =====
type UserSortKey = "username" | "email" | "createdAt";

function UsersPanel({
  currentUserId,
  highlightedUserId,
  userRowRefs,
  onIssueResetLink,
}: {
  currentUserId: number;
  highlightedUserId: number | null;
  userRowRefs: React.MutableRefObject<Map<number, HTMLDivElement>>;
  onIssueResetLink: (userId: number, username: string) => void;
}) {
  const { t } = useTranslation();
  const { toast } = useToast();
  const queryClient = useQueryClient();
  const [search, setSearch] = useState("");
  const [groupFilter, setGroupFilter] = useState<number | "all">("all");
  const [statusFilter, setStatusFilter] = useState<"all" | "active" | "inactive">("all");
  const [sortKey, setSortKey] = useState<UserSortKey>("username");
  const [sortDir, setSortDir] = useState<"asc" | "desc">("asc");
  const [editingUser, setEditingUser] = useState<UserProfile | null>(null);
  const [userDeleteTarget, setUserDeleteTarget] = useState<UserProfile | null>(null);
  const [userDeleteConflict, setUserDeleteConflict] = useState<UserDeletionConflict | null>(null);
  const [newUserOpen, setNewUserOpen] = useState(false);
  const [newUserForm, setNewUserForm] = useState<{
    username: string;
    email: string;
    password: string;
    groupIds: number[];
  }>({ username: "", email: "", password: "", groupIds: [] });

  const listParams = useMemo<ListUsersParams>(() => {
    const p: ListUsersParams = {};
    if (search.trim()) p.q = search.trim();
    if (groupFilter !== "all") p.groupId = groupFilter;
    if (statusFilter === "inactive") p.status = ListUsersStatus.disabled;
    else if (statusFilter === "active") p.status = ListUsersStatus.active;
    return p;
  }, [search, groupFilter, statusFilter]);
  const { data: users } = useListUsers(listParams, { query: { queryKey: getListUsersQueryKey(listParams) } });
  const { data: groups } = useListGroups(undefined, { query: { queryKey: getListGroupsQueryKey() } });
  const createUser = useCreateUser();
  const updateUser = useUpdateUser();
  const deleteUser = useDeleteUser();
  const addUserRole = useAddUserRole();
  const removeUserRole = useRemoveUserRole();

  const filtered = (users ?? []).slice().sort((a, b) => {
    const av =
      sortKey === "createdAt" ? a.createdAt : sortKey === "email" ? a.email : a.username;
    const bv =
      sortKey === "createdAt" ? b.createdAt : sortKey === "email" ? b.email : b.username;
    const cmp = String(av).localeCompare(String(bv));
    return sortDir === "asc" ? cmp : -cmp;
  });

  const invalidateUsers = () =>
    queryClient.invalidateQueries({ queryKey: [`/api/users`] });

  const closeUserDeleteDialog = () => {
    setUserDeleteTarget(null);
    setUserDeleteConflict(null);
  };

  const performUserDelete = (target: UserProfile, force: boolean) => {
    deleteUser.mutate(
      { id: target.id, params: force ? { force: true } : undefined },
      {
        onSuccess: () => {
          invalidateUsers();
          toast({ title: t("common.success") });
          closeUserDeleteDialog();
        },
        onError: (err: unknown) => {
          if (err instanceof ApiError && err.status === 409 && err.data) {
            setUserDeleteConflict(err.data as UserDeletionConflict);
            return;
          }
          toast({ title: t("common.error"), variant: "destructive" });
        },
      },
    );
  };

  return (
    <div className="space-y-3">
      <div className="glass-panel rounded-2xl p-3 space-y-2">
        <div className="flex items-center gap-2 flex-wrap">
          <Input
            placeholder={t("admin.users.searchPlaceholder")}
            value={search}
            onChange={(e) => setSearch(e.target.value)}
            className="bg-white/70 border-slate-200 flex-1 min-w-[180px]"
            data-testid="user-search"
          />
          <select
            value={groupFilter === "all" ? "all" : String(groupFilter)}
            onChange={(e) => setGroupFilter(e.target.value === "all" ? "all" : Number(e.target.value))}
            className="bg-white/70 border border-slate-200 rounded-lg px-3 py-2 text-sm"
            data-testid="user-group-filter"
          >
            <option value="all">{t("admin.users.allGroups")}</option>
            {groups?.map((g) => (
              <option key={g.id} value={g.id}>
                {g.name}
              </option>
            ))}
          </select>
          <select
            value={statusFilter}
            onChange={(e) => setStatusFilter(e.target.value as "all" | "active" | "inactive")}
            className="bg-white/70 border border-slate-200 rounded-lg px-3 py-2 text-sm"
            data-testid="user-status-filter"
          >
            <option value="all">{t("admin.users.allStatuses")}</option>
            <option value="active">{t("admin.users.statusActive")}</option>
            <option value="inactive">{t("admin.users.statusInactive")}</option>
          </select>
          <Button size="sm" onClick={() => { setNewUserForm({ username: "", email: "", password: "", groupIds: [] }); setNewUserOpen(true); }}>
            <Plus size={14} className="me-1" />
            {t("admin.addUser")}
          </Button>
        </div>
        <div className="text-xs text-muted-foreground">
          {t("admin.users.showing", { count: filtered.length, total: users?.length ?? 0 })}
        </div>
      </div>

      {/* Table (desktop) / cards (mobile) */}
      <div className="overflow-hidden rounded-2xl glass-panel">
        <div className="hidden md:grid grid-cols-[1fr_1fr_1fr_1fr_auto] gap-3 px-4 py-2 text-xs font-medium text-muted-foreground border-b border-slate-200/70">
          {(["username", "email", "createdAt"] as UserSortKey[]).map((k) => (
            <button
              key={k}
              onClick={() => {
                if (sortKey === k) setSortDir(sortDir === "asc" ? "desc" : "asc");
                else { setSortKey(k); setSortDir("asc"); }
              }}
              className="text-start uppercase tracking-wider hover:text-foreground"
            >
              {t(`admin.users.col.${k}`)}
              {sortKey === k && (sortDir === "asc" ? " ▲" : " ▼")}
            </button>
          ))}
          <div className="uppercase tracking-wider">{t("admin.users.col.groups")}</div>
          <div className="uppercase tracking-wider text-end">{t("admin.users.col.actions")}</div>
        </div>
        <div className="divide-y divide-slate-200/70">
          {filtered.map((u) => (
            <div
              key={u.id}
              ref={(node) => {
                if (node) userRowRefs.current.set(u.id, node);
                else userRowRefs.current.delete(u.id);
              }}
              className={cn(
                "grid grid-cols-1 md:grid-cols-[1fr_1fr_1fr_1fr_auto] gap-2 md:gap-3 px-4 py-3 items-center transition-all",
                highlightedUserId === u.id && "bg-primary/10 ring-2 ring-primary",
              )}
              data-testid={`user-row-${u.id}`}
            >
              <div className="min-w-0">
                <div className="font-medium text-sm text-foreground truncate">
                  {u.username}
                  {!u.isActive && (
                    <span className="ms-2 text-[10px] uppercase px-1.5 py-0.5 rounded-full bg-slate-200 text-muted-foreground">
                      {t("admin.users.statusInactive")}
                    </span>
                  )}
                </div>
                {(u.displayNameAr || u.displayNameEn) && (
                  <div className="text-[11px] text-muted-foreground truncate" data-testid={`user-displayname-${u.id}`}>
                    {u.displayNameAr || u.displayNameEn}
                  </div>
                )}
                {(() => {
                  const parts: string[] = [];
                  if ((u.noteCount ?? 0) > 0) parts.push(t("admin.users.counts.notes", { count: u.noteCount }));
                  if ((u.orderCount ?? 0) > 0) parts.push(t("admin.users.counts.orders", { count: u.orderCount }));
                  if ((u.conversationCount ?? 0) > 0) parts.push(t("admin.users.counts.conversations", { count: u.conversationCount }));
                  if ((u.messageCount ?? 0) > 0) parts.push(t("admin.users.counts.messages", { count: u.messageCount }));
                  if (parts.length === 0) return null;
                  return (
                    <div
                      className="flex flex-wrap items-center gap-x-2 gap-y-0.5 text-[11px] text-muted-foreground pt-0.5"
                      data-testid={`user-counts-${u.id}`}
                    >
                      {parts.map((p, i) => (
                        <span key={i} className="flex items-center gap-2">
                          {i > 0 && <span aria-hidden="true">•</span>}
                          <span>{p}</span>
                        </span>
                      ))}
                    </div>
                  );
                })()}
              </div>
              <div className="min-w-0">
                <div className="text-xs text-muted-foreground truncate">{u.email}</div>
                {u.preferredLanguage && (
                  <div className="text-[11px] uppercase tracking-wide text-muted-foreground" data-testid={`user-lang-${u.id}`}>
                    {u.preferredLanguage}
                  </div>
                )}
              </div>
              <div className="text-xs text-muted-foreground">
                {u.createdAt ? new Date(u.createdAt).toLocaleDateString() : "—"}
              </div>
              <div className="flex flex-wrap gap-1">
                {u.groups?.length ? u.groups.map((g) => (
                  <span key={g.id} className="text-[10px] px-2 py-0.5 rounded-full bg-primary/15 text-primary">
                    {g.name}
                  </span>
                )) : <span className="text-xs text-muted-foreground">—</span>}
              </div>
              <div className="flex items-center gap-1 justify-end">
                <Switch
                  checked={u.isActive}
                  onCheckedChange={(v) =>
                    updateUser.mutate(
                      { id: u.id, data: { isActive: v } },
                      { onSuccess: invalidateUsers },
                    )
                  }
                />
                <button
                  onClick={() => setEditingUser(u)}
                  title={t("admin.users.editGroups")}
                  className="p-1.5 hover:bg-slate-100 rounded-lg text-muted-foreground hover:text-foreground"
                  data-testid={`user-edit-${u.id}`}
                >
                  <Pencil size={14} />
                </button>
                <button
                  onClick={() => onIssueResetLink(u.id, u.username)}
                  title={t("admin.issueResetLink")}
                  className="p-1.5 hover:bg-primary/20 rounded-lg text-muted-foreground hover:text-primary"
                >
                  <KeyRound size={14} />
                </button>
                <label className="flex items-center gap-1 text-[10px] text-muted-foreground select-none">
                  <span className="hidden lg:inline">{t("admin.orderReceiverRole")}</span>
                  <Switch
                    checked={u.roles?.includes("order_receiver") ?? false}
                    onCheckedChange={(v) => {
                      const opts = {
                        onSuccess: invalidateUsers,
                        onError: () => toast({ title: t("common.error"), variant: "destructive" }),
                      };
                      if (v) addUserRole.mutate({ id: u.id, data: { roleName: "order_receiver" } }, opts);
                      else removeUserRole.mutate({ id: u.id, roleName: "order_receiver" }, opts);
                    }}
                  />
                </label>
                {u.id !== currentUserId && (
                  <button
                    onClick={() => {
                      // Pre-populate conflict from list-row counts so the
                      // warning dialog shows on the FIRST click. Falls back
                      // to the lazy 409 round-trip if counts are missing.
                      const noteCount = u.noteCount ?? 0;
                      const orderCount = u.orderCount ?? 0;
                      const conversationCount = u.conversationCount ?? 0;
                      const messageCount = u.messageCount ?? 0;
                      const hasDeps =
                        noteCount > 0 ||
                        orderCount > 0 ||
                        conversationCount > 0 ||
                        messageCount > 0;
                      setUserDeleteConflict(
                        hasDeps
                          ? {
                              error: "User has dependent records",
                              noteCount,
                              orderCount,
                              conversationCount,
                              messageCount,
                            }
                          : null,
                      );
                      setUserDeleteTarget(u);
                    }}
                    className="p-1.5 hover:bg-destructive/20 rounded-lg text-muted-foreground hover:text-destructive"
                    data-testid={`user-delete-${u.id}`}
                  >
                    <Trash2 size={14} />
                  </button>
                )}
              </div>
            </div>
          ))}
          {filtered.length === 0 && (
            <div className="px-4 py-8 text-center text-sm text-muted-foreground">
              {t("admin.users.empty")}
            </div>
          )}
        </div>
      </div>

      {newUserOpen && (
        <div className="fixed inset-0 bg-slate-900/30 backdrop-blur-sm z-50 flex items-center justify-center p-4">
          <div className="glass-panel rounded-3xl p-6 w-full max-w-sm space-y-3">
            <h2 className="font-semibold text-foreground">{t("admin.addUser")}</h2>
            <div className="space-y-1">
              <Label>{t("auth.username")}</Label>
              <Input value={newUserForm.username} onChange={(e) => setNewUserForm({ ...newUserForm, username: e.target.value })} className="bg-white/70 border-slate-200" />
            </div>
            <div className="space-y-1">
              <Label>{t("auth.email")}</Label>
              <Input type="email" value={newUserForm.email} onChange={(e) => setNewUserForm({ ...newUserForm, email: e.target.value })} className="bg-white/70 border-slate-200" />
            </div>
            <div className="space-y-1">
              <Label>{t("auth.password")}</Label>
              <Input type="password" value={newUserForm.password} onChange={(e) => setNewUserForm({ ...newUserForm, password: e.target.value })} className="bg-white/70 border-slate-200" />
            </div>
            <div className="space-y-1">
              <Label>{t("admin.users.col.groups")}</Label>
              <div className="max-h-40 overflow-y-auto rounded-xl border border-slate-200 bg-white/70 p-2 space-y-1">
                {(groups ?? []).length === 0 && (
                  <div className="text-xs text-muted-foreground px-1 py-2">{t("admin.groups.empty")}</div>
                )}
                {(groups ?? []).map((g) => {
                  const checked = newUserForm.groupIds.includes(g.id);
                  return (
                    <label key={g.id} className="flex items-center gap-2 text-sm cursor-pointer px-1 py-1 rounded hover:bg-slate-100/60">
                      <input
                        type="checkbox"
                        checked={checked}
                        onChange={(e) => {
                          setNewUserForm((prev) => ({
                            ...prev,
                            groupIds: e.target.checked
                              ? [...prev.groupIds, g.id]
                              : prev.groupIds.filter((id) => id !== g.id),
                          }));
                        }}
                      />
                      <span>{g.name}</span>
                    </label>
                  );
                })}
              </div>
            </div>
            <div className="flex gap-2 pt-2">
              <Button variant="outline" className="flex-1" onClick={() => setNewUserOpen(false)}>{t("common.cancel")}</Button>
              <Button
                className="flex-1"
                onClick={() => {
                  createUser.mutate(
                    {
                      data: {
                        username: newUserForm.username,
                        email: newUserForm.email,
                        password: newUserForm.password,
                        ...(newUserForm.groupIds.length > 0 ? { groupIds: newUserForm.groupIds } : {}),
                      },
                    },
                    {
                      onSuccess: () => {
                        invalidateUsers();
                        setNewUserOpen(false);
                        setNewUserForm({ username: "", email: "", password: "", groupIds: [] });
                        toast({ title: t("common.success") });
                      },
                      onError: () => toast({ title: t("common.error"), variant: "destructive" }),
                    },
                  );
                }}
              >
                {t("common.save")}
              </Button>
            </div>
          </div>
        </div>
      )}

      {editingUser && (
        <UserGroupsEditor
          user={editingUser}
          groups={groups ?? []}
          onClose={() => setEditingUser(null)}
          onSaved={() => { invalidateUsers(); setEditingUser(null); }}
        />
      )}

      {userDeleteTarget && (() => {
        const target = userDeleteTarget;
        const items: string[] = [];
        if (userDeleteConflict) {
          if (userDeleteConflict.noteCount > 0)
            items.push(t("admin.deleteUser.noteCount", { count: userDeleteConflict.noteCount }));
          if (userDeleteConflict.orderCount > 0)
            items.push(t("admin.deleteUser.orderCount", { count: userDeleteConflict.orderCount }));
          if (userDeleteConflict.conversationCount > 0)
            items.push(t("admin.deleteUser.conversationCount", { count: userDeleteConflict.conversationCount }));
          if (userDeleteConflict.messageCount > 0)
            items.push(t("admin.deleteUser.messageCount", { count: userDeleteConflict.messageCount }));
        }
        return (
          <DeletionWarningDialog
            title={t("admin.deleteUser.title", { name: target.username })}
            warning={t("admin.deleteUser.warning")}
            forceHint={t("admin.deleteUser.forceHint")}
            emptyBody={t("admin.deleteUser.emptyBody")}
            confirmLabel={t("admin.deleteUser.confirm")}
            anywayLabel={t("admin.deleteUser.anyway")}
            items={items}
            showWarning={userDeleteConflict !== null}
            isPending={deleteUser.isPending}
            onCancel={closeUserDeleteDialog}
            onConfirm={() => performUserDelete(target, userDeleteConflict !== null)}
            testId="user-delete-dialog"
            confirmTestId="user-delete-confirm"
          />
        );
      })()}
    </div>
  );
}

type UserEditChangeKey =
  | "displayNameAr"
  | "displayNameEn"
  | "preferredLanguage"
  | "isActive";

function UserGroupsEditor({
  user,
  groups,
  onClose,
  onSaved,
}: {
  user: UserProfile;
  groups: Group[];
  onClose: () => void;
  onSaved: () => void;
}) {
  const { t, i18n } = useTranslation();
  const { toast } = useToast();
  const updateUser = useUpdateUser();
  const { data: roles } = useListRoles({
    query: { queryKey: getListRolesQueryKey() },
  });

  const original = useMemo(
    () => ({
      groupIds: new Set<number>(user.groups?.map((g) => g.id) ?? []),
      displayNameAr: user.displayNameAr ?? "",
      displayNameEn: user.displayNameEn ?? "",
      preferredLanguage: user.preferredLanguage || "ar",
      isActive: user.isActive,
    }),
    [user.id],
  );

  const [selected, setSelected] = useState<Set<number>>(new Set(original.groupIds));
  const [displayNameAr, setDisplayNameAr] = useState(original.displayNameAr);
  const [displayNameEn, setDisplayNameEn] = useState(original.displayNameEn);
  const [preferredLanguage, setPreferredLanguage] = useState(original.preferredLanguage);
  const [isActive, setIsActive] = useState(original.isActive);
  const [groupSearch, setGroupSearch] = useState("");
  const [reviewOpen, setReviewOpen] = useState(false);

  useEffect(() => {
    if (!reviewOpen) return;
    const onKey = (e: KeyboardEvent) => {
      if (e.key === "Escape") setReviewOpen(false);
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [reviewOpen]);

  const toggle = (id: number) => {
    const next = new Set(selected);
    if (next.has(id)) next.delete(id);
    else next.add(id);
    setSelected(next);
  };

  const visibleGroups = groups.filter((g) =>
    !groupSearch.trim() || g.name.toLowerCase().includes(groupSearch.trim().toLowerCase()),
  );

  const groupNameById = useMemo(() => {
    const map = new Map<number, string>();
    for (const g of groups) map.set(g.id, g.name);
    return map;
  }, [groups]);

  const fieldChanges = useMemo(() => {
    const out: { key: UserEditChangeKey; before: unknown; after: unknown }[] = [];
    if (displayNameAr.trim() !== original.displayNameAr.trim()) {
      out.push({ key: "displayNameAr", before: original.displayNameAr, after: displayNameAr });
    }
    if (displayNameEn.trim() !== original.displayNameEn.trim()) {
      out.push({ key: "displayNameEn", before: original.displayNameEn, after: displayNameEn });
    }
    if (preferredLanguage !== original.preferredLanguage) {
      out.push({ key: "preferredLanguage", before: original.preferredLanguage, after: preferredLanguage });
    }
    if (isActive !== original.isActive) {
      out.push({ key: "isActive", before: original.isActive, after: isActive });
    }
    return out;
  }, [displayNameAr, displayNameEn, preferredLanguage, isActive, original]);

  const groupDelta = useMemo(() => {
    const added: string[] = [];
    const removed: string[] = [];
    for (const id of selected) {
      if (!original.groupIds.has(id)) added.push(groupNameById.get(id) ?? `#${id}`);
    }
    for (const id of original.groupIds) {
      if (!selected.has(id)) removed.push(groupNameById.get(id) ?? `#${id}`);
    }
    return { added, removed };
  }, [selected, original.groupIds, groupNameById]);

  const hasChanges =
    fieldChanges.length > 0 || groupDelta.added.length > 0 || groupDelta.removed.length > 0;

  const fieldLabel = (key: UserEditChangeKey): string => {
    switch (key) {
      case "displayNameAr":
        return t("admin.users.col.displayNameAr");
      case "displayNameEn":
        return t("admin.users.col.displayNameEn");
      case "preferredLanguage":
        return t("admin.users.col.language");
      case "isActive":
        return t("admin.users.statusActive");
    }
  };

  const renderValue = (key: UserEditChangeKey, value: unknown): string => {
    if (key === "isActive") {
      return value
        ? t("admin.users.statusActive")
        : t("admin.users.statusInactive");
    }
    if (key === "preferredLanguage") {
      return value === "ar" ? "العربية" : "English";
    }
    const str = typeof value === "string" ? value.trim() : String(value ?? "");
    return str === "" ? t("common.empty") : str;
  };

  const handleConfirmSave = () => {
    updateUser.mutate(
      {
        id: user.id,
        data: {
          groupIds: Array.from(selected),
          displayNameAr: displayNameAr.trim() || null,
          displayNameEn: displayNameEn.trim() || null,
          preferredLanguage,
          isActive,
        },
      },
      {
        onSuccess: () => {
          toast({ title: t("common.success") });
          setReviewOpen(false);
          onSaved();
        },
        onError: () => toast({ title: t("common.error"), variant: "destructive" }),
      },
    );
  };

  return (
    <div className="fixed inset-0 bg-slate-900/30 backdrop-blur-sm z-50 flex items-center justify-center p-4">
      <div className="glass-panel rounded-3xl p-6 w-full max-w-md space-y-3 max-h-[85vh] overflow-y-auto">
        <h2 className="font-semibold text-foreground">{t("admin.users.editUserGroups", { username: user.username })}</h2>
        <div className="grid grid-cols-2 gap-2">
          <div className="space-y-1">
            <Label className="text-xs">{t("admin.users.col.displayNameAr")}</Label>
            <Input
              value={displayNameAr}
              onChange={(e) => setDisplayNameAr(e.target.value)}
              className="bg-white/70 border-slate-200"
              data-testid="edit-user-display-ar"
            />
          </div>
          <div className="space-y-1">
            <Label className="text-xs">{t("admin.users.col.displayNameEn")}</Label>
            <Input
              value={displayNameEn}
              onChange={(e) => setDisplayNameEn(e.target.value)}
              className="bg-white/70 border-slate-200"
              data-testid="edit-user-display-en"
            />
          </div>
        </div>
        <div className="grid grid-cols-2 gap-2 items-end">
          <div className="space-y-1">
            <Label className="text-xs">{t("admin.users.col.language")}</Label>
            <select
              value={preferredLanguage}
              onChange={(e) => setPreferredLanguage(e.target.value)}
              className="bg-white/70 border border-slate-200 rounded-lg px-3 py-2 text-sm w-full"
              data-testid="edit-user-language"
            >
              <option value="ar">العربية</option>
              <option value="en">English</option>
            </select>
          </div>
          <label className="flex items-center gap-2 pb-2 text-sm">
            <Switch checked={isActive} onCheckedChange={setIsActive} />
            <span>{t("admin.users.statusActive")}</span>
          </label>
        </div>
        <div className="pt-1">
          <Label className="text-xs">{t("admin.users.editGroups")}</Label>
          <Input
            placeholder={t("admin.groups.searchPlaceholder")}
            value={groupSearch}
            onChange={(e) => setGroupSearch(e.target.value)}
            className="bg-white/70 border-slate-200 mt-1 mb-2"
            data-testid="edit-user-group-search"
          />
          <div className="space-y-1 max-h-48 overflow-y-auto">
            {visibleGroups.map((g) => (
              <label key={g.id} className="flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-slate-100 cursor-pointer">
                <input type="checkbox" checked={selected.has(g.id)} onChange={() => toggle(g.id)} />
                <span className="text-sm flex-1">{g.name}</span>
                {g.isSystem && <span className="text-[10px] px-1.5 py-0.5 rounded-full bg-slate-200 text-muted-foreground">{t("admin.groups.system")}</span>}
              </label>
            ))}
            {visibleGroups.length === 0 && <p className="text-sm text-muted-foreground p-2">{t("admin.groups.empty")}</p>}
          </div>
        </div>
        <PermissionAuditHistory
          targetKind="user"
          targetId={user.id}
          enabled={true}
          language={i18n.language}
          scopeI18nKey="admin.users.history"
          changeKindLabel={(ck) =>
            ck === "user.roles"
              ? t("admin.users.history.kind.roles")
              : t("admin.users.history.kind.groups")
          }
          labelResolver={(ck, id) => {
            if (ck === "user.groups") {
              return groupNameById.get(id) ?? `#${id}`;
            }
            const r = (roles ?? []).find((x) => x.id === id);
            return r ? r.name : `#${id}`;
          }}
        />
        <div className="flex gap-2 pt-2">
          <Button variant="outline" className="flex-1" onClick={onClose}>{t("common.cancel")}</Button>
          <Button
            className="flex-1"
            disabled={!hasChanges}
            title={hasChanges ? undefined : t("admin.users.review.noChanges")}
            onClick={() => setReviewOpen(true)}
            data-testid="edit-user-save"
          >
            {t("common.save")}
          </Button>
        </div>
      </div>

      {reviewOpen && (
        <div
          className="fixed inset-0 bg-slate-900/50 backdrop-blur-sm z-[60] flex items-center justify-center p-4"
          data-testid="edit-user-review-dialog"
          role="dialog"
          aria-modal="true"
        >
          <div className="glass-panel rounded-3xl p-6 w-full max-w-md space-y-4 max-h-[85vh] overflow-y-auto">
            <div className="space-y-1">
              <h3 className="font-semibold text-foreground">{t("admin.users.review.title")}</h3>
              <p className="text-xs text-muted-foreground">
                {t("admin.users.review.subtitle", { username: user.username })}
              </p>
            </div>
            <ul className="space-y-2 text-sm">
              {fieldChanges.map((c) => (
                <li
                  key={c.key}
                  className="flex flex-wrap items-baseline gap-x-2 gap-y-1"
                  data-testid={`edit-user-review-change-${c.key}`}
                >
                  <span className="text-muted-foreground">{fieldLabel(c.key)}:</span>
                  <span className="line-through text-muted-foreground/70">
                    {renderValue(c.key, c.before)}
                  </span>
                  <span className="text-muted-foreground">→</span>
                  <span className="font-medium text-foreground">
                    {renderValue(c.key, c.after)}
                  </span>
                </li>
              ))}
            </ul>
            {groupDelta.added.length > 0 && (
              <div data-testid="edit-user-review-group-added" className="space-y-1">
                <h4 className="text-xs font-medium text-foreground">
                  {t("admin.users.review.groupsAdded")}
                </h4>
                <div className="flex flex-wrap gap-1">
                  {groupDelta.added.map((name) => (
                    <span
                      key={`add-${name}`}
                      className="text-[11px] px-2 py-0.5 rounded-full bg-emerald-100 text-emerald-800"
                    >
                      {name}
                    </span>
                  ))}
                </div>
              </div>
            )}
            {groupDelta.removed.length > 0 && (
              <div data-testid="edit-user-review-group-removed" className="space-y-1">
                <h4 className="text-xs font-medium text-foreground">
                  {t("admin.users.review.groupsRemoved")}
                </h4>
                <div className="flex flex-wrap gap-1">
                  {groupDelta.removed.map((name) => (
                    <span
                      key={`rem-${name}`}
                      className="text-[11px] px-2 py-0.5 rounded-full bg-rose-100 text-rose-800 line-through"
                    >
                      {name}
                    </span>
                  ))}
                </div>
              </div>
            )}
            <div className="flex gap-2 pt-1">
              <Button
                variant="outline"
                className="flex-1"
                disabled={updateUser.isPending}
                onClick={() => setReviewOpen(false)}
                data-testid="edit-user-review-back"
              >
                {t("admin.users.review.back")}
              </Button>
              <Button
                className="flex-1"
                disabled={updateUser.isPending}
                onClick={handleConfirmSave}
                data-testid="edit-user-review-confirm"
              >
                {updateUser.isPending ? (
                  <Loader2 className="w-4 h-4 animate-spin" />
                ) : (
                  t("admin.users.review.confirm")
                )}
              </Button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// ===== Groups Management Panel =====
function GroupsPanel() {
  const { t } = useTranslation();
  const { toast } = useToast();
  const queryClient = useQueryClient();
  const { data: groups } = useListGroups(undefined, { query: { queryKey: getListGroupsQueryKey() } });
  const createGroup = useCreateGroup();
  const deleteGroup = useDeleteGroup();
  const [editingGroupId, setEditingGroupId] = useState<number | null>(null);
  const [createOpen, setCreateOpen] = useState(false);
  const [createForm, setCreateForm] = useState({ name: "", descriptionAr: "", descriptionEn: "" });
  const [groupSearch, setGroupSearch] = useState("");
  const [deleteTarget, setDeleteTarget] = useState<Group | null>(null);
  const [conflictCounts, setConflictCounts] = useState<{
    memberCount: number;
    appCount: number;
    roleCount: number;
  } | null>(null);

  const invalidate = () => queryClient.invalidateQueries({ queryKey: getListGroupsQueryKey() });

  const closeDeleteDialog = () => {
    setDeleteTarget(null);
    setConflictCounts(null);
  };

  const performDelete = (group: Group, force: boolean) => {
    deleteGroup.mutate(
      { id: group.id, params: force ? { force: true } : undefined },
      {
        onSuccess: () => {
          invalidate();
          toast({ title: t("common.success") });
          closeDeleteDialog();
        },
        onError: (err: unknown) => {
          if (err instanceof ApiError && err.status === 409 && err.data) {
            const data = err.data as GroupDeletionConflict;
            setConflictCounts({
              memberCount: data.memberCount,
              appCount: data.appCount,
              roleCount: data.roleCount,
            });
            return;
          }
          toast({ title: t("common.error"), variant: "destructive" });
        },
      },
    );
  };

  const visibleGroups = (groups ?? []).filter((g) =>
    !groupSearch.trim() ||
    g.name.toLowerCase().includes(groupSearch.trim().toLowerCase()) ||
    (g.descriptionAr ?? "").toLowerCase().includes(groupSearch.trim().toLowerCase()) ||
    (g.descriptionEn ?? "").toLowerCase().includes(groupSearch.trim().toLowerCase()),
  );

  return (
    <div className="space-y-3">
      <div className="flex items-center gap-2">
        <Input
          placeholder={t("admin.groups.searchPlaceholder")}
          value={groupSearch}
          onChange={(e) => setGroupSearch(e.target.value)}
          className="bg-white/70 border-slate-200 flex-1"
          data-testid="group-search"
        />
        <Button size="sm" onClick={() => { setCreateForm({ name: "", descriptionAr: "", descriptionEn: "" }); setCreateOpen(true); }} data-testid="create-group-btn">
          <Plus size={14} className="me-1" />
          {t("admin.groups.addGroup")}
        </Button>
      </div>

      <div className="grid gap-3 md:grid-cols-2">
        {visibleGroups.map((g) => (
          <div key={g.id} className="glass-panel rounded-2xl p-4 space-y-2" data-testid={`group-card-${g.id}`}>
            <div className="flex items-start justify-between gap-2">
              <div className="min-w-0">
                <div className="font-semibold text-foreground flex items-center gap-2">
                  <Shield size={16} className="text-primary" />
                  <span className="truncate">{g.name}</span>
                  {g.isSystem && (
                    <span className="text-[10px] px-1.5 py-0.5 rounded-full bg-amber-100 text-amber-700 uppercase tracking-wider">
                      {t("admin.groups.system")}
                    </span>
                  )}
                </div>
                {(g.descriptionAr || g.descriptionEn) && (
                  <p className="text-xs text-muted-foreground mt-1 truncate">
                    {g.descriptionEn || g.descriptionAr}
                  </p>
                )}
              </div>
              <div className="flex items-center gap-1 shrink-0">
                <button
                  onClick={() => setEditingGroupId(g.id)}
                  className="p-1.5 hover:bg-slate-100 rounded-lg text-muted-foreground hover:text-foreground"
                  data-testid={`edit-group-${g.id}`}
                >
                  <Pencil size={14} />
                </button>
                {!g.isSystem && (
                  <button
                    onClick={() => {
                      setConflictCounts(null);
                      setDeleteTarget(g);
                    }}
                    className="p-1.5 hover:bg-destructive/20 rounded-lg text-muted-foreground hover:text-destructive"
                    data-testid={`delete-group-${g.id}`}
                  >
                    <Trash2 size={14} />
                  </button>
                )}
              </div>
            </div>
            <div className="flex flex-wrap gap-3 text-xs text-muted-foreground pt-1">
              <span>{t("admin.groups.members", { count: g.memberCount })}</span>
              <span>•</span>
              <span>{t("admin.groups.appsCount", { count: g.appCount })}</span>
              <span>•</span>
              <span>{t("admin.groups.rolesCount", { count: g.roleCount })}</span>
            </div>
          </div>
        ))}
        {visibleGroups.length === 0 && (
          <div className="glass-panel rounded-2xl p-6 text-center text-sm text-muted-foreground md:col-span-2">
            {t("admin.groups.empty")}
          </div>
        )}
      </div>

      {createOpen && (
        <div className="fixed inset-0 bg-slate-900/30 backdrop-blur-sm z-50 flex items-center justify-center p-4">
          <div className="glass-panel rounded-3xl p-6 w-full max-w-sm space-y-3">
            <h2 className="font-semibold text-foreground">{t("admin.groups.addGroup")}</h2>
            <div className="space-y-1">
              <Label>{t("admin.groups.name")}</Label>
              <Input value={createForm.name} onChange={(e) => setCreateForm({ ...createForm, name: e.target.value })} className="bg-white/70 border-slate-200" data-testid="group-name-input" />
            </div>
            <div className="space-y-1">
              <Label>{t("admin.groups.descriptionAr")}</Label>
              <Input value={createForm.descriptionAr} onChange={(e) => setCreateForm({ ...createForm, descriptionAr: e.target.value })} className="bg-white/70 border-slate-200" dir="rtl" />
            </div>
            <div className="space-y-1">
              <Label>{t("admin.groups.descriptionEn")}</Label>
              <Input value={createForm.descriptionEn} onChange={(e) => setCreateForm({ ...createForm, descriptionEn: e.target.value })} className="bg-white/70 border-slate-200" dir="ltr" />
            </div>
            <div className="flex gap-2 pt-2">
              <Button variant="outline" className="flex-1" onClick={() => setCreateOpen(false)}>{t("common.cancel")}</Button>
              <Button
                className="flex-1"
                disabled={!createForm.name.trim()}
                onClick={() =>
                  createGroup.mutate(
                    { data: { name: createForm.name.trim(), descriptionAr: createForm.descriptionAr || null, descriptionEn: createForm.descriptionEn || null } },
                    {
                      onSuccess: () => { invalidate(); setCreateOpen(false); toast({ title: t("common.success") }); },
                      onError: () => toast({ title: t("common.error"), variant: "destructive" }),
                    },
                  )
                }
                data-testid="create-group-submit"
              >
                {t("common.save")}
              </Button>
            </div>
          </div>
        </div>
      )}

      {editingGroupId != null && (
        <GroupDetailEditor
          groupId={editingGroupId}
          onClose={() => setEditingGroupId(null)}
          onSaved={() => {
            invalidate();
            queryClient.invalidateQueries({ queryKey: getGetGroupQueryKey(editingGroupId) });
            setEditingGroupId(null);
          }}
        />
      )}

      {deleteTarget && (() => {
        const counts = conflictCounts ?? {
          memberCount: deleteTarget.memberCount,
          appCount: deleteTarget.appCount,
          roleCount: deleteTarget.roleCount,
        };
        const isNonEmpty =
          counts.memberCount > 0 || counts.appCount > 0 || counts.roleCount > 0;
        const showWarning = conflictCounts !== null || isNonEmpty;
        return (
          <div className="fixed inset-0 bg-slate-900/30 backdrop-blur-sm z-50 flex items-center justify-center p-4">
            <div className="glass-panel rounded-3xl p-6 w-full max-w-sm space-y-3" data-testid="delete-group-dialog">
              <h2 className="font-semibold text-foreground">
                {t("admin.groups.deleteTitle", { name: deleteTarget.name })}
              </h2>
              {showWarning ? (
                <>
                  <p className="text-sm text-destructive">
                    {t("admin.groups.deleteWarning")}
                  </p>
                  <ul className="text-sm text-foreground space-y-1 ps-4 list-disc">
                    <li>{t("admin.groups.members", { count: counts.memberCount })}</li>
                    <li>{t("admin.groups.appsCount", { count: counts.appCount })}</li>
                    <li>{t("admin.groups.rolesCount", { count: counts.roleCount })}</li>
                  </ul>
                  <p className="text-xs text-muted-foreground">
                    {t("admin.groups.deleteForceHint")}
                  </p>
                </>
              ) : (
                <p className="text-sm text-muted-foreground">
                  {t("admin.groups.deleteEmptyBody")}
                </p>
              )}
              <div className="flex gap-2 pt-2">
                <Button
                  variant="outline"
                  className="flex-1"
                  onClick={closeDeleteDialog}
                  disabled={deleteGroup.isPending}
                >
                  {t("common.cancel")}
                </Button>
                <Button
                  className="flex-1"
                  variant="destructive"
                  disabled={deleteGroup.isPending}
                  onClick={() => performDelete(deleteTarget, showWarning)}
                  data-testid="confirm-delete-group"
                >
                  {showWarning
                    ? t("admin.groups.deleteAnyway")
                    : t("admin.groups.deleteConfirm")}
                </Button>
              </div>
            </div>
          </div>
        );
      })()}
    </div>
  );
}

function GroupDetailEditor({ groupId, onClose, onSaved }: { groupId: number; onClose: () => void; onSaved: () => void }) {
  const { t, i18n } = useTranslation();
  const lang = i18n.language;
  const { toast } = useToast();
  const { data: group } = useGetGroup(groupId, { query: { queryKey: getGetGroupQueryKey(groupId) } });
  const { data: apps } = useQuery({ queryKey: ADMIN_APPS_KEY, queryFn: fetchAdminApps });
  const { data: users } = useListUsers(undefined, { query: { queryKey: getListUsersQueryKey() } });
  const { data: roles } = useListRoles({ query: { queryKey: getListRolesQueryKey() } });
  const updateGroup = useUpdateGroup();

  const [name, setName] = useState("");
  const [descriptionAr, setDescriptionAr] = useState("");
  const [descriptionEn, setDescriptionEn] = useState("");
  const [appIds, setAppIds] = useState<Set<number>>(new Set());
  const [userIds, setUserIds] = useState<Set<number>>(new Set());
  const [roleIds, setRoleIds] = useState<Set<number>>(new Set());
  const [tab, setTab] = useState<
    "info" | "apps" | "users" | "roles" | "history"
  >("info");
  const [userSearch, setUserSearch] = useState("");

  const visibleUsers = (users ?? []).filter((u) => {
    const q = userSearch.trim().toLowerCase();
    if (!q) return true;
    return (
      u.username.toLowerCase().includes(q) ||
      u.email.toLowerCase().includes(q) ||
      (u.displayNameAr ?? "").toLowerCase().includes(q) ||
      (u.displayNameEn ?? "").toLowerCase().includes(q)
    );
  });

  useEffect(() => {
    if (group) {
      setName(group.name);
      setDescriptionAr(group.descriptionAr ?? "");
      setDescriptionEn(group.descriptionEn ?? "");
      setAppIds(new Set(group.appIds));
      setUserIds(new Set(group.userIds));
      setRoleIds(new Set(group.roleIds));
    }
  }, [group]);

  const toggle = (set: Set<number>, id: number, setter: (s: Set<number>) => void) => {
    const next = new Set(set);
    if (next.has(id)) next.delete(id);
    else next.add(id);
    setter(next);
  };

  if (!group) {
    return (
      <div className="fixed inset-0 bg-slate-900/30 backdrop-blur-sm z-50 flex items-center justify-center p-4">
        <div className="glass-panel rounded-3xl p-6 w-full max-w-md text-center">
          <Loader2 className="animate-spin mx-auto" />
        </div>
      </div>
    );
  }

  return (
    <div className="fixed inset-0 bg-slate-900/30 backdrop-blur-sm z-50 flex items-center justify-center p-4">
      <div className="glass-panel rounded-3xl p-6 w-full max-w-lg space-y-3 max-h-[90vh] overflow-y-auto">
        <div className="flex items-center justify-between gap-2">
          <h2 className="font-semibold text-foreground flex items-center gap-2">
            <Shield size={18} className="text-primary" />
            {t("admin.groups.editGroup")}: {group.name}
          </h2>
          {group.isSystem && (
            <span className="text-[10px] px-1.5 py-0.5 rounded-full bg-amber-100 text-amber-700 uppercase tracking-wider">
              {t("admin.groups.system")}
            </span>
          )}
        </div>

        <div className="flex border-b border-slate-200/70">
          {(["info", "apps", "users", "roles", "history"] as const).map((k) => (
            <button
              key={k}
              onClick={() => setTab(k)}
              className={cn(
                "px-3 py-2 text-sm border-b-2 transition-colors",
                tab === k ? "border-primary text-primary font-semibold" : "border-transparent text-muted-foreground hover:text-foreground",
              )}
              data-testid={`group-tab-${k}`}
            >
              {t(`admin.groups.tab.${k}`)}
            </button>
          ))}
        </div>

        {tab === "info" && (
          <div className="space-y-2">
            <div className="space-y-1">
              <Label>{t("admin.groups.name")}</Label>
              <Input value={name} onChange={(e) => setName(e.target.value)} disabled={group.isSystem} className="bg-white/70 border-slate-200" />
            </div>
            <div className="space-y-1">
              <Label>{t("admin.groups.descriptionAr")}</Label>
              <Input value={descriptionAr} onChange={(e) => setDescriptionAr(e.target.value)} className="bg-white/70 border-slate-200" dir="rtl" />
            </div>
            <div className="space-y-1">
              <Label>{t("admin.groups.descriptionEn")}</Label>
              <Input value={descriptionEn} onChange={(e) => setDescriptionEn(e.target.value)} className="bg-white/70 border-slate-200" dir="ltr" />
            </div>
          </div>
        )}

        {tab === "apps" && (
          <div className="space-y-1 max-h-72 overflow-y-auto">
            <p className="text-xs text-muted-foreground px-1 pb-2">{t("admin.groups.appsHint")}</p>
            {apps?.map((a) => (
              <label key={a.id} className="flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-slate-100 cursor-pointer">
                <input type="checkbox" checked={appIds.has(a.id)} onChange={() => toggle(appIds, a.id, setAppIds)} />
                <span className="text-sm flex-1">{lang === "ar" ? a.nameAr : a.nameEn}</span>
                <span className="text-[10px] text-muted-foreground">{a.slug}</span>
              </label>
            ))}
          </div>
        )}

        {tab === "roles" && (
          <div className="space-y-1 max-h-72 overflow-y-auto">
            <p className="text-xs text-muted-foreground px-1 pb-2">{t("admin.groups.rolesHint")}</p>
            {roles?.map((r) => {
              const primaryDesc = lang === "ar" ? r.descriptionAr : r.descriptionEn;
              const secondaryDesc = lang === "ar" ? r.descriptionEn : r.descriptionAr;
              const secondaryDir = lang === "ar" ? "ltr" : "rtl";
              const heading = primaryDesc || secondaryDesc || r.name;
              const showSecondary = !!(primaryDesc && secondaryDesc);
              return (
                <Tooltip key={r.id}>
                  <TooltipTrigger asChild>
                    <label
                      className="flex items-start gap-2 px-3 py-2 rounded-lg hover:bg-slate-100 cursor-pointer"
                      data-testid={`group-role-${r.id}`}
                    >
                      <input
                        type="checkbox"
                        className="mt-1"
                        checked={roleIds.has(r.id)}
                        onChange={() => toggle(roleIds, r.id, setRoleIds)}
                      />
                      <span className="flex-1 min-w-0">
                        <span className="block text-sm">{heading}</span>
                        {showSecondary && (
                          <span
                            className="block text-xs text-muted-foreground"
                            dir={secondaryDir}
                            data-testid={`group-role-${r.id}-secondary`}
                          >
                            {secondaryDesc}
                          </span>
                        )}
                      </span>
                      <span className="text-[10px] text-muted-foreground mt-1 shrink-0 font-mono">
                        {r.name}
                      </span>
                    </label>
                  </TooltipTrigger>
                  <TooltipContent side="top" className="max-w-xs">
                    <div className="space-y-1 text-xs">
                      {r.descriptionAr && <div dir="rtl">{r.descriptionAr}</div>}
                      {r.descriptionEn && <div dir="ltr">{r.descriptionEn}</div>}
                      <div className="opacity-70 font-mono" dir="ltr">{r.name}</div>
                    </div>
                  </TooltipContent>
                </Tooltip>
              );
            })}
            {(!roles || roles.length === 0) && (
              <p className="text-sm text-muted-foreground p-2">{t("admin.groups.rolesEmpty")}</p>
            )}
          </div>
        )}

        {tab === "users" && (
          <div className="space-y-2">
            <p className="text-xs text-muted-foreground px-1">{t("admin.groups.usersHint")}</p>
            <Input
              placeholder={t("admin.users.searchPlaceholder")}
              value={userSearch}
              onChange={(e) => setUserSearch(e.target.value)}
              className="bg-white/70 border-slate-200"
              data-testid="group-user-search"
            />
            <div className="space-y-1 max-h-60 overflow-y-auto">
              {visibleUsers.map((u) => (
                <label key={u.id} className="flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-slate-100 cursor-pointer">
                  <input type="checkbox" checked={userIds.has(u.id)} onChange={() => toggle(userIds, u.id, setUserIds)} />
                  <span className="text-sm flex-1">{u.username}</span>
                  <span className="text-[10px] text-muted-foreground">{u.email}</span>
                </label>
              ))}
              {visibleUsers.length === 0 && (
                <p className="text-sm text-muted-foreground p-2">{t("admin.users.empty")}</p>
              )}
            </div>
          </div>
        )}

        {tab === "history" && (
          <PermissionAuditHistory
            targetKind="group"
            targetId={group.id}
            enabled={true}
            language={lang}
            scopeI18nKey="admin.groups.history"
            changeKindLabel={(ck) =>
              ck === "group.users"
                ? t("admin.groups.history.kind.users")
                : ck === "group.roles"
                  ? t("admin.groups.history.kind.roles")
                  : t("admin.groups.history.kind.apps")
            }
            labelResolver={(ck, id) => {
              if (ck === "group.users") {
                const u = (users ?? []).find((x) => x.id === id);
                return u ? u.username : `#${id}`;
              }
              if (ck === "group.roles") {
                const r = (roles ?? []).find((x) => x.id === id);
                return r ? r.name : `#${id}`;
              }
              const a = (apps ?? []).find((x) => x.id === id);
              return a ? (lang === "ar" ? a.nameAr : a.nameEn) : `#${id}`;
            }}
          />
        )}

        <div className="flex gap-2 pt-2 border-t border-slate-200/70">
          <Button variant="outline" className="flex-1" onClick={onClose}>{t("common.cancel")}</Button>
          <Button
            className="flex-1"
            onClick={() =>
              updateGroup.mutate(
                {
                  id: group.id,
                  data: {
                    name: group.isSystem ? undefined : name.trim() || undefined,
                    descriptionAr: descriptionAr || null,
                    descriptionEn: descriptionEn || null,
                    appIds: Array.from(appIds),
                    userIds: Array.from(userIds),
                    roleIds: Array.from(roleIds),
                  },
                },
                {
                  onSuccess: () => { toast({ title: t("common.success") }); onSaved(); },
                  onError: () => toast({ title: t("common.error"), variant: "destructive" }),
                },
              )
            }
            data-testid="save-group"
          >
            {t("common.save")}
          </Button>
        </div>
      </div>
    </div>
  );
}

const ROLES_PROTECTED_NAMES = new Set(["admin", "user", "order_receiver"]);

function permissionLabel(
  id: number,
  permissionsById: Map<number, Permission>,
): string {
  const p = permissionsById.get(id);
  return p ? p.name : `#${id}`;
}

const ROLE_HISTORY_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
const ROLE_HISTORY_PAGE_SIZE = 10;

function RolePermissionHistory({
  roleId,
  enabled,
  permissionsById,
  language,
}: {
  roleId: number;
  enabled: boolean;
  permissionsById: Map<number, Permission>;
  language: string;
}) {
  const { t } = useTranslation();

  // Two layers of state: the live <input> values (typed in by the admin)
  // and the "applied" values that are actually fed into the query. The
  // dropdown for the actor user is applied immediately because picking a
  // different person is the natural way to commit; the date inputs only
  // commit on the explicit Apply button so partial date typing doesn't
  // refetch on every keystroke.
  const [actorUserId, setActorUserId] = useState<number | "">("");
  const [fromInput, setFromInput] = useState<string>("");
  const [toInput, setToInput] = useState<string>("");
  const [appliedFrom, setAppliedFrom] = useState<string>("");
  const [appliedTo, setAppliedTo] = useState<string>("");

  // True offset pagination: the first page comes from the React Query cache
  // (so live updates from PUT /roles/:id/permissions invalidations can
  // refresh it), and subsequent pages are fetched imperatively and appended
  // to `extraEntries`. We track the next offset to request, plus per-page
  // load state, so admins can keep paging back through arbitrarily long
  // histories without a hard ceiling.
  const [extraEntries, setExtraEntries] = useState<RolePermissionAuditEntry[]>([]);
  const [extraNextOffset, setExtraNextOffset] = useState<number | null>(null);
  const [hasExtras, setHasExtras] = useState(false);
  const [isLoadingMore, setIsLoadingMore] = useState(false);
  const [loadMoreError, setLoadMoreError] = useState<string | null>(null);

  // Reset filters and pagination whenever the dialog switches to a
  // different role so an admin doesn't see stale filters carried over.
  useEffect(() => {
    setActorUserId("");
    setFromInput("");
    setToInput("");
    setAppliedFrom("");
    setAppliedTo("");
    setExtraEntries([]);
    setExtraNextOffset(null);
    setHasExtras(false);
    setLoadMoreError(null);
    setIsLoadingMore(false);
  }, [roleId]);

  const fromValid = !appliedFrom || ROLE_HISTORY_DATE_RE.test(appliedFrom);
  const toValid = !appliedTo || ROLE_HISTORY_DATE_RE.test(appliedTo);
  const orderValid = !appliedFrom || !appliedTo || appliedFrom <= appliedTo;
  const filtersValid = fromValid && toValid && orderValid;

  const { data: users } = useListUsers(undefined, {
    query: { queryKey: getListUsersQueryKey(), enabled },
  });
  const sortedUsers = useMemo(() => {
    const list = (users ?? []).slice();
    list.sort((a, b) => {
      const aLabel =
        (language === "ar"
          ? a.displayNameAr ?? a.displayNameEn
          : a.displayNameEn ?? a.displayNameAr) ?? a.username;
      const bLabel =
        (language === "ar"
          ? b.displayNameAr ?? b.displayNameEn
          : b.displayNameEn ?? b.displayNameAr) ?? b.username;
      return aLabel.localeCompare(bLabel);
    });
    return list;
  }, [users, language]);

  // First-page params — only this page lives in React Query so cache
  // invalidations after a permission save naturally refresh it. The other
  // pages are fetched imperatively below.
  const firstPageParams: GetRolePermissionAuditParams = {
    limit: ROLE_HISTORY_PAGE_SIZE,
    offset: 0,
    ...(actorUserId !== "" ? { actorUserId: actorUserId as number } : {}),
    ...(appliedFrom ? { from: appliedFrom } : {}),
    ...(appliedTo ? { to: appliedTo } : {}),
  };

  const { data, isLoading, isFetching } = useGetRolePermissionAudit(
    roleId,
    firstPageParams,
    {
      query: {
        queryKey: getGetRolePermissionAuditQueryKey(roleId, firstPageParams),
        enabled: enabled && filtersValid,
      },
    },
  );

  // When the first page changes (filters applied) wipe the appended extra
  // pages so we don't end up with overlapping or out-of-order rows.
  const filtersKey = JSON.stringify(firstPageParams);
  useEffect(() => {
    setExtraEntries([]);
    setExtraNextOffset(null);
    setHasExtras(false);
    setLoadMoreError(null);
    setIsLoadingMore(false);
  }, [filtersKey]);

  // Belt-and-braces: also reset the appended pages whenever React Query
  // refetches the first page under the same filter key (e.g. an external
  // cache invalidation triggered by a permission save while the dialog
  // is still open). Without this, the appended pages could drift out of
  // sync with the refreshed first page.
  const firstPageSignature = data
    ? `${data.totalCount}|${data.entries[0]?.id ?? ""}`
    : null;
  useEffect(() => {
    if (firstPageSignature == null) return;
    setExtraEntries([]);
    setExtraNextOffset(null);
    setHasExtras(false);
    setLoadMoreError(null);
    setIsLoadingMore(false);
  }, [firstPageSignature]);

  const firstPageEntries = data?.entries ?? [];
  const totalCount = data?.totalCount ?? 0;
  const entries = hasExtras
    ? [...firstPageEntries, ...extraEntries]
    : firstPageEntries;
  const showingCount = entries.length;
  const nextOffset = hasExtras ? extraNextOffset : data?.nextOffset ?? null;
  const hasMore = nextOffset !== null;

  const applyFilters = () => {
    setAppliedFrom(fromInput.trim());
    setAppliedTo(toInput.trim());
    // Pagination state is reset by the filtersKey effect above.
  };

  const resetFilters = () => {
    setActorUserId("");
    setFromInput("");
    setToInput("");
    setAppliedFrom("");
    setAppliedTo("");
  };

  const loadMore = async () => {
    if (nextOffset === null || isLoadingMore) return;
    setIsLoadingMore(true);
    setLoadMoreError(null);
    try {
      const next = await getRolePermissionAudit(roleId, {
        ...firstPageParams,
        offset: nextOffset,
      });
      setExtraEntries((prev) => [...prev, ...next.entries]);
      setExtraNextOffset(next.nextOffset);
      setHasExtras(true);
    } catch (e) {
      setLoadMoreError(
        (e as { message?: string }).message ?? t("common.error"),
      );
    } finally {
      setIsLoadingMore(false);
    }
  };

  const filtersActive =
    actorUserId !== "" || appliedFrom !== "" || appliedTo !== "";

  const filterErrors: string[] = [];
  if (!fromValid || !toValid)
    filterErrors.push(t("admin.roles.historyErrors.invalidDate"));
  else if (!orderValid)
    filterErrors.push(t("admin.roles.historyErrors.invertedDates"));

  return (
    <div className="space-y-2 pt-2" data-testid="role-history-section">
      <Label>{t("admin.roles.historyTitle")}</Label>
      <p className="text-xs text-muted-foreground">{t("admin.roles.historyHint")}</p>
      <div
        className="grid gap-2 sm:grid-cols-2 md:grid-cols-4"
        data-testid="role-history-filters"
      >
        <div className="space-y-1">
          <Label className="text-xs">{t("admin.roles.historyFilters.actor")}</Label>
          <select
            value={actorUserId === "" ? "" : String(actorUserId)}
            onChange={(e) => {
              const v = e.target.value;
              setActorUserId(v === "" ? "" : Number(v));
              // Pagination state is reset by the filtersKey effect.
            }}
            className="bg-white/70 border border-slate-200 rounded-lg px-3 py-2 text-sm w-full"
            data-testid="role-history-actor-filter"
          >
            <option value="">
              {t("admin.roles.historyFilters.allActors")}
            </option>
            {sortedUsers.map((u) => {
              const label =
                (language === "ar"
                  ? u.displayNameAr ?? u.displayNameEn
                  : u.displayNameEn ?? u.displayNameAr) ?? u.username;
              return (
                <option key={u.id} value={u.id}>
                  {label}
                </option>
              );
            })}
          </select>
        </div>
        <div className="space-y-1">
          <Label className="text-xs">{t("admin.roles.historyFilters.from")}</Label>
          <Input
            type="date"
            value={fromInput}
            onChange={(e) => setFromInput(e.target.value)}
            className="bg-white/70 border-slate-200"
            data-testid="role-history-from-input"
          />
        </div>
        <div className="space-y-1">
          <Label className="text-xs">{t("admin.roles.historyFilters.to")}</Label>
          <Input
            type="date"
            value={toInput}
            onChange={(e) => setToInput(e.target.value)}
            className="bg-white/70 border-slate-200"
            data-testid="role-history-to-input"
          />
        </div>
        <div className="flex items-end gap-2">
          <Button
            type="button"
            size="sm"
            onClick={applyFilters}
            className="flex-1"
            data-testid="role-history-apply-filters"
          >
            {t("admin.roles.historyFilters.apply")}
          </Button>
          <Button
            type="button"
            size="sm"
            variant="outline"
            onClick={resetFilters}
            disabled={!filtersActive && !fromInput && !toInput}
            data-testid="role-history-reset-filters"
          >
            {t("admin.roles.historyFilters.reset")}
          </Button>
        </div>
      </div>
      {filterErrors.map((msg, i) => (
        <p
          key={i}
          className="text-xs text-destructive"
          data-testid="role-history-filter-error"
        >
          {msg}
        </p>
      ))}
      <div
        className="text-xs text-muted-foreground"
        data-testid="role-history-showing"
      >
        {filtersValid
          ? t("admin.roles.historyShowing", {
              shown: showingCount,
              total: totalCount,
            })
          : t("admin.roles.historyErrors.fixFiltersFirst")}
      </div>
      <div
        className="border border-slate-200 rounded-xl bg-white/60 max-h-56 overflow-y-auto divide-y divide-slate-100"
        data-testid="role-history-list"
      >
        {!filtersValid ? (
          // Hide stale entries while the filter inputs are invalid so the
          // list can't be confused with the (now wrong) filtered results.
          <p
            className="text-xs text-muted-foreground p-3 text-center"
            data-testid="role-history-fix-filters"
          >
            {t("admin.roles.historyErrors.fixFiltersFirst")}
          </p>
        ) : isLoading ? (
          <div className="flex justify-center py-6 text-muted-foreground">
            <Loader2 size={16} className="animate-spin" />
          </div>
        ) : entries.length === 0 ? (
          <p
            className="text-xs text-muted-foreground p-3 text-center"
            data-testid="role-history-empty"
          >
            {filtersActive
              ? t("admin.roles.historyEmptyFiltered")
              : t("admin.roles.historyEmpty")}
          </p>
        ) : (
          entries.map((entry) => {
            const actor = entry.actor;
            const ar = actor?.displayNameAr ?? null;
            const en = actor?.displayNameEn ?? null;
            const preferred =
              language === "ar" ? ar ?? en : en ?? ar;
            const actorName =
              actor && (preferred?.trim() ? preferred : actor.username);
            return (
              <div
                key={entry.id}
                className="p-2.5 text-xs space-y-1"
                data-testid={`role-history-entry-${entry.id}`}
              >
                <div className="flex items-center justify-between gap-2 text-foreground">
                  <span className="font-medium" data-testid={`role-history-actor-${entry.id}`}>
                    {actorName ?? t("admin.roles.historyActorUnknown")}
                  </span>
                  <span
                    className="text-muted-foreground"
                    data-testid={`role-history-time-${entry.id}`}
                  >
                    {formatAuditTimestamp(entry.createdAt, language)}
                  </span>
                </div>
                {entry.addedPermissionIds.length > 0 && (
                  <div
                    className="text-emerald-700"
                    data-testid={`role-history-added-${entry.id}`}
                  >
                    <span className="font-semibold">
                      {t("admin.roles.historyAdded", {
                        count: entry.addedPermissionIds.length,
                      })}
                    </span>{" "}
                    <span className="font-mono break-all">
                      {entry.addedPermissionIds
                        .map((id) => permissionLabel(id, permissionsById))
                        .join(", ")}
                    </span>
                  </div>
                )}
                {entry.removedPermissionIds.length > 0 && (
                  <div
                    className="text-rose-700"
                    data-testid={`role-history-removed-${entry.id}`}
                  >
                    <span className="font-semibold">
                      {t("admin.roles.historyRemoved", {
                        count: entry.removedPermissionIds.length,
                      })}
                    </span>{" "}
                    <span className="font-mono break-all">
                      {entry.removedPermissionIds
                        .map((id) => permissionLabel(id, permissionsById))
                        .join(", ")}
                    </span>
                  </div>
                )}
                <div
                  className="text-muted-foreground"
                  data-testid={`role-history-total-${entry.id}`}
                >
                  {t("admin.roles.historyTotal", {
                    count: entry.newPermissionIds.length,
                  })}
                </div>
              </div>
            );
          })
        )}
      </div>
      {filtersValid && hasMore && (
        <div className="flex justify-center pt-1">
          <Button
            type="button"
            size="sm"
            variant="outline"
            disabled={isLoadingMore || isFetching}
            onClick={loadMore}
            data-testid="role-history-load-more"
          >
            {isLoadingMore || isFetching ? (
              <Loader2 size={14} className="animate-spin" />
            ) : (
              t("admin.roles.historyLoadMore")
            )}
          </Button>
        </div>
      )}
      {loadMoreError && (
        <p
          className="text-xs text-destructive text-center"
          data-testid="role-history-load-more-error"
        >
          {loadMoreError}
        </p>
      )}
    </div>
  );
}

// ---------------------------------------------------------------------------
// PermissionAuditHistory
//
// Generalized version of RolePermissionHistory that powers the "History"
// section embedded into the User, Group and App admin dialogs. The shape of
// the audit endpoint is identical across the three entities (see
// permission_audit table); the only thing that varies is which generated
// hook to call and how to render id labels for each changeKind. We always
// call all three React Query hooks (rules-of-hooks) but only the one
// matching `targetKind` is enabled.
// ---------------------------------------------------------------------------

type PermissionAuditScope = "user" | "group" | "app";
type PermAuditLabelResolver = (
  changeKind: PermissionAuditEntryChangeKind,
  id: number,
) => string;

function PermissionAuditHistory({
  targetKind,
  targetId,
  enabled,
  language,
  scopeI18nKey,
  labelResolver,
  changeKindLabel,
}: {
  targetKind: PermissionAuditScope;
  targetId: number;
  enabled: boolean;
  language: string;
  // Translation prefix, e.g. "admin.users.history" / "admin.groups.history"
  // / "admin.apps.history". Mirrors `admin.roles.history*` keys so we get
  // bilingual support for free without forking copy per call site.
  scopeI18nKey: "admin.users.history" | "admin.groups.history" | "admin.apps.history";
  labelResolver: PermAuditLabelResolver;
  // Group history mixes user/role/app changes — the caller can render a
  // small badge per entry indicating which set was changed. Pass `null`
  // for users/apps where the change kind is fixed.
  changeKindLabel?: ((changeKind: PermissionAuditEntryChangeKind) => string) | null;
}) {
  const { t } = useTranslation();
  const testIdRoot = `${targetKind}-history`;

  const [actorUserId, setActorUserId] = useState<number | "">("");
  const [fromInput, setFromInput] = useState<string>("");
  const [toInput, setToInput] = useState<string>("");
  const [appliedFrom, setAppliedFrom] = useState<string>("");
  const [appliedTo, setAppliedTo] = useState<string>("");

  const [extraEntries, setExtraEntries] = useState<PermissionAuditEntry[]>([]);
  const [extraNextOffset, setExtraNextOffset] = useState<number | null>(null);
  const [hasExtras, setHasExtras] = useState(false);
  const [isLoadingMore, setIsLoadingMore] = useState(false);
  const [loadMoreError, setLoadMoreError] = useState<string | null>(null);

  useEffect(() => {
    setActorUserId("");
    setFromInput("");
    setToInput("");
    setAppliedFrom("");
    setAppliedTo("");
    setExtraEntries([]);
    setExtraNextOffset(null);
    setHasExtras(false);
    setLoadMoreError(null);
    setIsLoadingMore(false);
  }, [targetKind, targetId]);

  const fromValid = !appliedFrom || ROLE_HISTORY_DATE_RE.test(appliedFrom);
  const toValid = !appliedTo || ROLE_HISTORY_DATE_RE.test(appliedTo);
  const orderValid = !appliedFrom || !appliedTo || appliedFrom <= appliedTo;
  const filtersValid = fromValid && toValid && orderValid;

  const { data: users } = useListUsers(undefined, {
    query: { queryKey: getListUsersQueryKey(), enabled },
  });
  const sortedUsers = useMemo(() => {
    const list = (users ?? []).slice();
    list.sort((a, b) => {
      const aLabel =
        (language === "ar"
          ? a.displayNameAr ?? a.displayNameEn
          : a.displayNameEn ?? a.displayNameAr) ?? a.username;
      const bLabel =
        (language === "ar"
          ? b.displayNameAr ?? b.displayNameEn
          : b.displayNameEn ?? b.displayNameAr) ?? b.username;
      return aLabel.localeCompare(bLabel);
    });
    return list;
  }, [users, language]);

  const firstPageParams: GetUserPermissionAuditParams = {
    limit: ROLE_HISTORY_PAGE_SIZE,
    offset: 0,
    ...(actorUserId !== "" ? { actorUserId: actorUserId as number } : {}),
    ...(appliedFrom ? { from: appliedFrom } : {}),
    ...(appliedTo ? { to: appliedTo } : {}),
  };

  // We must call all three hooks to satisfy rules-of-hooks; the
  // `enabled` flag ensures only the one matching `targetKind` actually
  // fires a request.
  const userQuery = useGetUserPermissionAudit(targetId, firstPageParams, {
    query: {
      queryKey: getGetUserPermissionAuditQueryKey(targetId, firstPageParams),
      enabled: enabled && filtersValid && targetKind === "user",
    },
  });
  const groupQuery = useGetGroupPermissionAudit(
    targetId,
    firstPageParams as GetGroupPermissionAuditParams,
    {
      query: {
        queryKey: getGetGroupPermissionAuditQueryKey(
          targetId,
          firstPageParams as GetGroupPermissionAuditParams,
        ),
        enabled: enabled && filtersValid && targetKind === "group",
      },
    },
  );
  const appQuery = useGetAppPermissionAudit(
    targetId,
    firstPageParams as GetAppPermissionAuditParams,
    {
      query: {
        queryKey: getGetAppPermissionAuditQueryKey(
          targetId,
          firstPageParams as GetAppPermissionAuditParams,
        ),
        enabled: enabled && filtersValid && targetKind === "app",
      },
    },
  );
  const activeQuery =
    targetKind === "user" ? userQuery : targetKind === "group" ? groupQuery : appQuery;
  const { data, isLoading, isFetching } = activeQuery;

  const filtersKey = JSON.stringify(firstPageParams);
  useEffect(() => {
    setExtraEntries([]);
    setExtraNextOffset(null);
    setHasExtras(false);
    setLoadMoreError(null);
    setIsLoadingMore(false);
  }, [filtersKey]);

  const firstPageSignature = data
    ? `${data.totalCount}|${data.entries[0]?.id ?? ""}`
    : null;
  useEffect(() => {
    if (firstPageSignature == null) return;
    setExtraEntries([]);
    setExtraNextOffset(null);
    setHasExtras(false);
    setLoadMoreError(null);
    setIsLoadingMore(false);
  }, [firstPageSignature]);

  const firstPageEntries = data?.entries ?? [];
  const totalCount = data?.totalCount ?? 0;
  const entries = hasExtras
    ? [...firstPageEntries, ...extraEntries]
    : firstPageEntries;
  const showingCount = entries.length;
  const nextOffset = hasExtras ? extraNextOffset : data?.nextOffset ?? null;
  const hasMore = nextOffset !== null;

  const applyFilters = () => {
    setAppliedFrom(fromInput.trim());
    setAppliedTo(toInput.trim());
  };

  const resetFilters = () => {
    setActorUserId("");
    setFromInput("");
    setToInput("");
    setAppliedFrom("");
    setAppliedTo("");
  };

  const loadMore = async () => {
    if (nextOffset === null || isLoadingMore) return;
    setIsLoadingMore(true);
    setLoadMoreError(null);
    try {
      const params = { ...firstPageParams, offset: nextOffset };
      const next =
        targetKind === "user"
          ? await getUserPermissionAudit(targetId, params)
          : targetKind === "group"
            ? await getGroupPermissionAudit(targetId, params)
            : await getAppPermissionAudit(targetId, params);
      setExtraEntries((prev) => [...prev, ...next.entries]);
      setExtraNextOffset(next.nextOffset);
      setHasExtras(true);
    } catch (e) {
      setLoadMoreError(
        (e as { message?: string }).message ?? t("common.error"),
      );
    } finally {
      setIsLoadingMore(false);
    }
  };

  const filtersActive =
    actorUserId !== "" || appliedFrom !== "" || appliedTo !== "";

  const filterErrors: string[] = [];
  if (!fromValid || !toValid)
    filterErrors.push(t("admin.roles.historyErrors.invalidDate"));
  else if (!orderValid)
    filterErrors.push(t("admin.roles.historyErrors.invertedDates"));

  return (
    <div className="space-y-2 pt-2" data-testid={`${testIdRoot}-section`}>
      <Label>{t(`${scopeI18nKey}Title`)}</Label>
      <p className="text-xs text-muted-foreground">{t(`${scopeI18nKey}Hint`)}</p>
      <div
        className="grid gap-2 sm:grid-cols-2 md:grid-cols-4"
        data-testid={`${testIdRoot}-filters`}
      >
        <div className="space-y-1">
          <Label className="text-xs">{t("admin.roles.historyFilters.actor")}</Label>
          <select
            value={actorUserId === "" ? "" : String(actorUserId)}
            onChange={(e) => {
              const v = e.target.value;
              setActorUserId(v === "" ? "" : Number(v));
            }}
            className="bg-white/70 border border-slate-200 rounded-lg px-3 py-2 text-sm w-full"
            data-testid={`${testIdRoot}-actor-filter`}
          >
            <option value="">
              {t("admin.roles.historyFilters.allActors")}
            </option>
            {sortedUsers.map((u) => {
              const label =
                (language === "ar"
                  ? u.displayNameAr ?? u.displayNameEn
                  : u.displayNameEn ?? u.displayNameAr) ?? u.username;
              return (
                <option key={u.id} value={u.id}>
                  {label}
                </option>
              );
            })}
          </select>
        </div>
        <div className="space-y-1">
          <Label className="text-xs">{t("admin.roles.historyFilters.from")}</Label>
          <Input
            type="date"
            value={fromInput}
            onChange={(e) => setFromInput(e.target.value)}
            className="bg-white/70 border-slate-200"
            data-testid={`${testIdRoot}-from-input`}
          />
        </div>
        <div className="space-y-1">
          <Label className="text-xs">{t("admin.roles.historyFilters.to")}</Label>
          <Input
            type="date"
            value={toInput}
            onChange={(e) => setToInput(e.target.value)}
            className="bg-white/70 border-slate-200"
            data-testid={`${testIdRoot}-to-input`}
          />
        </div>
        <div className="flex items-end gap-2">
          <Button
            type="button"
            size="sm"
            onClick={applyFilters}
            className="flex-1"
            data-testid={`${testIdRoot}-apply-filters`}
          >
            {t("admin.roles.historyFilters.apply")}
          </Button>
          <Button
            type="button"
            size="sm"
            variant="outline"
            onClick={resetFilters}
            disabled={!filtersActive && !fromInput && !toInput}
            data-testid={`${testIdRoot}-reset-filters`}
          >
            {t("admin.roles.historyFilters.reset")}
          </Button>
        </div>
      </div>
      {filterErrors.map((msg, i) => (
        <p
          key={i}
          className="text-xs text-destructive"
          data-testid={`${testIdRoot}-filter-error`}
        >
          {msg}
        </p>
      ))}
      <div
        className="text-xs text-muted-foreground"
        data-testid={`${testIdRoot}-showing`}
      >
        {filtersValid
          ? t("admin.roles.historyShowing", {
              shown: showingCount,
              total: totalCount,
            })
          : t("admin.roles.historyErrors.fixFiltersFirst")}
      </div>
      <div
        className="border border-slate-200 rounded-xl bg-white/60 max-h-56 overflow-y-auto divide-y divide-slate-100"
        data-testid={`${testIdRoot}-list`}
      >
        {!filtersValid ? (
          <p
            className="text-xs text-muted-foreground p-3 text-center"
            data-testid={`${testIdRoot}-fix-filters`}
          >
            {t("admin.roles.historyErrors.fixFiltersFirst")}
          </p>
        ) : isLoading ? (
          <div className="flex justify-center py-6 text-muted-foreground">
            <Loader2 size={16} className="animate-spin" />
          </div>
        ) : entries.length === 0 ? (
          <p
            className="text-xs text-muted-foreground p-3 text-center"
            data-testid={`${testIdRoot}-empty`}
          >
            {filtersActive
              ? t(`${scopeI18nKey}EmptyFiltered`)
              : t(`${scopeI18nKey}Empty`)}
          </p>
        ) : (
          entries.map((entry) => {
            const actor = entry.actor;
            const ar = actor?.displayNameAr ?? null;
            const en = actor?.displayNameEn ?? null;
            const preferred =
              language === "ar" ? ar ?? en : en ?? ar;
            const actorName =
              actor && (preferred?.trim() ? preferred : actor.username);
            const ck = entry.changeKind;
            return (
              <div
                key={entry.id}
                className="p-2.5 text-xs space-y-1"
                data-testid={`${testIdRoot}-entry-${entry.id}`}
              >
                <div className="flex items-center justify-between gap-2 text-foreground">
                  <span
                    className="font-medium"
                    data-testid={`${testIdRoot}-actor-${entry.id}`}
                  >
                    {actorName ?? t("admin.roles.historyActorUnknown")}
                  </span>
                  <span
                    className="text-muted-foreground"
                    data-testid={`${testIdRoot}-time-${entry.id}`}
                  >
                    {formatAuditTimestamp(entry.createdAt, language)}
                  </span>
                </div>
                {changeKindLabel && (
                  <div
                    className="text-[10px] uppercase tracking-wider text-muted-foreground"
                    data-testid={`${testIdRoot}-kind-${entry.id}`}
                  >
                    {changeKindLabel(ck)}
                  </div>
                )}
                {entry.addedIds.length > 0 && (
                  <div
                    className="text-emerald-700"
                    data-testid={`${testIdRoot}-added-${entry.id}`}
                  >
                    <span className="font-semibold">
                      {t(`${scopeI18nKey}Added`, {
                        count: entry.addedIds.length,
                      })}
                    </span>{" "}
                    <span className="font-mono break-all">
                      {entry.addedIds
                        .map((id) => labelResolver(ck, id))
                        .join(", ")}
                    </span>
                  </div>
                )}
                {entry.removedIds.length > 0 && (
                  <div
                    className="text-rose-700"
                    data-testid={`${testIdRoot}-removed-${entry.id}`}
                  >
                    <span className="font-semibold">
                      {t(`${scopeI18nKey}Removed`, {
                        count: entry.removedIds.length,
                      })}
                    </span>{" "}
                    <span className="font-mono break-all">
                      {entry.removedIds
                        .map((id) => labelResolver(ck, id))
                        .join(", ")}
                    </span>
                  </div>
                )}
                <div
                  className="text-muted-foreground"
                  data-testid={`${testIdRoot}-total-${entry.id}`}
                >
                  {t(`${scopeI18nKey}Total`, {
                    count: entry.newIds.length,
                  })}
                </div>
              </div>
            );
          })
        )}
      </div>
      {filtersValid && hasMore && (
        <div className="flex justify-center pt-1">
          <Button
            type="button"
            size="sm"
            variant="outline"
            disabled={isLoadingMore || isFetching}
            onClick={loadMore}
            data-testid={`${testIdRoot}-load-more`}
          >
            {isLoadingMore || isFetching ? (
              <Loader2 size={14} className="animate-spin" />
            ) : (
              t("admin.roles.historyLoadMore")
            )}
          </Button>
        </div>
      )}
      {loadMoreError && (
        <p
          className="text-xs text-destructive text-center"
          data-testid={`${testIdRoot}-load-more-error`}
        >
          {loadMoreError}
        </p>
      )}
    </div>
  );
}

function RolesPanel() {
  const { t, i18n } = useTranslation();
  const { toast } = useToast();
  const queryClient = useQueryClient();
  const { data: roles } = useListRoles({ query: { queryKey: getListRolesQueryKey() } });
  const { data: allPermissions } = useListPermissions({
    query: { queryKey: getListPermissionsQueryKey() },
  });
  const createRole = useCreateRole();
  const updateRole = useUpdateRole();
  const deleteRole = useDeleteRole();
  const replaceRolePermissions = useReplaceRolePermissions();
  const previewRolePermissionsImpact = usePreviewRolePermissionsImpact();
  const [search, setSearch] = useState("");
  const [createOpen, setCreateOpen] = useState(false);
  const [createForm, setCreateForm] = useState({ name: "", descriptionAr: "", descriptionEn: "" });
  const [editingId, setEditingId] = useState<number | null>(null);
  const [editingIsSystem, setEditingIsSystem] = useState(false);
  const [editForm, setEditForm] = useState({ name: "", descriptionAr: "", descriptionEn: "" });
  const [originalEditName, setOriginalEditName] = useState("");
  const [editPermissionIds, setEditPermissionIds] = useState<Set<number>>(new Set());
  const [initialPermissionIds, setInitialPermissionIds] = useState<Set<number>>(new Set());
  const [deleteTarget, setDeleteTarget] = useState<{ id: number; name: string } | null>(null);
  const [deleteConflict, setDeleteConflict] = useState<{ userCount: number; groupCount: number } | null>(null);
  const [permissionsImpact, setPermissionsImpact] = useState<RolePermissionsImpact | null>(null);
  const [impactLoading, setImpactLoading] = useState(false);
  const [impactError, setImpactError] = useState(false);
  const [confirmRemoval, setConfirmRemoval] = useState(false);
  const impactRequestSeq = useRef(0);

  const editingPermissionsId = editingId ?? 0;
  const { data: editingRolePermissions, isLoading: editingPermsLoading } = useGetRolePermissions(
    editingPermissionsId,
    {
      query: {
        queryKey: getGetRolePermissionsQueryKey(editingPermissionsId),
        enabled: editingId != null,
      },
    },
  );
  const { data: editingRoleUsage } = useGetRoleUsage(editingPermissionsId, {
    query: {
      queryKey: getGetRoleUsageQueryKey(editingPermissionsId),
      enabled: editingId != null && !editingIsSystem,
    },
  });
  const permissionsById = useMemo(() => {
    const map = new Map<number, Permission>();
    for (const p of allPermissions ?? []) map.set(p.id, p);
    return map;
  }, [allPermissions]);

  useEffect(() => {
    if (editingId == null) return;
    if (!editingRolePermissions) return;
    const ids = new Set(editingRolePermissions.map((p) => p.id));
    setEditPermissionIds(ids);
    setInitialPermissionIds(ids);
  }, [editingId, editingRolePermissions]);

  const invalidate = () => queryClient.invalidateQueries({ queryKey: getListRolesQueryKey() });

  const visibleRoles = (roles ?? []).filter((r) => {
    const q = search.trim().toLowerCase();
    if (!q) return true;
    return (
      r.name.toLowerCase().includes(q) ||
      (r.descriptionAr ?? "").toLowerCase().includes(q) ||
      (r.descriptionEn ?? "").toLowerCase().includes(q)
    );
  });

  const handleCreate = () => {
    const name = createForm.name.trim();
    if (!name) return;
    createRole.mutate(
      {
        data: {
          name,
          descriptionAr: createForm.descriptionAr.trim() || null,
          descriptionEn: createForm.descriptionEn.trim() || null,
        },
      },
      {
        onSuccess: () => {
          invalidate();
          setCreateOpen(false);
          setCreateForm({ name: "", descriptionAr: "", descriptionEn: "" });
          toast({ title: t("common.success") });
        },
        onError: (err: unknown) => {
          if (err instanceof ApiError && err.status === 409) {
            toast({ title: t("admin.roles.errorNameTaken"), variant: "destructive" });
            return;
          }
          if (err instanceof ApiError && err.status === 400) {
            toast({ title: t("admin.roles.errorInvalidName"), variant: "destructive" });
            return;
          }
          toast({ title: t("common.error"), variant: "destructive" });
        },
      },
    );
  };

  const beginEdit = (role: {
    id: number;
    name: string;
    isSystem: boolean;
    descriptionAr?: string | null;
    descriptionEn?: string | null;
  }) => {
    setEditingId(role.id);
    setEditingIsSystem(role.isSystem || ROLES_PROTECTED_NAMES.has(role.name));
    setEditForm({
      name: role.name,
      descriptionAr: role.descriptionAr ?? "",
      descriptionEn: role.descriptionEn ?? "",
    });
    setOriginalEditName(role.name);
  };

  const closeEditDialog = () => {
    setEditingId(null);
    setEditPermissionIds(new Set());
    setInitialPermissionIds(new Set());
    setOriginalEditName("");
    setPermissionsImpact(null);
    setImpactLoading(false);
    setImpactError(false);
    setConfirmRemoval(false);
  };

  const editNameTrimmed = editForm.name.trim();
  const isRenaming =
    !editingIsSystem &&
    originalEditName.length > 0 &&
    editNameTrimmed.length > 0 &&
    editNameTrimmed !== originalEditName;

  const permissionsChanged = (() => {
    if (editPermissionIds.size !== initialPermissionIds.size) return true;
    for (const id of editPermissionIds) {
      if (!initialPermissionIds.has(id)) return true;
    }
    return false;
  })();

  const removedPermissionIds = useMemo(() => {
    const removed: number[] = [];
    for (const id of initialPermissionIds) {
      if (!editPermissionIds.has(id)) removed.push(id);
    }
    return removed;
  }, [editPermissionIds, initialPermissionIds]);

  const hasRemovals = removedPermissionIds.length > 0;

  // Debounced on-demand fetch of impact preview when removals are present.
  useEffect(() => {
    if (editingId == null) return;
    if (editingIsSystem) return;
    if (!hasRemovals) {
      setPermissionsImpact(null);
      setImpactLoading(false);
      setImpactError(false);
      return;
    }
    setImpactLoading(true);
    setImpactError(false);
    const editingRoleId = editingId;
    const candidate = Array.from(editPermissionIds);
    const requestId = ++impactRequestSeq.current;
    const handle = setTimeout(() => {
      previewRolePermissionsImpact.mutate(
        { id: editingRoleId, data: { permissionIds: candidate } },
        {
          onSuccess: (data) => {
            if (impactRequestSeq.current !== requestId) return;
            setPermissionsImpact(data);
            setImpactError(false);
            setImpactLoading(false);
          },
          onError: () => {
            if (impactRequestSeq.current !== requestId) return;
            setPermissionsImpact(null);
            setImpactError(true);
            setImpactLoading(false);
          },
        },
      );
    }, 350);
    return () => clearTimeout(handle);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [editingId, editingIsSystem, hasRemovals, editPermissionIds, initialPermissionIds]);

  const handleEdit = () => {
    if (editingId == null) return;
    if (
      !editingIsSystem &&
      hasRemovals &&
      !confirmRemoval &&
      permissionsImpact &&
      permissionsImpact.totalAffectedUsers > 0
    ) {
      setConfirmRemoval(true);
      return;
    }
    const editingRoleId = editingId;
    const name = editForm.name.trim();
    if (!name) return;

    const savePermissionsThenClose = () => {
      if (editingIsSystem || !permissionsChanged) {
        invalidate();
        closeEditDialog();
        toast({ title: t("common.success") });
        return;
      }
      replaceRolePermissions.mutate(
        {
          id: editingRoleId,
          data: { permissionIds: Array.from(editPermissionIds) },
        },
        {
          onSuccess: () => {
            invalidate();
            queryClient.invalidateQueries({
              queryKey: getGetRolePermissionsQueryKey(editingRoleId),
            });
            // Invalidate every cached page/filter for this role's audit so
            // the History panel picks up the new entry no matter which
            // pagination/filter the admin currently has applied.
            queryClient.invalidateQueries({
              queryKey: [`/api/roles/${editingRoleId}/audit`],
            });
            closeEditDialog();
            toast({ title: t("common.success") });
          },
          onError: (err: unknown) => {
            if (err instanceof ApiError && err.status === 400) {
              const code = (err.data as { code?: string } | null)?.code;
              const key =
                code === "system_role_permissions"
                  ? "admin.roles.errorSystemPermissions"
                  : "admin.roles.errorPermissionsSave";
              toast({ title: t(key), variant: "destructive" });
              return;
            }
            toast({ title: t("admin.roles.errorPermissionsSave"), variant: "destructive" });
          },
        },
      );
    };

    updateRole.mutate(
      {
        id: editingRoleId,
        data: {
          ...(editingIsSystem ? {} : { name }),
          descriptionAr: editForm.descriptionAr.trim() || null,
          descriptionEn: editForm.descriptionEn.trim() || null,
        },
      },
      {
        onSuccess: () => {
          savePermissionsThenClose();
        },
        onError: (err: unknown) => {
          if (err instanceof ApiError && err.status === 409) {
            toast({ title: t("admin.roles.errorNameTaken"), variant: "destructive" });
            return;
          }
          if (err instanceof ApiError && err.status === 400) {
            const code = (err.data as { code?: string } | null)?.code;
            const key = code === "system_role_rename" ? "admin.roles.errorSystemRename" : "admin.roles.errorInvalidName";
            toast({ title: t(key), variant: "destructive" });
            return;
          }
          toast({ title: t("common.error"), variant: "destructive" });
        },
      },
    );
  };

  const togglePermission = (id: number) => {
    if (editingIsSystem) return;
    setEditPermissionIds((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      return next;
    });
  };

  const handleDelete = () => {
    if (!deleteTarget) return;
    deleteRole.mutate(
      { id: deleteTarget.id },
      {
        onSuccess: () => {
          invalidate();
          setDeleteTarget(null);
          setDeleteConflict(null);
          toast({ title: t("common.success") });
        },
        onError: (err: unknown) => {
          if (err instanceof ApiError && err.status === 409 && err.data) {
            const data = err.data as { userCount: number; groupCount: number };
            setDeleteConflict({ userCount: data.userCount, groupCount: data.groupCount });
            return;
          }
          toast({ title: t("common.error"), variant: "destructive" });
          setDeleteTarget(null);
        },
      },
    );
  };

  return (
    <div className="space-y-3">
      <div className="flex items-center gap-2">
        <Input
          placeholder={t("admin.roles.searchPlaceholder")}
          value={search}
          onChange={(e) => setSearch(e.target.value)}
          className="bg-white/70 border-slate-200 flex-1"
          data-testid="role-search"
        />
        <Button
          size="sm"
          onClick={() => {
            setCreateForm({ name: "", descriptionAr: "", descriptionEn: "" });
            setCreateOpen(true);
          }}
          data-testid="create-role-btn"
        >
          <Plus size={14} className="me-1" />
          {t("admin.roles.addRole")}
        </Button>
      </div>

      <div className="grid gap-3 md:grid-cols-2">
        {visibleRoles.map((r) => (
          <div key={r.id} className="glass-panel rounded-2xl p-4 space-y-2" data-testid={`role-card-${r.id}`}>
            <div className="flex items-start justify-between gap-2">
              <div className="min-w-0">
                <div className="font-semibold text-foreground flex items-center gap-2">
                  <KeyRound size={16} className="text-primary" />
                  <span className="truncate">{r.name}</span>
                  {(r.isSystem || ROLES_PROTECTED_NAMES.has(r.name)) && (
                    <span className="text-[10px] px-1.5 py-0.5 rounded-full bg-amber-100 text-amber-700 uppercase tracking-wider">
                      {t("admin.roles.system")}
                    </span>
                  )}
                </div>
                {(r.descriptionAr || r.descriptionEn) && (
                  <p className="text-xs text-muted-foreground mt-1">
                    {r.descriptionEn || r.descriptionAr}
                  </p>
                )}
                {r.descriptionAr && r.descriptionEn && (
                  <p className="text-xs text-muted-foreground mt-0.5" dir="rtl">
                    {r.descriptionAr}
                  </p>
                )}
              </div>
              <div className="flex items-center gap-1 shrink-0">
                <button
                  onClick={() => beginEdit(r)}
                  className="p-1.5 hover:bg-slate-100 rounded-lg text-muted-foreground hover:text-foreground"
                  data-testid={`edit-role-${r.id}`}
                  aria-label={t("admin.roles.edit")}
                >
                  <Pencil size={14} />
                </button>
                {!(r.isSystem || ROLES_PROTECTED_NAMES.has(r.name)) && (
                  <button
                    onClick={() => {
                      setDeleteConflict(null);
                      setDeleteTarget({ id: r.id, name: r.name });
                    }}
                    className="p-1.5 hover:bg-destructive/20 rounded-lg text-muted-foreground hover:text-destructive"
                    data-testid={`delete-role-${r.id}`}
                    aria-label={t("admin.roles.delete")}
                  >
                    <Trash2 size={14} />
                  </button>
                )}
              </div>
            </div>
          </div>
        ))}
        {visibleRoles.length === 0 && (
          <div className="glass-panel rounded-2xl p-6 text-center text-sm text-muted-foreground md:col-span-2">
            {t("admin.roles.empty")}
          </div>
        )}
      </div>

      {createOpen && (
        <div className="fixed inset-0 bg-slate-900/30 backdrop-blur-sm z-50 flex items-center justify-center p-4">
          <div className="glass-panel rounded-3xl p-6 w-full max-w-sm space-y-3" data-testid="create-role-dialog">
            <h2 className="font-semibold text-foreground">{t("admin.roles.addRole")}</h2>
            <div className="space-y-1">
              <Label>{t("admin.roles.name")}</Label>
              <Input
                value={createForm.name}
                onChange={(e) => setCreateForm({ ...createForm, name: e.target.value })}
                className="bg-white/70 border-slate-200"
                placeholder={t("admin.roles.namePlaceholder")}
                data-testid="role-name-input"
              />
              <p className="text-xs text-muted-foreground">{t("admin.roles.nameHint")}</p>
            </div>
            <div className="space-y-1">
              <Label>{t("admin.roles.descriptionAr")}</Label>
              <Input
                value={createForm.descriptionAr}
                onChange={(e) => setCreateForm({ ...createForm, descriptionAr: e.target.value })}
                className="bg-white/70 border-slate-200"
                dir="rtl"
                data-testid="role-desc-ar-input"
              />
            </div>
            <div className="space-y-1">
              <Label>{t("admin.roles.descriptionEn")}</Label>
              <Input
                value={createForm.descriptionEn}
                onChange={(e) => setCreateForm({ ...createForm, descriptionEn: e.target.value })}
                className="bg-white/70 border-slate-200"
                dir="ltr"
                data-testid="role-desc-en-input"
              />
            </div>
            <div className="flex gap-2 pt-2">
              <Button variant="outline" className="flex-1" onClick={() => setCreateOpen(false)}>
                {t("common.cancel")}
              </Button>
              <Button
                className="flex-1"
                disabled={!createForm.name.trim() || createRole.isPending}
                onClick={handleCreate}
                data-testid="create-role-submit"
              >
                {createRole.isPending ? <Loader2 size={14} className="animate-spin" /> : t("common.save")}
              </Button>
            </div>
          </div>
        </div>
      )}

      {editingId != null && (
        <div className="fixed inset-0 bg-slate-900/30 backdrop-blur-sm z-50 flex items-center justify-center p-4">
          <div
            className="glass-panel rounded-3xl p-6 w-full max-w-md space-y-3 max-h-[90vh] overflow-y-auto"
            data-testid="edit-role-dialog"
          >
            <h2 className="font-semibold text-foreground">{t("admin.roles.editRole")}</h2>
            <div className="space-y-1">
              <Label>{t("admin.roles.name")}</Label>
              <Input
                value={editForm.name}
                onChange={(e) => setEditForm({ ...editForm, name: e.target.value })}
                className="bg-white/70 border-slate-200"
                placeholder={t("admin.roles.namePlaceholder")}
                disabled={editingIsSystem}
                data-testid="role-edit-name"
              />
              <p className="text-xs text-muted-foreground">
                {editingIsSystem ? t("admin.roles.editSystemNameHint") : t("admin.roles.nameHint")}
              </p>
              {isRenaming && (
                <div
                  className="mt-2 rounded-xl border border-amber-300 bg-amber-50 p-3 text-xs text-amber-900 space-y-1"
                  data-testid="role-rename-warning"
                  role="alert"
                >
                  <p className="font-semibold">
                    {t("admin.roles.renameWarningTitle", {
                      from: originalEditName,
                      to: editNameTrimmed,
                    })}
                  </p>
                  <p>{t("admin.roles.renameWarningBody")}</p>
                  {editingRoleUsage && (
                    <p
                      className="text-amber-800"
                      data-testid="role-rename-usage"
                    >
                      {t("admin.roles.renameUsage", {
                        users: editingRoleUsage.userCount,
                        groups: editingRoleUsage.groupCount,
                      })}
                    </p>
                  )}
                </div>
              )}
            </div>
            <div className="space-y-1">
              <Label>{t("admin.roles.descriptionAr")}</Label>
              <Input
                value={editForm.descriptionAr}
                onChange={(e) => setEditForm({ ...editForm, descriptionAr: e.target.value })}
                className="bg-white/70 border-slate-200"
                dir="rtl"
                data-testid="role-edit-desc-ar"
              />
            </div>
            <div className="space-y-1">
              <Label>{t("admin.roles.descriptionEn")}</Label>
              <Input
                value={editForm.descriptionEn}
                onChange={(e) => setEditForm({ ...editForm, descriptionEn: e.target.value })}
                className="bg-white/70 border-slate-200"
                dir="ltr"
                data-testid="role-edit-desc-en"
              />
            </div>
            <div className="space-y-1 pt-2">
              <Label>{t("admin.roles.permissions")}</Label>
              <p className="text-xs text-muted-foreground">
                {editingIsSystem
                  ? t("admin.roles.permissionsSystemHint")
                  : t("admin.roles.permissionsHint")}
              </p>
              <div
                className="border border-slate-200 rounded-xl bg-white/60 max-h-56 overflow-y-auto divide-y divide-slate-100"
                data-testid="role-permissions-list"
              >
                {editingPermsLoading || !allPermissions ? (
                  <div className="flex justify-center py-6 text-muted-foreground">
                    <Loader2 size={16} className="animate-spin" />
                  </div>
                ) : allPermissions.length === 0 ? (
                  <p className="text-sm text-muted-foreground p-3 text-center">
                    {t("admin.roles.permissionsEmpty")}
                  </p>
                ) : (
                  allPermissions.map((p) => {
                    const checked = editPermissionIds.has(p.id);
                    const desc =
                      i18n.language === "ar"
                        ? p.descriptionAr ?? p.descriptionEn
                        : p.descriptionEn ?? p.descriptionAr;
                    return (
                      <label
                        key={p.id}
                        className={cn(
                          "flex items-start gap-2 p-2.5 text-sm",
                          editingIsSystem
                            ? "cursor-not-allowed opacity-80"
                            : "cursor-pointer hover:bg-slate-50",
                        )}
                        data-testid={`role-perm-row-${p.id}`}
                      >
                        <input
                          type="checkbox"
                          className="mt-0.5"
                          checked={checked}
                          disabled={editingIsSystem}
                          onChange={() => togglePermission(p.id)}
                          data-testid={`role-perm-checkbox-${p.id}`}
                        />
                        <div className="min-w-0 flex-1">
                          <div className="font-mono text-xs text-foreground">{p.name}</div>
                          {desc && (
                            <p className="text-xs text-muted-foreground mt-0.5">{desc}</p>
                          )}
                        </div>
                      </label>
                    );
                  })
                )}
              </div>
              {!editingIsSystem && hasRemovals && (
                <div
                  className="mt-2 rounded-xl border border-amber-300 bg-amber-50 p-3 text-xs text-amber-900 space-y-2"
                  data-testid="role-perm-impact"
                  role="status"
                  aria-live="polite"
                >
                  <p className="font-semibold">
                    {t("admin.roles.impactTitle", { count: removedPermissionIds.length })}
                  </p>
                  {impactLoading ? (
                    <div className="flex items-center gap-2 text-amber-800">
                      <Loader2 size={12} className="animate-spin" />
                      <span>{t("admin.roles.impactLoading")}</span>
                    </div>
                  ) : impactError || !permissionsImpact ? (
                    <p
                      className="text-amber-900"
                      data-testid="role-perm-impact-error"
                    >
                      {t("admin.roles.impactError")}
                    </p>
                  ) : permissionsImpact.removed.length === 0 ? (
                    <p>{t("admin.roles.impactNoUsers")}</p>
                  ) : (
                    <>
                      <ul
                        className="space-y-1.5 ps-4 list-disc"
                        data-testid="role-perm-impact-list"
                      >
                        {permissionsImpact.removed.map((item) => (
                          <li key={item.permissionId}>
                            <span className="font-mono">{item.permissionName}</span>
                            {": "}
                            <span data-testid={`role-perm-impact-counts-${item.permissionId}`}>
                              {t("admin.roles.impactPerPermission", {
                                users: item.userCount,
                                groups: item.groupCount,
                              })}
                            </span>
                            {item.groupCount > 0 && (
                              <span className="text-amber-800">
                                {" "}
                                {t("admin.roles.impactViaGroups", {
                                  groups: item.groups.map((g) => g.name).join(", "),
                                })}
                              </span>
                            )}
                          </li>
                        ))}
                      </ul>
                      {permissionsImpact.totalAffectedUsers > 0 && (
                        <p
                          className="font-semibold text-amber-900"
                          data-testid="role-perm-impact-total"
                        >
                          {t("admin.roles.impactTotal", {
                            count: permissionsImpact.totalAffectedUsers,
                          })}
                        </p>
                      )}
                    </>
                  )}
                </div>
              )}
            </div>
            <RolePermissionHistory
              roleId={editingPermissionsId}
              enabled={editingId != null}
              permissionsById={permissionsById}
              language={i18n.language}
            />
            <div className="flex gap-2 pt-2">
              <Button variant="outline" className="flex-1" onClick={closeEditDialog}>
                {t("common.cancel")}
              </Button>
              <Button
                className="flex-1"
                disabled={
                  !editForm.name.trim() ||
                  updateRole.isPending ||
                  replaceRolePermissions.isPending ||
                  (!editingIsSystem && hasRemovals && (impactLoading || impactError))
                }
                onClick={handleEdit}
                data-testid="edit-role-submit"
              >
                {updateRole.isPending || replaceRolePermissions.isPending ? (
                  <Loader2 size={14} className="animate-spin" />
                ) : (
                  t("common.save")
                )}
              </Button>
            </div>
          </div>
        </div>
      )}

      {confirmRemoval && permissionsImpact && permissionsImpact.totalAffectedUsers > 0 && (
        <div className="fixed inset-0 bg-slate-900/40 backdrop-blur-sm z-[60] flex items-center justify-center p-4">
          <div
            className="glass-panel rounded-3xl p-6 w-full max-w-md space-y-3"
            data-testid="role-perm-confirm-dialog"
            role="alertdialog"
            aria-modal="true"
          >
            <h2 className="font-semibold text-foreground">
              {t("admin.roles.confirmRemovalTitle")}
            </h2>
            <p className="text-sm text-foreground">
              {t("admin.roles.confirmRemovalBody", {
                count: permissionsImpact.totalAffectedUsers,
              })}
            </p>
            <ul
              className="text-xs text-muted-foreground space-y-1 ps-4 list-disc max-h-40 overflow-y-auto"
            >
              {permissionsImpact.removed.map((item) => (
                <li key={item.permissionId}>
                  <span className="font-mono text-foreground">{item.permissionName}</span>
                  {": "}
                  {t("admin.roles.impactPerPermission", {
                    users: item.userCount,
                    groups: item.groupCount,
                  })}
                </li>
              ))}
            </ul>
            <div className="flex gap-2 pt-2">
              <Button
                variant="outline"
                className="flex-1"
                onClick={() => setConfirmRemoval(false)}
                data-testid="role-perm-confirm-cancel"
              >
                {t("common.cancel")}
              </Button>
              <Button
                variant="destructive"
                className="flex-1"
                disabled={updateRole.isPending || replaceRolePermissions.isPending}
                onClick={handleEdit}
                data-testid="role-perm-confirm-submit"
              >
                {updateRole.isPending || replaceRolePermissions.isPending ? (
                  <Loader2 size={14} className="animate-spin" />
                ) : (
                  t("admin.roles.confirmRemovalAction")
                )}
              </Button>
            </div>
          </div>
        </div>
      )}

      {deleteTarget && (
        <div className="fixed inset-0 bg-slate-900/30 backdrop-blur-sm z-50 flex items-center justify-center p-4">
          <div className="glass-panel rounded-3xl p-6 w-full max-w-sm space-y-3" data-testid="delete-role-dialog">
            <h2 className="font-semibold text-foreground">
              {t("admin.roles.deleteTitle", { name: deleteTarget.name })}
            </h2>
            {deleteConflict ? (
              <>
                <p className="text-sm text-destructive">{t("admin.roles.deleteConflict")}</p>
                <ul className="text-sm text-foreground space-y-1 ps-4 list-disc">
                  <li>{t("admin.roles.usersCount", { count: deleteConflict.userCount })}</li>
                  <li>{t("admin.roles.groupsCount", { count: deleteConflict.groupCount })}</li>
                </ul>
                <p className="text-xs text-muted-foreground">{t("admin.roles.deleteConflictHint")}</p>
              </>
            ) : (
              <p className="text-sm text-muted-foreground">{t("admin.roles.deleteEmptyBody")}</p>
            )}
            <div className="flex gap-2 pt-2">
              <Button
                variant="outline"
                className="flex-1"
                onClick={() => {
                  setDeleteTarget(null);
                  setDeleteConflict(null);
                }}
              >
                {t("common.cancel")}
              </Button>
              {!deleteConflict && (
                <Button
                  variant="destructive"
                  className="flex-1"
                  disabled={deleteRole.isPending}
                  onClick={handleDelete}
                  data-testid="delete-role-confirm"
                >
                  {deleteRole.isPending ? <Loader2 size={14} className="animate-spin" /> : t("admin.roles.deleteConfirm")}
                </Button>
              )}
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// ===== Audit Log Panel =====
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;

function formatAuditTimestamp(value: string | Date, lang: string): string {
  const d = value instanceof Date ? value : new Date(value);
  return new Intl.DateTimeFormat(lang === "ar" ? "ar-u-nu-latn" : "en-US", {
    dateStyle: "medium",
    timeStyle: "short",
    numberingSystem: "latn",
    hour12: false,
  }).format(d);
}

function actorLabel(actor: AuditLogEntry["actor"], lang: string): string {
  if (!actor) return "—";
  const ar = actor.displayNameAr ?? null;
  const en = actor.displayNameEn ?? null;
  const preferred = lang === "ar" ? ar ?? en : en ?? ar;
  return preferred && preferred.trim() ? preferred : actor.username;
}

type AuditTFunction = ReturnType<typeof useTranslation>["t"];

function asRecord(value: unknown): Record<string, unknown> {
  return value && typeof value === "object" && !Array.isArray(value)
    ? (value as Record<string, unknown>)
    : {};
}

function asString(v: unknown): string | null {
  return typeof v === "string" && v.trim() ? v : null;
}

function asNumber(v: unknown): number | null {
  return typeof v === "number" && Number.isFinite(v) ? v : null;
}

function unitLabel(t: AuditTFunction, kind: string, count: number): string {
  return t(`admin.audit.unit.${kind}`, { count });
}

function appName(meta: Record<string, unknown>, lang: string, fallbackId: AuditLogEntry["targetId"]): string {
  const en = asString(meta.nameEn);
  const ar = asString(meta.nameAr);
  const slug = asString(meta.slug);
  const preferred = lang === "ar" ? ar ?? en : en ?? ar;
  return preferred ?? slug ?? `#${fallbackId ?? "?"}`;
}

function linkedAppName(meta: Record<string, unknown>, lang: string): string | null {
  const en = asString(meta.appNameEn);
  const ar = asString(meta.appNameAr);
  const slug = asString(meta.appSlug);
  return (lang === "ar" ? ar ?? en : en ?? ar) ?? slug ?? null;
}

function plainName(meta: Record<string, unknown>, fallbackId: AuditLogEntry["targetId"]): string {
  return asString(meta.name) ?? `#${fallbackId ?? "?"}`;
}

function changeCount(meta: Record<string, unknown>): number {
  const changes = meta.changes;
  if (changes && typeof changes === "object" && !Array.isArray(changes)) {
    return Object.keys(changes as Record<string, unknown>).length;
  }
  if (Array.isArray(changes)) return changes.length;
  let count = 0;
  for (const key of ["members", "apps", "roles"]) {
    const diff = meta[key];
    if (diff && typeof diff === "object" && !Array.isArray(diff)) {
      const d = diff as Record<string, unknown>;
      const added = Array.isArray(d.added) ? d.added.length : 0;
      const removed = Array.isArray(d.removed) ? d.removed.length : 0;
      if (added + removed > 0) count += 1;
    }
  }
  return count;
}

function formatAuditSummary(
  entry: AuditLogEntry,
  t: AuditTFunction,
  lang: string,
): string | null {
  const action = entry.action;
  const meta = asRecord(entry.metadata);
  const targetId = entry.targetId;

  switch (action) {
    case "app.create":
      return t("admin.audit.summary.app.create", {
        name: appName(meta, lang, targetId),
      });
    case "app.update": {
      const name = appName(meta, lang, targetId);
      const changes = asRecord(meta.changes);
      const order = lang === "ar"
        ? ["nameAr", "nameEn", "slug"]
        : ["nameEn", "nameAr", "slug"];
      for (const field of order) {
        if (!(field in changes)) continue;
        const change = asRecord(changes[field]);
        const previousName = asString(change.from);
        const nextName = asString(change.to);
        if (previousName && nextName && previousName !== nextName) {
          return t("admin.audit.summary.app.rename", {
            previousName,
            name: nextName,
          });
        }
      }
      return t("admin.audit.summary.app.update", {
        name,
        changes: unitLabel(t, "change", changeCount(meta)),
      });
    }
    case "app.delete": {
      const name = appName(meta, lang, targetId);
      if (meta.force) {
        const groupCount = asNumber(meta.groupCount) ?? 0;
        return t("admin.audit.summary.app.forceDelete", {
          name,
          groups: unitLabel(t, "group", groupCount),
        });
      }
      return t("admin.audit.summary.app.delete", { name });
    }
    case "auth.issue_reset_link":
      return t("admin.audit.summary.auth.issueResetLink", {
        username: asString(meta.username) ?? `#${targetId ?? "?"}`,
      });
    case "group.create":
      return t("admin.audit.summary.group.create", {
        name: plainName(meta, targetId),
        members: unitLabel(t, "member", asNumber(meta.memberCount) ?? 0),
        apps: unitLabel(t, "app", asNumber(meta.appCount) ?? 0),
      });
    case "group.update": {
      const name = plainName(meta, targetId);
      const previousName = asString(meta.previousName);
      const fieldsChanged = changeCount(meta);
      if (previousName && previousName !== name && fieldsChanged <= 1) {
        return t("admin.audit.summary.group.rename", {
          previousName,
          name,
        });
      }
      return t("admin.audit.summary.group.update", {
        name,
        changes: unitLabel(t, "change", Math.max(1, fieldsChanged)),
      });
    }
    case "group.delete": {
      const name = plainName(meta, targetId);
      if (meta.force) {
        const memberCount = asNumber(meta.memberCount) ?? 0;
        return t("admin.audit.summary.group.forceDelete", {
          name,
          members: unitLabel(t, "member", memberCount),
        });
      }
      return t("admin.audit.summary.group.delete", { name });
    }
    case "group.user.add": {
      const groupName = asString(meta.groupName) ?? `#${targetId ?? "?"}`;
      const username = asString(meta.username);
      return username
        ? t("admin.audit.summary.group.userAdd", { name: groupName, username })
        : t("admin.audit.summary.group.userAddById", {
            name: groupName,
            userId: asNumber(meta.userId) ?? "?",
          });
    }
    case "group.user.remove": {
      const groupName = asString(meta.groupName) ?? `#${targetId ?? "?"}`;
      const username = asString(meta.username);
      return username
        ? t("admin.audit.summary.group.userRemove", { name: groupName, username })
        : t("admin.audit.summary.group.userRemoveById", {
            name: groupName,
            userId: asNumber(meta.userId) ?? "?",
          });
    }
    case "group.app.add": {
      const groupName = asString(meta.groupName) ?? `#${targetId ?? "?"}`;
      const appNm = linkedAppName(meta, lang);
      return appNm
        ? t("admin.audit.summary.group.appAdd", { name: groupName, appName: appNm })
        : t("admin.audit.summary.group.appAddById", {
            name: groupName,
            appId: asNumber(meta.appId) ?? "?",
          });
    }
    case "group.app.remove": {
      const groupName = asString(meta.groupName) ?? `#${targetId ?? "?"}`;
      const appNm = linkedAppName(meta, lang);
      return appNm
        ? t("admin.audit.summary.group.appRemove", { name: groupName, appName: appNm })
        : t("admin.audit.summary.group.appRemoveById", {
            name: groupName,
            appId: asNumber(meta.appId) ?? "?",
          });
    }
    case "group.role.add": {
      const groupName = asString(meta.groupName) ?? `#${targetId ?? "?"}`;
      const roleName = asString(meta.roleName);
      return roleName
        ? t("admin.audit.summary.group.roleAdd", { name: groupName, roleName })
        : t("admin.audit.summary.group.roleAddById", {
            name: groupName,
            roleId: asNumber(meta.roleId) ?? "?",
          });
    }
    case "group.role.remove": {
      const groupName = asString(meta.groupName) ?? `#${targetId ?? "?"}`;
      const roleName = asString(meta.roleName);
      return roleName
        ? t("admin.audit.summary.group.roleRemove", { name: groupName, roleName })
        : t("admin.audit.summary.group.roleRemoveById", {
            name: groupName,
            roleId: asNumber(meta.roleId) ?? "?",
          });
    }
    case "role.create":
      return t("admin.audit.summary.role.create", {
        name: plainName(meta, targetId),
      });
    case "role.update": {
      const previousName = asString(meta.previousName);
      const name = plainName(meta, targetId);
      if (previousName && previousName !== name) {
        return t("admin.audit.summary.role.rename", {
          previousName,
          name,
        });
      }
      return t("admin.audit.summary.role.update", {
        name,
        changes: unitLabel(t, "change", Math.max(1, changeCount(meta))),
      });
    }
    case "role.delete":
      return t("admin.audit.summary.role.delete", {
        name: plainName(meta, targetId),
      });
    case "role.permissions.replace": {
      const added = Array.isArray(meta.added) ? meta.added.length : 0;
      const removed = Array.isArray(meta.removed) ? meta.removed.length : 0;
      return t("admin.audit.summary.role.permissionsReplace", {
        name: asString(meta.roleName) ?? `#${targetId ?? "?"}`,
        added: unitLabel(t, "permission", added),
        removed: unitLabel(t, "permission", removed),
      });
    }
    case "role.permission.add":
      return t("admin.audit.summary.role.permissionAdd", {
        name: asString(meta.roleName) ?? `#${targetId ?? "?"}`,
        permission:
          asString(meta.permissionName) ??
          (asNumber(meta.permissionId) != null ? `#${meta.permissionId}` : "?"),
      });
    case "role.permission.remove":
      return t("admin.audit.summary.role.permissionRemove", {
        name: asString(meta.roleName) ?? `#${targetId ?? "?"}`,
        permission:
          asString(meta.permissionName) ??
          (asNumber(meta.permissionId) != null ? `#${meta.permissionId}` : "?"),
      });
    case "service.force_delete":
      return t("admin.audit.summary.service.forceDelete", {
        name: asString(meta.nameEn) ?? `#${targetId ?? "?"}`,
        orders: unitLabel(t, "order", asNumber(meta.orderCount) ?? 0),
      });
    case "settings.update": {
      const changes = asRecord(meta.changes);
      const totalChanges = Object.keys(changes).length;
      if ("registrationOpen" in changes) {
        const change = asRecord(changes.registrationOpen);
        const opened = change.to === true;
        const otherCount = totalChanges - 1;
        if (otherCount <= 0) {
          return t(
            opened
              ? "admin.audit.summary.settings.registrationOpened"
              : "admin.audit.summary.settings.registrationClosed",
          );
        }
        return t(
          opened
            ? "admin.audit.summary.settings.registrationOpenedWith"
            : "admin.audit.summary.settings.registrationClosedWith",
          { others: unitLabel(t, "change", otherCount) },
        );
      }
      return t("admin.audit.summary.settings.update", {
        changes: unitLabel(t, "change", Math.max(1, totalChanges)),
      });
    }
    case "user.delete": {
      const username = asString(meta.username) ?? `#${targetId ?? "?"}`;
      return meta.force
        ? t("admin.audit.summary.user.forceDelete", { username })
        : t("admin.audit.summary.user.delete", { username });
    }
    default:
      return null;
  }
}

// Map of well-known dependency-count metadata keys → unit i18n root used by
// `unitLabel`. Keys not in this map are skipped from the inline chip strip.
const DEPENDENCY_COUNT_KEYS: Record<string, string> = {
  orderCount: "order",
  messageCount: "message",
  noteCount: "note",
  conversationCount: "conversation",
  groupCount: "group",
  memberCount: "member",
  appCount: "app",
  roleCount: "role",
  openCount: "open",
};

// When a dependency chip key maps to a known audit-log target_type, clicking
// the chip pivots to "audit log filtered by that target_type". Other chip
// keys (orders, messages, notes, conversations, opens) have no audit_logs
// rows of their own, so they pivot to the parent entity's audit history
// instead (target_type + target_id of the deletion row).
const DEPENDENCY_CHIP_TARGET_TYPES: Record<string, string> = {
  groupCount: "group",
  memberCount: "user",
  appCount: "app",
  roleCount: "role",
};

type AuditTargetPivot = {
  targetType: string;
  targetId: number | null;
};

function isForceDeleteEntry(entry: AuditLogEntry): boolean {
  const action = entry.action;
  if (action.endsWith(".force_delete")) return true;
  if (action === "user.delete" || action === "app.delete" || action === "group.delete") {
    const meta = asRecord(entry.metadata);
    return meta.force === true;
  }
  return false;
}

// Pulls a human-readable display name for a force-deleted entity from its
// audit metadata so the row can show "service #4821 · My Service" inline
// without admins having to expand the JSON. Returns null when the row is not
// a forced deletion or the metadata lacks any usable name field.
function forceDeletedEntityName(
  entry: AuditLogEntry,
  lang: string,
): string | null {
  if (!isForceDeleteEntry(entry)) return null;
  const meta = asRecord(entry.metadata);
  const en = asString(meta.nameEn) ?? asString(meta.displayNameEn);
  const ar = asString(meta.nameAr) ?? asString(meta.displayNameAr);
  const localized = lang === "ar" ? ar ?? en : en ?? ar;
  if (localized) return localized;
  const plain = asString(meta.name);
  if (plain) return plain;
  const username = asString(meta.username);
  if (username) return `@${username}`;
  return null;
}

function dependencyChips(
  entry: AuditLogEntry,
  t: AuditTFunction,
): Array<{ key: string; label: string; pivot: AuditTargetPivot }> {
  const meta = asRecord(entry.metadata);
  const chips: Array<{ key: string; label: string; pivot: AuditTargetPivot }> =
    [];
  for (const [metaKey, unitRoot] of Object.entries(DEPENDENCY_COUNT_KEYS)) {
    const value = asNumber(meta[metaKey]);
    if (value == null || value <= 0) continue;
    const mappedTargetType = DEPENDENCY_CHIP_TARGET_TYPES[metaKey];
    const pivot: AuditTargetPivot = mappedTargetType
      ? { targetType: mappedTargetType, targetId: null }
      : { targetType: entry.targetType, targetId: entry.targetId ?? null };
    chips.push({
      key: metaKey,
      label: unitLabel(t, unitRoot, value),
      pivot,
    });
  }
  return chips;
}

function AuditLogRow({
  entry,
  lang,
  onPivotToTarget,
}: {
  entry: AuditLogEntry;
  lang: string;
  onPivotToTarget: (pivot: AuditTargetPivot) => void;
}) {
  const { t } = useTranslation();
  const [expanded, setExpanded] = useState(false);
  const hasMetadata =
    entry.metadata != null && Object.keys(entry.metadata as object).length > 0;
  const actor = entry.actor;
  const initial = (actorLabel(actor, lang)[0] ?? "?").toUpperCase();
  const summary = formatAuditSummary(entry, t, lang);
  const isForced = isForceDeleteEntry(entry);
  const chips = isForced ? dependencyChips(entry, t) : [];
  const deletedName = forceDeletedEntityName(entry, lang);
  const showDeletedTarget = deletedName != null && entry.targetId != null;
  return (
    <div
      className="glass-panel rounded-2xl p-3 space-y-2"
      data-testid={`audit-row-${entry.id}`}
    >
      <div className="flex items-start gap-3">
        <LeaderboardAvatar
          src={actor?.avatarUrl ?? null}
          initial={initial}
          alt={actor?.username ?? ""}
        />
        <div className="min-w-0 flex-1">
          <div className="flex flex-wrap items-baseline gap-x-2 gap-y-0.5">
            <span className="font-medium text-sm text-foreground truncate">
              {actorLabel(actor, lang)}
            </span>
            {actor && (
              <span className="text-xs text-muted-foreground">
                @{actor.username}
              </span>
            )}
          </div>
          {summary ? (
            <div
              className="text-sm text-foreground mt-1 leading-snug"
              data-testid={`audit-summary-${entry.id}`}
            >
              {summary}
            </div>
          ) : (
            <div className="text-xs text-muted-foreground mt-1">
              {showDeletedTarget
                ? t("admin.audit.targetWithName", {
                    type: entry.targetType,
                    id: entry.targetId,
                    name: deletedName,
                  })
                : t("admin.audit.target", {
                    type: entry.targetType,
                    id: entry.targetId ?? "—",
                  })}
            </div>
          )}
          {summary && showDeletedTarget && (
            <div
              className="text-xs text-muted-foreground mt-0.5"
              data-testid={`audit-deleted-target-${entry.id}`}
            >
              {t("admin.audit.targetWithName", {
                type: entry.targetType,
                id: entry.targetId,
                name: deletedName,
              })}
            </div>
          )}
          <div className="flex flex-wrap items-center gap-1.5 mt-1">
            <span
              className={cn(
                "inline-block text-[11px] font-mono px-2 py-0.5 rounded-md",
                isForced
                  ? "bg-rose-100 text-rose-800"
                  : "bg-amber-100 text-amber-800",
              )}
              data-testid={`audit-action-${entry.id}`}
            >
              {entry.action}
            </span>
            <span className="text-xs text-muted-foreground">
              {formatAuditTimestamp(entry.createdAt, lang)}
            </span>
          </div>
          {chips.length > 0 && (
            <div
              className="flex flex-wrap items-center gap-1.5 mt-1.5"
              data-testid={`audit-deps-${entry.id}`}
              aria-label={t("admin.audit.dependencies.label")}
            >
              {chips.map((chip) => {
                const ariaLabel = chip.pivot.targetId != null
                  ? t("admin.audit.dependencies.chipAriaParent", {
                      label: chip.label,
                      type: entry.targetType,
                      id: chip.pivot.targetId,
                    })
                  : t("admin.audit.dependencies.chipAriaTargetType", {
                      label: chip.label,
                      type: chip.pivot.targetType,
                    });
                return (
                  <button
                    type="button"
                    key={chip.key}
                    onClick={() => onPivotToTarget(chip.pivot)}
                    aria-label={ariaLabel}
                    title={ariaLabel}
                    className="inline-flex items-center text-[11px] px-2 py-0.5 rounded-md bg-rose-50 text-rose-700 border border-rose-100 hover:bg-rose-100 hover:border-rose-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-rose-400 transition-colors cursor-pointer"
                    data-testid={`audit-dep-${entry.id}-${chip.key}`}
                  >
                    {chip.label}
                  </button>
                );
              })}
            </div>
          )}
        </div>
        {hasMetadata && (
          <button
            onClick={() => setExpanded((v) => !v)}
            className="p-1.5 rounded-lg hover:bg-slate-100 text-muted-foreground hover:text-foreground shrink-0"
            aria-expanded={expanded}
            data-testid={`audit-toggle-${entry.id}`}
          >
            <ChevronDown
              size={16}
              className={cn("transition-transform", expanded ? "rotate-180" : "")}
            />
          </button>
        )}
      </div>
      {expanded && hasMetadata && (
        <pre
          className="text-[11px] bg-slate-100/80 rounded-lg p-2 overflow-x-auto leading-snug whitespace-pre-wrap break-all"
          dir="ltr"
          data-testid={`audit-metadata-${entry.id}`}
        >
          {JSON.stringify(entry.metadata, null, 2)}
        </pre>
      )}
    </div>
  );
}

// Parses targetType / targetId values out of the admin page URL hash so the
// audit log panel can deep-link to a pre-filtered view (e.g. when an admin
// clicks a dependency chip on a forced-delete row).
function parseAuditHashTarget(): AuditTargetPivot {
  if (typeof window === "undefined") return { targetType: "", targetId: null };
  const raw = window.location.hash.replace(/^#/, "");
  const params = new URLSearchParams(raw);
  const targetType = params.get("targetType")?.trim() ?? "";
  const targetIdRaw = params.get("targetId")?.trim();
  let targetId: number | null = null;
  if (targetIdRaw) {
    const n = Number(targetIdRaw);
    if (Number.isFinite(n) && Number.isInteger(n) && n >= 1) {
      targetId = n;
    }
  }
  return { targetType, targetId };
}

// Updates the admin URL hash (which carries `section=audit-log`) so the
// active targetType/targetId filters survive reload and back/forward nav.
// Uses replaceState to avoid polluting browser history with every tweak.
function syncAuditHashTarget(pivot: AuditTargetPivot) {
  if (typeof window === "undefined") return;
  const raw = window.location.hash.replace(/^#/, "");
  const params = new URLSearchParams(raw);
  if (pivot.targetType) {
    params.set("targetType", pivot.targetType);
  } else {
    params.delete("targetType");
  }
  if (pivot.targetId != null) {
    params.set("targetId", String(pivot.targetId));
  } else {
    params.delete("targetId");
  }
  if (!params.has("section")) {
    params.set("section", "audit-log");
  }
  const newHash = `#${params.toString()}`;
  if (window.location.hash !== newHash) {
    window.history.replaceState(null, "", newHash);
  }
}

function AuditLogPanel() {
  const { t, i18n } = useTranslation();
  const lang = i18n.language;
  const [actionFilter, setActionFilter] = useState<string>("");
  const [forcedOnly, setForcedOnly] = useState<boolean>(false);
  const [fromInput, setFromInput] = useState<string>("");
  const [toInput, setToInput] = useState<string>("");
  const [appliedFrom, setAppliedFrom] = useState<string>("");
  const [appliedTo, setAppliedTo] = useState<string>("");
  const [pageLimit, setPageLimit] = useState<number>(50);
  const [exporting, setExporting] = useState<boolean>(false);
  const [exportError, setExportError] = useState<string | null>(null);
  // Deep-link target filters. Initialised from the URL hash so a user who
  // pastes /admin#section=audit-log&targetType=group lands directly on a
  // pre-filtered view. Updated by chip clicks and the explicit clear button.
  const [targetTypeFilter, setTargetTypeFilter] = useState<string>(
    () => parseAuditHashTarget().targetType,
  );
  const [targetIdFilter, setTargetIdFilter] = useState<number | null>(
    () => parseAuditHashTarget().targetId,
  );

  const fromValid = !appliedFrom || DATE_RE.test(appliedFrom);
  const toValid = !appliedTo || DATE_RE.test(appliedTo);
  const orderValid =
    !appliedFrom || !appliedTo || appliedFrom <= appliedTo;
  const filtersValid = fromValid && toValid && orderValid;

  const params: ListAuditLogsParams = {
    limit: pageLimit,
    offset: 0,
    ...(forcedOnly
      ? { forcedOnly: true }
      : actionFilter
      ? { action: actionFilter }
      : {}),
    ...(appliedFrom ? { from: appliedFrom } : {}),
    ...(appliedTo ? { to: appliedTo } : {}),
    ...(targetTypeFilter ? { targetType: targetTypeFilter } : {}),
    ...(targetIdFilter != null ? { targetId: targetIdFilter } : {}),
  };

  const queryKey = getListAuditLogsQueryKey(params);
  const { data, isLoading, isFetching } = useListAuditLogs(params, {
    query: { queryKey, enabled: filtersValid },
  });

  const inputErrors: string[] = [];
  if (!fromValid || !toValid) inputErrors.push(t("admin.audit.errors.invalidDate"));
  else if (!orderValid) inputErrors.push(t("admin.audit.errors.invertedDates"));

  const knownActions = data?.actions ?? [];
  const entries = data?.entries ?? [];
  const totalCount = data?.totalCount ?? 0;
  const showingCount = entries.length;
  const hasMore = data?.nextOffset != null;

  const applyFilters = () => {
    setAppliedFrom(fromInput.trim());
    setAppliedTo(toInput.trim());
    setPageLimit(50);
  };

  const resetFilters = () => {
    setActionFilter("");
    setForcedOnly(false);
    setFromInput("");
    setToInput("");
    setAppliedFrom("");
    setAppliedTo("");
    setPageLimit(50);
    setTargetTypeFilter("");
    setTargetIdFilter(null);
    syncAuditHashTarget({ targetType: "", targetId: null });
  };

  const toggleForcedOnly = () => {
    setForcedOnly((v) => {
      const next = !v;
      if (next) setActionFilter("");
      setPageLimit(50);
      return next;
    });
  };

  // Chip click on a forced-delete row: pre-filter the panel to the chosen
  // target_type (and optionally target_id). Resets pagination so the new
  // result set starts fresh, and updates the URL hash so the filter
  // survives reload / back-forward navigation.
  const pivotToTarget = (pivot: AuditTargetPivot) => {
    setTargetTypeFilter(pivot.targetType);
    setTargetIdFilter(pivot.targetId);
    setPageLimit(50);
    syncAuditHashTarget(pivot);
  };

  const clearTargetFilter = () => {
    setTargetTypeFilter("");
    setTargetIdFilter(null);
    setPageLimit(50);
    syncAuditHashTarget({ targetType: "", targetId: null });
  };

  const loadMore = () => setPageLimit((n) => Math.min(n + 50, 200));

  const handleExportCsv = async () => {
    if (!filtersValid || exporting) return;
    setExportError(null);
    setExporting(true);
    try {
      const url = getExportAuditLogsCsvUrl({
        ...(forcedOnly
          ? { forcedOnly: true }
          : actionFilter
          ? { action: actionFilter }
          : {}),
        ...(appliedFrom ? { from: appliedFrom } : {}),
        ...(appliedTo ? { to: appliedTo } : {}),
        ...(targetTypeFilter ? { targetType: targetTypeFilter } : {}),
        ...(targetIdFilter != null ? { targetId: targetIdFilter } : {}),
      });
      const res = await fetch(url, {
        method: "GET",
        credentials: "include",
        headers: { Accept: "text/csv" },
      });
      if (!res.ok) {
        throw new Error(`HTTP ${res.status}`);
      }
      const blob = await res.blob();
      const stamp = new Date().toISOString().slice(0, 10);
      const filename = `audit-log-${stamp}.csv`;
      const objectUrl = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = objectUrl;
      a.download = filename;
      document.body.appendChild(a);
      a.click();
      a.remove();
      // Defer revoke so the browser can finish the download.
      setTimeout(() => URL.revokeObjectURL(objectUrl), 1000);
    } catch (err) {
      setExportError(
        err instanceof Error ? err.message : t("admin.audit.export.failed"),
      );
    } finally {
      setExporting(false);
    }
  };

  const activeTargetPillLabel = targetTypeFilter
    ? targetIdFilter != null
      ? t("admin.audit.filters.targetWithId", {
          type: targetTypeFilter,
          id: targetIdFilter,
        })
      : t("admin.audit.filters.targetType", { type: targetTypeFilter })
    : null;

  return (
    <div className="space-y-3" data-testid="audit-log-panel">
      <div className="glass-panel rounded-2xl p-3 space-y-3">
        <div className="flex flex-wrap items-center gap-2">
          <button
            type="button"
            onClick={toggleForcedOnly}
            aria-pressed={forcedOnly}
            title={t("admin.audit.filters.forcedOnlyHint")}
            data-testid="audit-forced-only-chip"
            className={cn(
              "inline-flex items-center gap-1.5 text-xs px-3 py-1.5 rounded-full border transition-colors",
              forcedOnly
                ? "bg-rose-100 text-rose-800 border-rose-200"
                : "bg-white/70 text-foreground border-slate-200 hover:bg-slate-50",
            )}
          >
            <Trash2 size={12} />
            {t("admin.audit.filters.forcedOnly")}
          </button>
          {activeTargetPillLabel && (
            <span
              className="inline-flex items-center gap-1.5 text-xs px-3 py-1.5 rounded-full border bg-emerald-100 text-emerald-800 border-emerald-200"
              data-testid="audit-target-filter-pill"
            >
              <span className="truncate">{activeTargetPillLabel}</span>
              <button
                type="button"
                onClick={clearTargetFilter}
                aria-label={t("admin.audit.filters.clearTargetFilter")}
                title={t("admin.audit.filters.clearTargetFilter")}
                className="ms-0.5 inline-flex items-center justify-center w-4 h-4 rounded-full hover:bg-emerald-200/70 focus:outline-none focus-visible:ring-2 focus-visible:ring-emerald-400"
                data-testid="audit-clear-target-filter"
              >
                ×
              </button>
            </span>
          )}
        </div>
        <div className="grid gap-2 sm:grid-cols-2 md:grid-cols-4">
          <div className="space-y-1">
            <Label className="text-xs">{t("admin.audit.filters.action")}</Label>
            <select
              value={actionFilter}
              onChange={(e) => setActionFilter(e.target.value)}
              disabled={forcedOnly}
              className={cn(
                "bg-white/70 border border-slate-200 rounded-lg px-3 py-2 text-sm w-full",
                forcedOnly && "opacity-60 cursor-not-allowed",
              )}
              data-testid="audit-action-filter"
            >
              <option value="">{t("admin.audit.filters.allActions")}</option>
              {knownActions.map((a) => (
                <option key={a} value={a}>
                  {a}
                </option>
              ))}
            </select>
          </div>
          <div className="space-y-1">
            <Label className="text-xs">{t("admin.audit.filters.from")}</Label>
            <Input
              type="date"
              value={fromInput}
              onChange={(e) => setFromInput(e.target.value)}
              className="bg-white/70 border-slate-200"
              data-testid="audit-from-input"
            />
          </div>
          <div className="space-y-1">
            <Label className="text-xs">{t("admin.audit.filters.to")}</Label>
            <Input
              type="date"
              value={toInput}
              onChange={(e) => setToInput(e.target.value)}
              className="bg-white/70 border-slate-200"
              data-testid="audit-to-input"
            />
          </div>
          <div className="flex items-end gap-2">
            <Button
              size="sm"
              onClick={applyFilters}
              className="flex-1"
              data-testid="audit-apply-filters"
            >
              {t("admin.audit.filters.apply")}
            </Button>
            <Button
              size="sm"
              variant="outline"
              onClick={resetFilters}
              data-testid="audit-reset-filters"
            >
              {t("admin.audit.filters.reset")}
            </Button>
          </div>
        </div>
        <div className="flex items-center justify-end">
          <Button
            size="sm"
            variant="outline"
            onClick={handleExportCsv}
            disabled={!filtersValid || exporting}
            className="gap-1.5"
            data-testid="audit-export-csv"
          >
            {exporting ? (
              <Loader2 size={14} className="animate-spin" />
            ) : (
              <Download size={14} />
            )}
            {t("admin.audit.export.button")}
          </Button>
        </div>
        {inputErrors.map((msg, i) => (
          <p key={i} className="text-xs text-destructive">
            {msg}
          </p>
        ))}
        {exportError && (
          <p className="text-xs text-destructive" data-testid="audit-export-error">
            {t("admin.audit.export.failed")}
          </p>
        )}
        <div className="text-xs text-muted-foreground">
          {filtersValid
            ? t("admin.audit.showing", {
                shown: showingCount,
                total: totalCount,
              })
            : t("admin.audit.errors.fixFiltersFirst")}
        </div>
      </div>

      {isLoading ? (
        <div className="flex justify-center py-8 text-muted-foreground">
          <Loader2 size={18} className="animate-spin" />
        </div>
      ) : entries.length === 0 ? (
        <div className="glass-panel rounded-2xl p-6 text-center text-sm text-muted-foreground">
          {t("admin.audit.empty")}
        </div>
      ) : (
        <div className="space-y-2">
          {entries.map((entry) => (
            <AuditLogRow
              key={entry.id}
              entry={entry}
              lang={lang}
              onPivotToTarget={pivotToTarget}
            />
          ))}
        </div>
      )}

      {hasMore && (
        <div className="flex justify-center pt-1">
          <Button
            size="sm"
            variant="outline"
            disabled={pageLimit >= 200 || isFetching}
            onClick={loadMore}
            data-testid="audit-load-more"
          >
            {isFetching ? (
              <Loader2 size={14} className="animate-spin" />
            ) : (
              t("admin.audit.loadMore")
            )}
          </Button>
        </div>
      )}
    </div>
  );
}
