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 { getRoles } from "../../services/roleService";
import { createRbacRole, deleteRbacRole, getRbacRoleMenuIds, updateRbacRole } from "../../services/rbacRoleService";
import type { RoleDto } from "../../types/role";
import { getLocations } from "../../services/locationService";
import {
  createTeamMember,
  deleteTeamMember,
  getTeamMemberById,
  getTeamMembers,
  updateTeamMember,
} from "../../services/teamMemberService";
import type { LocationDto } from "../../types/location";
import type { TeamMemberCreateInput, TeamMemberDto, TeamMemberUpdateInput } from "../../types/teamMember";

// --- Mock Data ---

const MOCK_LOCATIONS = [
  { id: '1', name: 'Downtown Store (101)' },
  { id: '2', name: 'Uptown Store (102)' },
  { id: '3', name: 'Airport Kiosk (201)' },
  { id: '4', name: 'Mall Outlet (305)' },
];

const MOCK_ROLES = [
  { 
    id: 'r1', 
    name: 'Partner Admin', 
    permissions: ['all'], 
    notifications: ['system_updates', 'billing'] 
  },
  { 
    id: 'r2', 
    name: 'Group Admin', 
    permissions: ['manage_users', 'manage_products', 'view_reports'], 
    notifications: ['new_users'] 
  },
  { 
    id: 'r3', 
    name: 'Manager', 
    permissions: ['manage_store', 'view_reports', 'manage_inventory'], 
    notifications: ['label_expiry', 'low_stock'] 
  },
  { 
    id: 'r4', 
    name: 'Team Member', 
    permissions: ['view_tasks', 'print_labels'], 
    notifications: ['task_assignment'] 
  }
];

const MOCK_PARTNERS = [
  { id: 'p1', name: 'Global Foods Inc.', status: 'active', contact: 'admin@globalfoods.com', phone: '+1 (555) 100-2000' },
  { id: 'p2', name: 'Local Eateries Co.', status: 'active', contact: 'support@localeateries.com', phone: '+1 (555) 200-3000' },
];

const MOCK_GROUPS = [
  { id: 'g1', name: 'West Coast Region', partner: 'Global Foods Inc.', status: 'active' },
  { id: 'g2', name: 'East Coast Region', partner: 'Global Foods Inc.', status: 'inactive' },
];

const MOCK_MEMBERS = [
  { 
    id: 'm1', 
    name: 'Alice Johnson', 
    role: 'Manager', 
    locations: ['Downtown Store (101)', 'Uptown Store (102)'], 
    email: 'alice@example.com',
    phone: '+1 (555) 111-2222',
    status: 'active'
  },
  { 
    id: 'm2', 
    name: 'Bob Smith', 
    role: 'Team Member', 
    locations: ['Airport Kiosk (201)'], 
    email: 'bob@example.com',
    phone: '+1 (555) 222-3333',
    status: 'active'
  },
  { 
    id: 'm3', 
    name: 'Charlie Brown', 
    role: 'Team Member', 
    locations: ['Downtown Store (101)'], 
    email: 'charlie@example.com',
    phone: '+1 (555) 333-4444',
    status: 'inactive'
  },
];

// --- Types ---
type ViewTab = 'Roles' | 'Partner' | 'Group' | 'Team Member';

