"use client";

import React, { useState, useCallback, useEffect, useMemo } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Checkbox } from "@/components/ui/checkbox";
import {
  Plus,
  Edit,
  Trash2,
  Users,
  Filter,
  X,
  Search,
  AlertCircle,
  Shield,
  ChevronRight,
  ChevronDown,
} from "lucide-react";
import { roleAPI, RoleGroup } from "@/lib/api/role";
import { useAuth } from "@/hooks/useAuth";
import { RoleFormModal } from "@/components/admin/RoleFormModal";
import { RoleDeleteModal } from "@/components/admin/RoleDeleteModal";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { companyAPI } from "@/lib/api/company";
import { ScrollToTop } from "@/components/common/ScrollToTop";
import { CrossTenantBanner } from "@/components/common/CrossTenantBanner";
import { useMenu } from "@/contexts/MenuContext";
import { cn } from "@/lib/utils";

type UserItem = {
  user_id: string;
  user_name?: string;
  dept_name?: string;
};

type MenuItem = {
  objid: string;
  menu_name: string;
  parent_objid?: string;
  sort_order?: string | number;
  company_code?: string;
  menu_type?: string;
};

type MenuTreeNode = MenuItem & {
  children: MenuTreeNode[];
  level: number;
};

type PermMap = {
  create_yn: "Y" | "N";
  read_yn: "Y" | "N";
  update_yn: "Y" | "N";
  delete_yn: "Y" | "N";
};

const EMPTY_PERM: PermMap = { create_yn: "N", read_yn: "N", update_yn: "N", delete_yn: "N" };

/**
 * 권한 관리 (이미지 기반 통합 페이지)
 *
 * 레이아웃:
 *  [권한목록] | [권한있는 직원] | [추가/삭제] | [권한없는 직원]
 *  ──────────────────────────────────────────────────────────
 *  [메뉴 트리구조] | 등록/수정 | 삭제 | 조회
 *
 * 규칙:
 *  - 권한 그룹 선택 시 권한있는/없는 직원, 메뉴 권한 트리 자동 로드
 *  - 체크박스를 체크하면 바로 서버에 반영 (auto-save, 저장 버튼 없음)
 *  - 등록/수정 컬럼은 C+U 동시 토글
 */
