// role-settings.jsx — 권한 설정 페이지 (RBAC v1.1, 회의록 v3.13)
// ──────────────────────────────────────────────────────────────────
// 권한: settings.role_edit 보유자만 접근 (가능하면 owner)
// 디자인: 02_디자인/권한관리UI_v1.0.md + 권한관리_미리보기.html 기반 3-pane
// ──────────────────────────────────────────────────────────────────

const { useState, useEffect, useMemo } = React;
const AUTH = window.AUTH || { role: "owner", can: () => true, permissions: [] };

// v3.19: 매장 운영 톤에 맞춰 정리 (mechanic/manager/part_time 이모지 제거)
const EMOJI_PICK = ["👤","👑","🏪","🛠","📋","💼","🎯","⭐","🔐","🧾"];
const COLOR_PICK = ["#8B95A1","#191F28","#E31E26","#0064FF","#0E8F5F","#FFB800","#8B5CF6"];

// ── 헤더 / 네비 ─────────────────────────────────────────────
function Header() {
  return (
    <header className="cat-header">
      <div className="cat-header-inner">
        <a className="cat-brand" href="관리자.html">
          <img className="cat-brand-mark" src="assets/symbol-brush.svg" alt="" />
          <div>
            <div className="cat-brand-name">스페셜라이즈드 세종점</div>
            <div className="cat-brand-sub">Admin · 권한 설정</div>
          </div>
        </a>
        <div className="cat-header-sep" />
        <div className="cat-header-title">권한 <b>설정</b></div>
        <div className="cat-header-tools">
          {window.HeaderUser ? React.createElement(window.HeaderUser) : <span>현재 <b>{AUTH.user?.email || "owner"}</b> 로그인</span>}
        </div>
      </div>
    </header>
  );
}

function PageNav() {
  return (
    <nav className="cat-pagenav">
      <div className="cat-pagenav-inner">
        <a href="관리자.html">관리자 <small>Admin</small></a>
        <a href="2026 카탈로그.html">카탈로그 <small>Catalog</small></a>
        <a href="재고관리.html">재고관리 <small>Inventory</small></a>
        <a href="서비스 견적서.html">서비스 견적서 <small>Estimate</small></a>
        <a href="직원관리.html">직원 관리 <small>Staff</small></a>
        <a className="on" href="권한설정.html">🔐 권한 설정 <small>Roles</small></a>
        <a href="매장설정.html">매장 정보 <small>Shop</small></a>
      </div>
    </nav>
  );
}

function Forbidden() {
  return (
    <main className="adm-main">
      <div className="cat-inner" style={{padding:"60px 20px",textAlign:"center"}}>
        <div style={{fontSize:48,marginBottom:12}}>🔒</div>
        <h1 style={{margin:"0 0 8px",font:"700 24px/1.3 var(--font-sans)"}}>접근 권한이 없습니다</h1>
        <p style={{color:"var(--grey-600,#6B7684)",marginBottom:20}}>
          권한 설정은 "권한 시스템 편집" 권한 보유자만 사용할 수 있습니다.
        </p>
        <a href="관리자.html" className="cat-print-btn" style={{display:"inline-block"}}>
          ← 대시보드로
        </a>
      </div>
    </main>
  );
}

