import { useState, useEffect, useCallback, useRef } from 'react';
import {
  fetchUsers,
  fetchRoles,
  fetchPermTree,
  updatePermissionsApi,
  createRoleApi,
  updateRoleApi,
  deleteRoleApi,
  updateRoleDefaultApi,
  assignRolesApi,
  type UserListItem,
  type RoleWithPermissions,
  type PermTreeNode,
} from '@common/services/authApi';
import { getRoleColor } from './adminConstants';

// ─── 오퍼레이션 코드 ─────────────────────────────────
const OPER_CODES = ['READ', 'CREATE', 'UPDATE', 'DELETE'] as const;
type OperCode = (typeof OPER_CODES)[number];
const OPER_LABELS: Record<OperCode, string> = { READ: 'R', CREATE: 'C', UPDATE: 'U', DELETE: 'D' };
const OPER_FULL_LABELS: Record<OperCode, string> = {
  READ: '조회',
  CREATE: '생성',
  UPDATE: '수정',
  DELETE: '삭제',
};

// ─── 권한 상태 타입 ─────────────────────────────────────
type PermState = 'explicit-granted' | 'inherited-granted' | 'explicit-denied' | 'forced-denied';

// ─── 키 유틸 ──────────────────────────────────────────
function makeKey(rsrc: string, oper: string): string {
  return `${rsrc}::${oper}`;
}

// ─── 유틸: 플랫 노드 목록 추출 (트리 DFS) ─────────────
function flattenTree(nodes: PermTreeNode[]): PermTreeNode[] {
  const result: PermTreeNode[] = [];
  function walk(list: PermTreeNode[]) {
    for (const n of list) {
      result.push(n);
      if (n.children.length > 0) walk(n.children);
    }
  }
  walk(nodes);
  return result;
}

// ─── 유틸: 권한 상태 계산 (오퍼레이션별) ──────────────
function resolvePermStateForOper(
  code: string,
  parentCode: string | null,
  operCd: string,
  explicitPerms: Map<string, boolean>,
  cache: Map<string, PermState>,
): PermState {
  const key = makeKey(code, operCd);
  const cached = cache.get(key);
  if (cached) return cached;

  const explicit = explicitPerms.get(key);

  if (parentCode === null) {
    const state: PermState =
      explicit === true
        ? 'explicit-granted'
        : explicit === false
          ? 'explicit-denied'
          : 'explicit-denied';
    cache.set(key, state);
    return state;
  }

  // 부모 READ 확인 (접근 게이트)
  const parentReadKey = makeKey(parentCode, 'READ');
  const parentReadState = cache.get(parentReadKey);
  if (parentReadState === 'explicit-denied' || parentReadState === 'forced-denied') {
    cache.set(key, 'forced-denied');
    return 'forced-denied';
  }

  if (explicit === true) {
    cache.set(key, 'explicit-granted');
    return 'explicit-granted';
  }
  if (explicit === false) {
    cache.set(key, 'explicit-denied');
    return 'explicit-denied';
  }

  // 부모의 같은 오퍼레이션 상속
  const parentOperKey = makeKey(parentCode, operCd);
  const parentOperState = cache.get(parentOperKey);
  if (parentOperState === 'explicit-granted' || parentOperState === 'inherited-granted') {
    cache.set(key, 'inherited-granted');
    return 'inherited-granted';
  }
  if (parentOperState === 'forced-denied') {
    cache.set(key, 'forced-denied');
    return 'forced-denied';
  }

  cache.set(key, 'explicit-denied');
  return 'explicit-denied';
}

function buildEffectiveStates(
  flatNodes: PermTreeNode[],
  explicitPerms: Map<string, boolean>,
): Map<string, PermState> {
  const cache = new Map<string, PermState>();
  for (const node of flatNodes) {
    // READ 먼저 (CUD는 READ에 의존)
    resolvePermStateForOper(node.code, node.parentCode, 'READ', explicitPerms, cache);
    for (const oper of OPER_CODES) {
      if (oper === 'READ') continue;
      resolvePermStateForOper(node.code, node.parentCode, oper, explicitPerms, cache);
    }
  }
  return cache;
}

// ─── 체크박스 셀 컴포넌트 ────────────────────────────
interface PermCellProps {
  state: PermState;
  onToggle: () => void;
  label?: string;
  readOnly?: boolean;
}

