import React, { useEffect, useMemo, useRef, useState } from "react";
import { 
  Search, 
  Plus, 
  Download, 
  Upload, 
  Edit, 
  MoreHorizontal, 
  ChevronDown,
  ChevronRight,
  Trash2,
  FileText,
  MapPin,
  Shield,
  Bell,
  Check,
  X
} from "lucide-react";
import { Button } from "../ui/button";
import { Input } from "../ui/input";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "../ui/table";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "../ui/dialog";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "../ui/select";
import {
  Pagination,
  PaginationContent,
  PaginationItem,
  PaginationLink,
  PaginationNext,
  PaginationPrevious,
} from "../ui/pagination";
import { Label } from "../ui/label";
import { Switch } from "../ui/switch";
import { Badge } from "../ui/badge";
import { Checkbox } from "../ui/checkbox";
import { ScrollArea } from "../ui/scroll-area";
import { cn } from "../ui/utils";
import { toast } from "sonner";

import { getRbacMenuTree } from "../../services/systemMenuService";
import { deleteRoleMenus, setRoleMenus } from "../../services/rbacRoleMenuService";
import type { RbacMenuTreeNode } from "../../types/systemMenu";
import {
  createRbacRole,
  deleteRbacRole,
  getRbacRoleMenuIds,
  getRbacRoles,
  updateRbacRole,
} from "../../services/rbacRoleService";
import { displayRoleAccessPermissionLabel, ROLE_ACCESS_PERMISSION_OPTIONS, type RoleDto } from "../../types/role";
import { getLocations } from "../../services/locationService";
import {
  createTeamMember,
  deleteTeamMember,
  downloadTeamMemberImportTemplate,
  exportTeamMembersPdf,
  getTeamMemberById,
  getTeamMembers,
  importTeamMembersBatch,
  updateTeamMember,
} from "../../services/teamMemberService";
import type { LocationDto } from "../../types/location";
import type { TeamMemberCreateInput, TeamMemberDto, TeamMemberUpdateInput } from "../../types/teamMember";
import {
  createPartner,
  deletePartner,
  exportPartnersPdf,
  getPartners,
  updatePartner,
} from "../../services/partnerService";
import { createGroup, deleteGroup, exportGroupsPdf, getGroups, updateGroup } from "../../services/groupService";
import type { GroupListItem } from "../../types/group";
import type { PartnerListItem } from "../../types/partner";
import { LocationsView } from "../locations/LocationsView";
import { BatchImportDialog } from "../bulk/batch-import-dialog";
import { TeamMemberBulkEditPage } from "./team-member-bulk-edit-page";
import { useAuth } from "../auth/AuthProvider";
import { canMutateAccountManagementStructure } from "../../lib/accountManagementAccess";

function listStateFromFilter(v: "all" | "active" | "inactive"): boolean | undefined {
  if (v === "all") return undefined;
  if (v === "active") return true;
  return false;
}

async function loadAllPartnersForSelect(signal: AbortSignal): Promise<PartnerListItem[]> {
  const out: PartnerListItem[] = [];
  let page = 1;
  const size = 100;
  for (;;) {
    const res = await getPartners({ skipCount: page, maxResultCount: size }, signal);
    out.push(...(res.items ?? []));
    if (!res.items || res.items.length < size) break;
    page += 1;
    if (page > 200) break;
  }
  const m = new Map<string, PartnerListItem>();
  for (const p of out) if (p.id && !m.has(p.id)) m.set(p.id, p);
  return Array.from(m.values());
}

async function loadActivePartnersForSelect(signal: AbortSignal): Promise<PartnerListItem[]> {
  const out: PartnerListItem[] = [];
  let page = 1;
  const size = 100;
  for (;;) {
    const res = await getPartners({ skipCount: page, maxResultCount: size, state: true }, signal);
    out.push(...(res.items ?? []));
    if (!res.items || res.items.length < size) break;
    page += 1;
    if (page > 200) break;
  }
  const m = new Map<string, PartnerListItem>();
  for (const p of out) if (p.id && !m.has(p.id)) m.set(p.id, p);
  return Array.from(m.values());
}

async function loadAllGroupsForSelect(signal: AbortSignal): Promise<GroupListItem[]> {
  const out: GroupListItem[] = [];
  let page = 1;
  const size = 100;
  for (;;) {
    const res = await getGroups({ skipCount: page, maxResultCount: size }, signal);
    out.push(...(res.items ?? []));
    if (!res.items || res.items.length < size) break;
    page += 1;
    if (page > 200) break;
  }
  const m = new Map<string, GroupListItem>();
  for (const g of out) if (g.id && !m.has(g.id)) m.set(g.id, g);
  return Array.from(m.values());
}

async function loadAllLocationsForMemberFilter(signal?: AbortSignal): Promise<LocationDto[]> {
  const out: LocationDto[] = [];
  let page = 1;
  const size = 500;
  for (;;) {
    const res = await getLocations({ skipCount: page, maxResultCount: size }, signal);
    out.push(...(res.items ?? []));
    if (!res.items || res.items.length < size) break;
    page += 1;
    if (page > 200) break;
  }
  const m = new Map<string, LocationDto>();
  for (const l of out) if (l.id && !m.has(l.id)) m.set(l.id, l);
  return Array.from(m.values());
}

/** 成员是否至少绑定在 scope 允许的门店之一（仅前端 company/region 筛选用） */
function memberMatchesLocationScope(m: TeamMemberDto, allowedLocationIds: Set<string> | null): boolean {
  if (allowedLocationIds === null) return true;
  if (allowedLocationIds.size === 0) return false;
  const ids = (m.locationIds ?? []).map((x) => String(x).trim()).filter(Boolean);
  return ids.some((id) => allowedLocationIds.has(id));
}

// --- Types ---
type ViewTab = "Roles" | "Partner" | "Group" | "Location Manager" | "Team Member";

const ACCOUNT_TABS: { id: ViewTab; label: string }[] = [
  { id: "Roles", label: "Roles" },
  { id: "Partner", label: "Company" },
  { id: "Group", label: "Region" },
  { id: "Location Manager", label: "Location" },
  { id: "Team Member", label: "Team Member" },
];

/** Maps legacy backend role labels to UI copy (display only). */
function displayRoleNameForUi(name: string | null | undefined, fallback?: string | null): string {
  const s = (name ?? "").trim();
  if (!s) {
    const fb = (fallback ?? "").trim();
    return fb || "N/A";
  }
  if (/^partner admin$/i.test(s)) return "Company Admin";
  return s;
}

export type ProfileEditIntent = { userId: string; nonce: number };

export type PeopleViewProps = {
  /** 从侧栏「门店/Location」入口进入时由 App 传入，选中对应 Tab 后清除 */
  initialSubTab?: ViewTab | null;
  onInitialSubTabConsumed?: () => void;
  /** 顶栏「当前用户」进入编辑：打开 Team Member 编辑弹窗 */
  profileEditIntent?: ProfileEditIntent | null;
  onProfileEditIntentConsumed?: () => void;
};