// ── 새 역할 모달 ─────────────────────────────────────────────
function NewRoleModal({ open, onClose, onCreate, presets, totalRoles }) {
  const [name, setName] = useState("");
  const [emoji, setEmoji] = useState("👤");
  const [color, setColor] = useState("#8B95A1");
  const [desc, setDesc] = useState("");
  const [copyFrom, setCopyFrom] = useState("");
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState("");

  if (!open) return null;

  const canSubmit = name.trim().length > 0 && name.trim().length <= 20 && !busy && totalRoles < 10;

  const submit = async () => {
    setErr("");
    if (!canSubmit) return;
    setBusy(true);
    try {
      await onCreate({ name_ko: name.trim(), emoji, color, description: desc.trim(), copy_from: copyFrom || null });
      onClose();
    } catch (e) {
      setErr(e.message || "생성에 실패했습니다.");
    } finally {
      setBusy(false);
    }
  };

  return (
    <div style={{position:"fixed",inset:0,background:"rgba(0,0,0,0.45)",zIndex:9999,display:"flex",alignItems:"center",justifyContent:"center",padding:16}} onClick={onClose}>
      <div onClick={e=>e.stopPropagation()} style={{background:"#fff",borderRadius:16,maxWidth:520,width:"100%",padding:24,boxShadow:"0 20px 60px rgba(0,0,0,0.2)"}}>
        <div style={{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:8}}>
          <h2 style={{margin:0,font:"700 20px/1.3 var(--font-sans)"}}>➕ 새 역할 만들기</h2>
          <button onClick={onClose} style={{background:"none",border:"none",fontSize:22,cursor:"pointer",color:"var(--grey-500,#8B95A1)"}}>×</button>
        </div>
        {totalRoles >= 10 && (
          <div style={{background:"var(--red-50,#FFE5E6)",color:"var(--red-700,#9A0E14)",padding:"10px 12px",borderRadius:8,fontSize:13,marginBottom:12}}>
            ⚠️ 역할은 최대 10개까지 만들 수 있습니다. (현재 {totalRoles}개)
          </div>
        )}
        {err && <div style={{background:"var(--red-50,#FFE5E6)",color:"var(--red-700,#9A0E14)",padding:"10px 12px",borderRadius:8,fontSize:13,marginBottom:12}}>{err}</div>}

        <label style={{display:"block",fontSize:12,color:"var(--grey-700,#4E5968)",fontWeight:500,marginBottom:4}}>이름 * (최대 20자)</label>
        <input value={name} onChange={e=>setName(e.target.value.slice(0,20))} placeholder="예: 운영팀장" style={{width:"100%",padding:"10px 12px",border:"1px solid var(--grey-300,#D1D6DB)",borderRadius:8,fontSize:14,marginBottom:12,boxSizing:"border-box"}} />

        <label style={{display:"block",fontSize:12,color:"var(--grey-700,#4E5968)",fontWeight:500,marginBottom:4}}>아이콘</label>
        <div style={{display:"flex",gap:6,flexWrap:"wrap",marginBottom:12}}>
          {EMOJI_PICK.map(e => (
            <button key={e} type="button" onClick={()=>setEmoji(e)} style={{width:36,height:36,border:"2px solid "+(emoji===e?"var(--blue-500,#0064FF)":"var(--grey-300,#D1D6DB)"),borderRadius:8,background:emoji===e?"var(--blue-50,#E8F3FF)":"#fff",fontSize:18,cursor:"pointer"}}>{e}</button>
          ))}
        </div>

        <label style={{display:"block",fontSize:12,color:"var(--grey-700,#4E5968)",fontWeight:500,marginBottom:4}}>카드 색상</label>
        <div style={{display:"flex",gap:6,marginBottom:12}}>
          {COLOR_PICK.map(c => (
            <button key={c} type="button" onClick={()=>setColor(c)} style={{width:28,height:28,borderRadius:14,background:c,border:"3px solid "+(color===c?"var(--grey-900,#191F28)":"transparent"),cursor:"pointer"}} title={c} />
          ))}
        </div>

        <label style={{display:"block",fontSize:12,color:"var(--grey-700,#4E5968)",fontWeight:500,marginBottom:4}}>한 줄 설명 (선택)</label>
        <input value={desc} onChange={e=>setDesc(e.target.value)} placeholder="예: 매장 운영 전반" style={{width:"100%",padding:"10px 12px",border:"1px solid var(--grey-300,#D1D6DB)",borderRadius:8,fontSize:14,marginBottom:12,boxSizing:"border-box"}} />

        <label style={{display:"block",fontSize:12,color:"var(--grey-700,#4E5968)",fontWeight:500,marginBottom:4}}>프리셋 복사 (선택)</label>
        <div style={{background:"var(--grey-50,#F9FAFB)",borderRadius:8,padding:12,marginBottom:16}}>
          <label style={{display:"flex",alignItems:"center",gap:8,marginBottom:6,fontSize:13,cursor:"pointer"}}>
            <input type="radio" name="copy" checked={copyFrom===""} onChange={()=>setCopyFrom("")} />
            <span>빈 권한 (체크 모두 해제)</span>
          </label>
          {presets.map(p => (
            <label key={p.id} style={{display:"flex",alignItems:"center",gap:8,marginBottom:6,fontSize:13,cursor:"pointer"}}>
              <input type="radio" name="copy" checked={copyFrom===p.id} onChange={()=>setCopyFrom(p.id)} />
              <span>{p.emoji} {p.name_ko} 복사 ({p.perm_count}개 권한)</span>
            </label>
          ))}
        </div>

        <div style={{display:"flex",gap:8,justifyContent:"flex-end"}}>
          <button onClick={onClose} disabled={busy} style={{padding:"10px 18px",border:"1px solid var(--grey-300,#D1D6DB)",borderRadius:8,background:"#fff",cursor:"pointer",fontSize:14}}>취소</button>
          <button onClick={submit} disabled={!canSubmit} style={{padding:"10px 20px",border:"none",borderRadius:8,background:canSubmit?"var(--red-500,#E31E26)":"var(--grey-300,#D1D6DB)",color:"#fff",cursor:canSubmit?"pointer":"not-allowed",fontSize:14,fontWeight:600}}>
            {busy ? "생성 중..." : "생성"}
          </button>
        </div>
      </div>
    </div>
  );
}