function PermCell({ state, onToggle, label, readOnly = false }: PermCellProps) {
  const isDisabled = state === 'forced-denied' || readOnly;

  const baseClasses =
    'w-5 h-5 rounded border text-caption font-bold transition-all flex items-center justify-center';

  let classes: string;
  let icon: string;

  switch (state) {
    case 'explicit-granted':
      classes = readOnly
        ? `${baseClasses} bg-[rgba(6,182,212,0.2)] border-color-accent text-color-accent cursor-default`
        : `${baseClasses} bg-[rgba(6,182,212,0.2)] border-color-accent text-color-accent cursor-pointer hover:bg-[rgba(6,182,212,0.3)]`;
      icon = '✓';
      break;
    case 'inherited-granted':
      classes = readOnly
        ? `${baseClasses} bg-[rgba(6,182,212,0.08)] border-[rgba(6,182,212,0.3)] text-[rgba(6,182,212,0.5)] cursor-default`
        : `${baseClasses} bg-[rgba(6,182,212,0.08)] border-[rgba(6,182,212,0.3)] text-[rgba(6,182,212,0.5)] cursor-pointer hover:border-color-accent`;
      icon = '✓';
      break;
    case 'explicit-denied':
      classes = readOnly
        ? `${baseClasses} bg-[rgba(239,68,68,0.08)] border-[rgba(239,68,68,0.3)] text-red-400 cursor-default`
        : `${baseClasses} bg-[rgba(239,68,68,0.08)] border-[rgba(239,68,68,0.3)] text-red-400 cursor-pointer hover:border-red-400`;
      icon = '—';
      break;
    case 'forced-denied':
      classes = `${baseClasses} bg-bg-elevated border-stroke text-fg-disabled opacity-40 cursor-not-allowed`;
      icon = '—';
      break;
  }

  return (
    <button
      onClick={isDisabled ? undefined : onToggle}
      disabled={isDisabled}
      className={classes}
      title={
        readOnly
          ? state === 'explicit-granted'
            ? `${label ?? ''} 허용`
            : state === 'inherited-granted'
              ? `${label ?? ''} 상속 허용`
              : state === 'explicit-denied'
                ? `${label ?? ''} 거부`
                : `${label ?? ''} 비활성`
          : state === 'explicit-granted'
            ? `${label ?? ''} 명시적 허용 (클릭: 거부로 전환)`
            : state === 'inherited-granted'
              ? `${label ?? ''} 부모 상속 허용 (클릭: 명시적 거부)`
              : state === 'explicit-denied'
                ? `${label ?? ''} 명시적 거부 (클릭: 허용으로 전환)`
                : `${label ?? ''} 부모 거부로 비활성`
      }
    >
      {icon}
    </button>
  );
}

// ─── 트리 행 컴포넌트 ────────────────────────────────
interface TreeRowProps {
  node: PermTreeNode;
  stateMap: Map<string, PermState>;
  expanded: Set<string>;
  onToggleExpand: (code: string) => void;
  onTogglePerm: (code: string, oper: OperCode, currentState: PermState) => void;
  readOnly?: boolean;
}

function TreeRow({
  node,
  stateMap,
  expanded,
  onToggleExpand,
  onTogglePerm,
  readOnly = false,
}: TreeRowProps) {
  const hasChildren = node.children.length > 0;
  const isExpanded = expanded.has(node.code);
  const indent = node.level * 16;

  // 이 노드의 READ 상태 (CUD 비활성 판단용)
  const readState = stateMap.get(makeKey(node.code, 'READ')) ?? 'forced-denied';
  const readDenied = readState === 'explicit-denied' || readState === 'forced-denied';

  return (
    <>
      <tr className="border-b border-stroke hover:bg-[rgba(255,255,255,0.02)] transition-colors">
        <td className="px-3 py-1">
          <div className="flex items-center" style={{ paddingLeft: indent }}>
            {hasChildren ? (
              <button
                onClick={() => onToggleExpand(node.code)}
                className="w-4 h-4 flex items-center justify-center text-fg-disabled hover:text-fg transition-colors mr-1 flex-shrink-0"
              >
                <svg
                  width="10"
                  height="10"
                  viewBox="0 0 24 24"
                  fill="none"
                  stroke="currentColor"
                  strokeWidth="2.5"
                  className={`transition-transform ${isExpanded ? 'rotate-90' : ''}`}
                >
                  <polyline points="9 18 15 12 9 6" />
                </svg>
              </button>
            ) : (
              <span className="w-4 mr-1 flex-shrink-0 text-center text-fg-disabled text-caption">
                {node.level > 0 ? '├' : ''}
              </span>
            )}
            {node.icon && <span className="mr-1 flex-shrink-0 text-label-2">{node.icon}</span>}
            <div className="min-w-0">
              <div
                className={`text-label-2 font-korean truncate ${node.level === 0 ? 'font-bold text-fg' : 'font-medium text-fg-sub'}`}
              >
                {node.name}
              </div>
            </div>
          </div>
        </td>
        {OPER_CODES.map((oper) => {
          const key = makeKey(node.code, oper);
          const state = stateMap.get(key) ?? 'forced-denied';
          // READ 거부 시 CUD도 강제 거부
          const effectiveState =
            oper !== 'READ' && readDenied ? ('forced-denied' as PermState) : state;
          return (
            <td key={oper} className="px-1 py-1 text-center">
              <div className="flex justify-center">
                <PermCell
                  state={effectiveState}
                  label={OPER_FULL_LABELS[oper]}
                  onToggle={() => onTogglePerm(node.code, oper, effectiveState)}
                  readOnly={readOnly}
                />
              </div>
            </td>
          );
        })}
      </tr>
      {hasChildren &&
        isExpanded &&
        node.children.map((child) => (
          <TreeRow
            key={child.code}
            node={child}
            stateMap={stateMap}
            expanded={expanded}
            onToggleExpand={onToggleExpand}
            onTogglePerm={onTogglePerm}
            readOnly={readOnly}
          />
        ))}
    </>
  );
}