export function PeopleView() {
  const [activeTab, setActiveTab] = useState<ViewTab>('Roles');
  
  // 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 [partners, setPartners] = useState(MOCK_PARTNERS);
  const [groups, setGroups] = useState(MOCK_GROUPS);

  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 [editingMember, setEditingMember] = useState<TeamMemberDto | null>(null);
  const [isDeleteMemberDialogOpen, setIsDeleteMemberDialogOpen] = useState(false);
  const [deletingMember, setDeletingMember] = useState<TeamMemberDto | null>(null);

  // 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 [isGroupDialogOpen, setIsGroupDialogOpen] = useState(false);
  const [isMemberDialogOpen, setIsMemberDialogOpen] = useState(false);

  // Handlers
  const handleExportPdf = () => {
    alert(`Exporting ${activeTab} list to PDF...`);
  };

  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(() => {
    setRolePageIndex(1);
  }, [debouncedRoleKeyword, rolePageSize]);

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

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

      setRolesLoading(true);
      try {
        const res = await getRoles(
          {
            skipCount: Math.max(1, rolePageIndex),
            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 res = await getTeamMembers(
          {
            skipCount: Math.max(1, memberPageIndex),
            maxResultCount: memberPageSize,
            keyword: debouncedMemberKeyword || undefined,
          },
          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]);

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

  const renderToolbar = () => {
    const canBulkOps = activeTab === 'Team Member';

    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 : ""}
            onChange={(e) => {
              if (activeTab === "Roles") setRoleKeyword(e.target.value);
              if (activeTab === "Team Member") setMemberKeyword(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"
          />
          <div className="flex-1" />
          {canBulkOps && (
            <>
              <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">
                Bulk Import
              </Button>
              <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">
                Bulk Edit
              </Button>
            </>
          )}
          <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>
          <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>

        {/* Tabs - underline spans full width */}
        <div className="w-full border-b border-gray-200">
          <div className="flex overflow-x-auto w-fit">
            {['Roles', 'Partner', 'Group', 'Team Member'].map((tab) => (
            <button
              key={tab}
              onClick={() => setActiveTab(tab as ViewTab)}
              style={activeTab === tab ? { 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 === tab
                  ? "text-blue-600"
                  : "border-b-transparent text-gray-600 hover:text-gray-800"
              )}
            >
              {tab}
            </button>
            ))}
          </div>
        </div>
      </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">Role Code</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">{r.roleName ?? "N/A"}</TableCell>
                      <TableCell className="border-r text-gray-600">{r.roleCode ?? "N/A"}</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">
                        <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>
                      </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 (
          <Table>
             <TableHeader>
              <TableRow className="bg-gray-100">
                <TableHead className="font-bold text-black border-r">Partner Name</TableHead>
                <TableHead className="font-bold text-black border-r">Contact</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>
              {partners.map(p => (
                <TableRow key={p.id}>
                  <TableCell className="font-medium border-r">{p.name}</TableCell>
                  <TableCell className="border-r">{p.contact}</TableCell>
                  <TableCell className="border-r text-gray-600">{p.phone}</TableCell>
                  <TableCell className="border-r">
                    <Badge className={p.status === 'active' ? "bg-green-600" : "bg-gray-400"}>
                      {p.status}
                    </Badge>
                  </TableCell>
                  <TableCell className="text-center">
                    <Button variant="ghost" size="sm"><Edit className="w-4 h-4 text-gray-500" /></Button>
                  </TableCell>
                </TableRow>
              ))}
            </TableBody>
          </Table>
        );

      case 'Group':
        return (
          <Table>
             <TableHeader>
              <TableRow className="bg-gray-100">
                <TableHead className="font-bold text-black border-r">Group Name</TableHead>
                <TableHead className="font-bold text-black border-r">Parent Partner</TableHead>
                <TableHead className="font-bold text-black border-r">Status</TableHead>
                <TableHead className="font-bold text-black text-center">Actions</TableHead>
              </TableRow>
            </TableHeader>
            <TableBody>
              {groups.map(g => (
                <TableRow key={g.id}>
                  <TableCell className="font-medium border-r">{g.name}</TableCell>
                  <TableCell className="border-r">{g.partner}</TableCell>
                  <TableCell className="border-r">
                    <Badge className={g.status === 'active' ? "bg-green-600" : "bg-gray-400"}>
                      {g.status}
                    </Badge>
                  </TableCell>
                  <TableCell className="text-center">
                    <Button variant="ghost" size="sm"><Edit className="w-4 h-4 text-gray-500" /></Button>
                  </TableCell>
                </TableRow>
              ))}
            </TableBody>
          </Table>
        );

      case 'Team Member':
        return (
          <>
            <Table>
             <TableHeader>
              <TableRow className="bg-gray-100">
                <TableHead className="font-bold text-black border-r">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">Role</TableHead>
                <TableHead className="font-bold text-black border-r">Assigned Locations</TableHead>
                <TableHead className="font-bold text-black border-r">Status</TableHead>
                <TableHead className="font-bold text-black text-center">Actions</TableHead>
              </TableRow>
            </TableHeader>
            <TableBody>
              {membersLoading ? (
                <TableRow>
                  <TableCell colSpan={7} className="text-center text-sm text-gray-500 py-10">
                    Loading...
                  </TableCell>
                </TableRow>
              ) : members.length === 0 ? (
                <TableRow>
                  <TableCell colSpan={7} className="text-center text-sm text-gray-500 py-10">
                    No results.
                  </TableCell>
                </TableRow>
              ) : (
                members.map((m) => (
                  <TableRow key={m.id}>
                    <TableCell className="font-medium border-r">{m.fullName ?? m.userName ?? "N/A"}</TableCell>
                    <TableCell className="border-r text-gray-600">{m.email ?? "N/A"}</TableCell>
                    <TableCell className="border-r text-gray-600">{m.phone ?? "N/A"}</TableCell>
                    <TableCell className="border-r">
                      <Badge variant="outline" className="font-normal">
                        {m.roleName ?? m.roleId ?? "N/A"}
                      </Badge>
                    </TableCell>
                    <TableCell className="border-r">
                      <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 text-xs text-gray-600">
                            <MapPin className="w-3 h-3" /> {loc}
                          </div>
                        ))}
                        {(!m.locations?.length && !(m.locationIds?.length ?? 0)) && (
                          <div className="text-xs text-gray-500">None</div>
                        )}
                      </div>
                    </TableCell>
                    <TableCell className="border-r">
                      <Badge className={m.state ? "bg-green-600" : "bg-gray-400"}>
                        {m.state ? "Active" : "Inactive"}
                      </Badge>
                    </TableCell>
                    <TableCell className="text-center">
                      <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 text-gray-600">
              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>
          </>
        );
    }
  };

  return (
    <div className="h-full flex flex-col">
      {renderToolbar()}

      <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)}
      />
      <CreatePartnerDialog open={isPartnerDialogOpen} onOpenChange={setIsPartnerDialogOpen} />
      <CreateGroupDialog open={isGroupDialogOpen} onOpenChange={setIsGroupDialogOpen} />
      <MemberDialog
        open={isMemberDialogOpen}
        member={editingMember}
        onOpenChange={(open) => {
          setIsMemberDialogOpen(open);
          if (!open) setEditingMember(null);
        }}
        onSaved={() => {
          setMemberPageIndex(1);
          setMemberRefreshSeq((x) => x + 1);
        }}
      />
      <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);

  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);
  }, [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 submit = async () => {
    console.log("submit", role);
    if (!canSubmit) {
      toast.error("Please fill in required fields.", {
        description: "Role Name and Role Code are required.",
      });
      return;
    }
    setSubmitting(true);
    try {
      const payload = {
        roleName: roleName.trim(),
        roleCode: roleCode.trim(),
        remark: remark.trim() ? remark.trim() : null,
        state: !!state,
        orderNum: toIntOrNullLocal(orderNum),
      };
      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="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" disabled={submitting || selectedIds.size === 0 || !roleId} onClick={clearAll}>
            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"
            variant="destructive"
            disabled={submitting}
            onClick={submit}
          >
            {submitting ? "Deleting..." : "Delete"}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

function CreatePartnerDialog({ open, onOpenChange }: { open: boolean; onOpenChange: (open: boolean) => void }) {
  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Create New Partner</DialogTitle>
        </DialogHeader>
        <div className="space-y-4 py-4">
          <div className="space-y-2">
            <Label>Partner Name</Label>
            <Input placeholder="Company Name" />
          </div>
          <div className="space-y-2">
            <Label>Contact Email</Label>
            <Input placeholder="admin@partner.com" />
          </div>
          <div className="space-y-2">
            <Label>Phone Number</Label>
            <Input type="tel" placeholder="+1 (555) 000-0000" />
          </div>
          <div className="flex items-center gap-2">
            <Switch id="partner-status" defaultChecked />
            <Label htmlFor="partner-status">Active</Label>
          </div>
        </div>
        <DialogFooter>
          <Button variant="outline" onClick={() => onOpenChange(false)}>Cancel</Button>
          <Button onClick={() => onOpenChange(false)} className="bg-blue-600 text-white hover:bg-blue-700">Save Partner</Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

function CreateGroupDialog({ open, onOpenChange }: { open: boolean; onOpenChange: (open: boolean) => void }) {
  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Create New Group</DialogTitle>
        </DialogHeader>
        <div className="space-y-4 py-4">
          <div className="space-y-2">
            <Label>Group Name</Label>
            <Input placeholder="e.g. West Coast Region" />
          </div>
          <div className="space-y-2">
            <Label>Assign to Partner</Label>
            <Select>
              <SelectTrigger><SelectValue placeholder="Select Partner" /></SelectTrigger>
              <SelectContent>
                {MOCK_PARTNERS.map(p => <SelectItem key={p.id} value={p.id}>{p.name}</SelectItem>)}
              </SelectContent>
            </Select>
          </div>
          <div className="flex items-center gap-2">
            <Switch id="group-status" defaultChecked />
            <Label htmlFor="group-status">Active</Label>
          </div>
        </div>
        <DialogFooter>
          <Button variant="outline" onClick={() => onOpenChange(false)}>Cancel</Button>
          <Button onClick={() => onOpenChange(false)} className="bg-blue-600 text-white hover:bg-blue-700">Save Group</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 [roleOptions, setRoleOptions] = useState<RoleDto[]>([]);
  const [loadingRoles, setLoadingRoles] = useState(false);

  const [locationOptions, setLocationOptions] = useState<LocationDto[]>([]);
  const [loadingLocations, setLoadingLocations] = 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());
  };

  const loadAllRolesForSelect = async (signal: AbortSignal) => {
    const out: RoleDto[] = [];
    let page = 1;
    const size = 100;
    for (;;) {
      const res = await getRoles({ skipCount: page, maxResultCount: size }, signal);
      out.push(...(res.items ?? []));
      if (!res.items || res.items.length < size) break;
      page += 1;
      if (page > 200) 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 page = 1;
    const size = 200;
    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());
  };

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

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

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

        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 ids = detail.locationIds && detail.locationIds.length ? detail.locationIds : [];
          if (ids.length) {
            setSelectedLocationIds(new Set(ids));
          } 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);
          }
        }
      } 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);
      }
    };

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

  // 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 (!roleId.trim()) return false;
    if (selectedLocationIds.size === 0) return false;
    if (!isEdit && !password.trim()) return false;
    return true;
  }, [fullName, userName, roleId, selectedLocationIds, 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 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("User Name");
      if (!roleId.trim()) missing.push("Role");
      if (selectedLocationIds.size === 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 = Array.from(selectedLocationIds);
        console.log("[MemberDialog] Creating user", { fullName, userName, roleId, locationIds });
        await createTeamMember({
          fullName: fullName.trim(),
          userName: userName.trim(),
          password: password.trim(),
          email: email.trim() ? email.trim() : null,
          phone: phone != null && String(phone).trim() ? String(phone).trim() : null,
          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 = Array.from(selectedLocationIds);
        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() ? email.trim() : null,
          phone: phone != null && String(phone).trim() ? String(phone).trim() : null,
          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();
    if (!kw) return locationOptions;
    return locationOptions.filter((l) => locationLabel(l).toLowerCase().includes(kw));
  }, [locationOptions, 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>Role is single-select; 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>User Name *</Label>
              <Input value={userName} onChange={(e) => setUserName(e.target.value)} placeholder="username" />
            </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 type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="john@example.com" />
            </div>
            <div className="space-y-2">
              <Label>Phone</Label>
              <Input type="tel" 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}>
                    {r.roleName ?? r.roleCode ?? r.id}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </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">{selectedLocationIds.size} 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>
                ) : (
                  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"
            disabled={submitting}
            onClick={submit}
          >
            {submitting ? "Deleting..." : "Delete"}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}
