// ===========================================================
// State primitives: EmptyState, Skeleton, ErrorState, LoadingSpinner
// Coder dùng các primitive này khắp app khi data đang load/rỗng/lỗi
// ===========================================================

function EmptyState({ icon = 'package', title, desc, action }) {
  return (
    <div className="empty">
      <div className="icon-wrap"><Icon name={icon} size={28}/></div>
      <div className="bold fg-1" style={{fontSize:16,marginBottom:6}}>{title}</div>
      <div className="text-sm fg-muted" style={{maxWidth:380,margin:'0 auto var(--space-4)'}}>{desc}</div>
      {action}
    </div>
  );
}

function ErrorState({ title = 'Có lỗi xảy ra', desc = 'Không thể tải dữ liệu — vui lòng thử lại.', onRetry }) {
  return (
    <div className="empty">
      <div className="icon-wrap" style={{background:'rgba(191,9,35,0.08)',color:'var(--status-sale)'}}>
        <Icon name="close" size={28}/>
      </div>
      <div className="bold fg-1" style={{fontSize:16,marginBottom:6}}>{title}</div>
      <div className="text-sm fg-muted" style={{maxWidth:380,margin:'0 auto var(--space-4)'}}>{desc}</div>
      {onRetry && (
        <button className="btn btn-secondary" onClick={onRetry}>
          <Icon name="refresh" size={14}/>Thử lại
        </button>
      )}
    </div>
  );
}

function Skeleton({ w = '100%', h = 16, radius = 4, style }) {
  return (
    <span style={{
      display:'inline-block',
      width: typeof w === 'number' ? w + 'px' : w,
      height: typeof h === 'number' ? h + 'px' : h,
      borderRadius: radius,
      background:'linear-gradient(90deg, var(--bg-soft) 0%, var(--bg-muted) 50%, var(--bg-soft) 100%)',
      backgroundSize:'200% 100%',
      animation:'skeleton-shimmer 1.4s ease-in-out infinite',
      ...style,
    }}/>
  );
}

function SkeletonRow({ cols = 5 }) {
  return (
    <tr>
      {Array.from({length: cols}).map((_, i) => (
        <td key={i}><Skeleton w={i === 0 ? '70%' : '50%'} h={14}/></td>
      ))}
    </tr>
  );
}

function LoadingSpinner({ size = 24, label }) {
  return (
    <div style={{display:'inline-flex',gap:8,alignItems:'center'}}>
      <svg width={size} height={size} viewBox="0 0 24 24" style={{animation:'spin 0.8s linear infinite'}}>
        <circle cx="12" cy="12" r="10" stroke="var(--bg-muted)" strokeWidth="3" fill="none"/>
        <path d="M22 12 A 10 10 0 0 1 12 22" stroke="var(--vs-light-blue)" strokeWidth="3" fill="none" strokeLinecap="round"/>
      </svg>
      {label && <span className="text-sm fg-muted">{label}</span>}
    </div>
  );
}

// Inject animation keyframes once
if (typeof document !== 'undefined' && !document.getElementById('__tvs-state-keyframes')) {
  const s = document.createElement('style');
  s.id = '__tvs-state-keyframes';
  s.textContent = `
    @keyframes skeleton-shimmer {
      0% { background-position: 200% 0; }
      100% { background-position: -200% 0; }
    }
    @keyframes spin {
      to { transform: rotate(360deg); }
    }
  `;
  document.head.appendChild(s);
}

Object.assign(window, { EmptyState, ErrorState, Skeleton, SkeletonRow, LoadingSpinner });