// ─── 공통 범례 컴포넌트 ──────────────────────────────
function PermLegend() {
  return (
    <div
      className="flex items-center gap-3 px-4 py-1.5 border-b border-stroke bg-bg-surface text-caption text-fg-disabled font-korean"
      style={{ flexShrink: 0 }}
    >
      <span className="flex items-center gap-1">
        <span className="inline-block w-3 h-3 rounded border bg-[rgba(6,182,212,0.2)] border-color-accent text-color-accent text-center text-caption leading-3">
          ✓
        </span>
        허용
      </span>
      <span className="flex items-center gap-1">
        <span className="inline-block w-3 h-3 rounded border bg-[rgba(6,182,212,0.08)] border-[rgba(6,182,212,0.3)] text-[rgba(6,182,212,0.5)] text-center text-caption leading-3">
          ✓
        </span>
        상속
      </span>
      <span className="flex items-center gap-1">
        <span className="inline-block w-3 h-3 rounded border bg-[rgba(239,68,68,0.08)] border-[rgba(239,68,68,0.3)] text-red-400 text-center text-caption leading-3">
          —
        </span>
        거부
      </span>
      <span className="flex items-center gap-1">
        <span className="inline-block w-3 h-3 rounded border bg-bg-elevated border-stroke text-fg-disabled opacity-40 text-center text-caption leading-3">
          —
        </span>
        비활성
      </span>
      <span className="ml-2 border-l border-stroke pl-2 text-fg-disabled">
        R=조회 C=생성 U=수정 D=삭제
      </span>
    </div>
  );
}

// ─── RolePermTab: 기존 그룹별 권한 탭 ───────────────
interface RolePermTabProps {
  roles: RoleWithPermissions[];
  permTree: PermTreeNode[];
  rolePerms: Map<number, Map<string, boolean>>;
  setRolePerms: React.Dispatch<React.SetStateAction<Map<number, Map<string, boolean>>>>;
  selectedRoleSn: number | null;
  setSelectedRoleSn: (sn: number | null) => void;
  dirty: boolean;
  saving: boolean;
  saveError: string | null;
  handleSave: () => Promise<void>;
  handleToggleExpand: (code: string) => void;
  handleTogglePerm: (code: string, oper: OperCode, currentState: PermState) => void;
  expanded: Set<string>;
  flatNodes: PermTreeNode[];
  editingRoleSn: number | null;
  editRoleName: string;
  setEditRoleName: (name: string) => void;
  handleStartEditName: (role: RoleWithPermissions) => void;
  handleSaveRoleName: (roleSn: number) => Promise<void>;
  setEditingRoleSn: (sn: number | null) => void;
  toggleDefault: (roleSn: number) => Promise<void>;
  handleDeleteRole: (roleSn: number, roleName: string) => Promise<void>;
  showCreateForm: boolean;
  setShowCreateForm: (show: boolean) => void;
  setCreateError: (err: string) => void;
  newRoleCode: string;
  setNewRoleCode: (code: string) => void;
  newRoleName: string;
  setNewRoleName: (name: string) => void;
  newRoleDesc: string;
  setNewRoleDesc: (desc: string) => void;
  creating: boolean;
  createError: string;
  handleCreateRole: () => Promise<void>;
}