// ── 민감 권한 경고 모달 ────────────────────────────────────
function SensitiveConfirmModal({ open, role, changes, onConfirm, onCancel }) {
  if (!open) return null;
  const adds = changes.filter(c => c.is_sensitive && c.action === "add");
  return (
    <div style={{position:"fixed",inset:0,background:"rgba(0,0,0,0.5)",zIndex:10000,display:"flex",alignItems:"center",justifyContent:"center",padding:16}}>
      <div style={{background:"#fff",borderRadius:16,maxWidth:480,width:"100%",padding:24,boxShadow:"0 20px 60px rgba(0,0,0,0.2)"}}>
        <h2 style={{margin:"0 0 8px",font:"700 18px/1.3 var(--font-sans)"}}>⚠️ 민감 권한 변경 확인</h2>
        <div style={{background:"var(--red-50,#FFE5E6)",border:"1px solid var(--red-200,#FEAFB4)",borderRadius:8,padding:"12px 14px",marginBottom:12}}>
          <b style={{color:"var(--red-700,#9A0E14)"}}>'{role?.name_ko}'</b> 역할에 다음 민감 권한을 부여하려고 합니다:
          <ul style={{margin:"6px 0 0",paddingLeft:20,color:"var(--red-700,#9A0E14)"}}>
            {adds.map(c => <li key={c.key}>🔴 {c.name_ko}</li>)}
          </ul>
        </div>
        <p style={{margin:"0 0 16px",color:"var(--grey-600,#6B7684)",fontSize:13,lineHeight:1.6}}>
          이 권한을 가진 직원은 매장의 민감한 데이터를 다룰 수 있게 됩니다.
          <br/>영향 받는 직원: <b>{role?.user_count || 0}명</b>
        </p>
        <div style={{display:"flex",gap:8,justifyContent:"flex-end"}}>
          <button onClick={onCancel} style={{padding:"10px 18px",border:"1px solid var(--grey-300,#D1D6DB)",borderRadius:8,background:"#fff",cursor:"pointer",fontSize:14}}>취소</button>
          <button onClick={onConfirm} style={{padding:"10px 20px",border:"none",borderRadius:8,background:"var(--red-500,#E31E26)",color:"#fff",cursor:"pointer",fontSize:14,fontWeight:600}}>이해했음, 부여</button>
        </div>
      </div>
    </div>
  );
}