// ===========================================================
// Global toast — vanilla (no React plumbing needed)
// Any onClick can call window.tvsToast('message') or
// window.tvsToast('message', 'demo') for the "coming soon" style.
// ===========================================================
window.tvsToast = function(msg, kind) {
  let host = document.getElementById('__tvs-toast-host');
  if (!host) {
    host = document.createElement('div');
    host.id = '__tvs-toast-host';
    host.style.cssText = 'position:fixed;bottom:24px;left:50%;transform:translateX(-50%);z-index:2000;display:flex;flex-direction:column;gap:8px;align-items:center;pointer-events:none;';
    document.body.appendChild(host);
  }
  const isDemo = kind === 'demo';
  const el = document.createElement('div');
  el.style.cssText = `pointer-events:auto;background:${isDemo ? '#333' : '#012169'};color:#fff;padding:13px 20px;border-radius:8px;box-shadow:0 12px 36px rgba(1,33,105,0.28);display:flex;gap:11px;align-items:center;font:600 14px/1.4 Lato,system-ui,sans-serif;max-width:90vw;animation:tvsToastIn .26s cubic-bezier(.16,1,.3,1);`;
  el.innerHTML = `<span style="display:inline-flex;width:24px;height:24px;border-radius:50%;background:${isDemo ? 'rgba(255,255,255,0.16)' : '#41B6E6'};color:${isDemo ? '#fff' : '#012169'};align-items:center;justify-content:center;flex-shrink:0;">${isDemo ? '🔧' : '✓'}</span><span>${msg}</span>`;
  host.appendChild(el);
  setTimeout(() => { el.style.transition = 'opacity .3s, transform .3s'; el.style.opacity = '0'; el.style.transform = 'translateY(8px)'; setTimeout(() => el.remove(), 300); }, 2600);
};

// Convenience for placeholder/not-yet-built buttons
window.tvsDemo = function(feature) {
  window.tvsToast((feature || 'Tính năng này') + ' sẽ hoạt động ở bản chính thức', 'demo');
};

if (typeof document !== 'undefined' && !document.getElementById('__tvs-toast-keyframes')) {
  const s = document.createElement('style');
  s.id = '__tvs-toast-keyframes';
  s.textContent = `@keyframes tvsToastIn { from { opacity:0; transform:translateY(12px) } to { opacity:1; transform:none } }`;
  document.head.appendChild(s);
}

// ===========================================================
// PHÂN QUYỀN THEO VAI TRÒ — nguồn sự thật duy nhất
// Ma trận trong Cài đặt hiển thị & sửa đúng danh sách này;
// các màn kiểm tra bằng window.tvsCan(role, permId).
// Quản lý sửa ma trận → lưu vào TVS_SESSION.permissions → áp dụng ngay.
// ===========================================================
window.TVS_PERM_DEFS = [
  { id: 'customers.edit',   label: 'Tạo / sửa khách hàng' },
  { id: 'customers.import', label: 'Import khách hàng từ file' },
  { id: 'customers.assign', label: 'Gán khách cho nhân viên bán hàng khác' },
  { id: 'orders.create',    label: 'Ghi nhận đơn hàng' },
  { id: 'b2b.view',         label: 'Truy cập module Đối tác B2B' },
  { id: 'inventory.view',   label: 'Tra cứu tồn kho' },
  { id: 'products.add',     label: 'Thêm sản phẩm vào danh mục' },
  { id: 'products.remove',  label: 'Xoá sản phẩm khỏi danh mục' },
  { id: 'reports.view',     label: 'Xem báo cáo toàn hệ thống' },
  { id: 'settings.view',    label: 'Truy cập Cài đặt hệ thống' },
];
// ===========================================================
// BĂM MẬT KHẨU — SHA-256 thuần JS (không cần https / crypto.subtle), có salt cố định của app.
// Mật khẩu KHÔNG BAO GIỜ lưu dạng chữ thường: chỉ lưu tvsHash(mật khẩu) trong code/localStorage.
// Backend thật nên thay bằng bcrypt/argon2 phía server — hàm này chỉ đủ cho bản chạy trong trình duyệt.
// ===========================================================
window.tvsHash = function(str) {
  const bytes = new TextEncoder().encode('tvs-crm::' + String(str || ''));
  const K = [0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2];
  let H = [0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19];
  const l = bytes.length;
  const padLen = ((l + 9 + 63) >> 6) << 6;
  const m = new Uint8Array(padLen);
  m.set(bytes); m[l] = 0x80;
  const dv = new DataView(m.buffer);
  const bits = l * 8;
  dv.setUint32(padLen - 8, Math.floor(bits / 0x100000000));
  dv.setUint32(padLen - 4, bits >>> 0);
  const W = new Uint32Array(64);
  const rotr = (x, n) => (x >>> n) | (x << (32 - n));
  for (let off = 0; off < padLen; off += 64) {
    for (let i = 0; i < 16; i++) W[i] = dv.getUint32(off + i * 4);
    for (let i = 16; i < 64; i++) {
      const s0 = rotr(W[i - 15], 7) ^ rotr(W[i - 15], 18) ^ (W[i - 15] >>> 3);
      const s1 = rotr(W[i - 2], 17) ^ rotr(W[i - 2], 19) ^ (W[i - 2] >>> 10);
      W[i] = (W[i - 16] + s0 + W[i - 7] + s1) >>> 0;
    }
    let [a, b, c, d, e, f, g, h] = H;
    for (let i = 0; i < 64; i++) {
      const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
      const ch = (e & f) ^ (~e & g);
      const t1 = (h + S1 + ch + K[i] + W[i]) >>> 0;
      const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
      const maj = (a & b) ^ (a & c) ^ (b & c);
      const t2 = (S0 + maj) >>> 0;
      h = g; g = f; f = e; e = (d + t1) >>> 0; d = c; c = b; b = a; a = (t1 + t2) >>> 0;
    }
    H = [(H[0] + a) >>> 0, (H[1] + b) >>> 0, (H[2] + c) >>> 0, (H[3] + d) >>> 0, (H[4] + e) >>> 0, (H[5] + f) >>> 0, (H[6] + g) >>> 0, (H[7] + h) >>> 0];
  }
  return H.map(x => x.toString(16).padStart(8, '0')).join('');
};
// Chuẩn hoá SĐT: +84 → 0, thêm 0 cho số 9 chữ số đầu [35789]; dùng CHUNG cho đăng nhập & tạo người dùng
window.tvsNormPhone = function(raw) {
  let d = String(raw || '').replace(/\D/g, '');
  if (d.startsWith('84') && d.length >= 10) d = '0' + d.slice(2);
  else if (/^[35789]\d{8}$/.test(d)) d = '0' + d;
  return d;
};
window.tvsPhoneValid = function(raw) { const d = window.tvsNormPhone(raw); return d.length >= 9 && d.length <= 11; };
// Chuỗi nhập ở ô đăng nhập là SĐT (chỉ số / khoảng trắng / + . - ( )) hay email?
window.tvsLooksLikePhone = function(raw) { return /^[\d\s+.\-()]+$/.test(String(raw || '').trim()) && window.tvsPhoneValid(raw); };
// So mật khẩu nhập với bản ghi: ưu tiên hash; chỉ chấp nhận pwd chữ thường cho tài khoản DEMO cũ
window.tvsCheckPassword = function(record, input) {
  if (!record) return false;
  if (record.pwdHash || record.hash) return window.tvsHash(input) === (record.pwdHash || record.hash);
  if (record.pwd) return input === record.pwd;
  return false;
};