function RolePermTab({
  roles,
  permTree,
  selectedRoleSn,
  setSelectedRoleSn,
  dirty,
  saving,
  saveError,
  handleSave,
  handleToggleExpand,
  handleTogglePerm,
  expanded,
  flatNodes,
  rolePerms,
  editingRoleSn,
  editRoleName,
  setEditRoleName,
  handleStartEditName,
  handleSaveRoleName,
  setEditingRoleSn,
  toggleDefault,
  handleDeleteRole,
  showCreateForm,
  setShowCreateForm,
  setCreateError,
  newRoleCode,
  setNewRoleCode,
  newRoleName,
  setNewRoleName,
  newRoleDesc,
  setNewRoleDesc,
  creating,
  createError,
  handleCreateRole,
}: RolePermTabProps) {
  const currentStateMap = selectedRoleSn
    ? buildEffectiveStates(flatNodes, rolePerms.get(selectedRoleSn) ?? new Map())
    : new Map<string, PermState>();

  return (
    <>
      {/* 헤더 액션 버튼 */}
      <div
        className="flex items-center gap-2 px-4 py-2 border-b border-stroke"
        style={{ flexShrink: 0 }}
      >
        <button
          onClick={() => {
            setShowCreateForm(true);
            setCreateError('');
          }}
          className="px-3 py-1.5 text-label-2 font-semibold rounded-md border border-color-accent text-color-accent hover:bg-[rgba(6,182,212,0.08)] transition-all font-korean"
        >
          + 역할 추가
        </button>
        <button
          onClick={handleSave}
          disabled={!dirty || saving}
          className={`px-3 py-1.5 text-label-2 font-semibold rounded-md transition-all font-korean ${
            dirty
              ? 'bg-color-accent text-bg-0 hover:shadow-[0_0_12px_rgba(6,182,212,0.3)]'
              : 'bg-bg-card text-fg-disabled cursor-not-allowed'
          }`}
        >
          {saving ? '저장 중...' : '변경사항 저장'}
        </button>
        {saveError && (
          <span className="text-label-2 text-color-danger font-korean">{saveError}</span>
        )}
      </div>

      {/* 역할 탭 바 */}
      <div
        className="flex items-center gap-1.5 px-4 py-2 border-b border-stroke bg-bg-surface overflow-x-auto"
        style={{ flexShrink: 0 }}
      >
        {roles.map((role, idx) => {
          const color = getRoleColor(role.code, idx);
          const isSelected = selectedRoleSn === role.sn;
          return (
            <div key={role.sn} className="flex items-center gap-0.5 flex-shrink-0">
              <button
                onClick={() => setSelectedRoleSn(role.sn)}
                className={`px-2.5 py-1 text-label-2 font-semibold rounded-md transition-all font-korean ${
                  isSelected
                    ? 'border-2 shadow-[0_0_8px_rgba(6,182,212,0.2)]'
                    : 'border border-stroke text-fg-disabled hover:border-stroke'
                }`}
                style={isSelected ? { borderColor: color, color } : undefined}
              >
                {editingRoleSn === role.sn ? (
                  <input
                    type="text"
                    value={editRoleName}
                    onChange={(e) => setEditRoleName(e.target.value)}
                    onKeyDown={(e) => {
                      if (e.key === 'Enter') handleSaveRoleName(role.sn);
                      if (e.key === 'Escape') setEditingRoleSn(null);
                    }}
                    onBlur={() => handleSaveRoleName(role.sn)}
                    onClick={(e) => e.stopPropagation()}
                    autoFocus
                    className="w-20 px-1 py-0 text-label-2 font-semibold bg-bg-elevated border border-color-accent rounded text-center text-fg focus:outline-none font-korean"
                  />
                ) : (
                  <span onDoubleClick={() => handleStartEditName(role)}>{role.name}</span>
                )}
                <span className="ml-1 text-caption font-mono opacity-50">{role.code}</span>
                {role.isDefault && (
                  <span className="ml-1 text-caption text-color-accent">기본</span>
                )}
              </button>
              {isSelected && (
                <div className="flex items-center gap-0.5">
                  <button
                    onClick={() => toggleDefault(role.sn)}
                    className={`px-1.5 py-0.5 text-caption rounded transition-all font-korean ${
                      role.isDefault
                        ? 'bg-[rgba(6,182,212,0.15)] text-color-accent'
                        : 'text-fg-disabled hover:text-fg-sub'
                    }`}
                    title="신규 사용자 기본 역할 설정"
                  >
                    {role.isDefault ? '기본역할' : '기본설정'}
                  </button>
                  {role.code !== 'ADMIN' && (
                    <button
                      onClick={() => handleDeleteRole(role.sn, role.name)}
                      className="w-5 h-5 flex items-center justify-center text-fg-disabled hover:text-red-400 transition-colors"
                      title="역할 삭제"
                    >
                      <svg
                        width="10"
                        height="10"
                        viewBox="0 0 24 24"
                        fill="none"
                        stroke="currentColor"
                        strokeWidth="2.5"
                      >
                        <line x1="18" y1="6" x2="6" y2="18" />
                        <line x1="6" y1="6" x2="18" y2="18" />
                      </svg>
                    </button>
                  )}
                </div>
              )}
            </div>
          );
        })}
      </div>

      {/* 범례 */}
      <PermLegend />

      {/* CRUD 매트릭스 테이블 */}
      {selectedRoleSn ? (
        <div className="flex-1 overflow-auto">
          <table className="w-full">
            <thead>
              <tr className="border-b border-stroke bg-bg-surface sticky top-0 z-10">
                <th className="px-3 py-1.5 text-left text-caption font-semibold text-fg-disabled font-korean min-w-[200px]">
                  기능
                </th>
                {OPER_CODES.map((oper) => (
                  <th key={oper} className="px-1 py-1.5 text-center w-12">
                    <div className="text-caption font-semibold text-fg-sub">
                      {OPER_LABELS[oper]}
                    </div>
                    <div className="text-caption text-fg-disabled font-korean">
                      {OPER_FULL_LABELS[oper]}
                    </div>
                  </th>
                ))}
              </tr>
            </thead>
            <tbody>
              {permTree.map((rootNode) => (
                <TreeRow
                  key={rootNode.code}
                  node={rootNode}
                  stateMap={currentStateMap}
                  expanded={expanded}
                  onToggleExpand={handleToggleExpand}
                  onTogglePerm={handleTogglePerm}
                />
              ))}
            </tbody>
          </table>
        </div>
      ) : (
        <div className="flex-1 flex items-center justify-center text-fg-disabled text-body-2 font-korean">
          역할을 선택하세요
        </div>
      )}

      {/* 역할 생성 모달 */}
      {showCreateForm && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
          <div className="w-[400px] bg-bg-surface rounded-lg border border-stroke shadow-2xl">
            <div className="px-5 py-4 border-b border-stroke">
              <h3 className="text-body-2 font-bold text-fg font-korean">새 역할 추가</h3>
            </div>
            <div className="px-5 py-4 flex flex-col gap-3">
              <div>
                <label className="text-label-2 text-fg-disabled font-korean block mb-1">
                  역할 코드
                </label>
                <input
                  type="text"
                  value={newRoleCode}
                  onChange={(e) =>
                    setNewRoleCode(e.target.value.toUpperCase().replace(/[^A-Z0-9_]/g, ''))
                  }
                  placeholder="CUSTOM_ROLE"
                  className="w-full px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-mono"
                />
                <p className="text-caption text-fg-disabled mt-1 font-korean">
                  영문 대문자, 숫자, 언더스코어만 허용 (생성 후 변경 불가)
                </p>
              </div>
              <div>
                <label className="text-label-2 text-fg-disabled font-korean block mb-1">
                  역할 이름
                </label>
                <input
                  type="text"
                  value={newRoleName}
                  onChange={(e) => setNewRoleName(e.target.value)}
                  placeholder="사용자 정의 역할"
                  className="w-full px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean"
                />
              </div>
              <div>
                <label className="text-label-2 text-fg-disabled font-korean block mb-1">
                  설명 (선택)
                </label>
                <input
                  type="text"
                  value={newRoleDesc}
                  onChange={(e) => setNewRoleDesc(e.target.value)}
                  placeholder="역할에 대한 설명"
                  className="w-full px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean"
                />
              </div>
              {createError && (
                <div className="px-3 py-2 text-label-2 text-red-400 bg-[rgba(239,68,68,0.08)] border border-[rgba(239,68,68,0.2)] rounded-md font-korean">
                  {createError}
                </div>
              )}
            </div>
            <div className="px-5 py-3 border-t border-stroke flex justify-end gap-2">
              <button
                onClick={() => setShowCreateForm(false)}
                className="px-4 py-2 text-caption text-fg-disabled border border-stroke rounded-md hover:bg-bg-surface-hover font-korean"
              >
                취소
              </button>
              <button
                onClick={handleCreateRole}
                disabled={!newRoleCode || !newRoleName || creating}
                className="px-4 py-2 text-caption font-semibold rounded-md bg-color-accent text-bg-0 hover:shadow-[0_0_12px_rgba(6,182,212,0.3)] transition-all font-korean disabled:opacity-50"
              >
                {creating ? '생성 중...' : '생성'}
              </button>
            </div>
          </div>
        </div>
      )}
    </>
  );
}

