/* ============================================================
   CDE Access Control — Phân quyền (RBAC × ABAC, deny-by-default)
   Viết lại theo "Access Control (standalone)" + "Backend Brief - Phân quyền CDE".
   UI-only: engine evaluate()/resolveZoneRole() chạy client trên data demo;
   tab Thành viên dùng thành viên THẬT khi đăng nhập live (fallback demo).
   Xuất: window.AccessControl
   ============================================================ */
(function () {
  const { useState, useEffect } = React;
  const Icon = window.Icon;

  /* ========================================================
     1) Data model + can() engine
     ======================================================== */

  /* ---- 6 cấp quyền (ACC) ---- */
  const LEVELS = [
    { n: 1, key: "view",          short: "Chỉ xem",        label: "View only",            desc: "Xem tài liệu · markup riêng tư · tạo issue", cls: "lv1" },
    { n: 2, key: "view_download", short: "Xem + Tải",       label: "View + Download",      desc: "Như cấp 1 + tải bản sao về máy", cls: "lv2" },
    { n: 3, key: "upload",        short: "Chỉ upload",      label: "Upload only",          desc: "Hòm thư mù — đẩy tệp vào nhưng KHÔNG thấy nội dung folder", cls: "lv3" },
    { n: 4, key: "vdu",           short: "Xem+Tải+Upload",  label: "View+Download+Upload", desc: "Chia sẻ tệp của mình + xem mọi tệp khác", cls: "lv4" },
    { n: 5, key: "edit",          short: "+ Sửa",           label: "+ Edit",               desc: "Như cấp 4 + sửa mọi tài liệu + publish markup", cls: "lv5" },
    { n: 6, key: "control",       short: "Toàn quyền",      label: "Folder Control",       desc: "Quản trị: tạo title block, thêm thành viên, QUẢN LÝ QUYỀN", cls: "lv6" },
  ];
  const levelMeta = (n) => LEVELS.find(l => l.n === n) || { n: 0, short: "Không có", label: "No access", cls: "lv0", desc: "Chưa được cấp quyền nào" };

  /* ---- danh mục quyền nguyên tử (RBAC) ---- */
  const PERM_GROUPS = [
    { group: "Tài liệu", perms: [
      { key: "document.view", label: "Xem tài liệu", req: 1 }, { key: "document.download", label: "Tải về", req: 2 },
      { key: "document.upload", label: "Upload", req: 3 }, { key: "document.edit", label: "Sửa tài liệu", req: 5 },
      { key: "document.delete", label: "Xoá tài liệu", req: 6 }, { key: "document.change_state", label: "Đổi trạng thái", req: 99 } ] },
    { group: "Markup / Issue", perms: [
      { key: "markup.private", label: "Markup riêng tư", req: 1 }, { key: "markup.publish", label: "Publish markup", req: 5 },
      { key: "issue.create", label: "Tạo issue", req: 1 }, { key: "issue.manage", label: "Quản lý issue", req: 5 } ] },
    { group: "Thư mục", perms: [
      { key: "folder.create", label: "Tạo thư mục", req: 6 }, { key: "folder.control", label: "Folder control", req: 6 },
      { key: "folder.manage_permissions", label: "Quản lý phân quyền", req: 6 } ] },
    { group: "Phê duyệt & RFI", perms: [
      { key: "approval.initiate", label: "Khởi tạo phê duyệt", req: 3 }, { key: "approval.decide", label: "Quyết định cổng", req: 1 },
      { key: "approval.configure", label: "Cấu hình workflow", req: 1 }, { key: "rfi.create", label: "Tạo RFI", req: 1 },
      { key: "rfi.respond", label: "Trả lời RFI", req: 1 }, { key: "rfi.manage", label: "Quản lý RFI", req: 1 } ] },
    { group: "Dự án & Audit", perms: [
      { key: "project.settings", label: "Cấu hình dự án", req: 1 }, { key: "project.manage_members", label: "Quản lý thành viên", req: 1 },
      { key: "project.manage_roles", label: "Quản lý vai trò", req: 1 }, { key: "audit.view", label: "Xem audit", req: 1 }, { key: "audit.export", label: "Xuất audit", req: 1 } ] },
  ];
  const ALL_PERMS = PERM_GROUPS.flatMap(g => g.perms);
  const permMeta = (k) => ALL_PERMS.find(p => p.key === k);

  /* ---- vai trò (ISO 19650) → tập quyền ---- */
  const ROLES = {
    task_member:  { key: "task_member",  name: "Task Team Member", iso: "Appointed party", c: "#60a5fa", system: true,
      perms: ["document.view","document.download","document.upload","document.edit","markup.private","markup.publish","issue.create","issue.manage","approval.initiate","rfi.create","rfi.respond","audit.view"] },
    task_manager: { key: "task_manager", name: "Task Team Manager", iso: "Appointed party", c: "#14b8a6", system: true,
      perms: ["document.view","document.download","document.upload","document.edit","document.delete","markup.private","markup.publish","issue.create","issue.manage","approval.initiate","approval.decide","rfi.create","rfi.respond","rfi.manage","audit.view","audit.export"] },
    lead:         { key: "lead",         name: "Lead Appointed", iso: "Lead appointed party", c: "#a78bfa", system: true,
      perms: ["document.view","document.download","document.upload","document.edit","document.delete","markup.private","markup.publish","issue.create","issue.manage","folder.create","folder.control","folder.manage_permissions","approval.initiate","approval.decide","approval.configure","rfi.create","rfi.respond","rfi.manage","project.settings","project.manage_members","project.manage_roles","audit.view","audit.export"] },
    appointing:   { key: "appointing",   name: "Appointing Party", iso: "Appointing party", c: "#f59e0b", system: true,
      perms: ["document.view","document.download","approval.decide","rfi.respond","project.manage_members","audit.view","audit.export"] },
    info_manager: { key: "info_manager", name: "Information Manager", iso: "Information Manager", c: "#0d9488", system: true,
      perms: ["document.view","folder.create","folder.control","folder.manage_permissions","approval.initiate","approval.configure","project.settings","project.manage_members","project.manage_roles","audit.view","audit.export"] },
    viewer:       { key: "viewer",       name: "Viewer", iso: "—", c: "#8aa0a6", system: true,
      perms: ["document.view","issue.create","markup.private"] },
  };
  const ROLE_LIST = Object.values(ROLES);

  /* ---- công ty (party) ---- */
  const COMPANIES = {
    ventus: { id: "ventus", name: "Ventus BIM", party: "lead_appointed" },
    songda: { id: "songda", name: "Cty Sông Đà", party: "appointed" },
    mep_co: { id: "mep_co", name: "MEP Tech Co", party: "appointed" },
    qlda:   { id: "qlda",   name: "Ban QLDA Metro", party: "appointing" },
    tvgs:   { id: "tvgs",   name: "TVGS ABC", party: "appointed" },
  };
  const PARTY = { appointing: ["Appointing", "#f59e0b"], lead_appointed: ["Lead appointed", "#a78bfa"], appointed: ["Appointed", "#60a5fa"] };

  /* ---- task team (sở hữu WIP) ---- */
  const TEAMS = {
    t_str: { id: "t_str", name: "Tổ Kết cấu", disc: "str", company: "ventus" },
    t_mep: { id: "t_mep", name: "Tổ MEP", disc: "mep", company: "mep_co" },
    t_inf: { id: "t_inf", name: "Tổ Hạ tầng", disc: "inf", company: "songda" },
  };

  /* ---- người dùng (demo) ---- */
  const USERS = {
    duc:  { id: "duc",  name: "Trần Đức",    init: "TĐ", company: "ventus", tier: "project_admin", roles: ["info_manager"], teams: [], g: "#1ec7b6,#0d9488" },
    hong: { id: "hong", name: "Lê Hồng",     init: "LH", company: "ventus", tier: "member", roles: ["task_manager"], teams: ["t_str"], g: "#60a5fa,#2563eb" },
    mai:  { id: "mai",  name: "Mai Chi",     init: "MC", company: "ventus", tier: "member", roles: ["task_member"], teams: ["t_str"], g: "#818cf8,#4f46e5" },
    an:   { id: "an",   name: "Bùi An",      init: "BA", company: "mep_co", tier: "member", roles: ["task_member"], teams: ["t_mep"], g: "#f6b54e,#e0820a" },
    hai:  { id: "hai",  name: "Đỗ Hải",      init: "ĐH", company: "songda", tier: "member", roles: ["task_member"], teams: ["t_inf"], g: "#2dd4bf,#0891b2" },
    tuan: { id: "tuan", name: "Phạm Tuấn",   init: "PT", company: "ventus", tier: "project_admin", roles: ["lead"], teams: [], g: "#a78bfa,#7c3aed" },
    son:  { id: "son",  name: "Đặng V. Sơn", init: "ĐS", company: "qlda",   tier: "member", roles: ["appointing"], teams: [], g: "#334155,#0f1f24" },
    lan:  { id: "lan",  name: "Vũ Lan",      init: "VL", company: "ventus", tier: "member", roles: ["viewer"], teams: [], g: "#fb7185,#e11d48" },
  };
  const USER_LIST = Object.values(USERS);

  /* ---- cây vùng (zone = Bộ môn × Trạng thái, materialized) ---- */
  const FOLDERS = [
    { id: "root", parent: null, name: "Project Files", state: null },
    { id: "str",  parent: "root", name: "Kết cấu (STR)", state: null, disc: "str" },
    { id: "str_wip", parent: "str", name: "01 · WIP", state: "WIP", owner: "t_str", disc: "str" },
    { id: "str_shared", parent: "str", name: "02 · Shared", state: "Shared", disc: "str" },
    { id: "str_pub", parent: "str", name: "03 · Published", state: "Published", disc: "str" },
    { id: "mep",  parent: "root", name: "MEP", state: null, disc: "mep" },
    { id: "mep_wip", parent: "mep", name: "01 · WIP", state: "WIP", owner: "t_mep", disc: "mep" },
    { id: "mep_shared", parent: "mep", name: "02 · Shared", state: "Shared", disc: "mep" },
    { id: "inf",  parent: "root", name: "Hạ tầng (INF)", state: null, disc: "inf" },
    { id: "inf_wip", parent: "inf", name: "01 · WIP", state: "WIP", owner: "t_inf", disc: "inf" },
    { id: "inf_shared", parent: "inf", name: "02 · Shared", state: "Shared", disc: "inf" },
    { id: "inf_pub", parent: "inf", name: "03 · Published", state: "Published", disc: "inf" },
  ];
  const folderById = (id) => FOLDERS.find(f => f.id === id);
  const folderChildren = (id) => FOLDERS.filter(f => f.parent === id);
  function folderChain(id) { const c = []; let n = folderById(id); while (n) { c.unshift(n); n = n.parent ? folderById(n.parent) : null; } return c; }

  /* ---- folder_permissions grants (subjType: user|role|company) ---- */
  let GID = 1;
  const GRANTS = [
    { id: GID++, folder: "root", subjType: "role", subj: "lead", level: 6 },
    { id: GID++, folder: "root", subjType: "role", subj: "info_manager", level: 6 },
    { id: GID++, folder: "root", subjType: "role", subj: "viewer", level: 1 },
    { id: GID++, folder: "root", subjType: "company", subj: "qlda", level: 2 },
    { id: GID++, folder: "str", subjType: "company", subj: "ventus", level: 4 },
    { id: GID++, folder: "str_wip", subjType: "role", subj: "task_manager", level: 6 },
    { id: GID++, folder: "str_wip", subjType: "role", subj: "task_member", level: 5 },
    { id: GID++, folder: "str_shared", subjType: "company", subj: "ventus", level: 5 },
    { id: GID++, folder: "mep_wip", subjType: "company", subj: "mep_co", level: 5 },
    { id: GID++, folder: "mep", subjType: "user", subj: "duc", level: 4 },
    { id: GID++, folder: "inf_wip", subjType: "company", subj: "songda", level: 5 },
  ];

  /* ---- tài liệu demo (cho Inspector) ---- */
  const DOCUMENTS = [
    { id: "d1", folder: "str_wip", title: "STR-Beam-Detail-B04.dwg", state: "WIP", owner: "t_str", classification: "internal" },
    { id: "d2", folder: "str_shared", title: "STR-Frame-Station-C.nwc", state: "Shared", owner: "t_str", classification: "internal" },
    { id: "d3", folder: "str_pub", title: "STR-Column-Schedule.pdf", state: "Published", owner: "t_str", classification: "public" },
    { id: "d4", folder: "mep_wip", title: "MEP-HVAC-B2.dwg", state: "WIP", owner: "t_mep", classification: "confidential" },
    { id: "d5", folder: "inf_wip", title: "INF-Foundation-D.ifc", state: "WIP", owner: "t_inf", classification: "restricted" },
    { id: "d6", folder: "inf_shared", title: "INF-Drainage-L3.ifc", state: "Shared", owner: "t_inf", classification: "internal" },
  ];
  const docById = (id) => DOCUMENTS.find(d => d.id === id);

  const CLASS_RANK = { public: 0, internal: 1, confidential: 2, restricted: 3 };
  const CLASS_META = { public: ["Public", "#10b981"], internal: ["Internal", "#60a5fa"], confidential: ["Confidential", "#f59e0b"], restricted: ["Restricted", "#ef4444"] };
  const ROLE_CLEARANCE = { lead: 3, info_manager: 3, task_manager: 2, task_member: 2, appointing: 1, viewer: 1 };

  const ACTIONS = [
    { key: "document.view", label: "Xem tài liệu" },
    { key: "document.download", label: "Tải về" },
    { key: "document.upload", label: "Upload" },
    { key: "document.edit", label: "Sửa tài liệu" },
    { key: "folder.manage_permissions", label: "Quản lý phân quyền" },
  ];

  const DISC_COL  = window.DISC_COL  || {};   // nguồn động từ sys-fields.jsx
  const DISC_NAME = window.DISC_NAME || {};
  const DISCC = (d) => DISC_COL[d] || "var(--ink-3)";

  /* ---- can(user, action, document) — 3 lớp, deny-by-default, trả trace ---- */
  function userPerms(u) { const s = new Set(); u.roles.forEach(rk => (ROLES[rk] ? ROLES[rk].perms : []).forEach(p => s.add(p))); return s; }

  function effectiveFolderLevel(u, folderId) {
    let node = folderById(folderId);
    const roleSet = new Set(u.roles);
    while (node) {
      const here = GRANTS.filter(g => g.folder === node.id && (
        (g.subjType === "user" && g.subj === u.id) ||
        (g.subjType === "role" && roleSet.has(g.subj)) ||
        (g.subjType === "company" && g.subj === u.company)
      ));
      if (here.length) {
        const best = here.reduce((a, b) => b.level > a.level ? b : a);
        return { level: best.level, source: node, grant: best, all: here };
      }
      node = node.parent ? folderById(node.parent) : null;
    }
    return { level: 0, source: null, grant: null, all: [] };
  }

  function inTeam(u, teamId) { return teamId && u.teams.includes(teamId); }

  function finish(steps, allow, reason) { return { steps, allow, reason }; }

  function evaluate(u, actionKey, doc, classGate) {
    const folder = folderById(doc.folder);
    const perms = userPerms(u);
    const pm = permMeta(actionKey);
    const req = pm ? pm.req : 1;
    const fl = effectiveFolderLevel(u, doc.folder);
    const steps = [];

    // Lớp ①
    const member = !!u && !!u.tier;
    steps.push({ layer: "①", title: "Thành viên dự án (Membership)",
      pass: member, q: "Người này có thuộc dự án & đang hoạt động?",
      detail: member ? `${u.name} · ${COMPANIES[u.company] ? COMPANIES[u.company].name : "—"} · tier ${u.tier}` : "Không thuộc dự án" });
    if (!member) return finish(steps, false, "Không phải thành viên dự án");

    // Lớp ②
    const hasPerm = perms.has(actionKey);
    steps.push({ layer: "②", title: "Vai trò & quyền (RBAC)",
      pass: hasPerm, q: `Vai trò có chứa quyền "${actionKey}"?`,
      detail: hasPerm ? `Có — qua vai trò ${u.roles.map(r => ROLES[r].name).join(", ")}` : `Thiếu quyền · vai trò ${u.roles.map(r => ROLES[r].name).join(", ")}` });
    if (!hasPerm) return finish(steps, false, `Vai trò không có quyền ${actionKey}`);

    // Lớp ③ cấp quyền tài nguyên
    const okLevel = fl.level >= req;
    steps.push({ layer: "③", title: "Quyền tài nguyên trên vùng (kế thừa + higher-wins)",
      pass: okLevel, q: `Cấp quyền vùng ≥ ${req} (${levelMeta(req).short})?`,
      detail: fl.level ? `Cấp ${fl.level} · ${levelMeta(fl.level).label}${fl.source.id !== folder.id ? ` · kế thừa từ "${fl.source.name}"` : ""} · qua ${fl.grant.subjType === "role" ? ROLES[fl.grant.subj].name : fl.grant.subjType === "company" ? COMPANIES[fl.grant.subj].name : u.name}` : "Không có grant nào trên cây vùng" });
    if (!okLevel) return finish(steps, false, fl.level ? `Cấp vùng ${fl.level} < yêu cầu ${req}` : "Không có quyền vùng");

    // Cổng WIP
    const wip = doc.state === "WIP";
    const ownTeam = wip ? inTeam(u, doc.owner) : true;
    steps.push({ layer: "ABAC", title: "Cổng trạng thái (WIP-gate)",
      pass: ownTeam, skip: !wip, q: wip ? "File đang WIP — bạn có thuộc task team sở hữu?" : "File không ở WIP — cổng mở",
      detail: !wip ? `Trạng thái ${doc.state} · không chặn` : ownTeam ? `Thuộc ${TEAMS[doc.owner].name}` : `Chỉ ${TEAMS[doc.owner].name} thấy WIP này` });
    if (wip && !ownTeam) return finish(steps, false, `WIP-gate: chỉ ${TEAMS[doc.owner].name} sở hữu mới truy cập`);

    // Cổng phân loại (tuỳ chọn)
    if (classGate) {
      const clr = Math.max(...u.roles.map(r => ROLE_CLEARANCE[r] != null ? ROLE_CLEARANCE[r] : 0));
      const need = CLASS_RANK[doc.classification];
      const okClass = clr >= need;
      steps.push({ layer: "ISO-5", title: "Cổng phân loại an ninh (ISO 19650-5)",
        pass: okClass, q: `Clearance ≥ ${CLASS_META[doc.classification][0]}?`,
        detail: okClass ? `Clearance cấp ${clr} ≥ ${need}` : `Clearance ${clr} < ${need} (${CLASS_META[doc.classification][0]})` });
      if (!okClass) return finish(steps, false, `Không đủ clearance cho ${CLASS_META[doc.classification][0]}`);
    }

    return finish(steps, true, "Đủ cả ba lớp — cho phép");
  }

  /* ========================================================
     2) Bits chung
     ======================================================== */
  function initials(name) {
    const p = String(name || "?").trim().split(/\s+/);
    return (p.length === 1 ? p[0].slice(0, 2) : p[0][0] + p[p.length - 1][0]).toUpperCase();
  }
  function Av({ id, user, size = 28 }) {
    const u = user || USERS[id];
    if (!u) return null;
    const init = u.init || initials(u.name);
    const g = u.g || "#334155,#0f1f24";
    return <span title={u.name} style={{ width: size, height: size, borderRadius: Math.round(size * 0.28), flex: "none", display: "grid", placeItems: "center", color: "#fff", fontWeight: 800, fontSize: size * 0.36, background: `linear-gradient(150deg, ${g})` }}>{init}</span>;
  }
  function StateTag({ state }) {
    if (!state) return null;
    const map = { WIP: "st-wip", Shared: "st-shared", Published: "st-pub", Archived: "st-arch" };
    return <span className={`state-tag ${map[state] || ""}`}>{String(state).toUpperCase()}</span>;
  }

  /* ========================================================
     3) Tab — Ma trận Vùng × Vai trò
     ======================================================== */
  /* gán role rõ ràng theo vùng: folder → { userId: roleKey | null(chặn) } */
  const ZONE_ROLES = {
    root: { duc: "info_manager", tuan: "lead", lan: "viewer", son: "appointing" },
    str:  { hong: "task_manager", mai: "task_member" },
    str_wip: { hong: "task_manager", mai: "task_member", an: null },
    str_shared: { son: "viewer", an: "viewer" },
    str_pub: {},
    mep: { an: "task_member", duc: "lead" },
    mep_wip: { an: "task_manager", hong: null },
    mep_shared: { hong: "task_member" },
    inf: { hai: "task_member" },
    inf_wip: { hai: "task_manager" },
    inf_shared: { son: "viewer" },
    inf_pub: {},
  };
  function resolveZoneRole(folderId, userId) {
    let node = folderById(folderId);
    let depth = 0;
    while (node) {
      const map = ZONE_ROLES[node.id] || {};
      if (Object.prototype.hasOwnProperty.call(map, userId)) {
        const rk = map[userId];
        if (rk === null) return { roleKey: null, mode: "blocked", source: node };
        return { roleKey: rk, mode: depth === 0 ? "explicit" : "inherited", source: node };
      }
      node = node.parent ? folderById(node.parent) : null;
      depth++;
    }
    return { roleKey: null, mode: "none", source: null };
  }

  function MemberStack({ users, onAdd }) {
    if (!users.length) return <span className="cell-add" onClick={onAdd} title="Gán người vào vai trò này">+</span>;
    const shown = users.slice(0, 4);
    return (
      <div className="mem-stack" onClick={onAdd}>
        <div className="stack-avs">
          {shown.map((m, i) => (
            <span key={m.id} className={`stack-av ${m.mode === "inherited" ? "inh" : ""}`} style={{ marginLeft: i ? -8 : 0, zIndex: 10 - i }} title={`${USERS[m.id].name}${m.mode === "inherited" ? " · kế thừa" : ""}`}>
              <Av id={m.id} size={28} />
            </span>
          ))}
          {users.length > 4 && <span className="stack-more" style={{ marginLeft: -8 }}>+{users.length - 4}</span>}
        </div>
      </div>
    );
  }

  function ZoneMatrix() {
    const [scopeDisc, setScopeDisc] = useState("all");
    const folders = FOLDERS.filter(f => f.id !== "root" && (scopeDisc === "all" || f.disc === scopeDisc || !f.disc));
    const depthOf = (f) => folderChain(f.id).length - 1;

    const membersByRole = (folderId) => {
      const map = {}; const blocked = [];
      USER_LIST.forEach(u => {
        const r = resolveZoneRole(folderId, u.id);
        if (r.mode === "blocked") { blocked.push(u.id); return; }
        if (r.mode === "none" || !r.roleKey) return;
        (map[r.roleKey] = map[r.roleKey] || []).push({ id: u.id, mode: r.mode });
      });
      return { map, blocked };
    };

    return (
      <div className="content">
        <div style={{ display: "flex", alignItems: "flex-end", justifyContent: "space-between", marginBottom: 18, gap: 16, flexWrap: "wrap" }}>
          <div>
            <div style={{ fontSize: 16, fontWeight: 800 }}>Ma trận Vùng × Vai trò</div>
            <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 3, fontWeight: 500 }}>Mỗi cột là một vai trò · ô chứa thành viên mang vai trò đó tại vùng — gán nhiều người cùng lúc</div>
          </div>
          <div className="seg-wrap">
            {[["all", "Tất cả"], ["str", "Kết cấu"], ["mep", "MEP"], ["inf", "Hạ tầng"]].map(([k, l]) => (
              <button key={k} className={scopeDisc === k ? "on" : ""} onClick={() => setScopeDisc(k)}>{k !== "all" && <span style={{ width: 8, height: 8, borderRadius: 99, background: DISC_COL[k] }} />}{l}</button>
            ))}
          </div>
        </div>

        <div className="card" style={{ overflow: "auto" }}>
          <table className="rolemtx">
            <thead>
              <tr>
                <th className="zone-head">VÙNG TÀI LIỆU</th>
                {ROLE_LIST.map(r => (
                  <th key={r.key} className="rcol-head">
                    <span className="rcol-dot" style={{ background: r.c }} />
                    <span style={{ color: r.c }}>{r.name}</span>
                  </th>
                ))}
              </tr>
            </thead>
            <tbody>
              {folders.map(f => {
                const d = depthOf(f);
                const stateColor = f.state === "WIP" ? "var(--warn)" : f.state === "Shared" ? "var(--str)" : f.state === "Published" ? "var(--ok)" : null;
                const { map, blocked } = membersByRole(f.id);
                return (
                  <tr key={f.id} className={d <= 1 ? "zrow-top" : ""}>
                    <td className="zonename">
                      <div style={{ display: "flex", alignItems: "center", gap: 8, paddingLeft: 4 + (d - 1) * 18 }}>
                        {f.disc && d <= 1 ? <span style={{ width: 9, height: 9, borderRadius: 3, background: DISC_COL[f.disc], flex: "none" }} /> : <Icon.folder size={14} color="var(--ink-4)" style={{ flex: "none" }} />}
                        <span style={{ fontWeight: d <= 1 ? 800 : 600, fontSize: d <= 1 ? 13.5 : 13, whiteSpace: "nowrap" }}>{f.name}</span>
                        {f.state && <span className="state-tag" style={{ color: stateColor, borderColor: `${stateColor}55` }}>{f.state}</span>}
                        {f.owner && <span title={`WIP của ${TEAMS[f.owner].name}`} style={{ color: "var(--ink-4)", display: "flex" }}><Icon.lock size={11} /></span>}
                        {blocked.length > 0 && <span className="blocked-pill" title={`Chặn rõ ràng: ${blocked.map(b => USERS[b].name).join(", ")}`}><Icon.ban size={11} /> {blocked.length}</span>}
                      </div>
                    </td>
                    {ROLE_LIST.map(r => (
                      <td key={r.key} className="rcell"><MemberStack users={map[r.key] || []} onAdd={() => {}} /></td>
                    ))}
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>

        <div style={{ marginTop: 14, display: "flex", gap: 20, flexWrap: "wrap", alignItems: "center", fontSize: 11.5, fontWeight: 600, color: "var(--ink-3)" }}>
          <span style={{ display: "inline-flex", alignItems: "center", gap: 6 }}><span className="stack-av inh" style={{ width: 18, height: 18, position: "static", background: "var(--ink-4)" }} /> avatar mờ = kế thừa từ vùng cha</span>
          <span style={{ display: "inline-flex", alignItems: "center", gap: 6, color: "var(--danger)" }}><Icon.ban size={13} /> số người bị chặn rõ ràng tại vùng</span>
          <span style={{ marginLeft: "auto", display: "inline-flex", alignItems: "center", gap: 7 }}><Icon.shield size={13} color="var(--accent)" /> Role định nghĩa ở tab <b style={{ color: "var(--ink-2)" }}>Vai trò & Quyền</b> · bấm ô để gán người</span>
        </div>
      </div>
    );
  }

  /* ========================================================
     4) Tab — Vai trò & Quyền (RBAC matrix)
     ======================================================== */
  function RoleManager() {
    const [grants, setGrants] = useState(() => { const m = {}; ROLE_LIST.forEach(r => { m[r.key] = new Set(r.perms); }); return m; });
    const toggle = (roleKey, permKey) => setGrants(g => {
      const n = { ...g }; const s = new Set(n[roleKey]); s.has(permKey) ? s.delete(permKey) : s.add(permKey); n[roleKey] = s; return n;
    });

    return (
      <div className="content">
        <div style={{ display: "flex", alignItems: "flex-end", justifyContent: "space-between", marginBottom: 18, gap: 16, flexWrap: "wrap" }}>
          <div>
            <div style={{ fontSize: 16, fontWeight: 800 }}>Vai trò & Quyền (RBAC)</div>
            <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 3, fontWeight: 500 }}>Ma trận vai trò × quyền nguyên tử · tick để cấu hình, không cần code</div>
          </div>
          <button className="btn sm primary"><Icon.plus size={14} /> Tạo vai trò</button>
        </div>

        <div className="card" style={{ overflow: "auto" }}>
          <table className="mtx">
            <thead>
              <tr>
                <th style={{ textAlign: "left", padding: "0 14px 0", minWidth: 230, background: "var(--surface)", position: "sticky", left: 0, zIndex: 3 }}>
                  <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: ".06em", color: "var(--ink-4)", paddingBottom: 10 }}>QUYỀN \ VAI TRÒ</div>
                </th>
                {ROLE_LIST.map(r => (
                  <th key={r.key} className="rolehead" style={{ minWidth: 96 }}>
                    <div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 7 }}>
                      <span style={{ width: 30, height: 30, borderRadius: 9, background: `${r.c}1f`, color: r.c, display: "grid", placeItems: "center" }}><Icon.shield size={16} /></span>
                      <div style={{ fontSize: 11.5, fontWeight: 800, lineHeight: 1.2, maxWidth: 88 }}>{r.name}</div>
                      {r.system && <span style={{ fontSize: 9, fontWeight: 800, color: "var(--ink-4)", letterSpacing: ".04em" }}>HỆ THỐNG</span>}
                    </div>
                  </th>
                ))}
              </tr>
            </thead>
            <tbody>
              {PERM_GROUPS.map(grp => (
                <React.Fragment key={grp.group}>
                  <tr className="grouprow"><td colSpan={ROLE_LIST.length + 1} style={{ position: "sticky", left: 0 }}>{grp.group}</td></tr>
                  {grp.perms.map(p => (
                    <tr key={p.key}>
                      <td className="permname" style={{ position: "sticky", left: 0, background: "var(--surface)", zIndex: 1 }}>
                        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                          {p.label}
                          <span className="mono" style={{ fontSize: 10.5, color: "var(--ink-4)" }}>{p.key}</span>
                          {p.req === 99 && <span title="Chỉ engine workflow dùng" style={{ color: "var(--warn)", display: "flex" }}><Icon.lock size={12} /></span>}
                        </div>
                      </td>
                      {ROLE_LIST.map(r => {
                        const on = grants[r.key].has(p.key);
                        return <td key={r.key} className="permcell"><span className={`cellbox ${on ? "on" : "off"}`} onClick={() => toggle(r.key, p.key)}>{on && <Icon.check size={14} />}</span></td>;
                      })}
                    </tr>
                  ))}
                </React.Fragment>
              ))}
            </tbody>
          </table>
        </div>
        <div style={{ marginTop: 14, fontSize: 11.5, color: "var(--ink-3)", fontWeight: 600, display: "flex", alignItems: "center", gap: 7 }}>
          <Icon.lock size={13} color="var(--warn)" /> <span><b className="mono">document.change_state</b> không cấp cho vai trò thường — chỉ engine phê duyệt (RPC) đổi trạng thái sau khi cổng đóng.</span>
        </div>
      </div>
    );
  }

  /* ========================================================
     5) Tab — Thành viên (live nếu có, fallback demo)
     ======================================================== */
  const TIER_META = { org_admin: ["Org admin", "tier-org"], project_admin: ["Project admin", "tier-padmin"], member: ["Member", "tier-member"], admin: ["Quản trị", "tier-padmin"], editor: ["Biên tập", "tier-member"], viewer: ["Xem", "tier-member"] };

  function MembersDemo() {
    const [q, setQ] = useState("");
    const rows = USER_LIST.filter(u => !q || u.name.toLowerCase().includes(q.toLowerCase()));
    return (
      <div className="content">
        <div style={{ display: "flex", alignItems: "flex-end", justifyContent: "space-between", marginBottom: 18, gap: 16, flexWrap: "wrap" }}>
          <div>
            <div style={{ fontSize: 16, fontWeight: 800 }}>Thành viên dự án</div>
            <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 3, fontWeight: 500 }}>{USER_LIST.length} người · {Object.keys(COMPANIES).length} đơn vị · lớp ① multi-tenant isolation · <i>demo</i></div>
          </div>
          <div style={{ display: "flex", gap: 10 }}>
            <div className="search" style={{ width: 220, height: 38 }}><Icon.search size={15} /><input value={q} onChange={e => setQ(e.target.value)} placeholder="Tìm thành viên…" style={{ flex: 1, border: 0, background: "transparent", outline: "none", font: "inherit", fontSize: 13, color: "var(--ink)", minWidth: 0 }} /></div>
            <button className="btn sm primary"><Icon.plus size={14} /> Mời thành viên</button>
          </div>
        </div>
        <div className="card" style={{ padding: "8px 10px", overflowX: "auto" }}>
          <table className="tbl" style={{ width: "100%", minWidth: 820 }}>
            <thead><tr><th style={{ paddingLeft: 14 }}>Thành viên</th><th>Công ty (bên)</th><th>Access tier</th><th>Vai trò</th><th>Task team</th><th></th></tr></thead>
            <tbody>
              {rows.map(u => {
                const co = COMPANIES[u.company]; const [pl, pc] = PARTY[co.party]; const [tl, tcls] = TIER_META[u.tier];
                return (
                  <tr key={u.id}>
                    <td style={{ paddingLeft: 14, display: "flex", alignItems: "center", gap: 11 }}>
                      <Av id={u.id} size={36} />
                      <div className="t-strong">{u.name}</div>
                    </td>
                    <td><div style={{ display: "flex", alignItems: "center", gap: 8 }}>{co.name}<span className="party-chip" style={{ background: `${pc}1f`, color: pc }}>{pl}</span></div></td>
                    <td><span className={`tier-chip ${tcls}`}>{tl}</span></td>
                    <td><div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>{u.roles.map(r => <span key={r} style={{ display: "inline-flex", alignItems: "center", gap: 5, fontSize: 11.5, fontWeight: 700, color: ROLES[r].c, background: `${ROLES[r].c}16`, borderRadius: 6, padding: "2px 8px" }}><Icon.shield size={11} />{ROLES[r].name}</span>)}</div></td>
                    <td>{u.teams.length ? u.teams.map(t => <span key={t} className="chip idle" style={{ height: 22, fontSize: 11 }}>{TEAMS[t].name}</span>) : <span style={{ color: "var(--ink-4)", fontWeight: 700 }}>—</span>}</td>
                    <td style={{ width: 40 }}><button className="icon-btn" style={{ width: 32, height: 32, borderRadius: 8 }}><Icon.dots size={15} /></button></td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      </div>
    );
  }

  function MembersLive({ members, roles }) {
    const [q, setQ] = useState("");
    const roleName = (rid) => { const r = roles.find(x => x.id === rid); return r ? r.name : "—"; };
    const rows = members.filter(m => !q || (m.name || "").toLowerCase().includes(q.toLowerCase()));
    return (
      <div className="content">
        <div style={{ display: "flex", alignItems: "flex-end", justifyContent: "space-between", marginBottom: 18, gap: 16, flexWrap: "wrap" }}>
          <div>
            <div style={{ fontSize: 16, fontWeight: 800 }}>Thành viên dự án</div>
            <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 3, fontWeight: 500 }}>{members.length} người · {roles.length} vai trò · dữ liệu THẬT của dự án · lớp ① multi-tenant isolation</div>
          </div>
          <div className="search" style={{ width: 220, height: 38 }}><Icon.search size={15} /><input value={q} onChange={e => setQ(e.target.value)} placeholder="Tìm thành viên…" style={{ flex: 1, border: 0, background: "transparent", outline: "none", font: "inherit", fontSize: 13, color: "var(--ink)", minWidth: 0 }} /></div>
        </div>
        <div className="card" style={{ padding: "8px 10px", overflowX: "auto" }}>
          <table className="tbl" style={{ width: "100%", minWidth: 720 }}>
            <thead><tr><th style={{ paddingLeft: 14 }}>Thành viên</th><th>Tài khoản</th><th>Vai trò (RBAC)</th></tr></thead>
            <tbody>
              {rows.map(m => {
                const tm = TIER_META[m.accountRole] || ["—", "tier-member"];
                return (
                  <tr key={m.id}>
                    <td style={{ paddingLeft: 14, display: "flex", alignItems: "center", gap: 11 }}>
                      <Av user={{ name: m.name }} size={36} />
                      <div className="t-strong">{m.name}</div>
                    </td>
                    <td><span className={`tier-chip ${tm[1]}`}>{tm[0]}</span></td>
                    <td><div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>{m.roleIds.length ? m.roleIds.map(rid => <span key={rid} style={{ display: "inline-flex", alignItems: "center", gap: 5, fontSize: 11.5, fontWeight: 700, color: "var(--accent)", background: "var(--accent-soft)", borderRadius: 6, padding: "2px 8px" }}><Icon.shield size={11} />{roleName(rid)}</span>) : <span style={{ color: "var(--ink-4)", fontWeight: 700 }}>chưa gán</span>}</div></td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
        <div style={{ marginTop: 14, fontSize: 11.5, color: "var(--ink-3)", fontWeight: 600, display: "flex", alignItems: "center", gap: 7 }}>
          <Icon.shield size={13} color="var(--accent)" /> Bộ môn × Trạng thái là “vùng”; ma trận Vùng × Vai trò &amp; engine đang minh hoạ trên dữ liệu mẫu — nối vùng thật sau theo brief.
        </div>
      </div>
    );
  }

  /* ========================================================
     6) Tab — Kiểm tra quyền (Inspector)
     ======================================================== */
  function Inspector() {
    const [userId, setUserId] = useState("an");
    const [docId, setDocId] = useState("d1");
    const [action, setAction] = useState("document.view");
    const [classGate, setClassGate] = useState(false);

    const u = USERS[userId]; const doc = docById(docId);
    const res = evaluate(u, action, doc, classGate);

    return (
      <div className="content">
        <div style={{ marginBottom: 18 }}>
          <div style={{ fontSize: 16, fontWeight: 800 }}>Kiểm tra quyền hiệu lực</div>
          <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 3, fontWeight: 500 }}>Mô phỏng <span className="mono">can(user, action, resource)</span> — giải thích vì sao cho phép/từ chối qua từng lớp (deny-by-default)</div>
        </div>

        <div className="grid g-2v" style={{ alignItems: "start", gap: 20 }}>
          <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
            <div className="card card-pad">
              <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: ".06em", color: "var(--ink-4)", marginBottom: 10 }}>NGƯỜI DÙNG</div>
              <div className="pick-card">
                {USER_LIST.map(x => (
                  <div key={x.id} className={`pick-row ${userId === x.id ? "on" : ""}`} onClick={() => setUserId(x.id)}>
                    <Av id={x.id} size={32} />
                    <div style={{ flex: 1, minWidth: 0 }}><div style={{ fontSize: 13, fontWeight: 700 }}>{x.name}</div><div style={{ fontSize: 11, color: "var(--ink-3)", fontWeight: 600, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{x.roles.map(r => ROLES[r].name).join(", ")}</div></div>
                    {userId === x.id && <Icon.check size={16} color="var(--accent)" />}
                  </div>
                ))}
              </div>
            </div>

            <div className="card card-pad">
              <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: ".06em", color: "var(--ink-4)", marginBottom: 10 }}>TÀI LIỆU</div>
              <div className="pick-card" style={{ maxHeight: 220 }}>
                {DOCUMENTS.map(d => (
                  <div key={d.id} className={`pick-row ${docId === d.id ? "on" : ""}`} onClick={() => setDocId(d.id)}>
                    <span style={{ width: 30, height: 30, borderRadius: 8, background: `${DISCC(folderById(d.folder).disc)}1c`, color: DISCC(folderById(d.folder).disc), display: "grid", placeItems: "center", flex: "none" }}><Icon.doc size={15} /></span>
                    <div style={{ flex: 1, minWidth: 0 }}><div className="mono" style={{ fontSize: 12, fontWeight: 700, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{d.title}</div><div style={{ fontSize: 11, color: "var(--ink-3)", fontWeight: 600 }}>{folderById(d.folder).name}</div></div>
                    <StateTag state={d.state} />
                  </div>
                ))}
              </div>
            </div>

            <div className="card card-pad">
              <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: ".06em", color: "var(--ink-4)", marginBottom: 10 }}>HÀNH ĐỘNG</div>
              <div className="seg-wrap" style={{ display: "flex", flexDirection: "column", width: "100%" }}>
                {ACTIONS.map(a => <button key={a.key} className={action === a.key ? "on" : ""} style={{ textAlign: "left", width: "100%" }} onClick={() => setAction(a.key)}>{a.label} <span className="mono" style={{ fontSize: 10.5, color: "var(--ink-4)" }}>{a.key}</span></button>)}
              </div>
              <label style={{ display: "flex", alignItems: "center", gap: 10, marginTop: 14, cursor: "pointer" }} onClick={() => setClassGate(c => !c)}>
                <span style={{ width: 38, height: 22, borderRadius: 99, background: classGate ? "var(--accent)" : "var(--line)", padding: 3, transition: ".2s", display: "flex", justifyContent: classGate ? "flex-end" : "flex-start", flex: "none" }}><span style={{ width: 16, height: 16, borderRadius: 99, background: "#fff" }} /></span>
                <span style={{ fontSize: 12.5, fontWeight: 700 }}>Bật cổng phân loại ISO 19650-5</span>
              </label>
            </div>
          </div>

          <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
            <div className={`verdict ${res.allow ? "allow" : "deny"}`}>
              <span className="verdict-ic" style={{ background: res.allow ? "var(--ok)" : "var(--danger)" }}>{res.allow ? <Icon.check size={26} /> : <Icon.ban size={24} />}</span>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 18, fontWeight: 800, letterSpacing: "-.02em", color: res.allow ? "var(--ok)" : "var(--danger)" }}>{res.allow ? "CHO PHÉP" : "TỪ CHỐI"}</div>
                <div style={{ fontSize: 13, color: "var(--ink-2)", fontWeight: 600, marginTop: 2 }}>{res.reason}</div>
              </div>
              <div style={{ textAlign: "right", flex: "none" }}>
                <div style={{ fontSize: 12, color: "var(--ink-3)", fontWeight: 700 }}>{u.name}</div>
                <div className="mono" style={{ fontSize: 11, color: "var(--ink-4)" }}>{action.split(".")[1]}</div>
              </div>
            </div>

            <div className="card card-pad">
              <div style={{ fontSize: 13, fontWeight: 800, marginBottom: 16 }}>Luồng quyết định 3 lớp</div>
              <div className="layerflow">
                {res.steps.map((s, i) => {
                  const cls = s.skip ? "layer-skip" : s.pass ? "layer-pass" : "layer-deny";
                  const last = i === res.steps.length - 1;
                  return (
                    <div className={`layer ${cls}`} key={i}>
                      <div className="layer-rail">
                        <span className="layer-dot">{s.skip ? <Icon.chevD size={16} /> : s.pass ? <Icon.check size={18} /> : <Icon.ban size={17} />}</span>
                        {!last && <span className="layer-line" />}
                      </div>
                      <div className="layer-body">
                        <div className="layer-card">
                          <div style={{ display: "flex", alignItems: "center", gap: 9, marginBottom: 5, flexWrap: "wrap" }}>
                            <span style={{ fontFamily: "var(--mono)", fontSize: 11, fontWeight: 800, color: s.skip ? "var(--ink-4)" : s.pass ? "var(--ok)" : "var(--danger)", border: `1px solid ${s.skip ? "var(--line)" : s.pass ? "var(--ok)" : "var(--danger)"}`, borderRadius: 6, padding: "1px 7px" }}>{s.layer}</span>
                            <span style={{ fontSize: 13.5, fontWeight: 800 }}>{s.title}</span>
                            <span style={{ marginLeft: "auto", fontSize: 11.5, fontWeight: 800, color: s.skip ? "var(--ink-4)" : s.pass ? "var(--ok)" : "var(--danger)" }}>{s.skip ? "BỎ QUA" : s.pass ? "PASS" : "DENY"}</span>
                          </div>
                          <div style={{ fontSize: 12.5, color: "var(--ink-3)", fontWeight: 600, marginBottom: 6 }}>{s.q}</div>
                          <div style={{ fontSize: 12.5, color: "var(--ink-2)", fontWeight: 600 }}>{s.detail}</div>
                        </div>
                      </div>
                    </div>
                  );
                })}
              </div>
            </div>
          </div>
        </div>
      </div>
    );
  }

  /* ========================================================
     7) Tab — Nhật ký truy cập (Audit)
     ======================================================== */
  const AUDIT = [
    { actor: "duc", ev: "permission_grant", subj: "hong", res: "str_wip", detail: "Cấp Folder Control (6) cho Task Manager", time: "07/06 09:42" },
    { actor: "tuan", ev: "role_assign", subj: "mai", res: "task_member", detail: "Gán vai trò Task Team Member", time: "07/06 09:10" },
    { actor: "an", ev: "denied_access", subj: "an", res: "STR-Beam-Detail-B04", detail: "WIP-gate chặn — không thuộc Tổ Kết cấu", time: "07/06 08:30" },
    { actor: "hong", ev: "document_download", subj: "hong", res: "STR-Frame-Station-C", detail: "Tải về qua signed URL R2", time: "06/06 16:20" },
    { actor: "duc", ev: "permission_revoke", subj: "lan", res: "mep_wip", detail: "Thu hồi quyền upload", time: "06/06 14:00" },
    { actor: "son", ev: "denied_access", subj: "son", res: "INF-Foundation-D", detail: "Classification-gate — clearance < Restricted", time: "06/06 11:15" },
    { actor: "tuan", ev: "role_assign", subj: "an", res: "task_member", detail: "Gán vai trò + thêm vào Tổ MEP", time: "05/06 10:00" },
    { actor: "duc", ev: "permission_grant", subj: "qlda", res: "root", detail: "Cấp View+Download (2) cho Ban QLDA", time: "05/06 09:00" },
  ];
  const EV_META = {
    permission_grant: { label: "Cấp quyền", ic: "key", c: "var(--ok)" },
    permission_revoke: { label: "Thu hồi quyền", ic: "ban", c: "var(--danger)" },
    role_assign: { label: "Gán vai trò", ic: "shield", c: "var(--arc)" },
    document_download: { label: "Tải tài liệu", ic: "download", c: "var(--str)" },
    denied_access: { label: "Truy cập bị chặn", ic: "lock", c: "var(--danger)" },
  };
  function Audit() {
    const [f, setF] = useState("all");
    const rows = AUDIT.filter(a => f === "all" || a.ev === f);
    return (
      <div className="content">
        <div style={{ display: "flex", alignItems: "flex-end", justifyContent: "space-between", marginBottom: 18, gap: 16, flexWrap: "wrap" }}>
          <div><div style={{ fontSize: 16, fontWeight: 800 }}>Nhật ký truy cập (Audit)</div><div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 3, fontWeight: 500 }}>Bất biến · mọi thay đổi quyền & truy cập nhạy cảm — phục vụ tranh chấp/bàn giao · <i>demo</i></div></div>
          <button className="btn sm"><Icon.download size={14} /> Xuất log</button>
        </div>
        <div className="seg-wrap" style={{ marginBottom: 16 }}>
          {[["all", "Tất cả"], ["permission_grant", "Cấp quyền"], ["permission_revoke", "Thu hồi"], ["role_assign", "Gán vai trò"], ["denied_access", "Bị chặn"], ["document_download", "Tải về"]].map(([k, l]) => <button key={k} className={f === k ? "on" : ""} onClick={() => setF(k)}>{l}</button>)}
        </div>
        <div className="card card-pad">
          {rows.map((a, i) => {
            const m = EV_META[a.ev]; const Ico = Icon[m.ic];
            return (
              <div key={i} style={{ display: "flex", gap: 14, padding: "14px 0", borderTop: i ? "1px solid var(--line-soft)" : "none", alignItems: "center" }}>
                <span style={{ width: 36, height: 36, borderRadius: 10, background: `${m.c}1c`, color: m.c, display: "grid", placeItems: "center", flex: "none" }}><Ico size={17} /></span>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 13.5, color: "var(--ink)", fontWeight: 600 }}><b style={{ fontWeight: 800 }}>{USERS[a.actor] ? USERS[a.actor].name : a.actor}</b> · <span style={{ color: m.c, fontWeight: 700 }}>{m.label}</span> · <span className="mono" style={{ fontSize: 12 }}>{a.res}</span></div>
                  <div style={{ fontSize: 12, color: "var(--ink-3)", fontWeight: 500, marginTop: 2 }}>{a.detail}</div>
                </div>
                <div style={{ fontSize: 12, color: "var(--ink-4)", fontWeight: 700, flex: "none" }}>{a.time}</div>
              </div>
            );
          })}
        </div>
      </div>
    );
  }

  /* ========================================================
     8) Root — tab strip + screen, live Members loader
     ======================================================== */
  const TABS = [
    { id: "matrix",  label: "Ma trận quyền", icon: "grid" },
    { id: "roles",   label: "Vai trò & quyền", icon: "shield" },
    { id: "members", label: "Thành viên", icon: "users" },
    { id: "inspect", label: "Kiểm tra quyền", icon: "key" },
    { id: "audit",   label: "Nhật ký truy cập", icon: "clock" },
  ];

  function AccessControl() {
    const [tab, setTab] = useState("matrix");
    const auth = (typeof window.useAuthCtx === "function") ? window.useAuthCtx() : null;
    const live = !!(auth && auth.user && window.CDE_READY && window.cdeRoles);
    const [members, setMembers] = useState(null);
    const [roles, setRoles] = useState([]);

    useEffect(() => {
      let alive = true;
      if (!live) { setMembers(null); return; }
      Promise.all([window.cdeRoles.membersWithRoles(), window.cdeRoles.list()])
        .then(([ms, rs]) => { if (!alive) return; setMembers(ms || []); setRoles(rs || []); })
        .catch(e => { console.warn("access live:", e.message); if (alive) setMembers(null); });
      return () => { alive = false; };
    }, [live]);

    const screen = () => {
      switch (tab) {
        case "matrix":  return <ZoneMatrix />;
        case "roles":   return <RoleManager />;
        case "members": return (members && members.length) ? <MembersLive members={members} roles={roles} /> : <MembersDemo />;
        case "inspect": return <Inspector />;
        case "audit":   return <Audit />;
        default:        return <ZoneMatrix />;
      }
    };

    return (
      <div style={{ flex: 1, minHeight: 0, display: "flex", flexDirection: "column" }}>
        <div style={{ padding: "16px 28px 0", borderBottom: "1px solid var(--line)" }}>
          <div className="seg-wrap" style={{ marginBottom: 14 }}>
            {TABS.map(t => { const Ico = Icon[t.icon]; return (
              <button key={t.id} className={tab === t.id ? "on" : ""} onClick={() => setTab(t.id)}><Ico size={15} /> {t.label}</button>
            ); })}
          </div>
        </div>
        <div style={{ flex: 1, minHeight: 0, overflowY: "auto" }}>{screen()}</div>
      </div>
    );
  }

  window.AccessControl = AccessControl;
})();