// ── 메인 App ─────────────────────────────────────────────────
function App() {
  // 권한 가드
  const canEdit = AUTH.can ? AUTH.can("settings.role_edit") : (AUTH.role === "owner");

  const [roles, setRoles] = useState([]);
  const [perms, setPerms] = useState([]);
  const [selectedRoleId, setSelectedRoleId] = useState(null);
  const [rolePerms, setRolePerms] = useState([]);       // 현재 선택된 역할의 권한 키 배열 (저장된 상태)
  const [draftPerms, setDraftPerms] = useState([]);     // 사용자가 토글한 후 미저장 상태
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState(false);
  const [showNew, setShowNew] = useState(false);
  const [sensitiveModal, setSensitiveModal] = useState(null);
  const [toast, setToast] = useState("");

  // ── 초기 로드 ───────────────────────────────────────────
  useEffect(() => {
    if (!canEdit) { setLoading(false); return; }
    (async () => {
      try {
        const [r, p] = await Promise.all([window.DAL.listRoles(), window.DAL.listPermissions()]);
        setRoles(r);
        setPerms(p);
        if (r.length > 0) {
          // 첫 비시스템 역할 선택 (없으면 owner)
          const first = r.find(x => !x.is_system) || r[0];
          setSelectedRoleId(first.id);
        }
      } catch (e) {
        console.error("[role-settings] load fail:", e);
      } finally {
        setLoading(false);
      }
    })();
  }, []);

  // ── 역할 선택 시 권한 로드 ────────────────────────────
  useEffect(() => {
    if (!selectedRoleId) return;
    (async () => {
      const keys = await window.DAL.getRolePermissions(selectedRoleId);
      setRolePerms(keys);
      setDraftPerms(keys);
    })();
  }, [selectedRoleId]);

  // ── 변경 사항 계산 ───────────────────────────────────
  const changes = useMemo(() => {
    const added = draftPerms.filter(k => !rolePerms.includes(k));
    const removed = rolePerms.filter(k => !draftPerms.includes(k));
    return { added, removed, total: added.length + removed.length };
  }, [draftPerms, rolePerms]);

  const selectedRole = roles.find(r => r.id === selectedRoleId);

  // ── 권한 카테고리 그룹화 ─────────────────────────────
  const permsByCategory = useMemo(() => {
    const groups = {};
    perms.forEach(p => {
      (groups[p.category] = groups[p.category] || []).push(p);
    });
    return groups;
  }, [perms]);

  // ── 권한 토글 ────────────────────────────────────────
  const togglePerm = (key) => {
    setDraftPerms(prev => prev.includes(key) ? prev.filter(k => k !== key) : [...prev, key]);
  };

  // ── 변경 되돌리기 ────────────────────────────────────
  const revert = () => setDraftPerms(rolePerms);

  // ── 저장 ────────────────────────────────────────────
  const doSave = async () => {
    setSaving(true);
    try {
      await window.DAL.saveRolePermissions(selectedRoleId, draftPerms);
      // mock 저장 영속
      if (!window.DAL.isOnline) {
        try { localStorage.setItem("sejong-mock-role-perms", JSON.stringify(window.__RBAC_MOCK_ROLE_PERMS)); } catch {}
      }
      setRolePerms(draftPerms);
      // 역할 목록의 perm_count 갱신
      setRoles(r => r.map(x => x.id === selectedRoleId ? { ...x, perm_count: draftPerms.length } : x));
      setToast("✓ '" + selectedRole?.name_ko + "' 권한이 저장되었습니다");
      setTimeout(() => setToast(""), 3000);
    } catch (e) {
      alert("저장 실패: " + e.message);
    } finally {
      setSaving(false);
    }
  };

  const handleSave = () => {
    // 민감 권한 추가가 있는지 체크
    const sensitiveAdds = changes.added
      .map(k => perms.find(p => p.key === k))
      .filter(p => p && p.is_sensitive);
    if (sensitiveAdds.length > 0) {
      setSensitiveModal({
        role: selectedRole,
        changes: sensitiveAdds.map(p => ({ key: p.key, name_ko: p.name_ko, is_sensitive: true, action: "add" })),
      });
      return;
    }
    doSave();
  };

  // ── 새 역할 생성 ─────────────────────────────────────
  const handleCreate = async (data) => {
    const id = await window.DAL.createRole(data);
    const r = await window.DAL.listRoles();
    setRoles(r);
    setSelectedRoleId(id);
    setToast("✓ 새 역할이 생성되었습니다");
    setTimeout(() => setToast(""), 3000);
  };

  if (!canEdit) {
    return <div className="cat-app"><Header /><PageNav /><Forbidden /></div>;
  }

  if (loading) {
    return (
      <div className="cat-app">
        <Header /><PageNav />
        <main className="adm-main">
          <div className="cat-inner" style={{padding:"60px 20px",textAlign:"center",color:"var(--grey-500,#8B95A1)"}}>
            불러오는 중...
          </div>
        </main>
      </div>
    );
  }

  return (
    <div className="cat-app">
      <Header />
      <PageNav />
      <main className="adm-main">
        <div className="cat-inner">

          {/* 페이지 헤더 */}
          <div className="adm-page-head">
            <div>
              <h1>🔐 권한 설정</h1>
              <p>역할을 만들고 각 역할의 권한을 토글하세요. 변경은 직원이 <b>다음 로그인 시</b> 적용됩니다.</p>
            </div>
            <div className="adm-page-head-tools">
              <a href="관리자.html" className="cat-print-btn" style={{height:36,lineHeight:"36px"}}>
                ← 관리자
              </a>
            </div>
          </div>

          {/* 변경 사항 sticky bar — v3.19: 시그널 강화 (펄스 + 아이콘) */}
          {changes.total > 0 && (
            <div className="rs-changebar" style={{position:"sticky",top:8,zIndex:50,marginBottom:16,padding:"12px 16px",background:"var(--blue-50,#E8F3FF)",border:"1px solid #B0D2FF",borderRadius:12,display:"flex",alignItems:"center",gap:12,boxShadow:"0 2px 8px rgba(0,100,255,0.08)"}}>
              <span aria-hidden style={{
                display:"inline-flex",alignItems:"center",justifyContent:"center",
                width:24,height:24,borderRadius:12,background:"#0064FF",color:"#fff",fontSize:13,
                animation:"rs-pulse 1.6s ease-in-out infinite",
              }}>🔔</span>
              <b style={{color:"#0049BD"}}>저장되지 않은 변경 {changes.total}건</b>
              <span style={{fontSize:12,color:"var(--grey-700,#4E5968)"}}>
                · {changes.added.length}개 추가 / {changes.removed.length}개 제거
              </span>
              <span style={{flex:1}}/>
              <button onClick={revert} disabled={saving} style={{padding:"6px 12px",border:"1px solid var(--grey-300,#D1D6DB)",borderRadius:8,background:"#fff",cursor:"pointer",fontSize:13}}>되돌리기</button>
              <button onClick={handleSave} disabled={saving} style={{padding:"6px 14px",border:"none",borderRadius:8,background:"var(--blue-500,#0064FF)",color:"#fff",cursor:saving?"wait":"pointer",fontSize:13,fontWeight:600}}>
                {saving ? "저장 중..." : "💾 저장"}
              </button>
              <style>{`
                @keyframes rs-pulse { 0%,100%{transform:scale(1);opacity:1} 50%{transform:scale(1.12);opacity:0.85} }
                @media (max-width: 980px) { .rs-grid { grid-template-columns: 1fr !important; } .rs-aside-preview { position: static !important; } }
              `}</style>
            </div>
          )}

          {/* 3-pane 레이아웃 — v3.19: 모바일 반응형 */}
          <div className="rs-grid" style={{display:"grid",gridTemplateColumns:"260px 1fr 300px",gap:14,alignItems:"start"}}>

            {/* ① 역할 리스트 */}
            <aside style={{display:"flex",flexDirection:"column",gap:8}}>
              {roles.map(r => (
                <div key={r.id} onClick={()=>{
                  if (changes.total > 0 && !confirm("저장하지 않은 변경 사항이 있습니다. 다른 역할로 이동하면 사라집니다. 계속하시겠습니까?")) return;
                  setSelectedRoleId(r.id);
                }} style={{
                  position:"relative",
                  border:"2px solid "+(selectedRoleId===r.id?"var(--blue-500,#0064FF)":"var(--grey-200,#E5E8EB)"),
                  borderRadius:12,padding:"12px 14px",cursor:"pointer",
                  background: selectedRoleId===r.id?"var(--blue-50,#E8F3FF)":"#fff",
                }}>
                  <div style={{display:"flex",alignItems:"center",gap:8,marginBottom:6}}>
                    <span style={{fontSize:24,lineHeight:1}}>{r.emoji}</span>
                    <span style={{fontSize:14,fontWeight:700,color:"var(--grey-900,#191F28)"}}>{r.name_ko}</span>
                    {r.is_system && <span title="시스템 역할 (삭제·편집 불가)" style={{fontSize:11,color:"var(--grey-500,#8B95A1)",position:"absolute",top:10,right:12}}>🔒</span>}
                  </div>
                  <div style={{fontSize:11,color:"var(--grey-600,#6B7684)"}}>
                    보유 직원 {r.user_count}명 · 권한 {r.perm_count}개
                  </div>
                </div>
              ))}
              <button onClick={()=>setShowNew(true)} disabled={roles.length >= 10} style={{padding:"12px",border:"2px dashed var(--grey-300,#D1D6DB)",borderRadius:12,background:"transparent",cursor:roles.length>=10?"not-allowed":"pointer",color:"var(--grey-600,#6B7684)",fontSize:13,opacity:roles.length>=10?0.4:1}}>
                + 새 역할 추가 ({roles.length}/10)
              </button>
            </aside>

            {/* ② 권한 매트릭스 */}
            <main style={{background:"#fff",border:"1px solid var(--grey-200,#E5E8EB)",borderRadius:12,overflow:"hidden"}}>
              {selectedRole?.is_system ? (
                <div style={{padding:"20px 24px",background:"var(--yellow-50,#FFF9E7)",color:"#7C5A00",fontSize:13,borderBottom:"1px solid var(--grey-100,#F2F4F6)",lineHeight:1.6}}>
                  🔒 <b>{selectedRole.name_ko}({selectedRole.key})</b>는 시스템 역할이라 <b>모든 권한이 자동 부여</b>되며 편집할 수 없습니다.<br/>
                  <span style={{fontSize:12,color:"#9C7400"}}>※ 사장님 지시(IMM-OWNER-001)에 따라 ytwoss@gmail.com의 권한은 어떤 경로로도 회수되지 않습니다.</span>
                </div>
              ) : null}
              {Object.entries(permsByCategory).map(([cat, items]) => {
                const checked = items.filter(p => draftPerms.includes(p.key)).length;
                return (
                  <section key={cat} style={{borderBottom:"1px solid var(--grey-100,#F2F4F6)"}}>
                    <div style={{padding:"12px 18px",background:"var(--grey-50,#F9FAFB)",display:"flex",alignItems:"center",gap:10}}>
                      <span style={{fontWeight:700,fontSize:14,flex:1}}>{cat}</span>
                      <span style={{
                        fontSize:11,fontWeight:700,padding:"3px 10px",borderRadius:10,
                        background: checked===items.length ? "#E6F7F0" : (checked===0 ? "#F2F4F6" : "#E8F3FF"),
                        color:      checked===items.length ? "#0E8F5F" : (checked===0 ? "#6B7684" : "#0064FF"),
                      }}>{checked} / {items.length}</span>
                    </div>
                    {items.map(p => {
                      const on = draftPerms.includes(p.key);
                      const disabled = selectedRole?.is_system;
                      return (
                        <div key={p.key} style={{
                          display:"flex",alignItems:"center",gap:10,padding:"12px 18px",
                          borderTop:"1px solid var(--grey-50,#F9FAFB)",
                          background: p.is_sensitive ? "#FFF7F7" : "#fff",
                          borderLeft: p.is_sensitive ? "4px solid #E31E26" : "4px solid transparent",
                          opacity: disabled ? 0.6 : 1,
                          minHeight: 56,
                        }}>
                          <div style={{flex:1,minWidth:0}}>
                            {p.is_sensitive && (
                              <span style={{display:"inline-block",padding:"2px 8px",background:"#FFE5E6",color:"#9A0E14",fontSize:10,fontWeight:800,borderRadius:4,marginRight:6,letterSpacing:0.3}}>
                                🔴 민감
                              </span>
                            )}
                            <span style={{fontSize:13,fontWeight:600,color:"var(--grey-800,#333D4B)"}}>{p.name_ko}</span>
                            {p.description && <div style={{fontSize:11,color:"var(--grey-500,#8B95A1)",marginTop:3}}>{p.description}</div>}
                          </div>
                          <label style={{position:"relative",width:48,height:28,cursor:disabled?"not-allowed":"pointer",flexShrink:0}}>
                            <input type="checkbox" checked={on} disabled={disabled} onChange={()=>!disabled && togglePerm(p.key)} style={{display:"none"}} />
                            <span style={{width:"100%",height:"100%",display:"block",background:on?(p.is_sensitive?"#E31E26":"#0064FF"):"#D1D6DB",borderRadius:14,position:"relative",transition:"background .2s"}}>
                              <span style={{position:"absolute",top:3,left:on?23:3,width:22,height:22,background:"#fff",borderRadius:"50%",transition:"left .2s",boxShadow:"0 1px 3px rgba(0,0,0,0.18)"}} />
                            </span>
                          </label>
                        </div>
                      );
                    })}
                  </section>
                );
              })}
            </main>

            {/* ③ 미리보기 — v3.19: sticky top 120 + 라벨 통일 */}
            <aside className="rs-aside-preview" style={{background:"#fff",border:"1px solid var(--grey-200,#E5E8EB)",borderRadius:12,padding:18,position:"sticky",top:120}}>
              <h3 style={{margin:"0 0 14px",fontSize:14,color:"var(--grey-700,#4E5968)"}}>🔍 미리보기 — {selectedRole?.name_ko || "—"}</h3>
              <div style={{marginBottom:16}}>
                <span style={{fontSize:11,color:"var(--grey-500,#8B95A1)",marginBottom:6,display:"block",fontWeight:600}}>🧑 보유 직원</span>
                <span style={{fontSize:13,color:"var(--grey-800,#333D4B)",fontWeight:600}}>{selectedRole?.user_count || 0}명</span>
              </div>
              <div style={{marginBottom:16}}>
                <span style={{fontSize:11,color:"var(--grey-500,#8B95A1)",marginBottom:6,display:"block",fontWeight:600}}>📦 카테고리별 보유 권한</span>
                {Object.entries(permsByCategory).map(([cat, items]) => {
                  const c = items.filter(p => draftPerms.includes(p.key)).length;
                  if (c === 0) return null;
                  return <div key={cat} style={{fontSize:12,padding:"3px 0",color:"var(--grey-700,#4E5968)"}}>· {cat}: <b>{c}/{items.length}</b></div>;
                })}
                {draftPerms.length === 0 && <div style={{fontSize:12,color:"var(--grey-500,#8B95A1)",fontStyle:"italic"}}>선택된 권한이 없습니다 — 로그인만 가능합니다.</div>}
              </div>
              <div>
                <span style={{fontSize:11,color:"#E31E26",marginBottom:6,display:"block",fontWeight:700}}>🔴 민감 권한 ({draftPerms.filter(k => perms.find(p=>p.key===k)?.is_sensitive).length}개)</span>
                <div style={{display:"flex",gap:4,flexWrap:"wrap"}}>
                  {draftPerms.filter(k => perms.find(p=>p.key===k)?.is_sensitive).map(k => {
                    const p = perms.find(x=>x.key===k);
                    return <span key={k} style={{fontSize:10,padding:"3px 8px",background:"#FFE5E6",color:"#9A0E14",borderRadius:4,fontWeight:700}}>{p?.name_ko}</span>;
                  })}
                  {draftPerms.filter(k => perms.find(p=>p.key===k)?.is_sensitive).length === 0 && <span style={{fontSize:12,color:"var(--grey-500,#8B95A1)"}}>없음</span>}
                </div>
              </div>
            </aside>
          </div>

          {/* 토스트 */}
          {toast && (
            <div style={{position:"fixed",bottom:24,right:24,padding:"12px 18px",background:"var(--green-500,#0E8F5F)",color:"#fff",borderRadius:8,fontSize:14,fontWeight:600,boxShadow:"0 4px 16px rgba(14,143,95,0.3)",zIndex:200}}>
              {toast}
            </div>
          )}
        </div>
      </main>

      <NewRoleModal
        open={showNew}
        onClose={()=>setShowNew(false)}
        onCreate={handleCreate}
        presets={roles.filter(r => !r.is_system)}
        totalRoles={roles.length}
      />
      <SensitiveConfirmModal
        open={!!sensitiveModal}
        role={sensitiveModal?.role}
        changes={sensitiveModal?.changes || []}
        onConfirm={()=>{ setSensitiveModal(null); doSave(); }}
        onCancel={()=>setSensitiveModal(null)}
      />
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