// ─── UserPermTab: 사용자별 권한 탭 ───────────────────
interface UserPermTabProps {
  roles: RoleWithPermissions[];
  permTree: PermTreeNode[];
  rolePerms: Map<number, Map<string, boolean>>;
}

function UserPermTab({ roles, permTree, rolePerms }: UserPermTabProps) {
  const [users, setUsers] = useState<UserListItem[]>([]);
  const [loadingUsers, setLoadingUsers] = useState(true);
  const [searchQuery, setSearchQuery] = useState('');
  const [showDropdown, setShowDropdown] = useState(false);
  const [selectedUser, setSelectedUser] = useState<UserListItem | null>(null);
  const [assignedRoleSns, setAssignedRoleSns] = useState<number[]>([]);
  const [savingRoles, setSavingRoles] = useState(false);
  const [rolesDirty, setRolesDirty] = useState(false);
  const [expanded, setExpanded] = useState<Set<string>>(new Set());
  const dropdownRef = useRef<HTMLDivElement>(null);

  const flatNodes = flattenTree(permTree);

  useEffect(() => {
    const loadUsers = async () => {
      setLoadingUsers(true);
      try {
        const data = await fetchUsers();
        setUsers(data);
      } catch (err) {
        console.error('사용자 목록 조회 실패:', err);
      } finally {
        setLoadingUsers(false);
      }
    };
    loadUsers();
  }, []);

  // 최상위 노드 기본 펼침
  useEffect(() => {
    if (permTree.length > 0) {
      setExpanded(new Set(permTree.map((n) => n.code)));
    }
  }, [permTree]);

  // 드롭다운 외부 클릭 시 닫기
  useEffect(() => {
    const handleClickOutside = (e: MouseEvent) => {
      if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
        setShowDropdown(false);
      }
    };
    document.addEventListener('mousedown', handleClickOutside);
    return () => document.removeEventListener('mousedown', handleClickOutside);
  }, []);

  const filteredUsers = users.filter((u) => {
    if (!searchQuery) return true;
    const q = searchQuery.toLowerCase();
    return (
      u.name.toLowerCase().includes(q) ||
      u.account.toLowerCase().includes(q) ||
      (u.orgName?.toLowerCase().includes(q) ?? false)
    );
  });

  const handleSelectUser = (user: UserListItem) => {
    setSelectedUser(user);
    setSearchQuery(user.name);
    setShowDropdown(false);
    setAssignedRoleSns(user.roleSns ?? []);
    setRolesDirty(false);
  };

  const handleToggleRole = (roleSn: number) => {
    setAssignedRoleSns((prev) => {
      const next = prev.includes(roleSn) ? prev.filter((sn) => sn !== roleSn) : [...prev, roleSn];
      return next;
    });
    setRolesDirty(true);
  };

  const handleSaveRoles = async () => {
    if (!selectedUser) return;
    setSavingRoles(true);
    try {
      await assignRolesApi(selectedUser.id, assignedRoleSns);
      setRolesDirty(false);
      // 로컬 users 상태 갱신
      setUsers((prev) =>
        prev.map((u) =>
          u.id === selectedUser.id
            ? {
                ...u,
                roleSns: assignedRoleSns,
                roles: roles.filter((r) => assignedRoleSns.includes(r.sn)).map((r) => r.name),
              }
            : u,
        ),
      );
      setSelectedUser((prev) =>
        prev
          ? {
              ...prev,
              roleSns: assignedRoleSns,
              roles: roles.filter((r) => assignedRoleSns.includes(r.sn)).map((r) => r.name),
            }
          : null,
      );
    } catch (err) {
      console.error('역할 저장 실패:', err);
    } finally {
      setSavingRoles(false);
    }
  };

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

  // 사용자의 유효 권한: 할당된 역할들의 권한 병합 (OR 결합)
  const effectiveStateMap = (() => {
    if (!selectedUser || assignedRoleSns.length === 0) {
      return new Map<string, PermState>();
    }

    // 각 역할의 명시적 권한 병합: 어느 역할이든 granted=true면 허용
    const mergedPerms = new Map<string, boolean>();
    for (const roleSn of assignedRoleSns) {
      const perms = rolePerms.get(roleSn);
      if (!perms) continue;
      for (const [key, granted] of perms) {
        if (granted) {
          mergedPerms.set(key, true);
        } else if (!mergedPerms.has(key)) {
          mergedPerms.set(key, false);
        }
      }
    }

    return buildEffectiveStates(flatNodes, mergedPerms);
  })();

  const noOpToggle = useCallback((_code: string, _oper: OperCode, _state: PermState): void => {
    void _code;
    void _oper;
    void _state;
    // 읽기 전용 — 토글 없음
  }, []);

  return (
    <div className="flex flex-col flex-1 min-h-0">
      {/* 사용자 검색/선택 */}
      <div className="px-4 py-2.5 border-b border-stroke" style={{ flexShrink: 0 }}>
        <label className="text-caption text-fg-disabled font-korean block mb-1.5">
          사용자 선택
        </label>
        <div className="relative" ref={dropdownRef}>
          <input
            type="text"
            value={searchQuery}
            onChange={(e) => {
              setSearchQuery(e.target.value);
              setShowDropdown(true);
              if (selectedUser && e.target.value !== selectedUser.name) {
                setSelectedUser(null);
                setAssignedRoleSns([]);
                setRolesDirty(false);
              }
            }}
            onFocus={() => setShowDropdown(true)}
            placeholder={loadingUsers ? '불러오는 중...' : '이름, 계정, 조직으로 검색...'}
            disabled={loadingUsers}
            className="w-full max-w-sm px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean disabled:opacity-50"
          />
          {showDropdown && filteredUsers.length > 0 && (
            <div className="absolute left-0 top-full mt-1 w-full max-w-sm bg-bg-surface border border-stroke rounded-md shadow-xl z-20 overflow-auto max-h-52">
              {filteredUsers.map((user) => (
                <button
                  key={user.id}
                  onClick={() => handleSelectUser(user)}
                  className="w-full px-3 py-2 text-left hover:bg-bg-surface-hover transition-colors flex items-center gap-2"
                >
                  <div className="min-w-0 flex-1">
                    <div className="text-caption font-semibold text-fg font-korean truncate">
                      {user.name}
                      {user.rank && (
                        <span className="ml-1 text-caption text-fg-disabled font-korean">
                          {user.rank}
                        </span>
                      )}
                    </div>
                    <div className="text-caption text-fg-disabled font-mono truncate">
                      {user.account}
                    </div>
                  </div>
                  {user.orgName && (
                    <span className="text-caption text-fg-disabled font-korean flex-shrink-0 truncate max-w-[100px]">
                      {user.orgName}
                    </span>
                  )}
                </button>
              ))}
            </div>
          )}
          {showDropdown && !loadingUsers && filteredUsers.length === 0 && searchQuery && (
            <div className="absolute left-0 top-full mt-1 w-full max-w-sm bg-bg-surface border border-stroke rounded-md shadow-xl z-20 px-3 py-2 text-caption text-fg-disabled font-korean">
              검색 결과 없음
            </div>
          )}
        </div>
      </div>

      {selectedUser ? (
        <>
          {/* 역할 할당 섹션 */}
          <div
            className="px-4 py-2.5 border-b border-stroke bg-bg-surface"
            style={{ flexShrink: 0 }}
          >
            <div className="flex items-center justify-between mb-2">
              <span className="text-caption font-semibold text-fg-sub font-korean">역할 할당</span>
              <button
                onClick={handleSaveRoles}
                disabled={!rolesDirty || savingRoles}
                className={`px-3 py-1.5 text-label-2 font-semibold rounded-md transition-all font-korean ${
                  rolesDirty
                    ? 'bg-color-accent text-bg-0 hover:shadow-[0_0_12px_rgba(6,182,212,0.3)]'
                    : 'bg-bg-card text-fg-disabled cursor-not-allowed'
                }`}
              >
                {savingRoles ? '저장 중...' : '역할 저장'}
              </button>
            </div>
            <div className="flex flex-wrap gap-1.5">
              {roles.map((role, idx) => {
                const color = getRoleColor(role.code, idx);
                const isChecked = assignedRoleSns.includes(role.sn);
                return (
                  <label
                    key={role.sn}
                    className={[
                      'flex items-center gap-1 px-2 py-1 rounded-md border cursor-pointer transition-all font-korean text-label-2 select-none',
                      isChecked ? '' : 'border-stroke text-fg-disabled hover:border-text-2',
                    ].join(' ')}
                    style={
                      isChecked
                        ? { borderColor: color, color, backgroundColor: `${color}18` }
                        : undefined
                    }
                  >
                    <input
                      type="checkbox"
                      checked={isChecked}
                      onChange={() => handleToggleRole(role.sn)}
                      className="w-3 h-3 accent-primary-cyan"
                    />
                    <span>{role.name}</span>
                    <span className="text-caption font-mono opacity-60">{role.code}</span>
                  </label>
                );
              })}
            </div>
          </div>

          {/* 유효 권한 매트릭스 (읽기 전용) */}
          <div
            className="px-4 py-1.5 border-b border-stroke bg-bg-surface text-caption text-fg-disabled font-korean"
            style={{ flexShrink: 0 }}
          >
            <span className="font-semibold text-fg-sub">유효 권한 (읽기 전용)</span>
            <span className="ml-2">— 할당된 역할의 권한 합산 결과</span>
          </div>

          <PermLegend />

          {assignedRoleSns.length > 0 ? (
            <div className="flex-1 overflow-auto">
              <table className="w-full">
                <thead>
                  <tr className="border-b border-stroke bg-bg-surface sticky top-0 z-10">
                    <th className="px-4 py-3 text-left text-label-2 font-semibold text-fg-disabled font-korean min-w-[240px]">
                      기능
                    </th>
                    {OPER_CODES.map((oper) => (
                      <th key={oper} className="px-2 py-3 text-center w-16">
                        <div className="text-label-2 font-semibold text-fg-sub">
                          {OPER_LABELS[oper]}
                        </div>
                        <div className="text-caption text-fg-disabled font-korean">
                          {OPER_FULL_LABELS[oper]}
                        </div>
                      </th>
                    ))}
                  </tr>
                </thead>
                <tbody>
                  {permTree.map((rootNode) => (
                    <TreeRow
                      key={rootNode.code}
                      node={rootNode}
                      stateMap={effectiveStateMap}
                      expanded={expanded}
                      onToggleExpand={handleToggleExpand}
                      onTogglePerm={noOpToggle}
                      readOnly={true}
                    />
                  ))}
                </tbody>
              </table>
            </div>
          ) : (
            <div className="flex-1 flex items-center justify-center text-fg-disabled text-body-2 font-korean">
              역할을 하나 이상 할당하면 유효 권한이 표시됩니다
            </div>
          )}
        </>
      ) : (
        <div className="flex-1 flex items-center justify-center text-fg-disabled text-body-2 font-korean">
          사용자를 선택하세요
        </div>
      )}
    </div>
  );
}