export default function RolesPage() {
  const { user: currentUser } = useAuth();
  const { refreshMenus } = useMenu();

  const isAdmin =
    (currentUser?.company_code === "*" && currentUser?.user_type === "SUPER_ADMIN") ||
    currentUser?.user_type === "COMPANY_ADMIN" ||
    currentUser?.user_type === "ADMIN";
  const isSuperAdmin =
    currentUser?.company_code === "*" && currentUser?.user_type === "SUPER_ADMIN";

  // 권한 그룹 목록
  const [roleGroups, setRoleGroups] = useState<RoleGroup[]>([]);
  const [crossTenantMeta, setCrossTenantMeta] = useState<Record<string, any> | null>(null);
  const [selectedRole, setSelectedRole] = useState<RoleGroup | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [isLoadingWorkspace, setIsLoadingWorkspace] = useState(false);
  const [error, setError] = useState<string | null>(null);

  // 필터
  const [companies, setCompanies] = useState<Array<{ company_code: string; company_name: string }>>(
    [],
  );
  const [selectedCompany, setSelectedCompany] = useState<string>("all");
  const [searchText, setSearchText] = useState("");

  // 모달
  const [formModal, setFormModal] = useState({
    isOpen: false,
    editingRole: null as RoleGroup | null,
  });
  const [deleteModal, setDeleteModal] = useState({
    isOpen: false,
    role: null as RoleGroup | null,
  });

  // 워크스페이스 데이터
  const [members, setMembers] = useState<UserItem[]>([]);
  const [nonMembers, setNonMembers] = useState<UserItem[]>([]);
  const [menus, setMenus] = useState<MenuItem[]>([]);
  const [permissions, setPermissions] = useState<Record<string, PermMap>>({});

  // 멤버 이동 선택
  const [checkedMembers, setCheckedMembers] = useState<Set<string>>(new Set());
  const [checkedNonMembers, setCheckedNonMembers] = useState<Set<string>>(new Set());

  // 멤버 영역 검색
  const [memberSearch, setMemberSearch] = useState("");
  const [nonMemberSearch, setNonMemberSearch] = useState("");

  // 메뉴 트리 펼침
  const [expandedMenus, setExpandedMenus] = useState<Set<string>>(new Set());

  // ─────────── 회사 목록 (최고 관리자) ───────────
  const loadCompanies = useCallback(async () => {
    if (!isSuperAdmin) return;
    try {
      const list = await companyAPI.getList();
      setCompanies(list);
    } catch (err) {
      console.error("회사 목록 로드 오류:", err);
    }
  }, [isSuperAdmin]);

  // ─────────── 권한 그룹 목록 ───────────
  const loadRoleGroups = useCallback(async () => {
    setIsLoading(true);
    setError(null);
    try {
      const companyFilter =
        isSuperAdmin && selectedCompany !== "all"
          ? selectedCompany
          : isSuperAdmin
            ? undefined
            : currentUser?.company_code;

      const response = await roleAPI.getList({ companyCode: companyFilter });
      // cross-tenant 메타 (단일 모드면 undefined → null)
      setCrossTenantMeta((response as any)?.cross_tenant_meta ?? null);
      if (response.success && response.data) {
        setRoleGroups(response.data);
      } else {
        setError(response.message || "권한 그룹 목록을 불러오는데 실패했습니다.");
      }
    } catch (err) {
      console.error(err);
      setError("권한 그룹 목록을 불러오는 중 오류가 발생했습니다.");
    } finally {
      setIsLoading(false);
    }
  }, [isSuperAdmin, selectedCompany, currentUser?.company_code]);

  useEffect(() => {
    if (!isAdmin) {
      setIsLoading(false);
      return;
    }
    if (isSuperAdmin) loadCompanies();
    loadRoleGroups();
  }, [isAdmin, isSuperAdmin, loadCompanies, loadRoleGroups]);

  // 선택 유효성
  useEffect(() => {
    if (!selectedRole) return;
    if (
      !roleGroups.find(
        (r) => r.objid === selectedRole.objid && r.company_code === selectedRole.company_code,
      )
    ) {
      setSelectedRole(null);
    }
  }, [roleGroups, selectedRole]);

  // ─────────── 워크스페이스 로드 ───────────
  const loadWorkspace = useCallback(async (roleId: number | string, companyCode?: string) => {
    setIsLoadingWorkspace(true);
    setCheckedMembers(new Set());
    setCheckedNonMembers(new Set());
    try {
      const res = await roleAPI.getWorkspace(roleId, companyCode);
      if (res.success && res.data) {
        setMembers(res.data.members || []);
        setNonMembers(res.data.nonMembers || []);
        setMenus(res.data.menus || []);
        const permMap: Record<string, PermMap> = {};
        (res.data.permissions || []).forEach((p: any) => {
          permMap[String(p.menu_objid)] = {
            create_yn: p.create_yn === "Y" ? "Y" : "N",
            read_yn: p.read_yn === "Y" ? "Y" : "N",
            update_yn: p.update_yn === "Y" ? "Y" : "N",
            delete_yn: p.delete_yn === "Y" ? "Y" : "N",
          };
        });
        setPermissions(permMap);
      }
    } catch (err) {
      console.error("워크스페이스 로드 오류:", err);
    } finally {
      setIsLoadingWorkspace(false);
    }
  }, []);

  useEffect(() => {
    if (selectedRole) {
      loadWorkspace(selectedRole.objid, selectedRole.company_code);
    } else {
      setMembers([]);
      setNonMembers([]);
      setMenus([]);
      setPermissions({});
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [selectedRole?.objid]);

  // ─────────── 권한 그룹 목록 필터 ───────────
  const filteredRoleGroups = useMemo(() => {
    let list = roleGroups;
    // SUPER_ADMIN cross-tenant 모드: fan-out 결과는 모든 회사 그룹 합본이라
    // 회사 dropdown 선택을 client-side 에서 한 번 더 적용해야 그 회사 그룹만 보임.
    if (isSuperAdmin && selectedCompany !== "all") {
      list = list.filter((r) => r.company_code === selectedCompany);
    }
    if (!searchText.trim()) return list;
    const q = searchText.toLowerCase();
    return list.filter(
      (r) =>
        (r.auth_name || "").toLowerCase().includes(q) ||
        (r.auth_code || "").toLowerCase().includes(q),
    );
  }, [roleGroups, searchText, isSuperAdmin, selectedCompany]);

  // ─────────── 멤버 이동 (이미지: --> 삭제 / <-- 추가) ───────────
  const handleAddMembers = useCallback(async () => {
    if (!selectedRole || checkedNonMembers.size === 0) return;
    const ids = Array.from(checkedNonMembers);

    // 낙관적 UI 업데이트
    const moving = nonMembers.filter((u) => checkedNonMembers.has(u.user_id));
    setMembers((prev) => [...prev, ...moving]);
    setNonMembers((prev) => prev.filter((u) => !checkedNonMembers.has(u.user_id)));
    setCheckedNonMembers(new Set());

    try {
      for (const userId of ids) {
        const res = await roleAPI.addSingleMember(selectedRole.objid, userId, selectedRole.company_code);
        if (!res.success) throw new Error(res.message);
      }
      await refreshMenus();
      loadRoleGroups();
    } catch (err) {
      console.error("멤버 추가 오류:", err);
      alert("멤버 추가에 실패했습니다. 화면을 새로고침합니다.");
      loadWorkspace(selectedRole.objid, selectedRole.company_code);
    }
  }, [selectedRole, checkedNonMembers, nonMembers, refreshMenus, loadRoleGroups, loadWorkspace]);

  const handleRemoveMembers = useCallback(async () => {
    if (!selectedRole || checkedMembers.size === 0) return;
    const ids = Array.from(checkedMembers);

    const moving = members.filter((u) => checkedMembers.has(u.user_id));
    setNonMembers((prev) => [...prev, ...moving]);
    setMembers((prev) => prev.filter((u) => !checkedMembers.has(u.user_id)));
    setCheckedMembers(new Set());

    try {
      for (const userId of ids) {
        const res = await roleAPI.removeSingleMember(selectedRole.objid, userId, selectedRole.company_code);
        if (!res.success) throw new Error(res.message);
      }
      await refreshMenus();
      loadRoleGroups();
    } catch (err) {
      console.error("멤버 제거 오류:", err);
      alert("멤버 제거에 실패했습니다. 화면을 새로고침합니다.");
      loadWorkspace(selectedRole.objid, selectedRole.company_code);
    }
  }, [selectedRole, checkedMembers, members, refreshMenus, loadRoleGroups, loadWorkspace]);

  // 리스트 필터링 (멤버/비멤버 검색)
  const filteredMembers = useMemo(() => {
    if (!memberSearch.trim()) return members;
    const q = memberSearch.toLowerCase();
    return members.filter(
      (u) =>
        (u.user_name || "").toLowerCase().includes(q) ||
        u.user_id.toLowerCase().includes(q) ||
        (u.dept_name || "").toLowerCase().includes(q),
    );
  }, [members, memberSearch]);

  const filteredNonMembers = useMemo(() => {
    if (!nonMemberSearch.trim()) return nonMembers;
    const q = nonMemberSearch.toLowerCase();
    return nonMembers.filter(
      (u) =>
        (u.user_name || "").toLowerCase().includes(q) ||
        u.user_id.toLowerCase().includes(q) ||
        (u.dept_name || "").toLowerCase().includes(q),
    );
  }, [nonMembers, nonMemberSearch]);

  const toggleMemberCheck = useCallback((id: string) => {
    setCheckedMembers((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      return next;
    });
  }, []);
  const toggleNonMemberCheck = useCallback((id: string) => {
    setCheckedNonMembers((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      return next;
    });
  }, []);

  const handleMembersSelectAll = useCallback(
    (checked: boolean) => {
      setCheckedMembers(checked ? new Set(filteredMembers.map((u) => u.user_id)) : new Set());
    },
    [filteredMembers],
  );
  const handleNonMembersSelectAll = useCallback(
    (checked: boolean) => {
      setCheckedNonMembers(
        checked ? new Set(filteredNonMembers.map((u) => u.user_id)) : new Set(),
      );
    },
    [filteredNonMembers],
  );

  // ─────────── 메뉴 트리 빌드 ───────────
  const menuTree = useMemo<MenuTreeNode[]>(() => {
    const map = new Map<string, MenuTreeNode>();
    menus.forEach((m) => {
      map.set(String(m.objid), { ...m, children: [], level: 0 });
    });
    const roots: MenuTreeNode[] = [];
    map.forEach((node) => {
      const parentId =
        node.parent_objid && String(node.parent_objid) !== "0"
          ? String(node.parent_objid)
          : null;
      if (parentId && map.has(parentId)) {
        const parent = map.get(parentId)!;
        node.level = parent.level + 1;
        parent.children.push(node);
      } else {
        roots.push(node);
      }
    });
    const sortTree = (nodes: MenuTreeNode[]) => {
      nodes.sort((a, b) => {
        const sa = Number(a.sort_order ?? 0);
        const sb = Number(b.sort_order ?? 0);
        if (!isNaN(sa) && !isNaN(sb) && sa !== sb) return sa - sb;
        return (a.menu_name || "").localeCompare(b.menu_name || "");
      });
      nodes.forEach((n) => sortTree(n.children));
    };
    sortTree(roots);
    return roots;
  }, [menus]);

  const toggleExpand = useCallback((id: string) => {
    setExpandedMenus((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      return next;
    });
  }, []);

  // ─────────── 메뉴 권한 즉시 반영 (PATCH) ───────────
  const applyMenuPermission = useCallback(
    async (menuId: string, changes: Partial<PermMap>) => {
      if (!selectedRole) return;
      const prevPerm = permissions[menuId] || EMPTY_PERM;
      const nextPerm: PermMap = { ...prevPerm, ...changes };

      // 낙관적 UI 업데이트
      setPermissions((prev) => ({ ...prev, [menuId]: nextPerm }));

      try {
        const res = await roleAPI.toggleMenuPermission(selectedRole.objid, menuId, changes, selectedRole.company_code);
        if (!res.success) throw new Error(res.message);

        if (res.data) {
          setPermissions((prev) => ({
            ...prev,
            [menuId]: {
              create_yn: res.data.create_yn === "Y" ? "Y" : "N",
              read_yn: res.data.read_yn === "Y" ? "Y" : "N",
              update_yn: res.data.update_yn === "Y" ? "Y" : "N",
              delete_yn: res.data.delete_yn === "Y" ? "Y" : "N",
            },
          }));
        }
        await refreshMenus();
      } catch (err) {
        console.error("메뉴 권한 저장 오류:", err);
        setPermissions((prev) => ({ ...prev, [menuId]: prevPerm }));
        alert("권한 변경에 실패했습니다.");
      }
    },
    [selectedRole, permissions, refreshMenus],
  );

  // 이미지 3컬럼: 등록/수정(C+U 동시), 삭제(D), 조회(R)
  const handleEditCol = useCallback(
    (menuId: string, checked: boolean) => {
      const v: "Y" | "N" = checked ? "Y" : "N";
      applyMenuPermission(menuId, { create_yn: v, update_yn: v });
    },
    [applyMenuPermission],
  );
  const handleDeleteCol = useCallback(
    (menuId: string, checked: boolean) => {
      applyMenuPermission(menuId, { delete_yn: checked ? "Y" : "N" });
    },
    [applyMenuPermission],
  );
  const handleReadCol = useCallback(
    (menuId: string, checked: boolean) => {
      applyMenuPermission(menuId, { read_yn: checked ? "Y" : "N" });
    },
    [applyMenuPermission],
  );

  const flatMenuIds = useMemo(() => {
    const ids: string[] = [];
    const walk = (nodes: MenuTreeNode[]) => {
      nodes.forEach((n) => {
        ids.push(String(n.objid));
        walk(n.children);
      });
    };
    walk(menuTree);
    return ids;
  }, [menuTree]);

  const handleBulkColumn = useCallback(
    async (column: "edit" | "delete" | "read", checked: boolean) => {
      if (!selectedRole) return;
      const v: "Y" | "N" = checked ? "Y" : "N";
      const change: Partial<PermMap> =
        column === "edit"
          ? { create_yn: v, update_yn: v }
          : column === "delete"
            ? { delete_yn: v }
            : { read_yn: v };

      setPermissions((prev) => {
        const next = { ...prev };
        flatMenuIds.forEach((id) => {
          next[id] = { ...(next[id] || EMPTY_PERM), ...change };
        });
        return next;
      });

      try {
        for (const id of flatMenuIds) {
          const res = await roleAPI.toggleMenuPermission(selectedRole.objid, id, change, selectedRole.company_code);
          if (!res.success) throw new Error(res.message);
        }
        await refreshMenus();
      } catch (err) {
        console.error("일괄 변경 오류:", err);
        alert("일괄 변경 실패 — 화면을 새로고침합니다.");
        loadWorkspace(selectedRole.objid, selectedRole.company_code);
      }
    },
    [selectedRole, flatMenuIds, refreshMenus, loadWorkspace],
  );

  const isColumnAllChecked = useCallback(
    (column: "edit" | "delete" | "read"): boolean => {
      if (flatMenuIds.length === 0) return false;
      return flatMenuIds.every((id) => {
        const p = permissions[id] || EMPTY_PERM;
        if (column === "edit") return p.create_yn === "Y" && p.update_yn === "Y";
        if (column === "delete") return p.delete_yn === "Y";
        return p.read_yn === "Y";
      });
    },
    [flatMenuIds, permissions],
  );

  const renderMenuRow = (node: MenuTreeNode): React.ReactNode => {
    const perm = permissions[String(node.objid)] || EMPTY_PERM;
    const hasChildren = node.children.length > 0;
    const isExpanded = expandedMenus.has(String(node.objid));
    const editChecked = perm.create_yn === "Y" && perm.update_yn === "Y";

    return (
      <React.Fragment key={String(node.objid)}>
        <tr className="border-t hover:bg-muted/30 transition-colors">
          <td className="py-1.5 pr-2" style={{ paddingLeft: `${node.level * 16 + 10}px` }}>
            <div className="flex items-center gap-1">
              {hasChildren ? (
                <button
                  onClick={() => toggleExpand(String(node.objid))}
                  className="hover:bg-muted rounded p-0.5"
                >
                  {isExpanded ? (
                    <ChevronDown className="h-3 w-3 text-muted-foreground" />
                  ) : (
                    <ChevronRight className="h-3 w-3 text-muted-foreground" />
                  )}
                </button>
              ) : (
                <span className="inline-block w-3" />
              )}
              <span className={cn("text-xs", hasChildren && "font-semibold")}>
                {node.menu_name}
              </span>
            </div>
          </td>
          <td className="py-1.5 text-center">
            <Checkbox
              checked={editChecked}
              onCheckedChange={(c) => handleEditCol(String(node.objid), c === true)}
            />
          </td>
          <td className="py-1.5 text-center">
            <Checkbox
              checked={perm.delete_yn === "Y"}
              onCheckedChange={(c) => handleDeleteCol(String(node.objid), c === true)}
            />
          </td>
          <td className="py-1.5 text-center">
            <Checkbox
              checked={perm.read_yn === "Y"}
              onCheckedChange={(c) => handleReadCol(String(node.objid), c === true)}
            />
          </td>
        </tr>
        {hasChildren && isExpanded && node.children.map((c) => renderMenuRow(c))}
      </React.Fragment>
    );
  };

  const handleCreateRole = () => setFormModal({ isOpen: true, editingRole: null });
  const handleEditRole = (r: RoleGroup) => setFormModal({ isOpen: true, editingRole: r });
  const handleDeleteRole = (r: RoleGroup) => setDeleteModal({ isOpen: true, role: r });
  const handleFormClose = () => setFormModal({ isOpen: false, editingRole: null });
  const handleDeleteClose = () => setDeleteModal({ isOpen: false, role: null });
  const handleModalSuccess = () => loadRoleGroups();

  if (!isAdmin) {
    return (
      <div className="flex min-h-screen flex-col bg-background">
        <div className="w-full space-y-4 p-6">
          <div className="space-y-1 border-b pb-3">
            <h1 className="text-2xl font-bold tracking-tight">권한 관리</h1>
          </div>
          <div className="bg-card flex h-64 flex-col items-center justify-center rounded-lg border p-6 shadow-sm">
            <AlertCircle className="text-destructive mb-4 h-12 w-12" />
            <h3 className="mb-2 text-lg font-semibold">접근 권한 없음</h3>
            <p className="text-muted-foreground mb-4 text-center text-sm">
              권한 관리는 관리자만 접근할 수 있습니다.
            </p>
            <Button variant="outline" onClick={() => window.history.back()}>
              뒤로 가기
            </Button>
          </div>
        </div>
        <ScrollToTop />
      </div>
    );
  }

  return (
    <div className="flex h-screen flex-col overflow-hidden bg-background">
      <div className="flex w-full flex-1 flex-col space-y-3 overflow-hidden p-6">
        <div className="shrink-0 space-y-1 border-b pb-3">
          <h1 className="text-xl font-bold tracking-tight">권한 관리</h1>
          <p className="text-muted-foreground text-xs">
            권한 그룹 선택 시 권한있는/없는 직원과 메뉴 권한이 로드되고, 체크 즉시 반영됩니다.
          </p>
        </div>

        <div className="shrink-0">
          <CrossTenantBanner meta={crossTenantMeta} />
        </div>

        {error && (
          <div className="border-destructive/50 bg-destructive/10 shrink-0 rounded-lg border p-3">
            <div className="flex items-center justify-between">
              <p className="text-destructive text-sm font-semibold">{error}</p>
              <button onClick={() => setError(null)} className="text-destructive">
                <X className="h-4 w-4" />
              </button>
            </div>
          </div>
        )}

        {/* 상단 4분할: 권한목록 | 권한있는직원 | 이동버튼 | 권한없는직원 */}
        <div className="grid shrink-0 grid-cols-1 gap-3 xl:grid-cols-[220px_1fr_auto_1fr]">
          {/* 권한 목록 */}
          <div className="bg-card flex flex-col rounded-lg border shadow-sm">
            <div className="flex items-center justify-between border-b p-2.5">
              <div className="flex items-center gap-1.5">
                <Shield className="text-primary h-3.5 w-3.5" />
                <h2 className="text-xs font-semibold">권한 목록</h2>
              </div>
              <Button size="sm" onClick={handleCreateRole} className="h-6 gap-1 px-2 text-[11px]">
                <Plus className="h-3 w-3" />
                생성
              </Button>
            </div>
            <div className="space-y-1.5 border-b p-2">
              <div className="relative">
                <Search className="text-muted-foreground absolute top-1/2 left-2 h-3 w-3 -translate-y-1/2" />
                <Input
                  placeholder="검색..."
                  value={searchText}
                  onChange={(e) => setSearchText(e.target.value)}
                  className="h-7 pl-7 text-[11px]"
                />
              </div>
              {isSuperAdmin && (
                <div className="flex items-center gap-1">
                  <Filter className="text-muted-foreground h-3 w-3 flex-shrink-0" />
                  <Select value={selectedCompany} onValueChange={setSelectedCompany}>
                    <SelectTrigger className="h-7 flex-1 text-[11px]">
                      <SelectValue placeholder="회사 선택" />
                    </SelectTrigger>
                    <SelectContent>
                      <SelectItem value="all">전체 회사</SelectItem>
                      {companies.map((c) => (
                        <SelectItem key={c.company_code} value={c.company_code}>
                          {c.company_name}
                        </SelectItem>
                      ))}
                    </SelectContent>
                  </Select>
                </div>
              )}
            </div>
            <div
              className="flex-1 overflow-y-auto"
              style={{ maxHeight: "clamp(200px, 28vh, 280px)" }}
            >
              {isLoading ? (
                <div className="flex h-32 items-center justify-center">
                  <div className="border-primary h-5 w-5 animate-spin rounded-full border-2 border-t-transparent" />
                </div>
              ) : filteredRoleGroups.length === 0 ? (
                <p className="text-muted-foreground p-6 text-center text-xs">
                  등록된 권한 그룹이 없습니다
                </p>
              ) : (
                <ul className="divide-y">
                  {filteredRoleGroups.map((role) => {
                    const isSelected =
                      selectedRole?.objid === role.objid &&
                      selectedRole?.company_code === role.company_code;
                    return (
                      <li
                        key={`${role.company_code ?? "_"}-${role.objid}`}
                        onClick={() => setSelectedRole(role)}
                        className={cn(
                          "group cursor-pointer p-2 transition-colors",
                          isSelected ? "bg-primary/10" : "hover:bg-muted/50",
                        )}
                      >
                        <div className="flex items-start justify-between gap-2">
                          <div className="min-w-0 flex-1">
                            <div
                              className={cn(
                                "truncate text-xs font-semibold",
                                isSelected && "text-primary",
                              )}
                            >
                              {role.auth_name}
                            </div>
                            <div className="text-muted-foreground mt-0.5 truncate font-mono text-[10px]">
                              {role.auth_code}
                            </div>
                          </div>
                          <div
                            className={cn(
                              "flex flex-shrink-0 gap-0.5 transition-opacity",
                              isSelected ? "opacity-100" : "opacity-0 group-hover:opacity-100",
                            )}
                          >
                            <button
                              onClick={(e) => {
                                e.stopPropagation();
                                handleEditRole(role);
                              }}
                              className="hover:bg-background rounded p-1"
                              title="수정"
                            >
                              <Edit className="h-3 w-3" />
                            </button>
                            <button
                              onClick={(e) => {
                                e.stopPropagation();
                                handleDeleteRole(role);
                              }}
                              className="hover:bg-destructive hover:text-destructive-foreground rounded p-1"
                              title="삭제"
                            >
                              <Trash2 className="h-3 w-3" />
                            </button>
                          </div>
                        </div>
                      </li>
                    );
                  })}
                </ul>
              )}
            </div>
          </div>

          {/* 권한있는 직원 */}
          <div className="bg-card flex flex-col rounded-lg border shadow-sm">
            <div className="border-b p-2.5">
              <div className="mb-1.5 flex items-center gap-1.5">
                <Users className="text-primary h-3.5 w-3.5" />
                <h2 className="text-xs font-semibold">권한있는 직원 ({members.length})</h2>
              </div>
              <div className="flex items-center gap-1.5">
                <Checkbox
                  checked={
                    filteredMembers.length > 0 &&
                    checkedMembers.size === filteredMembers.length
                  }
                  onCheckedChange={(c) => handleMembersSelectAll(c === true)}
                  disabled={!selectedRole}
                />
                <span className="text-muted-foreground text-[11px]">전체선택</span>
                <div className="relative ml-auto flex-1 max-w-[180px]">
                  <Search className="text-muted-foreground absolute top-1/2 left-2 h-3 w-3 -translate-y-1/2" />
                  <Input
                    placeholder="검색"
                    value={memberSearch}
                    onChange={(e) => setMemberSearch(e.target.value)}
                    className="h-6 pl-6 text-[11px]"
                    disabled={!selectedRole}
                  />
                </div>
              </div>
            </div>
            <div
              className="flex-1 overflow-y-auto"
              style={{ height: "clamp(200px, 28vh, 280px)" }}
            >
              {!selectedRole ? (
                <div className="flex h-full items-center justify-center">
                  <p className="text-muted-foreground text-xs">권한 그룹을 선택하세요</p>
                </div>
              ) : isLoadingWorkspace ? (
                <div className="flex h-full items-center justify-center">
                  <div className="border-primary h-5 w-5 animate-spin rounded-full border-2 border-t-transparent" />
                </div>
              ) : filteredMembers.length === 0 ? (
                <p className="text-muted-foreground p-6 text-center text-xs">
                  {memberSearch ? "검색 결과 없음" : "권한있는 직원이 없습니다"}
                </p>
              ) : (
                <ul className="divide-y">
                  {filteredMembers.map((u) => (
                    <li
                      key={u.user_id}
                      onClick={() => toggleMemberCheck(u.user_id)}
                      className={cn(
                        "flex cursor-pointer items-center gap-1.5 p-1.5 transition-colors",
                        checkedMembers.has(u.user_id) ? "bg-muted" : "hover:bg-muted/50",
                      )}
                    >
                      <Checkbox
                        checked={checkedMembers.has(u.user_id)}
                        onCheckedChange={() => toggleMemberCheck(u.user_id)}
                        onClick={(e) => e.stopPropagation()}
                      />
                      <div className="min-w-0 flex-1">
                        <div className="truncate text-xs">{u.user_name || u.user_id}</div>
                        {u.dept_name && (
                          <div className="text-muted-foreground truncate text-[10px]">
                            {u.dept_name}
                          </div>
                        )}
                      </div>
                    </li>
                  ))}
                </ul>
              )}
            </div>
          </div>

          {/* 이동 버튼: --> 삭제 / <-- 추가 */}
          <div className="flex flex-row items-center justify-center gap-1.5 xl:flex-col">
            <Button
              variant="outline"
              size="sm"
              onClick={handleRemoveMembers}
              disabled={!selectedRole || checkedMembers.size === 0}
              className="h-7 gap-1 px-2 text-[11px]"
            >
              <span>--&gt;</span>
              <span>삭제</span>
            </Button>
            <Button
              variant="outline"
              size="sm"
              onClick={handleAddMembers}
              disabled={!selectedRole || checkedNonMembers.size === 0}
              className="h-7 gap-1 px-2 text-[11px]"
            >
              <span>&lt;--</span>
              <span>추가</span>
            </Button>
          </div>

          {/* 권한없는 직원 */}
          <div className="bg-card flex flex-col rounded-lg border shadow-sm">
            <div className="border-b p-2.5">
              <div className="mb-1.5 flex items-center gap-1.5">
                <Users className="text-muted-foreground h-3.5 w-3.5" />
                <h2 className="text-xs font-semibold">권한없는 직원 ({nonMembers.length})</h2>
              </div>
              <div className="flex items-center gap-1.5">
                <Checkbox
                  checked={
                    filteredNonMembers.length > 0 &&
                    checkedNonMembers.size === filteredNonMembers.length
                  }
                  onCheckedChange={(c) => handleNonMembersSelectAll(c === true)}
                  disabled={!selectedRole}
                />
                <span className="text-muted-foreground text-[11px]">전체선택</span>
                <div className="relative ml-auto flex-1 max-w-[180px]">
                  <Search className="text-muted-foreground absolute top-1/2 left-2 h-3 w-3 -translate-y-1/2" />
                  <Input
                    placeholder="검색"
                    value={nonMemberSearch}
                    onChange={(e) => setNonMemberSearch(e.target.value)}
                    className="h-6 pl-6 text-[11px]"
                    disabled={!selectedRole}
                  />
                </div>
              </div>
            </div>
            <div
              className="flex-1 overflow-y-auto"
              style={{ height: "clamp(200px, 28vh, 280px)" }}
            >
              {!selectedRole ? (
                <div className="flex h-full items-center justify-center">
                  <p className="text-muted-foreground text-xs">권한 그룹을 선택하세요</p>
                </div>
              ) : isLoadingWorkspace ? (
                <div className="flex h-full items-center justify-center">
                  <div className="border-primary h-5 w-5 animate-spin rounded-full border-2 border-t-transparent" />
                </div>
              ) : filteredNonMembers.length === 0 ? (
                <p className="text-muted-foreground p-6 text-center text-xs">
                  {nonMemberSearch ? "검색 결과 없음" : "권한없는 직원이 없습니다"}
                </p>
              ) : (
                <ul className="divide-y">
                  {filteredNonMembers.map((u) => (
                    <li
                      key={u.user_id}
                      onClick={() => toggleNonMemberCheck(u.user_id)}
                      className={cn(
                        "flex cursor-pointer items-center gap-1.5 p-1.5 transition-colors",
                        checkedNonMembers.has(u.user_id) ? "bg-muted" : "hover:bg-muted/50",
                      )}
                    >
                      <Checkbox
                        checked={checkedNonMembers.has(u.user_id)}
                        onCheckedChange={() => toggleNonMemberCheck(u.user_id)}
                        onClick={(e) => e.stopPropagation()}
                      />
                      <div className="min-w-0 flex-1">
                        <div className="truncate text-xs">{u.user_name || u.user_id}</div>
                        {u.dept_name && (
                          <div className="text-muted-foreground truncate text-[10px]">
                            {u.dept_name}
                          </div>
                        )}
                      </div>
                    </li>
                  ))}
                </ul>
              )}
            </div>
          </div>
        </div>

        {/* 하단: 메뉴 권한 트리 (등록/수정, 삭제, 조회 3컬럼) */}
        <div
          className="bg-card flex min-h-0 flex-1 flex-col rounded-lg border shadow-sm"
          style={{ maxHeight: "clamp(280px, 40vh, 430px)" }}
        >
          <div className="shrink-0 border-b p-2.5">
            <h2 className="text-xs font-semibold">
              메뉴 전체 트리구조{" "}
              {selectedRole && (
                <span className="text-muted-foreground text-[11px]">({selectedRole.auth_name})</span>
              )}
            </h2>
            <p className="text-muted-foreground mt-0.5 text-[10px]">
              체크된 것들만 시스템에서 해당 버튼이 노출됩니다 · 체크 즉시 서버 반영
            </p>
          </div>
          {!selectedRole ? (
            <div className="flex flex-1 items-center justify-center">
              <p className="text-muted-foreground text-sm">권한 그룹을 선택하세요</p>
            </div>
          ) : isLoadingWorkspace ? (
            <div className="flex flex-1 items-center justify-center">
              <div className="border-primary h-6 w-6 animate-spin rounded-full border-2 border-t-transparent" />
            </div>
          ) : menuTree.length === 0 ? (
            <div className="flex flex-1 items-center justify-center">
              <p className="text-muted-foreground text-sm">등록된 메뉴가 없습니다</p>
            </div>
          ) : (
            <div className="min-h-0 flex-1 overflow-auto">
              <table className="w-full text-xs">
                <thead className="bg-muted sticky top-0 z-10">
                  <tr>
                    <th className="py-1.5 pl-2.5 text-left text-[11px] font-semibold w-[40%]">
                      메뉴 전체 트리구조
                    </th>
                    <th className="py-1.5 text-center text-[11px] font-semibold w-[20%]">
                      <div className="flex flex-col items-center gap-0.5">
                        <span>등록/수정</span>
                        <Checkbox
                          checked={isColumnAllChecked("edit")}
                          onCheckedChange={(c) => handleBulkColumn("edit", c === true)}
                        />
                      </div>
                    </th>
                    <th className="py-1.5 text-center text-[11px] font-semibold w-[20%]">
                      <div className="flex flex-col items-center gap-0.5">
                        <span>삭제</span>
                        <Checkbox
                          checked={isColumnAllChecked("delete")}
                          onCheckedChange={(c) => handleBulkColumn("delete", c === true)}
                        />
                      </div>
                    </th>
                    <th className="py-1.5 text-center text-[11px] font-semibold w-[20%]">
                      <div className="flex flex-col items-center gap-0.5">
                        <span>조회</span>
                        <Checkbox
                          checked={isColumnAllChecked("read")}
                          onCheckedChange={(c) => handleBulkColumn("read", c === true)}
                        />
                      </div>
                    </th>
                  </tr>
                </thead>
                <tbody>{menuTree.map((n) => renderMenuRow(n))}</tbody>
              </table>
            </div>
          )}
        </div>

        <RoleFormModal
          isOpen={formModal.isOpen}
          onClose={handleFormClose}
          onSuccess={handleModalSuccess}
          editingRole={formModal.editingRole}
        />
        <RoleDeleteModal
          isOpen={deleteModal.isOpen}
          onClose={handleDeleteClose}
          onSuccess={handleModalSuccess}
          role={deleteModal.role}
        />
      </div>
      <ScrollToTop />
    </div>
  );
}