export function PeopleView({
  initialSubTab,
  onInitialSubTabConsumed,
  profileEditIntent,
  onProfileEditIntentConsumed,
}: PeopleViewProps = {}) {
  const auth = useAuth();
  const canAmStructureMutate = useMemo(
    () =>
      canMutateAccountManagementStructure({
        roleCodes: auth.roleCodes,
        permissionCodes: auth.permissionCodes,
        userName: auth.user?.userName,
      }),
    [auth.roleCodes, auth.permissionCodes, auth.user?.userName],
  );

  const [activeTab, setActiveTab] = useState<ViewTab>("Roles");

  useEffect(() => {
    if (initialSubTab === "Location Manager") {
      setActiveTab("Location Manager");
      onInitialSubTabConsumed?.();
    }
  }, [initialSubTab, onInitialSubTabConsumed]);

  useEffect(() => {
    if (activeTab !== "Team Member") {
      setMemberBulkEditPage(false);
      setMemberBulkEditSeed([]);
    }
  }, [activeTab]);

  // Data States
  const [roles, setRoles] = useState<RoleDto[]>([]);
  const [roleTotal, setRoleTotal] = useState(0);
  const [rolesLoading, setRolesLoading] = useState(false);
  const [roleRefreshSeq, setRoleRefreshSeq] = useState(0);
  const [rolePageIndex, setRolePageIndex] = useState(1);
  const [rolePageSize, setRolePageSize] = useState(10);
  const roleTotalPages = Math.max(1, Math.ceil(roleTotal / rolePageSize));
  const rolesAbortRef = useRef<AbortController | null>(null);
  const [roleKeyword, setRoleKeyword] = useState("");
  const roleKeywordTimerRef = useRef<number | null>(null);
  const [debouncedRoleKeyword, setDebouncedRoleKeyword] = useState("");

  const [partnerRows, setPartnerRows] = useState<PartnerListItem[]>([]);
  const [partnerTotal, setPartnerTotal] = useState(0);
  const [partnersLoading, setPartnersLoading] = useState(false);
  const [partnerPageIndex, setPartnerPageIndex] = useState(1);
  const [partnerPageSize, setPartnerPageSize] = useState(10);
  const partnerTotalPages = Math.max(1, Math.ceil(partnerTotal / partnerPageSize));
  const partnersAbortRef = useRef<AbortController | null>(null);
  const [partnerKeyword, setPartnerKeyword] = useState("");
  const partnerKeywordTimerRef = useRef<number | null>(null);
  const [debouncedPartnerKeyword, setDebouncedPartnerKeyword] = useState("");
  const [partnerStatusFilter, setPartnerStatusFilter] = useState<"all" | "active" | "inactive">("all");
  const [partnerRefreshSeq, setPartnerRefreshSeq] = useState(0);

  const [groupRows, setGroupRows] = useState<GroupListItem[]>([]);
  const [groupTotal, setGroupTotal] = useState(0);
  const [groupsLoading, setGroupsLoading] = useState(false);
  const [groupPageIndex, setGroupPageIndex] = useState(1);
  const [groupPageSize, setGroupPageSize] = useState(10);
  const groupTotalPages = Math.max(1, Math.ceil(groupTotal / groupPageSize));
  const groupsAbortRef = useRef<AbortController | null>(null);
  const [groupKeyword, setGroupKeyword] = useState("");
  const groupKeywordTimerRef = useRef<number | null>(null);
  const [debouncedGroupKeyword, setDebouncedGroupKeyword] = useState("");
  const [groupPartnerFilter, setGroupPartnerFilter] = useState<string>("all");
  const [groupStatusFilter, setGroupStatusFilter] = useState<"all" | "active" | "inactive">("all");
  const [groupRefreshSeq, setGroupRefreshSeq] = useState(0);
  const [groupFilterPartnerOptions, setGroupFilterPartnerOptions] = useState<PartnerListItem[]>([]);

  const [members, setMembers] = useState<TeamMemberDto[]>([]);
  const [membersLoading, setMembersLoading] = useState(false);
  const [memberTotal, setMemberTotal] = useState(0);
  const [memberRefreshSeq, setMemberRefreshSeq] = useState(0);
  const [memberPageIndex, setMemberPageIndex] = useState(1);
  const [memberPageSize, setMemberPageSize] = useState(10);
  const memberTotalPages = Math.max(1, Math.ceil(memberTotal / memberPageSize));
  const membersAbortRef = useRef<AbortController | null>(null);

  const [memberKeyword, setMemberKeyword] = useState("");
  const memberKeywordTimerRef = useRef<number | null>(null);
  const [debouncedMemberKeyword, setDebouncedMemberKeyword] = useState("");
  const [memberPartnerFilter, setMemberPartnerFilter] = useState("all");
  const [memberGroupFilter, setMemberGroupFilter] = useState("all");
  const [memberLocationFilter, setMemberLocationFilter] = useState("all");
  const [memberFilterPartners, setMemberFilterPartners] = useState<PartnerListItem[]>([]);
  const [memberFilterGroups, setMemberFilterGroups] = useState<GroupListItem[]>([]);
  const [memberFilterLocations, setMemberFilterLocations] = useState<LocationDto[]>([]);

  const [editingMember, setEditingMember] = useState<TeamMemberDto | null>(null);
  const [isDeleteMemberDialogOpen, setIsDeleteMemberDialogOpen] = useState(false);
  const [deletingMember, setDeletingMember] = useState<TeamMemberDto | null>(null);
  const [selectedMemberIds, setSelectedMemberIds] = useState<Set<string>>(() => new Set());
  const [memberBulkImportOpen, setMemberBulkImportOpen] = useState(false);
  const [memberBulkEditPage, setMemberBulkEditPage] = useState(false);
  const [memberBulkEditSeed, setMemberBulkEditSeed] = useState<TeamMemberDto[]>([]);
  const [tmplMemberDownloading, setTmplMemberDownloading] = useState(false);
  const [memberPdfExporting, setMemberPdfExporting] = useState(false);

  // Dialog States
  const [isRoleDialogOpen, setIsRoleDialogOpen] = useState(false);
  const [editingRole, setEditingRole] = useState<RoleDto | null>(null);
  const [isRoleMenuDialogOpen, setIsRoleMenuDialogOpen] = useState(false);
  const [menuRole, setMenuRole] = useState<RoleDto | null>(null);
  const [isDeleteRoleDialogOpen, setIsDeleteRoleDialogOpen] = useState(false);
  const [deletingRole, setDeletingRole] = useState<RoleDto | null>(null);
  const [isPartnerDialogOpen, setIsPartnerDialogOpen] = useState(false);
  const [editingPartnerRow, setEditingPartnerRow] = useState<PartnerListItem | null>(null);
  const [isDeletePartnerDialogOpen, setIsDeletePartnerDialogOpen] = useState(false);
  const [deletingPartnerRow, setDeletingPartnerRow] = useState<PartnerListItem | null>(null);

  const [isGroupDialogOpen, setIsGroupDialogOpen] = useState(false);
  const [editingGroupRow, setEditingGroupRow] = useState<GroupListItem | null>(null);
  const [isDeleteGroupDialogOpen, setIsDeleteGroupDialogOpen] = useState(false);
  const [deletingGroupRow, setDeletingGroupRow] = useState<GroupListItem | null>(null);

  const [isMemberDialogOpen, setIsMemberDialogOpen] = useState(false);

  useEffect(() => {
    const uid = profileEditIntent?.userId?.trim() ?? "";
    const nonce = profileEditIntent?.nonce ?? 0;
    if (!uid || !nonce) return;

    const ac = new AbortController();
    let cancelled = false;

    (async () => {
      try {
        setActiveTab("Team Member");
        const detail = await getTeamMemberById(uid, ac.signal);
        if (cancelled) return;
        setEditingMember(detail);
        setIsMemberDialogOpen(true);
      } catch (e: unknown) {
        if (cancelled) return;
        const msg = e instanceof Error ? e.message : "Please try again.";
        toast.error("Could not open profile editor", { description: msg });
      } finally {
        if (!cancelled) onProfileEditIntentConsumed?.();
      }
    })();

    return () => {
      cancelled = true;
      ac.abort();
    };
  }, [profileEditIntent?.userId, profileEditIntent?.nonce, onProfileEditIntentConsumed]);

  // Handlers
  const handleExportPdf = async () => {
    try {
      if (activeTab === "Partner") {
        const st = listStateFromFilter(partnerStatusFilter);
        await exportPartnersPdf({
          keyword: debouncedPartnerKeyword || undefined,
          state: st,
          sorting: "CreationTime desc",
        });
        toast.success("Export started.", { description: "Company PDF downloaded." });
        return;
      }
      if (activeTab === "Group") {
        const st = listStateFromFilter(groupStatusFilter);
        await exportGroupsPdf({
          keyword: debouncedGroupKeyword || undefined,
          partnerId: groupPartnerFilter !== "all" ? groupPartnerFilter : undefined,
          state: st,
          sorting: "CreationTime desc",
        });
        toast.success("Export started.", { description: "Region PDF downloaded." });
        return;
      }
      toast.message("Export not available for this tab.", { description: "Use Company or Region tab." });
    } catch (e: any) {
      toast.error("Export failed.", {
        description: e?.message ? String(e.message) : "Please try again.",
      });
    }
  };

  useEffect(() => {
    if (roleKeywordTimerRef.current) window.clearTimeout(roleKeywordTimerRef.current);
    roleKeywordTimerRef.current = window.setTimeout(() => setDebouncedRoleKeyword(roleKeyword.trim()), 300);
    return () => {
      if (roleKeywordTimerRef.current) window.clearTimeout(roleKeywordTimerRef.current);
    };
  }, [roleKeyword]);

  useEffect(() => {
    if (memberKeywordTimerRef.current) window.clearTimeout(memberKeywordTimerRef.current);
    memberKeywordTimerRef.current = window.setTimeout(() => setDebouncedMemberKeyword(memberKeyword.trim()), 300);
    return () => {
      if (memberKeywordTimerRef.current) window.clearTimeout(memberKeywordTimerRef.current);
    };
  }, [memberKeyword]);

  useEffect(() => {
    setSelectedMemberIds(new Set());
  }, [debouncedMemberKeyword, memberPageIndex]);

  useEffect(() => {
    if (partnerKeywordTimerRef.current) window.clearTimeout(partnerKeywordTimerRef.current);
    partnerKeywordTimerRef.current = window.setTimeout(() => setDebouncedPartnerKeyword(partnerKeyword.trim()), 300);
    return () => {
      if (partnerKeywordTimerRef.current) window.clearTimeout(partnerKeywordTimerRef.current);
    };
  }, [partnerKeyword]);

  useEffect(() => {
    if (groupKeywordTimerRef.current) window.clearTimeout(groupKeywordTimerRef.current);
    groupKeywordTimerRef.current = window.setTimeout(() => setDebouncedGroupKeyword(groupKeyword.trim()), 300);
    return () => {
      if (groupKeywordTimerRef.current) window.clearTimeout(groupKeywordTimerRef.current);
    };
  }, [groupKeyword]);

  useEffect(() => {
    setRolePageIndex(1);
  }, [debouncedRoleKeyword, rolePageSize]);

  useEffect(() => {
    setMemberPageIndex(1);
  }, [
    debouncedMemberKeyword,
    memberPageSize,
    memberPartnerFilter,
    memberGroupFilter,
    memberLocationFilter,
  ]);

  useEffect(() => {
    setMemberGroupFilter("all");
    setMemberLocationFilter("all");
  }, [memberPartnerFilter]);

  useEffect(() => {
    setMemberLocationFilter("all");
  }, [memberGroupFilter]);

  const memberCompanyOptions = useMemo(
    () =>
      [...memberFilterPartners]
        .filter((p) => (p.partnerName ?? "").trim())
        .sort((a, b) => (a.partnerName ?? "").localeCompare(b.partnerName ?? "")),
    [memberFilterPartners],
  );

  const memberSelectedPartner = useMemo(
    () =>
      memberPartnerFilter === "all"
        ? null
        : memberFilterPartners.find((p) => p.id === memberPartnerFilter) ?? null,
    [memberPartnerFilter, memberFilterPartners],
  );

  const memberRegionOptions = useMemo(() => {
    let list = memberFilterGroups;
    if (memberPartnerFilter !== "all") {
      list = list.filter((g) => g.partnerId === memberPartnerFilter);
    }
    return [...list]
      .filter((g) => (g.groupName ?? "").trim())
      .sort((a, b) => (a.groupName ?? "").localeCompare(b.groupName ?? ""));
  }, [memberFilterGroups, memberPartnerFilter]);

  const memberSelectedGroup = useMemo(
    () =>
      memberGroupFilter === "all"
        ? null
        : memberFilterGroups.find((g) => g.id === memberGroupFilter) ?? null,
    [memberGroupFilter, memberFilterGroups],
  );

  const memberLocationsForSelect = useMemo(
    () =>
      memberFilterLocations.filter((loc) => {
        if (memberSelectedPartner) {
          const pn = (memberSelectedPartner.partnerName ?? "").trim();
          if (pn && (loc.partner ?? "").trim() !== pn) return false;
        }
        if (memberSelectedGroup) {
          const gn = (memberSelectedGroup.groupName ?? "").trim();
          if (gn && (loc.groupName ?? "").trim() !== gn) return false;
        }
        return true;
      }),
    [memberFilterLocations, memberSelectedPartner, memberSelectedGroup],
  );

  const memberUsesClientScopeFilter = useMemo(
    () =>
      memberLocationFilter === "all" &&
      (memberPartnerFilter !== "all" || memberGroupFilter !== "all"),
    [memberPartnerFilter, memberGroupFilter, memberLocationFilter],
  );

  const memberAllowedLocationIds = useMemo((): Set<string> | null => {
    if (memberLocationFilter !== "all" || !memberUsesClientScopeFilter) return null;
    const ids = new Set<string>();
    for (const loc of memberLocationsForSelect) {
      if (loc.id) ids.add(loc.id);
    }
    return ids;
  }, [memberLocationFilter, memberUsesClientScopeFilter, memberLocationsForSelect]);

  useEffect(() => {
    if (activeTab !== "Team Member") return;
    const ac = new AbortController();
    (async () => {
      try {
        const [partners, groups, locations] = await Promise.all([
          loadActivePartnersForSelect(ac.signal),
          loadAllGroupsForSelect(ac.signal),
          loadAllLocationsForMemberFilter(ac.signal),
        ]);
        setMemberFilterPartners(partners);
        setMemberFilterGroups(groups);
        setMemberFilterLocations(locations);
      } catch {
        setMemberFilterPartners([]);
        setMemberFilterGroups([]);
        setMemberFilterLocations([]);
      }
    })();
    return () => ac.abort();
  }, [activeTab, memberRefreshSeq]);

  useEffect(() => {
    if (memberPartnerFilter === "all") return;
    if (!memberCompanyOptions.some((p) => p.id === memberPartnerFilter)) setMemberPartnerFilter("all");
  }, [memberPartnerFilter, memberCompanyOptions]);

  useEffect(() => {
    if (memberGroupFilter === "all") return;
    if (!memberRegionOptions.some((g) => g.id === memberGroupFilter)) setMemberGroupFilter("all");
  }, [memberGroupFilter, memberRegionOptions]);

  useEffect(() => {
    if (memberLocationFilter === "all") return;
    if (!memberLocationsForSelect.some((l) => l.id === memberLocationFilter)) setMemberLocationFilter("all");
  }, [memberLocationFilter, memberLocationsForSelect]);

  useEffect(() => {
    setPartnerPageIndex(1);
  }, [debouncedPartnerKeyword, partnerPageSize, partnerStatusFilter]);

  useEffect(() => {
    setGroupPageIndex(1);
  }, [debouncedGroupKeyword, groupPageSize, groupPartnerFilter, groupStatusFilter]);

  useEffect(() => {
    if (activeTab !== "Group") return;
    const ac = new AbortController();
    (async () => {
      try {
        const opts = await loadAllPartnersForSelect(ac.signal);
        setGroupFilterPartnerOptions(opts);
      } catch (e: any) {
        if (e?.name === "AbortError") return;
        setGroupFilterPartnerOptions([]);
      }
    })();
    return () => ac.abort();
  }, [activeTab, partnerRefreshSeq]);

  useEffect(() => {
    if (activeTab !== "Partner") return;
    const run = async () => {
      partnersAbortRef.current?.abort();
      const ac = new AbortController();
      partnersAbortRef.current = ac;
      setPartnersLoading(true);
      try {
        const res = await getPartners(
          {
            skipCount: Math.max(1, partnerPageIndex),
            maxResultCount: partnerPageSize,
            keyword: debouncedPartnerKeyword || undefined,
            state: listStateFromFilter(partnerStatusFilter),
            sorting: "CreationTime desc",
          },
          ac.signal,
        );
        setPartnerRows(res.items ?? []);
        setPartnerTotal(res.totalCount ?? 0);
      } catch (e: any) {
        if (e?.name === "AbortError") return;
        toast.error("Failed to load companies.", {
          description: e?.message ? String(e.message) : "Please try again.",
        });
        setPartnerRows([]);
        setPartnerTotal(0);
      } finally {
        setPartnersLoading(false);
      }
    };
    run();
    return () => partnersAbortRef.current?.abort();
  }, [activeTab, debouncedPartnerKeyword, partnerPageIndex, partnerPageSize, partnerStatusFilter, partnerRefreshSeq]);

  useEffect(() => {
    if (activeTab !== "Group") return;
    const run = async () => {
      groupsAbortRef.current?.abort();
      const ac = new AbortController();
      groupsAbortRef.current = ac;
      setGroupsLoading(true);
      try {
        const res = await getGroups(
          {
            skipCount: Math.max(1, groupPageIndex),
            maxResultCount: groupPageSize,
            keyword: debouncedGroupKeyword || undefined,
            partnerId: groupPartnerFilter !== "all" ? groupPartnerFilter : undefined,
            state: listStateFromFilter(groupStatusFilter),
            sorting: "CreationTime desc",
          },
          ac.signal,
        );
        setGroupRows(res.items ?? []);
        setGroupTotal(res.totalCount ?? 0);
      } catch (e: any) {
        if (e?.name === "AbortError") return;
        toast.error("Failed to load regions.", {
          description: e?.message ? String(e.message) : "Please try again.",
        });
        setGroupRows([]);
        setGroupTotal(0);
      } finally {
        setGroupsLoading(false);
      }
    };
    run();
    return () => groupsAbortRef.current?.abort();
  }, [
    activeTab,
    debouncedGroupKeyword,
    groupPageIndex,
    groupPageSize,
    groupPartnerFilter,
    groupStatusFilter,
    groupRefreshSeq,
  ]);

  useEffect(() => {
    if (activeTab !== "Roles") return;
    const run = async () => {
      rolesAbortRef.current?.abort();
      const ac = new AbortController();
      rolesAbortRef.current = ac;

      setRolesLoading(true);
      try {
        const res = await getRbacRoles(
          {
            skipCount: Math.max(0, (rolePageIndex - 1) * rolePageSize),
            maxResultCount: rolePageSize,
            roleName: debouncedRoleKeyword || undefined,
          },
          ac.signal,
        );
        setRoles(res.items ?? []);
        setRoleTotal(res.totalCount ?? 0);
      } catch (e: any) {
        if (e?.name === "AbortError") return;
        toast.error("Failed to load roles.", {
          description: e?.message ? String(e.message) : "Please try again.",
        });
        setRoles([]);
        setRoleTotal(0);
      } finally {
        setRolesLoading(false);
      }
    };

    run();
    return () => rolesAbortRef.current?.abort();
  }, [activeTab, debouncedRoleKeyword, rolePageIndex, rolePageSize, roleRefreshSeq]);

  useEffect(() => {
    if (activeTab !== "Team Member") return;
    const run = async () => {
      membersAbortRef.current?.abort();
      const ac = new AbortController();
      membersAbortRef.current = ac;

      setMembersLoading(true);
      try {
        const locationIdParam =
          memberLocationFilter !== "all" ? memberLocationFilter : undefined;

        if (memberUsesClientScopeFilter) {
          const res = await getTeamMembers(
            {
              skipCount: 1,
              maxResultCount: 500,
              keyword: debouncedMemberKeyword || undefined,
            },
            ac.signal,
          );
          const filtered = (res.items ?? []).filter((m) =>
            memberMatchesLocationScope(m, memberAllowedLocationIds),
          );
          const total = filtered.length;
          const start = (memberPageIndex - 1) * memberPageSize;
          setMembers(filtered.slice(start, start + memberPageSize));
          setMemberTotal(total);
        } else {
          const res = await getTeamMembers(
            {
              skipCount: Math.max(1, memberPageIndex),
              maxResultCount: memberPageSize,
              keyword: debouncedMemberKeyword || undefined,
              locationId: locationIdParam,
            },
            ac.signal,
          );
          setMembers(res.items ?? []);
          setMemberTotal(res.totalCount ?? 0);
        }
      } catch (e: any) {
        if (e?.name === "AbortError") return;
        toast.error("Failed to load team members.", {
          description: e?.message ? String(e.message) : "Please try again.",
        });
        setMembers([]);
        setMemberTotal(0);
      } finally {
        setMembersLoading(false);
      }
    };

    run();
    return () => membersAbortRef.current?.abort();
  }, [
    activeTab,
    debouncedMemberKeyword,
    memberPageIndex,
    memberPageSize,
    memberRefreshSeq,
    memberLocationFilter,
    memberUsesClientScopeFilter,
    memberAllowedLocationIds,
  ]);

  const openCreateDialog = () => {
    switch (activeTab) {
      case "Roles":
        setEditingRole(null);
        setIsRoleDialogOpen(true);
        break;
      case "Partner":
        setEditingPartnerRow(null);
        setIsPartnerDialogOpen(true);
        break;
      case "Group":
        setEditingGroupRow(null);
        setIsGroupDialogOpen(true);
        break;
      case "Team Member":
        setEditingMember(null);
        setIsMemberDialogOpen(true);
        break;
      case "Location Manager":
        break;
    }
  };

  const renderAccountTabsRow = () => (
    <div className="w-full border-b border-gray-200">
      <div className="flex overflow-x-auto w-fit">
        {ACCOUNT_TABS.map(({ id, label }) => (
          <button
            key={id}
            type="button"
            onClick={() => setActiveTab(id)}
            style={
              activeTab === id
                ? { borderBottomWidth: 2, borderBottomStyle: "solid", borderBottomColor: "#2563eb" }
                : undefined
            }
            className={cn(
              "px-4 py-2.5 text-sm font-medium whitespace-nowrap cursor-pointer transition-colors -mb-px border-b-2",
              activeTab === id
                ? "text-blue-600"
                : "border-b-transparent text-gray-600 hover:text-gray-800",
            )}
          >
            {label}
          </button>
        ))}
      </div>
    </div>
  );

  const renderToolbar = () => {
    return (
      <div className="flex flex-col gap-4 pb-4">
        {/* Search + Actions - one row, style consistent with Labels / Location Manager */}
        <div className="flex flex-nowrap items-center gap-3">
          <Input
            placeholder="Search"
            value={
              activeTab === "Roles"
                ? roleKeyword
                : activeTab === "Team Member"
                  ? memberKeyword
                  : activeTab === "Partner"
                    ? partnerKeyword
                    : activeTab === "Group"
                      ? groupKeyword
                      : ""
            }
            onChange={(e) => {
              if (activeTab === "Roles") setRoleKeyword(e.target.value);
              if (activeTab === "Team Member") setMemberKeyword(e.target.value);
              if (activeTab === "Partner") setPartnerKeyword(e.target.value);
              if (activeTab === "Group") setGroupKeyword(e.target.value);
            }}
            style={{ height: 40, boxSizing: "border-box" }}
            className="border border-gray-300 rounded-md w-40 shrink-0 bg-white placeholder:text-gray-500"
          />
          {activeTab === "Partner" && (
            <Select
              value={partnerStatusFilter}
              onValueChange={(v) => setPartnerStatusFilter(v as "all" | "active" | "inactive")}
            >
              <SelectTrigger className="w-[140px] h-10 rounded-md border border-gray-300 bg-white font-medium text-gray-900 shrink-0">
                <SelectValue placeholder="Status" />
              </SelectTrigger>
              <SelectContent>
                <SelectItem value="all">All statuses</SelectItem>
                <SelectItem value="active">Active</SelectItem>
                <SelectItem value="inactive">Inactive</SelectItem>
              </SelectContent>
            </Select>
          )}
          {activeTab === "Team Member" && (
            <>
              <Select value={memberPartnerFilter} onValueChange={setMemberPartnerFilter}>
                <SelectTrigger className="w-[160px] h-10 rounded-md border border-gray-300 bg-white font-medium text-gray-900 shrink-0">
                  <SelectValue placeholder="Company" />
                </SelectTrigger>
                <SelectContent>
                  <SelectItem value="all">All Companies</SelectItem>
                  {memberCompanyOptions.map((p) => (
                    <SelectItem key={p.id} value={p.id}>
                      {(p.partnerName ?? "").trim() || p.id}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
              <Select value={memberGroupFilter} onValueChange={setMemberGroupFilter}>
                <SelectTrigger className="w-[160px] h-10 rounded-md border border-gray-300 bg-white font-medium text-gray-900 shrink-0">
                  <SelectValue placeholder="Region" />
                </SelectTrigger>
                <SelectContent>
                  <SelectItem value="all">All Regions</SelectItem>
                  {memberRegionOptions.map((g) => (
                    <SelectItem key={g.id} value={g.id}>
                      {(g.groupName ?? "").trim() || g.id}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
              <Select value={memberLocationFilter} onValueChange={setMemberLocationFilter}>
                <SelectTrigger className="w-[180px] h-10 rounded-md border border-gray-300 bg-white font-medium text-gray-900 shrink-0">
                  <SelectValue placeholder="Location" />
                </SelectTrigger>
                <SelectContent>
                  <SelectItem value="all">All Locations</SelectItem>
                  {memberLocationsForSelect.map((loc) => (
                    <SelectItem key={loc.id} value={loc.id}>
                      {(loc.locationName ?? loc.locationCode ?? loc.id).trim() || loc.id}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </>
          )}
          {activeTab === "Group" && (
            <>
              <Select value={groupPartnerFilter} onValueChange={setGroupPartnerFilter}>
                <SelectTrigger className="w-[180px] h-10 rounded-md border border-gray-300 bg-white font-medium text-gray-900 shrink-0">
                  <SelectValue placeholder="Parent company" />
                </SelectTrigger>
                <SelectContent>
                  <SelectItem value="all">Company (All)</SelectItem>
                  {groupFilterPartnerOptions.map((p) => (
                    <SelectItem key={p.id} value={p.id}>
                      {p.partnerName ?? p.id}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
              <Select
                value={groupStatusFilter}
                onValueChange={(v) => setGroupStatusFilter(v as "all" | "active" | "inactive")}
              >
                <SelectTrigger className="w-[140px] h-10 rounded-md border border-gray-300 bg-white font-medium text-gray-900 shrink-0">
                  <SelectValue placeholder="Status" />
                </SelectTrigger>
                <SelectContent>
                  <SelectItem value="all">All statuses</SelectItem>
                  <SelectItem value="active">Active</SelectItem>
                  <SelectItem value="inactive">Inactive</SelectItem>
                </SelectContent>
              </Select>
            </>
          )}
          <div className="flex-1" />
          {activeTab === "Team Member" && (
            <>
              <Button
                type="button"
                variant="outline"
                className="h-10 border border-gray-300 rounded-md text-gray-900 px-4 bg-white hover:bg-gray-50 shrink-0"
                onClick={() => setMemberBulkImportOpen(true)}
              >
                Bulk Import
              </Button>
              <Button
                type="button"
                variant="outline"
                className="h-10 border border-gray-300 rounded-md text-gray-900 px-4 bg-white hover:bg-gray-50 shrink-0"
                disabled={memberPdfExporting}
                onClick={async () => {
                  setMemberPdfExporting(true);
                  try {
                    await exportTeamMembersPdf({
                      keyword: debouncedMemberKeyword || undefined,
                    });
                    toast.success("Export started.", { description: "Team member PDF download should begin shortly." });
                  } catch (e: unknown) {
                    const msg = e instanceof Error ? e.message : "Please try again.";
                    toast.error("Export failed.", { description: msg });
                  } finally {
                    setMemberPdfExporting(false);
                  }
                }}
              >
                {memberPdfExporting ? "Exporting…" : "Bulk Export (PDF)"}
              </Button>
              <Button
                type="button"
                variant="outline"
                className="h-10 border border-gray-300 rounded-md text-gray-900 px-4 bg-white hover:bg-gray-50 shrink-0"
                onClick={() => {
                  const seed = members.filter((m) => selectedMemberIds.has(m.id));
                  if (seed.length === 0) {
                    toast.error("No rows selected", {
                      description: "Use the checkboxes on the left, then open Bulk Edit.",
                    });
                    return;
                  }
                  setMemberBulkEditSeed(seed);
                  setMemberBulkEditPage(true);
                }}
              >
                Bulk Edit
              </Button>
            </>
          )}
          {(activeTab === "Partner" || activeTab === "Group") && (
            <Button variant="outline" onClick={handleExportPdf} className="h-10 border border-gray-300 rounded-md text-gray-900 px-4 bg-white hover:bg-gray-50 shrink-0">
              Bulk Export (PDF)
            </Button>
          )}
          {(activeTab === "Team Member" || canAmStructureMutate) && (
            <Button
              className="h-10 bg-blue-600 hover:bg-blue-700 text-white rounded-md px-6 font-medium shrink-0"
              onClick={openCreateDialog}
            >
              New+
            </Button>
          )}
        </div>

        {renderAccountTabsRow()}
      </div>
    );
  };

  const renderContent = () => {
    switch (activeTab) {
      case 'Roles':
        return (
          <div className="flex flex-col">
            <Table>
              <TableHeader>
                <TableRow className="bg-gray-100">
                  <TableHead className="font-bold text-black border-r">Role Name</TableHead>
                  <TableHead className="font-bold text-black border-r">Access Permissions</TableHead>
                  <TableHead className="font-bold text-black border-r">Status</TableHead>
                  <TableHead className="font-bold text-black border-r">Order</TableHead>
                  <TableHead className="font-bold text-black text-center">Actions</TableHead>
                </TableRow>
              </TableHeader>
              <TableBody>
                {roles.length === 0 ? (
                  <TableRow>
                    <TableCell colSpan={5} className="text-center text-sm text-gray-500 py-10">
                      {rolesLoading ? "Loading..." : "No data"}
                    </TableCell>
                  </TableRow>
                ) : (
                  roles.map((r) => (
                    <TableRow key={r.id}>
                      <TableCell className="font-medium border-r">{displayRoleNameForUi(r.roleName)}</TableCell>
                      <TableCell className="border-r align-top">
                        <div className="flex flex-wrap gap-1.5 max-w-[360px]">
                          {!(r.accessPermissionCodes?.length) ? (
                            <span className="text-sm text-gray-400">—</span>
                          ) : (
                            (r.accessPermissionCodes ?? []).map((c) => (
                              <Badge
                                key={c}
                                variant="secondary"
                                className="rounded-full bg-sky-100 text-sky-900 border-0 font-normal"
                              >
                                {displayRoleAccessPermissionLabel(c)}
                              </Badge>
                            ))
                          )}
                        </div>
                      </TableCell>
                      <TableCell className="border-r">
                        <Badge className={r.state ? "bg-green-600" : "bg-gray-400"}>
                          {r.state ? "Active" : "Inactive"}
                        </Badge>
                      </TableCell>
                      <TableCell className="border-r text-gray-600">{r.orderNum ?? "N/A"}</TableCell>
                      <TableCell className="text-center">
                        {canAmStructureMutate ? (
                          <>
                            <Button
                              variant="ghost"
                              size="sm"
                              onClick={() => {
                                setMenuRole(r);
                                setIsRoleMenuDialogOpen(true);
                              }}
                              title="Menu Permissions"
                            >
                              <Shield className="w-4 h-4 text-blue-600" />
                            </Button>
                            <Button
                              variant="ghost"
                              size="sm"
                              onClick={() => {
                                setEditingRole(r);
                                setIsRoleDialogOpen(true);
                              }}
                            >
                              <Edit className="w-4 h-4 text-gray-500" />
                            </Button>
                            <Button
                              variant="ghost"
                              size="sm"
                              onClick={async () => {
                                setDeletingRole(r);
                                setIsDeleteRoleDialogOpen(true);
                              }}
                              title="Delete role"
                            >
                              <Trash2 className="w-4 h-4 text-red-600" />
                            </Button>
                          </>
                        ) : (
                          <span className="text-sm text-gray-400">—</span>
                        )}
                      </TableCell>
                    </TableRow>
                  ))
                )}
              </TableBody>
            </Table>

            <div className="px-4 py-3 border-t border-gray-200 bg-white flex flex-wrap items-center justify-between gap-3">
              <div className="text-sm text-gray-600">
                Showing {roleTotal === 0 ? 0 : (rolePageIndex - 1) * rolePageSize + 1}-
                {Math.min(rolePageIndex * rolePageSize, roleTotal)} of {roleTotal}
              </div>

              <div className="flex items-center gap-3">
                <Select value={String(rolePageSize)} onValueChange={(v) => setRolePageSize(Number(v))}>
                  <SelectTrigger className="w-[110px] h-9 rounded-md border border-gray-300 bg-white text-gray-900">
                    <SelectValue />
                  </SelectTrigger>
                  <SelectContent>
                    {[10, 20, 50].map((n) => (
                      <SelectItem key={n} value={String(n)}>
                        {n} / page
                      </SelectItem>
                    ))}
                  </SelectContent>
                </Select>

                <Pagination className="mx-0 w-auto justify-end">
                  <PaginationContent>
                    <PaginationItem>
                      <PaginationPrevious
                        href="#"
                        size="default"
                        onClick={(e) => {
                          e.preventDefault();
                          setRolePageIndex((p) => Math.max(1, p - 1));
                        }}
                        aria-disabled={rolePageIndex <= 1}
                        className={rolePageIndex <= 1 ? "pointer-events-none opacity-50" : ""}
                      />
                    </PaginationItem>
                    <PaginationItem>
                      <PaginationLink href="#" isActive size="default" onClick={(e) => e.preventDefault()}>
                        Page {rolePageIndex} / {roleTotalPages}
                      </PaginationLink>
                    </PaginationItem>
                    <PaginationItem>
                      <PaginationNext
                        href="#"
                        size="default"
                        onClick={(e) => {
                          e.preventDefault();
                          setRolePageIndex((p) => Math.min(roleTotalPages, p + 1));
                        }}
                        aria-disabled={rolePageIndex >= roleTotalPages}
                        className={rolePageIndex >= roleTotalPages ? "pointer-events-none opacity-50" : ""}
                      />
                    </PaginationItem>
                  </PaginationContent>
                </Pagination>
              </div>
            </div>
          </div>
        );
      
      case 'Partner':
        return (
          <div className="flex flex-col">
            <Table>
              <TableHeader>
                <TableRow className="bg-gray-100">
                  <TableHead className="font-bold text-black border-r">Company Name</TableHead>
                  <TableHead className="font-bold text-black border-r">Email</TableHead>
                  <TableHead className="font-bold text-black border-r">Phone</TableHead>
                  <TableHead className="font-bold text-black border-r">Status</TableHead>
                  <TableHead className="font-bold text-black text-center">Actions</TableHead>
                </TableRow>
              </TableHeader>
              <TableBody>
                {partnersLoading ? (
                  <TableRow>
                    <TableCell colSpan={5} className="text-center text-sm text-gray-500 py-10">
                      Loading...
                    </TableCell>
                  </TableRow>
                ) : partnerRows.length === 0 ? (
                  <TableRow>
                    <TableCell colSpan={5} className="text-center text-sm text-gray-500 py-10">
                      No results.
                    </TableCell>
                  </TableRow>
                ) : (
                  partnerRows.map((p) => (
                    <TableRow key={p.id}>
                      <TableCell className="font-medium border-r">{p.partnerName ?? "N/A"}</TableCell>
                      <TableCell className="border-r">{(p.contactEmail ?? "").trim() ? p.contactEmail : "N/A"}</TableCell>
                      <TableCell className="border-r text-gray-600">
                        {(p.phoneNumber ?? "").trim() ? p.phoneNumber : "N/A"}
                      </TableCell>
                      <TableCell className="border-r">
                        <Badge className={p.state ? "bg-green-600" : "bg-gray-400"}>
                          {p.state ? "active" : "inactive"}
                        </Badge>
                      </TableCell>
                      <TableCell className="text-center">
                        {canAmStructureMutate ? (
                          <div className="flex items-center justify-center gap-2">
                            <Button
                              variant="ghost"
                              size="sm"
                              onClick={() => {
                                setEditingPartnerRow(p);
                                setIsPartnerDialogOpen(true);
                              }}
                              title="Edit"
                            >
                              <Edit className="w-4 h-4 text-gray-500" />
                            </Button>
                            <Button
                              variant="ghost"
                              size="sm"
                              onClick={() => {
                                setDeletingPartnerRow(p);
                                setIsDeletePartnerDialogOpen(true);
                              }}
                              title="Delete"
                            >
                              <Trash2 className="w-4 h-4 text-red-600" />
                            </Button>
                          </div>
                        ) : (
                          <span className="text-sm text-gray-400">—</span>
                        )}
                      </TableCell>
                    </TableRow>
                  ))
                )}
              </TableBody>
            </Table>

            <div className="px-4 py-3 border-t border-gray-200 bg-white flex flex-wrap items-center justify-between gap-3">
              <div className="text-sm text-gray-600">
                Showing {partnerTotal === 0 ? 0 : (partnerPageIndex - 1) * partnerPageSize + 1}-
                {Math.min(partnerPageIndex * partnerPageSize, partnerTotal)} of {partnerTotal}
              </div>
              <div className="flex items-center gap-3">
                <Select value={String(partnerPageSize)} onValueChange={(v) => setPartnerPageSize(Number(v))}>
                  <SelectTrigger className="w-[110px] h-9 rounded-md border border-gray-300 bg-white text-gray-900">
                    <SelectValue />
                  </SelectTrigger>
                  <SelectContent>
                    {[10, 20, 50].map((n) => (
                      <SelectItem key={n} value={String(n)}>
                        {n} / page
                      </SelectItem>
                    ))}
                  </SelectContent>
                </Select>
                <Pagination className="mx-0 w-auto justify-end">
                  <PaginationContent>
                    <PaginationItem>
                      <PaginationPrevious
                        href="#"
                        size="default"
                        onClick={(e) => {
                          e.preventDefault();
                          setPartnerPageIndex((p) => Math.max(1, p - 1));
                        }}
                        aria-disabled={partnerPageIndex <= 1}
                        className={partnerPageIndex <= 1 ? "pointer-events-none opacity-50" : ""}
                      />
                    </PaginationItem>
                    <PaginationItem>
                      <PaginationLink href="#" isActive size="default" onClick={(e) => e.preventDefault()}>
                        Page {partnerPageIndex} / {partnerTotalPages}
                      </PaginationLink>
                    </PaginationItem>
                    <PaginationItem>
                      <PaginationNext
                        href="#"
                        size="default"
                        onClick={(e) => {
                          e.preventDefault();
                          setPartnerPageIndex((p) => Math.min(partnerTotalPages, p + 1));
                        }}
                        aria-disabled={partnerPageIndex >= partnerTotalPages}
                        className={partnerPageIndex >= partnerTotalPages ? "pointer-events-none opacity-50" : ""}
                      />
                    </PaginationItem>
                  </PaginationContent>
                </Pagination>
              </div>
            </div>
          </div>
        );

      case 'Group':
        return (
          <div className="flex flex-col">
            <Table>
              <TableHeader>
                <TableRow className="bg-gray-100">
                  <TableHead className="font-bold text-black border-r">Region Name</TableHead>
                  <TableHead className="font-bold text-black border-r">Parent company</TableHead>
                  <TableHead className="font-bold text-black border-r">Status</TableHead>
                  <TableHead className="font-bold text-black text-center">Actions</TableHead>
                </TableRow>
              </TableHeader>
              <TableBody>
                {groupsLoading ? (
                  <TableRow>
                    <TableCell colSpan={4} className="text-center text-sm text-gray-500 py-10">
                      Loading...
                    </TableCell>
                  </TableRow>
                ) : groupRows.length === 0 ? (
                  <TableRow>
                    <TableCell colSpan={4} className="text-center text-sm text-gray-500 py-10">
                      No results.
                    </TableCell>
                  </TableRow>
                ) : (
                  groupRows.map((g) => (
                    <TableRow key={g.id}>
                      <TableCell className="font-medium border-r">{g.groupName ?? "N/A"}</TableCell>
                      <TableCell className="border-r">{(g.partnerName ?? "").trim() ? g.partnerName : "N/A"}</TableCell>
                      <TableCell className="border-r">
                        <Badge className={g.state ? "bg-green-600" : "bg-gray-400"}>
                          {g.state ? "active" : "inactive"}
                        </Badge>
                      </TableCell>
                      <TableCell className="text-center">
                        {canAmStructureMutate ? (
                          <div className="flex items-center justify-center gap-2">
                            <Button
                              variant="ghost"
                              size="sm"
                              onClick={() => {
                                setEditingGroupRow(g);
                                setIsGroupDialogOpen(true);
                              }}
                              title="Edit"
                            >
                              <Edit className="w-4 h-4 text-gray-500" />
                            </Button>
                            <Button
                              variant="ghost"
                              size="sm"
                              onClick={() => {
                                setDeletingGroupRow(g);
                                setIsDeleteGroupDialogOpen(true);
                              }}
                              title="Delete"
                            >
                              <Trash2 className="w-4 h-4 text-red-600" />
                            </Button>
                          </div>
                        ) : (
                          <span className="text-sm text-gray-400">—</span>
                        )}
                      </TableCell>
                    </TableRow>
                  ))
                )}
              </TableBody>
            </Table>

            <div className="px-4 py-3 border-t border-gray-200 bg-white flex flex-wrap items-center justify-between gap-3">
              <div className="text-sm text-gray-600">
                Showing {groupTotal === 0 ? 0 : (groupPageIndex - 1) * groupPageSize + 1}-
                {Math.min(groupPageIndex * groupPageSize, groupTotal)} of {groupTotal}
              </div>
              <div className="flex items-center gap-3">
                <Select value={String(groupPageSize)} onValueChange={(v) => setGroupPageSize(Number(v))}>
                  <SelectTrigger className="w-[110px] h-9 rounded-md border border-gray-300 bg-white text-gray-900">
                    <SelectValue />
                  </SelectTrigger>
                  <SelectContent>
                    {[10, 20, 50].map((n) => (
                      <SelectItem key={n} value={String(n)}>
                        {n} / page
                      </SelectItem>
                    ))}
                  </SelectContent>
                </Select>
                <Pagination className="mx-0 w-auto justify-end">
                  <PaginationContent>
                    <PaginationItem>
                      <PaginationPrevious
                        href="#"
                        size="default"
                        onClick={(e) => {
                          e.preventDefault();
                          setGroupPageIndex((p) => Math.max(1, p - 1));
                        }}
                        aria-disabled={groupPageIndex <= 1}
                        className={groupPageIndex <= 1 ? "pointer-events-none opacity-50" : ""}
                      />
                    </PaginationItem>
                    <PaginationItem>
                      <PaginationLink href="#" isActive size="default" onClick={(e) => e.preventDefault()}>
                        Page {groupPageIndex} / {groupTotalPages}
                      </PaginationLink>
                    </PaginationItem>
                    <PaginationItem>
                      <PaginationNext
                        href="#"
                        size="default"
                        onClick={(e) => {
                          e.preventDefault();
                          setGroupPageIndex((p) => Math.min(groupTotalPages, p + 1));
                        }}
                        aria-disabled={groupPageIndex >= groupTotalPages}
                        className={groupPageIndex >= groupTotalPages ? "pointer-events-none opacity-50" : ""}
                      />
                    </PaginationItem>
                  </PaginationContent>
                </Pagination>
              </div>
            </div>
          </div>
        );

      case 'Team Member':
        return (
          <>
            <Table>
             <TableHeader>
              <TableRow className="bg-gray-100">
                <TableHead className="border-r text-sm font-semibold text-gray-900 w-12 shrink-0 text-center px-3">
                  <Checkbox
                    checked={members.length > 0 && members.every((m) => selectedMemberIds.has(m.id))}
                    onCheckedChange={(c) => {
                      if (c === true) setSelectedMemberIds(new Set(members.map((m) => m.id)));
                      else setSelectedMemberIds(new Set());
                    }}
                    aria-label="Select all on page"
                  />
                </TableHead>
                <TableHead className="border-r text-sm font-semibold text-gray-900">Name</TableHead>
                <TableHead className="border-r text-sm font-semibold text-gray-900">Email</TableHead>
                <TableHead className="border-r text-sm font-semibold text-gray-900">Phone</TableHead>
                <TableHead className="border-r text-sm font-semibold text-gray-900">Role</TableHead>
                <TableHead className="border-r text-sm font-semibold text-gray-900">Assigned Locations</TableHead>
                <TableHead className="border-r text-sm font-semibold text-gray-900">Status</TableHead>
                <TableHead className="text-sm font-semibold text-gray-900 text-center">Actions</TableHead>
              </TableRow>
            </TableHeader>
            <TableBody>
              {membersLoading ? (
                <TableRow>
                  <TableCell colSpan={8} className="text-center text-sm font-normal text-gray-900 py-10">
                    Loading...
                  </TableCell>
                </TableRow>
              ) : members.length === 0 ? (
                <TableRow>
                  <TableCell colSpan={8} className="text-center text-sm font-normal text-gray-900 py-10">
                    No results.
                  </TableCell>
                </TableRow>
              ) : (
                members.map((m) => (
                  <TableRow key={m.id}>
                    <TableCell className="border-r w-12 shrink-0 text-center px-3">
                      <Checkbox
                        checked={selectedMemberIds.has(m.id)}
                        onCheckedChange={(c) => {
                          setSelectedMemberIds((prev) => {
                            const n = new Set(prev);
                            if (c === true) n.add(m.id);
                            else n.delete(m.id);
                            return n;
                          });
                        }}
                        aria-label="Select row"
                      />
                    </TableCell>
                    <TableCell className="border-r text-sm font-normal text-gray-900">
                      {m.fullName ?? m.userName ?? "N/A"}
                    </TableCell>
                    <TableCell className="border-r text-sm font-normal text-gray-900">{m.email ?? "N/A"}</TableCell>
                    <TableCell className="border-r text-sm font-normal text-gray-900">{m.phone ?? "N/A"}</TableCell>
                    <TableCell className="border-r">
                      <Badge variant="outline" className="text-sm font-normal">
                        {displayRoleNameForUi(m.roleName, m.roleId)}
                      </Badge>
                    </TableCell>
                    <TableCell className="border-r text-sm font-normal text-gray-900">
                      <div className="flex flex-col gap-1">
                        {(m.locations?.length ? m.locations : m.locationIds ?? []).map((loc) => (
                          <div key={loc} className="flex items-center gap-1">
                            <MapPin className="w-3.5 h-3.5 shrink-0 text-gray-500" /> {loc}
                          </div>
                        ))}
                        {(!m.locations?.length && !(m.locationIds?.length ?? 0)) && <span>None</span>}
                      </div>
                    </TableCell>
                    <TableCell className="border-r">
                      <Badge
                        className={cn(
                          "text-sm font-normal",
                          m.state ? "bg-green-600" : "bg-gray-400",
                        )}
                      >
                        {m.state ? "Active" : "Inactive"}
                      </Badge>
                    </TableCell>
                    <TableCell className="text-center text-sm font-normal text-gray-900">
                      <div className="flex items-center justify-center gap-2">
                        <Button
                          variant="ghost"
                          size="sm"
                          onClick={() => {
                            setEditingMember(m);
                            setIsMemberDialogOpen(true);
                          }}
                          title="Edit"
                        >
                          <Edit className="w-4 h-4 text-gray-500" />
                        </Button>
                        <Button
                          variant="ghost"
                          size="sm"
                          onClick={() => {
                            setDeletingMember(m);
                            setIsDeleteMemberDialogOpen(true);
                          }}
                          title="Delete"
                        >
                          <Trash2 className="w-4 h-4 text-red-600" />
                        </Button>
                      </div>
                    </TableCell>
                  </TableRow>
                ))
              )}
            </TableBody>
          </Table>

          <div className="px-4 py-3 border-t border-gray-200 bg-white flex flex-wrap items-center justify-between gap-3">
            <div className="text-sm font-normal text-gray-900">
              Showing {memberTotal === 0 ? 0 : (memberPageIndex - 1) * memberPageSize + 1}-
              {Math.min(memberPageIndex * memberPageSize, memberTotal)} of {memberTotal}
            </div>

            <div className="flex items-center gap-3">
              <Select value={String(memberPageSize)} onValueChange={(v) => setMemberPageSize(Number(v))}>
                <SelectTrigger className="w-[110px] h-9 rounded-md border border-gray-300 bg-white text-gray-900">
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  {[10, 20, 50].map((n) => (
                    <SelectItem key={n} value={String(n)}>
                      {n} / page
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>

              <Pagination className="mx-0 w-auto justify-end">
                <PaginationContent>
                  <PaginationItem>
                    <PaginationPrevious
                      href="#"
                      size="default"
                      onClick={(e) => {
                        e.preventDefault();
                        setMemberPageIndex((p) => Math.max(1, p - 1));
                      }}
                      aria-disabled={memberPageIndex <= 1}
                      className={memberPageIndex <= 1 ? "pointer-events-none opacity-50" : ""}
                    />
                  </PaginationItem>
                  <PaginationItem>
                    <PaginationLink href="#" isActive size="default" onClick={(e) => e.preventDefault()}>
                      Page {memberPageIndex} / {memberTotalPages}
                    </PaginationLink>
                  </PaginationItem>
                  <PaginationItem>
                    <PaginationNext
                      href="#"
                      size="default"
                      onClick={(e) => {
                        e.preventDefault();
                        setMemberPageIndex((p) => Math.min(memberTotalPages, p + 1));
                      }}
                      aria-disabled={memberPageIndex >= memberTotalPages}
                      className={memberPageIndex >= memberTotalPages ? "pointer-events-none opacity-50" : ""}
                    />
                  </PaginationItem>
                </PaginationContent>
              </Pagination>
            </div>
          </div>
          </>
        );

      case "Location Manager":
        return (
          <LocationsView
            canMutateLocations={canAmStructureMutate}
            renderBeforeTabs={(toolbar) => (
              <div className="flex flex-col gap-4 pb-4 shrink-0">
                {toolbar}
                {renderAccountTabsRow()}
              </div>
            )}
          />
        );
    }
  };

  const showTeamMemberBulkPage = activeTab === "Team Member" && memberBulkEditPage;

  return (
    <div className="h-full flex flex-col">
      {activeTab !== "Location Manager" && !showTeamMemberBulkPage ? renderToolbar() : null}

      {activeTab === "Location Manager" ? (
        <div className="flex-1 min-h-0 overflow-hidden flex flex-col">{renderContent()}</div>
      ) : showTeamMemberBulkPage ? (
        <div className="flex-1 min-h-0 flex flex-col overflow-hidden pt-6">
          <div className="shrink-0 pb-4">{renderAccountTabsRow()}</div>
          <div className="flex-1 min-h-0 flex flex-col overflow-hidden">
            <div className="bg-white border border-gray-200 shadow-sm rounded-md flex-1 flex flex-col min-h-0 overflow-hidden">
              <TeamMemberBulkEditPage
                seed={memberBulkEditSeed}
                onBack={() => {
                  setMemberBulkEditPage(false);
                  setMemberBulkEditSeed([]);
                }}
                onSaved={() => {
                  setSelectedMemberIds(new Set());
                  setMemberRefreshSeq((x) => x + 1);
                }}
              />
            </div>
          </div>
        </div>
      ) : (
        <div className="flex-1 overflow-auto pt-6">
          <div className="bg-white border border-gray-200 shadow-sm rounded-md">{renderContent()}</div>
        </div>
      )}

      {/* --- Dialogs --- */}
      <RoleDialog
        open={isRoleDialogOpen}
        role={editingRole}
        onOpenChange={(open) => {
          setIsRoleDialogOpen(open);
          if (!open) setEditingRole(null);
        }}
        onSaved={() => {
          setRolePageIndex(1);
          setRoleRefreshSeq((x) => x + 1);
        }}
      />
      <RoleMenuPermissionsDialog
        open={isRoleMenuDialogOpen}
        role={menuRole}
        onOpenChange={(open) => {
          setIsRoleMenuDialogOpen(open);
          if (!open) setMenuRole(null);
        }}
      />
      <DeleteRoleDialog
        open={isDeleteRoleDialogOpen}
        role={deletingRole}
        onOpenChange={(open) => {
          setIsDeleteRoleDialogOpen(open);
          if (!open) setDeletingRole(null);
        }}
        onDeleted={() => setRoleRefreshSeq((x) => x + 1)}
      />
      <PartnerFormDialog
        open={isPartnerDialogOpen}
        partner={editingPartnerRow}
        onOpenChange={(open) => {
          setIsPartnerDialogOpen(open);
          if (!open) setEditingPartnerRow(null);
        }}
        onSaved={() => {
          setPartnerPageIndex(1);
          setPartnerRefreshSeq((x) => x + 1);
        }}
      />
      <DeletePartnerDialog
        open={isDeletePartnerDialogOpen}
        partner={deletingPartnerRow}
        onOpenChange={(open) => {
          setIsDeletePartnerDialogOpen(open);
          if (!open) setDeletingPartnerRow(null);
        }}
        onDeleted={() => {
          setPartnerRefreshSeq((x) => x + 1);
        }}
      />
      <GroupFormDialog
        open={isGroupDialogOpen}
        group={editingGroupRow}
        onOpenChange={(open) => {
          setIsGroupDialogOpen(open);
          if (!open) setEditingGroupRow(null);
        }}
        onSaved={() => {
          setGroupPageIndex(1);
          setGroupRefreshSeq((x) => x + 1);
        }}
      />
      <DeleteGroupDialog
        open={isDeleteGroupDialogOpen}
        group={deletingGroupRow}
        onOpenChange={(open) => {
          setIsDeleteGroupDialogOpen(open);
          if (!open) setDeletingGroupRow(null);
        }}
        onDeleted={() => {
          setGroupRefreshSeq((x) => x + 1);
        }}
      />
      <MemberDialog
        open={isMemberDialogOpen}
        member={editingMember}
        onOpenChange={(open) => {
          setIsMemberDialogOpen(open);
          if (!open) setEditingMember(null);
        }}
        onSaved={() => {
          setMemberPageIndex(1);
          setMemberRefreshSeq((x) => x + 1);
        }}
      />
      <BatchImportDialog
        open={memberBulkImportOpen}
        onOpenChange={setMemberBulkImportOpen}
        title="Bulk import team members"
        description="Upload an .xlsx file. Use the official template for column headers."
        downloadingTemplate={tmplMemberDownloading}
        onDownloadTemplate={async () => {
          setTmplMemberDownloading(true);
          try {
            await downloadTeamMemberImportTemplate();
            toast.success("Template downloaded.");
          } catch (e: unknown) {
            const msg = e instanceof Error ? e.message : "Download failed.";
            toast.error("Template download failed", { description: msg });
          } finally {
            setTmplMemberDownloading(false);
          }
        }}
        onImportFile={async (file) => {
          const r = await importTeamMembersBatch(file);
          setMemberRefreshSeq((x) => x + 1);
          return { successCount: r.successCount, failCount: r.failCount };
        }}
      />
      <DeleteMemberDialog
        open={isDeleteMemberDialogOpen}
        member={deletingMember}
        onOpenChange={(open) => {
          setIsDeleteMemberDialogOpen(open);
          if (!open) setDeletingMember(null);
        }}
        onDeleted={() => {
          setMemberRefreshSeq((x) => x + 1);
        }}
      />
    </div>
  );
}

// --- Sub-Components (Dialogs) ---

function RoleDialog({
  open,
  role,
  onOpenChange,
  onSaved,
}: {
  open: boolean;
  role: RoleDto | null;
  onOpenChange: (open: boolean) => void;
  onSaved: () => void;
}) {
  const isEdit = !!role?.id;
  const [submitting, setSubmitting] = useState(false);
  const [roleName, setRoleName] = useState("");
  const [roleCode, setRoleCode] = useState("");
  const [remark, setRemark] = useState("");
  const [orderNum, setOrderNum] = useState("");
  const [state, setState] = useState(true);
  const [permSet, setPermSet] = useState<Set<string>>(() => new Set());

  useEffect(() => {
    if (!open) return;
    setSubmitting(false);
    setRoleName(role?.roleName ?? "");
    setRoleCode(role?.roleCode ?? "");
    setRemark(role?.remark ?? "");
    setOrderNum(role?.orderNum === null || role?.orderNum === undefined ? "" : String(role.orderNum));
    setState(role?.state ?? true);
    setPermSet(new Set((role?.accessPermissionCodes ?? []).filter(Boolean)));
  }, [open, role]);

  const canSubmit = useMemo(() => {
    return Boolean(roleName.trim() && roleCode.trim());
  }, [roleName, roleCode]);

  const toIntOrNullLocal = (v: string): number | null => {
    const s = v.trim();
    if (!s) return null;
    const n = Number.parseInt(s, 10);
    return Number.isFinite(n) ? n : null;
  };

  const togglePerm = (code: string, checked: boolean) => {
    setPermSet((prev) => {
      const next = new Set(prev);
      if (checked) next.add(code);
      else next.delete(code);
      return next;
    });
  };

  const submit = async () => {
    if (!canSubmit) {
      toast.error("Please fill in required fields.", {
        description: "Role Name and Role Code are required.",
      });
      return;
    }
    setSubmitting(true);
    try {
      const payload: RbacRoleUpsertInput = {
        roleName: roleName.trim(),
        roleCode: roleCode.trim(),
        remark: remark.trim() ? remark.trim() : null,
        state: !!state,
        orderNum: toIntOrNullLocal(orderNum),
        accessPermissionCodes: Array.from(permSet),
      };
      if (isEdit && role?.id) {
        await updateRbacRole(role.id, payload);
        toast.success("Role updated.", { description: "Role fields have been saved successfully." });
      } else {
        await createRbacRole(payload);
        toast.success("Role created.", { description: "A new role has been created successfully." });
      }
      onOpenChange(false);
      onSaved();
    } catch (e: any) {
      toast.error(isEdit ? "Failed to update role." : "Failed to create role.", {
        description: e?.message ? String(e.message) : "Please try again.",
      });
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent className="sm:max-w-[600px]">
        <DialogHeader>
          <DialogTitle>{isEdit ? "Edit Role" : "Create Role"}</DialogTitle>
          <DialogDescription>
            {isEdit ? "Update role fields and save changes." : "Fill out the form to create a new role."}
          </DialogDescription>
        </DialogHeader>
        <div className="space-y-4 py-4">
          <div className="space-y-2">
            <Label>Role Name *</Label>
            <Input value={roleName} onChange={(e) => setRoleName(e.target.value)} placeholder="e.g. Inventory Specialist" />
          </div>

          <div className="space-y-2">
            <Label>Role Code *</Label>
            <Input value={roleCode} onChange={(e) => setRoleCode(e.target.value)} placeholder="e.g. inventory_specialist" />
          </div>

          <div className="space-y-2">
            <Label>Remark</Label>
            <Input value={remark} onChange={(e) => setRemark(e.target.value)} placeholder="Optional" />
          </div>

          <div className="rounded-xl border border-gray-200 bg-gray-50 p-4 space-y-3">
            <div className="flex items-center gap-2 font-semibold text-gray-900">
              <Shield className="w-4 h-4" />
              Access Permissions
            </div>
            <div className="grid grid-cols-2 gap-x-4 gap-y-3">
              {ROLE_ACCESS_PERMISSION_OPTIONS.map((opt) => (
                <label key={opt.code} className="flex items-center gap-2 text-sm text-gray-800 cursor-pointer select-none">
                  <Checkbox
                    checked={permSet.has(opt.code)}
                    onCheckedChange={(v) => togglePerm(opt.code, v === true)}
                  />
                  <span>{opt.label}</span>
                </label>
              ))}
            </div>
          </div>

          <div className="grid grid-cols-2 gap-4">
            <div className="space-y-2">
              <Label>Order</Label>
              <Input value={orderNum} onChange={(e) => setOrderNum(e.target.value)} placeholder="e.g. 10" />
            </div>
            <div className="flex items-center justify-between border border-gray-200 rounded-md px-3 bg-white" style={{ height: 40 }}>
              <div className="text-sm font-medium text-gray-900">Enabled</div>
              <Switch checked={state} onCheckedChange={setState} />
            </div>
          </div>
        </div>
        <DialogFooter>
          <Button variant="outline" onClick={() => onOpenChange(false)}>Cancel</Button>
          <Button
            disabled={submitting}
            onClick={submit}
            className="bg-blue-600 text-white hover:bg-blue-700"
          >
            {submitting ? "Saving..." : "Save"}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

function RoleMenuPermissionsDialog({
  open,
  role,
  onOpenChange,
}: {
  open: boolean;
  role: RoleDto | null;
  onOpenChange: (open: boolean) => void;
}) {
  const roleId = role?.id ?? "";
  const roleName = role?.roleName ?? "";
  const [submitting, setSubmitting] = useState(false);

  const [menuTree, setMenuTree] = useState<RbacMenuTreeNode[]>([]);
  const [menuExpandedIds, setMenuExpandedIds] = useState<Set<string>>(new Set());
  const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
  const [loadingMenus, setLoadingMenus] = useState(false);
  const abortRef = useRef<AbortController | null>(null);

  const [menuKeyword, setMenuKeyword] = useState("");
  const menuKeywordTimerRef = useRef<number | null>(null);
  const [debouncedMenuKeyword, setDebouncedMenuKeyword] = useState("");

  useEffect(() => {
    if (menuKeywordTimerRef.current) window.clearTimeout(menuKeywordTimerRef.current);
    menuKeywordTimerRef.current = window.setTimeout(() => setDebouncedMenuKeyword(menuKeyword.trim()), 300);
    return () => {
      if (menuKeywordTimerRef.current) window.clearTimeout(menuKeywordTimerRef.current);
    };
  }, [menuKeyword]);

  useEffect(() => {
    if (!open) return;
    setSubmitting(false);
    setSelectedIds(new Set());
    setMenuExpandedIds(new Set());

    const run = async () => {
      abortRef.current?.abort();
      const ac = new AbortController();
      abortRef.current = ac;
      setLoadingMenus(true);
      try {
        const tree = await getRbacMenuTree(ac.signal);
        setMenuTree(tree ?? []);
        if (roleId) {
          const checked = await getRbacRoleMenuIds(roleId, ac.signal);
          setSelectedIds(new Set(checked));
        }
      } catch (e: any) {
        if (e?.name === "AbortError") return;
        toast.error("Failed to load menus.", { description: e?.message ? String(e.message) : "Please try again." });
        setMenuTree([]);
        setSelectedIds(new Set());
      } finally {
        setLoadingMenus(false);
      }
    };

    run();
    return () => abortRef.current?.abort();
  }, [open, roleId]);

  const menuTotal = useMemo(() => {
    const walk = (nodes: RbacMenuTreeNode[]): number =>
      nodes.reduce((acc, n) => acc + 1 + (n.children ? walk(n.children) : 0), 0);
    return walk(menuTree);
  }, [menuTree]);

  const filterTree = useMemo(() => {
    const kw = debouncedMenuKeyword.trim().toLowerCase();
    if (!kw) return menuTree;
    const match = (n: RbacMenuTreeNode) => {
      const name = (n.menuName ?? "").toLowerCase();
      const url = (n.routeUrl ?? "").toLowerCase();
      return name.includes(kw) || url.includes(kw);
    };
    const recur = (nodes: RbacMenuTreeNode[]): RbacMenuTreeNode[] => {
      const out: RbacMenuTreeNode[] = [];
      for (const n of nodes) {
        const children = n.children ? recur(n.children) : [];
        if (match(n) || children.length) out.push({ ...n, children: children.length ? children : undefined });
      }
      return out;
    };
    return recur(menuTree);
  }, [menuTree, debouncedMenuKeyword]);

  useEffect(() => {
    const kw = debouncedMenuKeyword.trim();
    if (!kw) return;
    const next = new Set<string>();
    const walk = (nodes: RbacMenuTreeNode[]) => {
      for (const n of nodes) {
        if (n.children?.length) next.add(n.id);
        if (n.children?.length) walk(n.children);
      }
    };
    walk(filterTree);
    setMenuExpandedIds(next);
  }, [debouncedMenuKeyword, filterTree]);

  const getNodeAllIds = (node: RbacMenuTreeNode): string[] => {
    const ids: string[] = [];
    const walk = (n: RbacMenuTreeNode) => {
      if (n.id) ids.push(n.id);
      if (n.children?.length) n.children.forEach(walk);
    };
    walk(node);
    return ids;
  };

  const isCheckedState = (node: RbacMenuTreeNode): { checked: boolean; indeterminate: boolean } => {
    const ids = getNodeAllIds(node);
    if (!ids.length) return { checked: false, indeterminate: false };
    let hit = 0;
    for (const id of ids) if (selectedIds.has(id)) hit += 1;
    if (hit === 0) return { checked: false, indeterminate: false };
    if (hit === ids.length) return { checked: true, indeterminate: false };
    return { checked: false, indeterminate: true };
  };

  const toggleNode = (node: RbacMenuTreeNode, checked: boolean) => {
    setSelectedIds((prev) => {
      const next = new Set(prev);
      const ids = getNodeAllIds(node);
      if (checked) ids.forEach((id) => next.add(id));
      else ids.forEach((id) => next.delete(id));
      return next;
    });
  };

  const toggleExpanded = (id: string) => {
    setMenuExpandedIds((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      return next;
    });
  };

  const highlight = (text: string | null | undefined) => {
    const kw = debouncedMenuKeyword.trim();
    const t = text ?? "";
    if (!kw) return t || "N/A";
    const idx = t.toLowerCase().indexOf(kw.toLowerCase());
    if (idx < 0) return t || "N/A";
    const a = t.slice(0, idx);
    const b = t.slice(idx, idx + kw.length);
    const c = t.slice(idx + kw.length);
    return (
      <span>
        {a}
        <span className="bg-yellow-200 rounded px-0.5">{b}</span>
        {c}
      </span>
    );
  };

  const TreeNodeRow = ({ node, depth }: { node: RbacMenuTreeNode; depth: number }) => {
    const hasChildren = !!node.children?.length;
    const expanded = menuExpandedIds.has(node.id);
    const { checked, indeterminate } = isCheckedState(node);
    return (
      <div>
        <div className="flex items-center gap-2 py-1" style={{ paddingLeft: depth * 16 }}>
          <button
            type="button"
            className={cn(
              "h-6 w-6 flex items-center justify-center rounded hover:bg-gray-100",
              !hasChildren && "opacity-0 pointer-events-none",
            )}
            onClick={() => hasChildren && toggleExpanded(node.id)}
            aria-label={hasChildren ? (expanded ? "Collapse" : "Expand") : "No children"}
          >
            {hasChildren ? (expanded ? <ChevronDown className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />) : null}
          </button>
          <Checkbox
            id={`perm-menu-${node.id}`}
            checked={indeterminate ? "indeterminate" : checked}
            onCheckedChange={(v) => toggleNode(node, !!v)}
          />
          <label htmlFor={`perm-menu-${node.id}`} className="text-sm leading-none cursor-pointer select-none">
            {highlight(node.menuName ?? node.routeUrl ?? node.id)}
          </label>
        </div>
        {hasChildren && expanded && (
          <div>
            {node.children!.map((c) => (
              <TreeNodeRow key={c.id} node={c} depth={depth + 1} />
            ))}
          </div>
        )}
      </div>
    );
  };

  const submit = async () => {
    console.log("submit", role);
    if (!roleId) return;
    setSubmitting(true);
    try {
      await setRoleMenus({
        roleId,
        menuIds: Array.from(selectedIds),
      });
      toast.success("Role menu permissions saved.", {
        description: "Menu permissions have been updated successfully.",
      });
      onOpenChange(false);
    } catch (e: any) {
      toast.error("Failed to save menu permissions.", {
        description: e?.message ? String(e.message) : "Please try again.",
      });
    } finally {
      setSubmitting(false);
    }
  };

  const clearAll = async () => {
    if (!roleId || selectedIds.size === 0) return;
    setSubmitting(true);
    try {
      await deleteRoleMenus({
        roleId,
        menuIds: Array.from(selectedIds),
      });
      setSelectedIds(new Set());
      toast.success("Role menu permissions cleared.", {
        description: "Selected permissions have been removed.",
      });
    } catch (e: any) {
      toast.error("Failed to delete menu permissions.", {
        description: e?.message ? String(e.message) : "Please try again.",
      });
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent className="sm:max-w-none" style={{ width: "50%" }}>
        <DialogHeader>
          <DialogTitle>Menu Permissions</DialogTitle>
          <DialogDescription>
            {roleName ? `Set menu permissions for role: ${roleName}` : "Set menu permissions for this role."}
          </DialogDescription>
        </DialogHeader>
        <div className="space-y-4 py-4">
          <div className="rounded border border-gray-200 bg-white">
            <div className="px-3 py-2 text-xs text-gray-500 border-b border-gray-200">
              <div className="flex items-center gap-2 justify-between">
                <div>{loadingMenus ? "Loading menus..." : `Total ${menuTotal} menus`}</div>
                <Input
                  value={menuKeyword}
                  onChange={(e) => setMenuKeyword(e.target.value)}
                  placeholder="Search menus"
                  className="h-8 w-44 bg-white"
                />
              </div>
            </div>
            <ScrollArea className="h-72">
              <div className="p-3 space-y-2">
                {filterTree.map((n) => (
                  <TreeNodeRow key={n.id} node={n} depth={0} />
                ))}
                {!loadingMenus && filterTree.length === 0 && (
                  <div className="text-sm text-gray-500 py-6 text-center">No menus.</div>
                )}
              </div>
            </ScrollArea>
          </div>
        </div>
        <DialogFooter className="flex flex-row justify-end gap-2">
          <Button variant="outline" onClick={() => onOpenChange(false)}>
            Cancel
          </Button>
          <Button
            variant="destructive"
            className="gap-2"
            disabled={submitting || selectedIds.size === 0 || !roleId}
            onClick={clearAll}
          >
            <Trash2 className="h-4 w-4 shrink-0" />
            Delete Selected
          </Button>
          <Button disabled={submitting || !roleId} onClick={submit} className="bg-blue-600 text-white hover:bg-blue-700">
            {submitting ? "Saving..." : "Save"}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

function DeleteRoleDialog({
  open,
  role,
  onOpenChange,
  onDeleted,
}: {
  open: boolean;
  role: RoleDto | null;
  onOpenChange: (open: boolean) => void;
  onDeleted: () => void;
}) {
  const [submitting, setSubmitting] = useState(false);

  const name = useMemo(() => {
    const n = (role?.roleName ?? "").trim();
    return n || role?.roleCode || role?.id || "this role";
  }, [role]);

  const submit = async () => {
    console.log("submit", role);
    if (!role?.id) return;
    setSubmitting(true);
    try {
      await deleteRbacRole(role.id);
      toast.success("Role deleted.", {
        description: "The role has been removed successfully.",
      });
      onOpenChange(false);
      onDeleted();
    } catch (e: any) {
      toast.error("Failed to delete role.", {
        description: e?.message ? String(e.message) : "Please try again.",
      });
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent className="sm:max-w-none" style={{ width: "30%" }}>
        <DialogHeader>
          <DialogTitle>Delete Role</DialogTitle>
          <DialogDescription>This action cannot be undone.</DialogDescription>
        </DialogHeader>

        <div className="text-sm text-gray-700">
          Are you sure you want to delete <span className="font-medium">{name}</span>?
        </div>

        <DialogFooter className="flex-row flex-wrap justify-end">
          <Button className="min-w-24" variant="outline" onClick={() => onOpenChange(false)}>
            Cancel
          </Button>
          <Button
            className="min-w-24 gap-2"
            variant="destructive"
            disabled={submitting}
            onClick={submit}
          >
            <Trash2 className="h-4 w-4 shrink-0" />
            {submitting ? "Deleting..." : "Delete"}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

function PartnerFormDialog({
  open,
  partner,
  onOpenChange,
  onSaved,
}: {
  open: boolean;
  partner: PartnerListItem | null;
  onOpenChange: (open: boolean) => void;
  onSaved: () => void;
}) {
  const isEdit = !!partner?.id;
  const [submitting, setSubmitting] = useState(false);
  const [partnerName, setPartnerName] = useState("");
  const [contactEmail, setContactEmail] = useState("");
  const [phoneNumber, setPhoneNumber] = useState("");
  const [street, setStreet] = useState("");
  const [city, setCity] = useState("");
  const [stateCode, setStateCode] = useState("");
  const [country, setCountry] = useState("");
  const [zipCode, setZipCode] = useState("");
  const [state, setState] = useState(true);

  useEffect(() => {
    if (!open) return;
    setSubmitting(false);
    setPartnerName(partner?.partnerName ?? "");
    setContactEmail(partner?.contactEmail ?? "");
    setPhoneNumber(partner?.phoneNumber ?? "");
    setStreet(partner?.street ?? "");
    setCity(partner?.city ?? "");
    setStateCode(partner?.stateCode ?? "");
    setCountry(partner?.country ?? "");
    setZipCode(partner?.zipCode ?? "");
    setState(partner?.state ?? true);
  }, [open, partner]);

  const submit = async () => {
    if (!partnerName.trim()) {
      toast.error("Company name is required.");
      return;
    }
    setSubmitting(true);
    try {
      const addressPayload = {
        street: street.trim() || null,
        city: city.trim() || null,
        stateCode: stateCode.trim() || null,
        country: country.trim() || null,
        zipCode: zipCode.trim() || null,
      };
      if (isEdit && partner?.id) {
        await updatePartner(partner.id, {
          partnerName: partnerName.trim(),
          contactEmail: contactEmail.trim() ? contactEmail.trim() : null,
          phoneNumber: phoneNumber.trim() ? phoneNumber.trim() : null,
          state,
          ...addressPayload,
        });
        toast.success("Company updated.");
      } else {
        await createPartner({
          partnerName: partnerName.trim(),
          contactEmail: contactEmail.trim() ? contactEmail.trim() : null,
          phoneNumber: phoneNumber.trim() ? phoneNumber.trim() : null,
          state,
          ...addressPayload,
        });
        toast.success("Company created.");
      }
      onOpenChange(false);
      onSaved();
    } catch (e: any) {
      toast.error(isEdit ? "Failed to update company." : "Failed to create company.", {
        description: e?.message ? String(e.message) : "Please try again.",
      });
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent
        className="sm:max-w-none max-h-[90vh] overflow-y-auto"
        style={{ width: "40%", maxWidth: "calc(100vw - 2rem)" }}
      >
        <DialogHeader>
          <DialogTitle>{isEdit ? "Edit Company" : "Create New Company"}</DialogTitle>
          <DialogDescription>
            {isEdit ? "Update company details and save." : "Add a new company record."}
          </DialogDescription>
        </DialogHeader>
        <div className="space-y-4 py-4">
          <div className="space-y-2">
            <Label>Company Name *</Label>
            <Input
              placeholder="Company Name"
              value={partnerName}
              onChange={(e) => setPartnerName(e.target.value)}
            />
          </div>
          <div className="space-y-2">
            <Label>Contact Email</Label>
            <Input
              placeholder="admin@company.com"
              value={contactEmail}
              onChange={(e) => setContactEmail(e.target.value)}
            />
          </div>
          <div className="space-y-2">
            <Label>Phone Number</Label>
            <Input
              type="tel"
              placeholder="+1 (555) 000-0000"
              value={phoneNumber}
              onChange={(e) => setPhoneNumber(e.target.value)}
            />
          </div>

          <div className="space-y-2">
            <Label>Street *</Label>
            <Input
              placeholder="e.g. 123 Main St"
              value={street}
              onChange={(e) => setStreet(e.target.value)}
            />
          </div>
          <div className="grid grid-cols-2 gap-4">
            <div className="space-y-2">
              <Label>City *</Label>
              <Input placeholder="e.g. New York" value={city} onChange={(e) => setCity(e.target.value)} />
            </div>
            <div className="space-y-2">
              <Label>State *</Label>
              <Input placeholder="e.g. NY" value={stateCode} onChange={(e) => setStateCode(e.target.value)} />
            </div>
          </div>
          <div className="grid grid-cols-2 gap-4">
            <div className="space-y-2">
              <Label>Country *</Label>
              <Input placeholder="e.g. USA" value={country} onChange={(e) => setCountry(e.target.value)} />
            </div>
            <div className="space-y-2">
              <Label>Zip Code *</Label>
              <Input placeholder="e.g. 10001" value={zipCode} onChange={(e) => setZipCode(e.target.value)} />
            </div>
          </div>

          <div className="flex items-center gap-2">
            <Switch id="partner-status" checked={state} onCheckedChange={setState} />
            <Label htmlFor="partner-status">Active</Label>
          </div>
        </div>
        <DialogFooter>
          <Button variant="outline" onClick={() => onOpenChange(false)}>
            Cancel
          </Button>
          <Button
            disabled={submitting}
            onClick={submit}
            className="bg-blue-600 text-white hover:bg-blue-700"
          >
            {submitting ? "Saving..." : "Save"}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

function DeletePartnerDialog({
  open,
  partner,
  onOpenChange,
  onDeleted,
}: {
  open: boolean;
  partner: PartnerListItem | null;
  onOpenChange: (open: boolean) => void;
  onDeleted: () => void;
}) {
  const [submitting, setSubmitting] = useState(false);
  const name = (partner?.partnerName ?? "").trim() || partner?.id || "this company";

  const submit = async () => {
    if (!partner?.id) return;
    setSubmitting(true);
    try {
      await deletePartner(partner.id);
      toast.success("Company deleted.");
      onOpenChange(false);
      onDeleted();
    } catch (e: any) {
      toast.error("Failed to delete company.", {
        description: e?.message ? String(e.message) : "Please try again.",
      });
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent className="sm:max-w-none" style={{ width: "30%" }}>
        <DialogHeader>
          <DialogTitle>Delete Company</DialogTitle>
          <DialogDescription>This action cannot be undone.</DialogDescription>
        </DialogHeader>
        <div className="text-sm text-gray-700">
          Delete <span className="font-medium">{name}</span>?
        </div>
        <DialogFooter className="flex-row flex-wrap justify-end">
          <Button className="min-w-24" variant="outline" onClick={() => onOpenChange(false)}>
            Cancel
          </Button>
          <Button className="min-w-24 gap-2" variant="destructive" disabled={submitting} onClick={submit}>
            <Trash2 className="h-4 w-4 shrink-0" />
            {submitting ? "Deleting..." : "Delete"}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

function GroupFormDialog({
  open,
  group,
  onOpenChange,
  onSaved,
}: {
  open: boolean;
  group: GroupListItem | null;
  onOpenChange: (open: boolean) => void;
  onSaved: () => void;
}) {
  const isEdit = !!group?.id;
  const [submitting, setSubmitting] = useState(false);
  const [groupName, setGroupName] = useState("");
  const [partnerId, setPartnerId] = useState("");
  const [state, setState] = useState(true);
  const [partnerOptions, setPartnerOptions] = useState<PartnerListItem[]>([]);
  const [loadingPartners, setLoadingPartners] = useState(false);

  useEffect(() => {
    if (!open) return;
    const ac = new AbortController();
    (async () => {
      setLoadingPartners(true);
      try {
        const opts = await loadActivePartnersForSelect(ac.signal);
        setPartnerOptions(opts);
      } catch (e: any) {
        if (e?.name !== "AbortError") {
          toast.error("Failed to load companies.", {
            description: e?.message ? String(e.message) : "Please try again.",
          });
          setPartnerOptions([]);
        }
      } finally {
        setLoadingPartners(false);
      }
    })();
    return () => ac.abort();
  }, [open]);

  useEffect(() => {
    if (!open) return;
    setSubmitting(false);
    setGroupName(group?.groupName ?? "");
    setPartnerId(group?.partnerId ?? "");
    setState(group?.state ?? true);
  }, [open, group]);

  const submit = async () => {
    if (!groupName.trim() || !partnerId.trim()) {
      toast.error("Region name and company are required.");
      return;
    }
    setSubmitting(true);
    try {
      if (isEdit && group?.id) {
        await updateGroup(group.id, {
          groupName: groupName.trim(),
          partnerId: partnerId.trim(),
          state,
        });
        toast.success("Region updated.");
      } else {
        await createGroup({
          groupName: groupName.trim(),
          partnerId: partnerId.trim(),
          state,
        });
        toast.success("Region created.");
      }
      onOpenChange(false);
      onSaved();
    } catch (e: any) {
      toast.error(isEdit ? "Failed to update region." : "Failed to create region.", {
        description: e?.message ? String(e.message) : "Please try again.",
      });
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent className="sm:max-w-[600px]">
        <DialogHeader>
          <DialogTitle>{isEdit ? "Edit Region" : "Create New Region"}</DialogTitle>
          <DialogDescription>
            {isEdit ? "Update region and parent company." : "Create a region under an active company."}
          </DialogDescription>
        </DialogHeader>
        <div className="space-y-4 py-4">
          <div className="space-y-2">
            <Label>Region Name *</Label>
            <Input
              placeholder="e.g. West Coast Region"
              value={groupName}
              onChange={(e) => setGroupName(e.target.value)}
            />
          </div>
          <div className="space-y-2">
            <Label>Assign to Company *</Label>
            <Select value={partnerId || undefined} onValueChange={setPartnerId} disabled={loadingPartners}>
              <SelectTrigger className="h-10 rounded-md border border-gray-200 bg-white">
                <SelectValue placeholder={loadingPartners ? "Loading companies..." : "Select Company"} />
              </SelectTrigger>
              <SelectContent>
                {partnerOptions.map((p) => (
                  <SelectItem key={p.id} value={p.id}>
                    {p.partnerName ?? p.id}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </div>
          <div className="flex items-center gap-2">
            <Switch id="group-status" checked={state} onCheckedChange={setState} />
            <Label htmlFor="group-status">Active</Label>
          </div>
        </div>
        <DialogFooter>
          <Button variant="outline" onClick={() => onOpenChange(false)}>
            Cancel
          </Button>
          <Button
            disabled={submitting || loadingPartners}
            onClick={submit}
            className="bg-blue-600 text-white hover:bg-blue-700"
          >
            {submitting ? "Saving..." : "Save"}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

function DeleteGroupDialog({
  open,
  group,
  onOpenChange,
  onDeleted,
}: {
  open: boolean;
  group: GroupListItem | null;
  onOpenChange: (open: boolean) => void;
  onDeleted: () => void;
}) {
  const [submitting, setSubmitting] = useState(false);
  const name = (group?.groupName ?? "").trim() || group?.id || "this region";

  const submit = async () => {
    if (!group?.id) return;
    setSubmitting(true);
    try {
      await deleteGroup(group.id);
      toast.success("Region deleted.");
      onOpenChange(false);
      onDeleted();
    } catch (e: any) {
      toast.error("Failed to delete region.", {
        description: e?.message ? String(e.message) : "Please try again.",
      });
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent className="sm:max-w-none" style={{ width: "30%" }}>
        <DialogHeader>
          <DialogTitle>Delete Region</DialogTitle>
          <DialogDescription>This action cannot be undone.</DialogDescription>
        </DialogHeader>
        <div className="text-sm text-gray-700">
          Delete <span className="font-medium">{name}</span>?
        </div>
        <DialogFooter className="flex-row flex-wrap justify-end">
          <Button className="min-w-24" variant="outline" onClick={() => onOpenChange(false)}>
            Cancel
          </Button>
          <Button className="min-w-24 gap-2" variant="destructive" disabled={submitting} onClick={submit}>
            <Trash2 className="h-4 w-4 shrink-0" />
            {submitting ? "Deleting..." : "Delete"}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

function MemberDialog({
  open,
  member,
  onOpenChange,
  onSaved,
}: {
  open: boolean;
  member: TeamMemberDto | null;
  onOpenChange: (open: boolean) => void;
  onSaved: () => void;
}) {
  const isEdit = !!member?.id;
  const [submitting, setSubmitting] = useState(false);

  const [fullName, setFullName] = useState("");
  const [userName, setUserName] = useState("");
  const [password, setPassword] = useState("");
  const [email, setEmail] = useState("");
  const [phone, setPhone] = useState("");
  const [roleId, setRoleId] = useState("");
  const [state, setState] = useState(true);
  const [selectedLocationIds, setSelectedLocationIds] = useState<Set<string>>(new Set());
  /** 绑定公司 / 区域（单选；接口对接前仅前端状态与门店范围联动） */
  const [companyId, setCompanyId] = useState("");
  const [regionId, setRegionId] = useState("");

  const [roleOptions, setRoleOptions] = useState<RoleDto[]>([]);
  const [loadingRoles, setLoadingRoles] = useState(false);

  const [locationOptions, setLocationOptions] = useState<LocationDto[]>([]);
  const [loadingLocations, setLoadingLocations] = useState(false);
  const [partnerOptions, setPartnerOptions] = useState<PartnerListItem[]>([]);
  const [groupOptionsAll, setGroupOptionsAll] = useState<GroupListItem[]>([]);
  const [loadingPartnersGroups, setLoadingPartnersGroups] = useState(false);
  const [locationKeyword, setLocationKeyword] = useState("");

  const abortRef = useRef<AbortController | null>(null);

  const resetForm = () => {
    setFullName("");
    setUserName("");
    setPassword("");
    setEmail("");
    setPhone("");
    setRoleId("");
    setState(true);
    setSelectedLocationIds(new Set());
    setCompanyId("");
    setRegionId("");
  };

  const loadAllRolesForSelect = async (signal: AbortSignal) => {
    const out: RoleDto[] = [];
    let skip = 0;
    const size = 100;
    for (;;) {
      const res = await getRbacRoles({ skipCount: skip, maxResultCount: size }, signal);
      out.push(...(res.items ?? []));
      if (!res.items || res.items.length < size) break;
      skip += size;
      if (skip > 20000) break; // safety bound
    }
    // 去重（防止后端实现差异）
    const m = new Map<string, RoleDto>();
    for (const r of out) if (r.id && !m.has(r.id)) m.set(r.id, r);
    return Array.from(m.values());
  };

  const loadAllLocationsForSelect = async (signal: AbortSignal) => {
    const out: LocationDto[] = [];
    let skip = 0;
    const size = 200;
    for (;;) {
      const res = await getLocations({ skipCount: skip, maxResultCount: size }, signal);
      out.push(...(res.items ?? []));
      if (!res.items || res.items.length < size) break;
      skip += size;
      if (skip > 40000) break;
    }
    const m = new Map<string, LocationDto>();
    for (const l of out) if (l.id && !m.has(l.id)) m.set(l.id, l);
    return Array.from(m.values());
  };

  useEffect(() => {
    if (!open) return;
    abortRef.current?.abort();
    const ac = new AbortController();
    abortRef.current = ac;

    setSubmitting(false);
    resetForm();
    setLoadingRoles(true);
    setLoadingLocations(true);
    setLoadingPartnersGroups(true);

    const run = async () => {
      try {
        const [rolesRes, locationsRes, partnersRes, groupsRes] = await Promise.all([
          loadAllRolesForSelect(ac.signal),
          loadAllLocationsForSelect(ac.signal),
          loadAllPartnersForSelect(ac.signal),
          loadAllGroupsForSelect(ac.signal),
        ]);
        setRoleOptions(rolesRes);
        setLocationOptions(locationsRes);
        setPartnerOptions(partnersRes);
        setGroupOptionsAll(groupsRes);

        if (member?.id) {
          const detail = await getTeamMemberById(member.id, ac.signal);
          setFullName(detail.fullName ?? "");
          setUserName(detail.userName ?? "");
          setEmail(detail.email ?? "");
          setPhone(detail.phone != null ? String(detail.phone) : "");
          // 有些后端只返回 roleName 而不返回 roleId；这里做兜底匹配，避免编辑后 Save 变成 disabled
          let nextRoleId = (detail.roleId ?? "").toString().trim();
          if (!nextRoleId && detail.roleName) {
            const roleName = String(detail.roleName).trim().toLowerCase();
            const matched = rolesRes.find((r) => {
              const rn = String(r.roleName ?? "").trim().toLowerCase();
              const rc = String(r.roleCode ?? "").trim().toLowerCase();
              const rid = String(r.id ?? "").trim().toLowerCase();
              return rn === roleName || rc === roleName || rid === roleName;
            });
            if (matched?.id) nextRoleId = matched.id;
          }
          setRoleId(nextRoleId);
          setState(!!detail.state);

          const optionIds = new Set(
            locationsRes.map((l) => String(l.id ?? "").trim()).filter(Boolean),
          );
          const rawIds = (detail.locationIds ?? [])
            .map((x) => String(x).trim())
            .filter(Boolean);
          const matchedFromIds = new Set<string>();
          for (const id of rawIds) {
            if (optionIds.has(id)) matchedFromIds.add(id);
          }
          let assignedIds = new Set<string>();
          if (matchedFromIds.size > 0) {
            setSelectedLocationIds(matchedFromIds);
            assignedIds = matchedFromIds;
          } else if (detail.locations?.length) {
            // 如果后端只返回 locations（名字），则尽力映射回 id
            const labels = new Set(detail.locations);
            const inferred = new Set<string>();
            for (const l of locationsRes) {
              const label1 = `${(l.locationCode ?? "").trim()} - ${(l.locationName ?? "").trim()}`.trim();
              const label2 = (l.locationName ?? "").trim();
              const label3 = (l.locationCode ?? "").trim();
              if (labels.has(label1) || labels.has(label2) || labels.has(label3)) inferred.add(l.id);
            }
            setSelectedLocationIds(inferred);
            assignedIds = inferred;
          }

          let nextCompany = String(detail.partnerId ?? "").trim();
          let nextRegion = String(detail.groupId ?? "").trim();
          if ((!nextCompany || !nextRegion) && assignedIds.size > 0) {
            const firstId = [...assignedIds][0];
            const loc0 = locationsRes.find((l) => l.id === firstId);
            if (loc0) {
              const pn = (loc0.partner ?? "").trim();
              const gn = (loc0.groupName ?? "").trim();
              if (!nextCompany && pn) {
                const p = partnersRes.find((x) => (x.partnerName ?? "").trim() === pn);
                if (p) nextCompany = p.id;
              }
              if (!nextRegion && gn && nextCompany) {
                const g = groupsRes.find(
                  (x) => (x.groupName ?? "").trim() === gn && x.partnerId === nextCompany,
                );
                if (g) nextRegion = g.id;
              }
            }
          }
          setCompanyId(nextCompany);
          setRegionId(nextRegion);
        }
      } catch (e: any) {
        if (e?.name !== "AbortError") {
          toast.error("Failed to load user form.", {
            description: e?.message ? String(e.message) : "Please try again.",
          });
        }
      } finally {
        setLoadingRoles(false);
        setLoadingLocations(false);
        setLoadingPartnersGroups(false);
      }
    };

    run();
    return () => ac.abort();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [open, member?.id]);

  const selectedPartnerForScope = useMemo(
    () => partnerOptions.find((p) => p.id === companyId) ?? null,
    [partnerOptions, companyId],
  );

  const selectedGroupForScope = useMemo(
    () => groupOptionsAll.find((g) => g.id === regionId) ?? null,
    [groupOptionsAll, regionId],
  );

  const regionSelectOptions = useMemo(
    () => groupOptionsAll.filter((g) => g.partnerId === companyId),
    [groupOptionsAll, companyId],
  );

  const locationsForMemberPicker = useMemo(() => {
    if (!companyId.trim() || !regionId.trim()) return [];
    const p = selectedPartnerForScope;
    const g = selectedGroupForScope;
    if (!p || !g || g.partnerId !== companyId) return [];
    const pn = (p.partnerName ?? "").trim();
    const gn = (g.groupName ?? "").trim();
    return locationOptions.filter(
      (l) => (l.partner ?? "").trim() === pn && (l.groupName ?? "").trim() === gn,
    );
  }, [companyId, regionId, locationOptions, selectedPartnerForScope, selectedGroupForScope]);

  useEffect(() => {
    if (!open) return;
    const allowed = new Set(locationsForMemberPicker.map((l) => String(l.id ?? "").trim()).filter(Boolean));
    setSelectedLocationIds((prev) => {
      const next = new Set([...prev].filter((id) => allowed.has(id)));
      if (next.size === prev.size && [...next].every((id) => prev.has(id))) return prev;
      return next;
    });
  }, [open, locationsForMemberPicker]);

  const selectedLocationIdsForSubmit = useMemo(() => {
    const allowed = new Set(
      locationsForMemberPicker.map((l) => String(l.id ?? "").trim()).filter(Boolean),
    );
    return Array.from(selectedLocationIds).filter((id) => allowed.has(id));
  }, [locationsForMemberPicker, selectedLocationIds]);

  // Same as create: Save enabled when required fields are filled; empty values show error on submit.
  const canSubmit = useMemo(() => {
    if (!fullName.trim()) return false;
    if (!userName.trim()) return false;
    if (!email.trim()) return false;
    if (!String(phone ?? "").trim()) return false;
    if (!roleId.trim()) return false;
    if (!companyId.trim()) return false;
    if (!regionId.trim()) return false;
    if (selectedLocationIdsForSubmit.length === 0) return false;
    if (!isEdit && !password.trim()) return false;
    return true;
  }, [
    fullName,
    userName,
    email,
    phone,
    roleId,
    companyId,
    regionId,
    selectedLocationIdsForSubmit,
    isEdit,
    password,
  ]);

  const toggleLocation = (id: string, checked: boolean) => {
    setSelectedLocationIds((prev) => {
      const next = new Set(prev);
      if (checked) next.add(id);
      else next.delete(id);
      return next;
    });
  };

  const allLocationIdsForToggle = useMemo(
    () => locationsForMemberPicker.map((l) => String(l.id ?? "").trim()).filter(Boolean),
    [locationsForMemberPicker],
  );

  const allLocationsSelected = useMemo(
    () =>
      allLocationIdsForToggle.length > 0 &&
      allLocationIdsForToggle.every((id) => selectedLocationIds.has(id)),
    [allLocationIdsForToggle, selectedLocationIds],
  );

  const someLocationsSelected = useMemo(
    () =>
      allLocationIdsForToggle.some((id) => selectedLocationIds.has(id)) && !allLocationsSelected,
    [allLocationIdsForToggle, selectedLocationIds, allLocationsSelected],
  );

  const toggleAllLocations = (checked: boolean) => {
    setSelectedLocationIds((prev) => {
      const next = new Set(prev);
      for (const id of allLocationIdsForToggle) {
        if (checked) next.add(id);
        else next.delete(id);
      }
      return next;
    });
  };

  const submit = async (e?: React.MouseEvent) => {
    e?.preventDefault();
    e?.stopPropagation();
    
    console.log("[MemberDialog] submit called", { isEdit, memberId: member?.id, canSubmit, roleId, fullName, userName, selectedLocationIds: selectedLocationIds.size });
    
    // 先校验必填项
    if (!canSubmit) {
      const missing: string[] = [];
      if (!fullName.trim()) missing.push("Full Name");
      if (!userName.trim()) missing.push("Employee ID");
      if (!email.trim()) missing.push("Email");
      if (!String(phone ?? "").trim()) missing.push("Phone");
      if (!roleId.trim()) missing.push("Role");
      if (!companyId.trim()) missing.push("Company");
      if (!regionId.trim()) missing.push("Region");
      if (selectedLocationIdsForSubmit.length === 0) missing.push("Locations");
      if (!isEdit && !password.trim()) missing.push("Password");
      toast.error("Missing required fields.", {
        description: `Please fill: ${missing.join(", ")}.`,
      });
      return;
    }

    if (!isEdit && !member?.id) {
      // 新增模式
      setSubmitting(true);
      try {
        const locationIds = selectedLocationIdsForSubmit;
        console.log("[MemberDialog] Creating user", { fullName, userName, roleId, locationIds });
        await createTeamMember({
          fullName: fullName.trim(),
          userName: userName.trim(),
          password: password.trim(),
          email: email.trim(),
          phone: String(phone).trim(),
          roleId: roleId.trim(),
          locationIds,
          state,
        });
        toast.success("User created.", { description: "A new user has been created successfully." });
        onOpenChange(false);
        onSaved();
      } catch (e: any) {
        console.error("[MemberDialog] Create error", e);
        toast.error("Failed to create user.", {
          description: e?.message ? String(e.message) : "Please try again.",
        });
      } finally {
        setSubmitting(false);
      }
    } else if (isEdit && member?.id) {
      // 编辑模式
      setSubmitting(true);
      try {
        const locationIds = selectedLocationIdsForSubmit;
        console.log("[MemberDialog] Updating user", { id: member.id, fullName, userName, roleId, locationIds });
        await updateTeamMember(member.id, {
          fullName: fullName.trim(),
          userName: userName.trim(),
          password: password.trim() ? password.trim() : null,
          email: email.trim(),
          phone: String(phone).trim(),
          roleId: roleId.trim(),
          locationIds,
          state,
        });
        toast.success("User updated.", { description: "Changes have been saved successfully." });
        onOpenChange(false);
        onSaved();
      } catch (e: any) {
        console.error("[MemberDialog] Update error", e);
        toast.error("Failed to update user.", {
          description: e?.message ? String(e.message) : "Please try again.",
        });
      } finally {
        setSubmitting(false);
      }
    } else {
      console.error("[MemberDialog] Invalid state", { isEdit, memberId: member?.id });
      toast.error("Invalid form state.", {
        description: "Please refresh and try again.",
      });
    }
  };

  const locationLabel = (l: LocationDto) => {
    const code = (l.locationCode ?? "").trim();
    const name = (l.locationName ?? "").trim();
    return code && name ? `${code} - ${name}` : name || code || l.id;
  };

  const filteredLocations = useMemo(() => {
    const kw = locationKeyword.trim().toLowerCase();
    const base = locationsForMemberPicker;
    if (!kw) return base;
    return base.filter((l) => locationLabel(l).toLowerCase().includes(kw));
  }, [locationsForMemberPicker, locationKeyword]);

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent className="sm:max-w-none" style={{ width: "50%" }}>
        <DialogHeader>
          <DialogTitle>{isEdit ? "Edit User" : "New User"}</DialogTitle>
          <DialogDescription>
            Company and Region are single-select and scope which locations you can assign; Locations is
            multi-select.
          </DialogDescription>
        </DialogHeader>

        <div className="space-y-4 py-4 max-h-[70vh] overflow-y-auto pr-1">
          <div className="grid grid-cols-2 gap-4">
            <div className="space-y-2">
              <Label>Full Name *</Label>
              <Input value={fullName} onChange={(e) => setFullName(e.target.value)} placeholder="John Doe" />
            </div>
            <div className="space-y-2">
              <Label>Employee ID *</Label>
              <Input value={userName} onChange={(e) => setUserName(e.target.value)} placeholder="Employee ID" />
            </div>
          </div>

          {!isEdit && (
            <div className="space-y-2">
              <Label>Password *</Label>
              <Input
                type="password"
                value={password}
                onChange={(e) => setPassword(e.target.value)}
                placeholder="Enter password"
                autoComplete="new-password"
                className="w-full"
              />
            </div>
          )}

          {isEdit && (
            <div className="space-y-2">
              <Label>Password (Optional)</Label>
              <Input
                type="password"
                value={password}
                onChange={(e) => setPassword(e.target.value)}
                placeholder="Enter new password (optional)"
                autoComplete="new-password"
                className="w-full"
              />
            </div>
          )}

          <div className="grid grid-cols-2 gap-4">
            <div className="space-y-2">
              <Label>Email *</Label>
              <Input value={email} onChange={(e) => setEmail(e.target.value)} placeholder="john@example.com" />
            </div>
            <div className="space-y-2">
              <Label>Phone *</Label>
              <Input value={phone} onChange={(e) => setPhone(e.target.value)} placeholder="+1 (555) 000-0000" />
            </div>
          </div>

          <div className="space-y-2">
            <Label>Role *</Label>
            <Select
              value={roleId ? roleId : ""}
              onValueChange={(v) => {
                const newRoleId = (v && v.trim()) ? v.trim() : "";
                console.log("[MemberDialog] Role changed", { old: roleId, new: newRoleId, v });
                setRoleId(newRoleId);
              }}
              disabled={loadingRoles}
            >
              <SelectTrigger className="h-10 rounded-md border border-gray-200 bg-white">
                <SelectValue placeholder={loadingRoles ? "Loading roles..." : "Select role"} />
              </SelectTrigger>
              <SelectContent>
                {roleOptions.map((r) => (
                  <SelectItem key={r.id} value={r.id}>
                    {displayRoleNameForUi(r.roleName, r.roleCode ?? r.id)}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </div>

          <div className="grid grid-cols-2 gap-4">
            <div className="space-y-2">
              <Label>Company *</Label>
              <Select
                value={companyId ? companyId : ""}
                onValueChange={(v) => {
                  const next = (v && v.trim()) ? v.trim() : "";
                  setCompanyId(next);
                  setRegionId("");
                }}
                disabled={loadingRoles || loadingPartnersGroups}
              >
                <SelectTrigger className="h-10 rounded-md border border-gray-200 bg-white">
                  <SelectValue
                    placeholder={
                      loadingPartnersGroups ? "Loading companies..." : "Select company"
                    }
                  />
                </SelectTrigger>
                <SelectContent>
                  {partnerOptions.map((p) => (
                    <SelectItem key={p.id} value={p.id}>
                      {(p.partnerName ?? "").trim() || p.id}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </div>
            <div className="space-y-2">
              <Label>Region *</Label>
              <Select
                value={regionId ? regionId : ""}
                onValueChange={(v) => setRegionId((v && v.trim()) ? v.trim() : "")}
                disabled={loadingRoles || loadingPartnersGroups || !companyId.trim()}
              >
                <SelectTrigger className="h-10 rounded-md border border-gray-200 bg-white">
                  <SelectValue
                    placeholder={
                      !companyId.trim()
                        ? "Select company first"
                        : loadingPartnersGroups
                          ? "Loading regions..."
                          : "Select region"
                    }
                  />
                </SelectTrigger>
                <SelectContent>
                  {regionSelectOptions.map((g) => (
                    <SelectItem key={g.id} value={g.id}>
                      {(g.groupName ?? "").trim() || g.id}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </div>
          </div>

          <div className="space-y-2">
            <Label>Locations *</Label>
            <div className="flex items-center justify-between gap-2">
              <Input
                value={locationKeyword}
                onChange={(e) => setLocationKeyword(e.target.value)}
                placeholder="Search locations"
                className="h-9"
              />
              <div className="text-xs text-gray-500 shrink-0">
                {selectedLocationIdsForSubmit.length} selected
              </div>
            </div>
            <ScrollArea className="h-[180px] w-full border rounded-md p-2">
              <div className="space-y-2">
                {loadingLocations ? (
                  <div className="text-sm text-gray-500 py-2">Loading...</div>
                ) : !companyId.trim() || !regionId.trim() ? (
                  <div className="text-sm text-gray-500 py-2">
                    Please select company and region to see locations for assignment.
                  </div>
                ) : (
                  <>
                    {locationsForMemberPicker.length > 0 ? (
                      <div className="flex items-center space-x-2 border-b border-gray-100 pb-2 mb-1">
                        <Checkbox
                          id="loc-select-all"
                          checked={
                            allLocationsSelected
                              ? true
                              : someLocationsSelected
                                ? "indeterminate"
                                : false
                          }
                          onCheckedChange={(v) => toggleAllLocations(v === true)}
                        />
                        <label
                          htmlFor="loc-select-all"
                          className="text-sm font-medium cursor-pointer w-full hover:bg-gray-50 p-1 rounded"
                        >
                          ALL
                        </label>
                      </div>
                    ) : null}
                    {filteredLocations.length === 0 ? (
                      <div className="text-sm text-gray-500 py-2">
                        No locations match this company/region (or search keyword).
                      </div>
                    ) : null}
                    {filteredLocations.map((l) => (
                      <div key={l.id} className="flex items-center space-x-2">
                        <Checkbox
                          id={`loc-${l.id}`}
                          checked={selectedLocationIds.has(l.id)}
                          onCheckedChange={(v) => toggleLocation(l.id, !!v)}
                        />
                        <label htmlFor={`loc-${l.id}`} className="text-sm cursor-pointer w-full hover:bg-gray-50 p-1 rounded">
                          {locationLabel(l)}
                        </label>
                      </div>
                    ))}
                  </>
                )}
              </div>
            </ScrollArea>
            <p className="text-xs text-gray-500">* Users must be assigned to at least one location.</p>
          </div>

          <div className="flex items-center gap-2 pt-2">
            <Switch id="member-status" checked={state} onCheckedChange={setState} />
            <Label htmlFor="member-status">{state ? "Active" : "Inactive"}</Label>
          </div>
        </div>

        <DialogFooter>
          <Button variant="outline" onClick={() => onOpenChange(false)}>
            Cancel
          </Button>
          <Button
            disabled={submitting || !canSubmit}
            onClick={(e) => {
              e.preventDefault();
              e.stopPropagation();
              submit(e);
            }}
            className="bg-blue-600 text-white hover:bg-blue-700"
          >
            {submitting ? "Saving..." : isEdit ? "Save" : "Create"}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

function DeleteMemberDialog({
  open,
  member,
  onOpenChange,
  onDeleted,
}: {
  open: boolean;
  member: TeamMemberDto | null;
  onOpenChange: (open: boolean) => void;
  onDeleted: () => void;
}) {
  const [submitting, setSubmitting] = useState(false);

  const name = useMemo(() => {
    const n = (member?.fullName ?? "").trim();
    const code = (member?.userName ?? "").trim();
    return n || code || "this user";
  }, [member?.fullName, member?.userName]);

  const submit = async () => {
    console.log("submit", member);
    if (!member?.id) return;
    setSubmitting(true);
    try {
      await deleteTeamMember(member.id);
      toast.success("User deleted.", { description: "The user has been removed successfully." });
      onOpenChange(false);
      onDeleted();
    } catch (e: any) {
      toast.error("Failed to delete user.", {
        description: e?.message ? String(e.message) : "Please try again.",
      });
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent className="sm:max-w-none" style={{ width: "30%" }}>
        <DialogHeader>
          <DialogTitle>Delete User</DialogTitle>
          <DialogDescription>This action cannot be undone.</DialogDescription>
        </DialogHeader>

        <div className="text-sm text-gray-700">
          Are you sure you want to delete <span className="font-medium">{name}</span>?
        </div>

        <DialogFooter className="flex-row flex-wrap justify-end">
          <Button variant="outline" className="min-w-24" onClick={() => onOpenChange(false)}>
            Cancel
          </Button>
          <Button
            variant="destructive"
            className="min-w-24 gap-2"
            disabled={submitting}
            onClick={submit}
          >
            <Trash2 className="h-4 w-4 shrink-0" />
            {submitting ? "Deleting..." : "Delete"}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}