// ─── 메인 PermissionsPanel ──────────────────────────
type ActiveTab = 'role' | 'user';

function PermissionsPanel() {
  const [activeTab, setActiveTab] = useState<ActiveTab>('role');
  const [roles, setRoles] = useState<RoleWithPermissions[]>([]);
  const [permTree, setPermTree] = useState<PermTreeNode[]>([]);
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState(false);
  const [saveError, setSaveError] = useState<string | null>(null);
  const [dirty, setDirty] = useState(false);
  const [showCreateForm, setShowCreateForm] = useState(false);
  const [newRoleCode, setNewRoleCode] = useState('');
  const [newRoleName, setNewRoleName] = useState('');
  const [newRoleDesc, setNewRoleDesc] = useState('');
  const [creating, setCreating] = useState(false);
  const [createError, setCreateError] = useState('');
  const [editingRoleSn, setEditingRoleSn] = useState<number | null>(null);
  const [editRoleName, setEditRoleName] = useState('');
  const [expanded, setExpanded] = useState<Set<string>>(new Set());
  const [selectedRoleSn, setSelectedRoleSn] = useState<number | null>(null);

  // 역할별 명시적 권한: Map<roleSn, Map<"rsrc::oper", boolean>>
  const [rolePerms, setRolePerms] = useState<Map<number, Map<string, boolean>>>(new Map());

  const loadData = useCallback(async () => {
    setLoading(true);
    try {
      const [rolesData, treeData] = await Promise.all([fetchRoles(), fetchPermTree()]);
      setRoles(rolesData);
      setPermTree(treeData);

      // 명시적 권한 맵 초기화 (rsrc::oper 키 형식)
      const permsMap = new Map<number, Map<string, boolean>>();
      for (const role of rolesData) {
        const roleMap = new Map<string, boolean>();
        for (const p of role.permissions) {
          roleMap.set(makeKey(p.resourceCode, p.operationCode), p.granted);
        }
        permsMap.set(role.sn, roleMap);
      }
      setRolePerms(permsMap);

      // 최상위 노드 기본 펼침
      setExpanded(new Set(treeData.map((n) => n.code)));
      // 첫 번째 역할 선택
      if (rolesData.length > 0 && !selectedRoleSn) {
        setSelectedRoleSn(rolesData[0].sn);
      }
      setDirty(false);
    } catch (err) {
      console.error('권한 데이터 조회 실패:', err);
    } finally {
      setLoading(false);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps -- 초기 마운트 시 1회만 실행
  }, []);

  useEffect(() => {
    loadData();
  }, [loadData]);

  // 플랫 노드 목록
  const flatNodes = flattenTree(permTree);

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

  const handleTogglePerm = useCallback(
    (code: string, oper: OperCode, currentState: PermState) => {
      if (!selectedRoleSn) return;

      setRolePerms((prev) => {
        const next = new Map(prev);
        const roleMap = new Map(next.get(selectedRoleSn) ?? new Map());

        const key = makeKey(code, oper);
        const node = flatNodes.find((n) => n.code === code);
        const isRoot = node ? node.parentCode === null : false;

        switch (currentState) {
          case 'explicit-granted':
            roleMap.set(key, false);
            break;
          case 'inherited-granted':
            roleMap.set(key, false);
            break;
          case 'explicit-denied':
            if (isRoot) {
              roleMap.set(key, true);
            } else {
              roleMap.delete(key);
            }
            break;
          default:
            return prev;
        }

        next.set(selectedRoleSn, roleMap);
        return next;
      });
      setDirty(true);
    },
    [selectedRoleSn, flatNodes],
  );

  const handleSave = async () => {
    setSaving(true);
    setSaveError(null);
    try {
      for (const role of roles) {
        const perms = rolePerms.get(role.sn);
        if (!perms) continue;

        const permsList: Array<{ resourceCode: string; operationCode: string; granted: boolean }> =
          [];
        for (const [key, granted] of perms) {
          const sepIdx = key.indexOf('::');
          permsList.push({
            resourceCode: key.substring(0, sepIdx),
            operationCode: key.substring(sepIdx + 2),
            granted,
          });
        }
        await updatePermissionsApi(role.sn, permsList);
      }
      setDirty(false);
    } catch (err) {
      console.error('권한 저장 실패:', err);
      setSaveError('권한 저장에 실패했습니다. 다시 시도해주세요.');
    } finally {
      setSaving(false);
    }
  };

  const handleCreateRole = async () => {
    setCreating(true);
    setCreateError('');
    try {
      await createRoleApi({
        code: newRoleCode,
        name: newRoleName,
        description: newRoleDesc || undefined,
      });
      await loadData();
      setShowCreateForm(false);
      setNewRoleCode('');
      setNewRoleName('');
      setNewRoleDesc('');
    } catch (err) {
      const message = err instanceof Error ? err.message : '역할 생성에 실패했습니다.';
      setCreateError(message);
    } finally {
      setCreating(false);
    }
  };

  const handleDeleteRole = async (roleSn: number, roleName: string) => {
    if (
      !window.confirm(
        `"${roleName}" 역할을 삭제하시겠습니까?\n이 역할을 가진 모든 사용자에서 해당 역할이 제거됩니다.`,
      )
    ) {
      return;
    }
    try {
      await deleteRoleApi(roleSn);
      if (selectedRoleSn === roleSn) setSelectedRoleSn(null);
      await loadData();
    } catch (err) {
      console.error('역할 삭제 실패:', err);
    }
  };

  const handleStartEditName = (role: RoleWithPermissions) => {
    setEditingRoleSn(role.sn);
    setEditRoleName(role.name);
  };

  const handleSaveRoleName = async (roleSn: number) => {
    if (!editRoleName.trim()) return;
    try {
      await updateRoleApi(roleSn, { name: editRoleName.trim() });
      setRoles((prev) =>
        prev.map((r) => (r.sn === roleSn ? { ...r, name: editRoleName.trim() } : r)),
      );
      setEditingRoleSn(null);
    } catch (err) {
      console.error('역할 이름 수정 실패:', err);
    }
  };

  const toggleDefault = async (roleSn: number) => {
    const role = roles.find((r) => r.sn === roleSn);
    if (!role) return;
    const newValue = !role.isDefault;
    try {
      await updateRoleDefaultApi(roleSn, newValue);
      setRoles((prev) => prev.map((r) => (r.sn === roleSn ? { ...r, isDefault: newValue } : r)));
    } catch (err) {
      console.error('기본 역할 변경 실패:', err);
    }
  };

  if (loading) {
    return (
      <div className="flex items-center justify-center h-32 text-fg-disabled text-body-2 font-korean">
        불러오는 중...
      </div>
    );
  }

  return (
    <div className="flex flex-col h-full">
      {/* 헤더 */}
      <div
        className="flex items-center justify-between px-4 py-2.5 border-b border-stroke"
        style={{ flexShrink: 0 }}
      >
        <div>
          <h1 className="text-body-2 font-bold text-fg font-korean">권한 관리</h1>
          <p className="text-caption text-fg-disabled mt-0.5 font-korean">
            역할별 리소스 × CRUD 권한 설정
          </p>
        </div>
        {/* 탭 전환 */}
        <div className="flex items-center gap-1 p-1 bg-bg-elevated rounded-lg border border-stroke">
          <button
            onClick={() => setActiveTab('role')}
            className={`px-4 py-1.5 text-caption font-semibold rounded-md transition-all font-korean ${
              activeTab === 'role'
                ? 'bg-color-accent text-bg-0 shadow-[0_0_8px_rgba(6,182,212,0.25)]'
                : 'text-fg-disabled hover:text-fg-sub'
            }`}
          >
            그룹별
          </button>
          <button
            onClick={() => setActiveTab('user')}
            className={`px-4 py-1.5 text-caption font-semibold rounded-md transition-all font-korean ${
              activeTab === 'user'
                ? 'bg-color-accent text-bg-0 shadow-[0_0_8px_rgba(6,182,212,0.25)]'
                : 'text-fg-disabled hover:text-fg-sub'
            }`}
          >
            사용자별
          </button>
        </div>
      </div>

      {activeTab === 'role' ? (
        <RolePermTab
          roles={roles}
          permTree={permTree}
          rolePerms={rolePerms}
          setRolePerms={setRolePerms}
          selectedRoleSn={selectedRoleSn}
          setSelectedRoleSn={setSelectedRoleSn}
          dirty={dirty}
          saving={saving}
          saveError={saveError}
          handleSave={handleSave}
          handleToggleExpand={handleToggleExpand}
          handleTogglePerm={handleTogglePerm}
          expanded={expanded}
          flatNodes={flatNodes}
          editingRoleSn={editingRoleSn}
          editRoleName={editRoleName}
          setEditRoleName={setEditRoleName}
          handleStartEditName={handleStartEditName}
          handleSaveRoleName={handleSaveRoleName}
          setEditingRoleSn={setEditingRoleSn}
          toggleDefault={toggleDefault}
          handleDeleteRole={handleDeleteRole}
          showCreateForm={showCreateForm}
          setShowCreateForm={setShowCreateForm}
          setCreateError={setCreateError}
          newRoleCode={newRoleCode}
          setNewRoleCode={setNewRoleCode}
          newRoleName={newRoleName}
          setNewRoleName={setNewRoleName}
          newRoleDesc={newRoleDesc}
          setNewRoleDesc={setNewRoleDesc}
          creating={creating}
          createError={createError}
          handleCreateRole={handleCreateRole}
        />
      ) : (
        <UserPermTab roles={roles} permTree={permTree} rolePerms={rolePerms} />
      )}
    </div>
  );
}

export default PermissionsPanel;