// DANH SÁCH VỊ TRÍ dùng chung cho form "Thêm người dùng" VÀ ma trận phân quyền
// — một nguồn duy nhất để hai nơi không lệch nhau
window.TVS_ROLE_DEFS = [
  { id: 'telesale',   label: 'Telesale',            teams: ['Telesale TP.HCM', 'Telesale Hà Nội'] },
  { id: 'instore',    label: 'Cửa hàng',            teams: ['TVS Q.1 — Lê Lợi', 'TVS Q.3', 'TVS Hà Nội'] },
  { id: 'b2b',        label: 'Đối tác B2B',          teams: ['B2B Partnerships'] },
  { id: 'tiktok',     label: 'Quản lý TikTok',       teams: ['Sales Online — TikTok'] },
  { id: 'shopee',     label: 'Quản lý Shopee',       teams: ['Sales Online — Shopee'] },
  { id: 'livestream', label: 'Livestream',           teams: ['Livestream Studio'] },
  { id: 'cskh',       label: 'Chăm sóc khách hàng',  teams: ['CSKH'] },
  { id: 'leader',     label: 'Team Leader',          teams: ['Telesale TP.HCM', 'Sales Online — TikTok', 'B2B Partnerships'] },
  { id: 'marketing',  label: 'Marketing',            teams: ['Marketing / TMĐT', 'Store Tik tok'] },
  { id: 'accounting', label: 'Kế toán / Kho',        teams: ['Kho Công Ty', 'Phòng Kế Toán'] },
  { id: 'director',   label: 'Giám đốc',             teams: ['Ban Giám đốc'] },
  { id: 'manager',    label: 'Admin (Quản trị hệ thống)', teams: ['Quản trị hệ thống'] },
];
// QUY TẮC (chốt 23/08/2026): MỌI vị trí chăm sóc khách hàng dùng CHUNG một bộ quyền —
// Telesale, Cửa hàng, B2B, TikTok, Shopee, Livestream, CSKH, Team Leader, Giám đốc đều như nhau.
// CHỈ Admin (id nội bộ 'manager') có toàn quyền: báo cáo toàn hệ thống, cài đặt, tạo tài khoản, thêm/xoá sản phẩm.
window.TVS_STAFF_PERMS = ['customers.edit', 'customers.import', 'orders.create', 'b2b.view', 'inventory.view'];
// Marketing: ĐẨY dữ liệu khách cho các vị trí bán hàng → có thêm quyền 'customers.assign' (chọn người phụ trách khi tạo / import / sửa)
window.TVS_MARKETING_PERMS = ['customers.edit', 'customers.import', 'customers.assign', 'inventory.view'];
window.TVS_PERM_DEFAULTS = Object.fromEntries([
  ...window.TVS_ROLE_DEFS.filter(r => r.id !== 'manager' && r.id !== 'marketing').map(r => [r.id, window.TVS_STAFF_PERMS]),
  ['marketing', window.TVS_MARKETING_PERMS],
  ['manager', '*'],
]);
// Vị trí BÁN HÀNG / chăm khách — có thể được gán khách (Marketing, Admin gán cho họ)
window.TVS_SALES_ROLES = ['instore', 'telesale', 'b2b', 'leader', 'director', 'livestream', 'tiktok', 'shopee', 'cskh'];
// TẤT CẢ tài khoản hiệu lực = tài khoản cài sẵn (accounts.js) + phần Admin đã SỬA (TVS_SESSION.userEdits[id])
// + tài khoản Admin tạo thêm trong app (TVS_SESSION.users). _source: 'code' | 'session'; _ref: object gốc trong session.
window.tvsAllAccounts = function() {
  const S = window.TVS_SESSION;
  const edits = (S && S.userEdits) || {};
  const sess = ((S && S.users) || []).map(u => ({ id: u.id || ('S-' + (u.phone || u.email || u.name)), ...u, _source: 'session', _ref: u }));
  const code = (window.TVS_ACCOUNTS || []).map(a => ({ ...a, ...(edits[a.id] || {}), _source: 'code' }));
  return [...sess, ...code];
};
// Danh sách tên nhân viên bán hàng đang hoạt động — dùng cho ô "Người phụ trách"
window.tvsSalesStaff = function() {
  const all = window.tvsAllAccounts();
  const names = all.filter(u => u.status !== 'inactive' && (u.roles || []).some(r => window.TVS_SALES_ROLES.includes(r))).map(u => u.name);
  return [...new Set(names)];
};
// Tìm tài khoản theo email HOẶC SĐT (tài khoản cài sẵn trước, rồi tới tài khoản tạo trong app)
window.tvsFindAccount = function(raw) {
  const key = String(raw || '').trim().toLowerCase();
  const isPhone = window.tvsLooksLikePhone(raw);
  const pk = isPhone ? window.tvsNormPhone(raw) : null;
  const all = window.tvsAllAccounts();
  return all.find(u => isPhone ? (u.phone && window.tvsNormPhone(u.phone) === pk) : (String(u.email || '').trim().toLowerCase() === key && key)) || null;
};
// role: object {id} hoặc chuỗi id; mảng id = nhân sự kiêm nhiều vị trí (quyền cộng dồn).
// Ưu tiên quyền quản lý đã tuỳ chỉnh (session), fallback mặc định.
window.tvsCan = function(role, permId) {
  const roleId = role && role.id ? (role.ids || role.id) : role; // role.ids = nhân sự kiêm nhiều vị trí
  if (Array.isArray(roleId)) return roleId.some(id => window.tvsCan(id, permId));
  if (roleId === 'manager') return true; // Admin luôn đủ quyền — không tự khoá được chính mình
  const S = window.TVS_SESSION;
  const custom = S && S.permissions ? S.permissions[roleId] : null;
  const list = custom || window.TVS_PERM_DEFAULTS[roleId] || window.TVS_STAFF_PERMS || []; // vị trí lạ → quyền nhân viên chung
  if (list === '*') return true;
  return list.includes(permId);
};
window.tvsDenied = function() {
  window.tvsToast('Vị trí của bạn không có quyền thực hiện thao tác này — liên hệ quản lý để được cấp quyền', 'demo');
};
