/* primitives.jsx — Workforce Planner — shared UI primitives.
   Styled with Boxme tokens, Vietnamese copy. */

// ---------- Vietnamese label dictionary (status, role, day type, tier) ----------
const VI_LABEL = {
  // Status badges
  DRAFT:      'Nháp',
  APPROVED:   'Đã duyệt',
  SENT:       'Đã gửi',
  FULFILLED:  'Hoàn tất',
  CANCELLED:  'Đã hủy',
  ACTIVE:     'Hoạt động',
  INACTIVE:   'Tạm dừng',
  PROMOTED:   'Đã chốt',
  PENDING:    'Chờ duyệt',
  REJECTED:   'Từ chối',
  SUBMITTED:  'Đã gửi',
  OVERDUE:    'Trễ hạn',
  // Import diff
  new:        'Mới',
  update:     'Sửa',
  unchanged:  'Giữ nguyên',
  // Severity
  INFO:       'Thông tin',
  WARNING:    'Cảnh báo',
  CRITICAL:   'Nghiêm trọng',
  // Day type
  MEGA_SALE:  'Sale lớn',
  PAYDAY:     'Ngày lương',
  MINI_SPIKE: 'Sale nhỏ',
  NORMAL:     'Bình thường',
  // Customer tier
  KEY:        'Trọng điểm',
  MAJOR:      'Lớn',
  STANDARD:   'Phổ thông',
  // Plan flags
  FORECAST:   'Dự báo',
  ACTUAL:     'Thực tế',
  UNCERTAIN:  'Chưa chắc',
  // Staff type
  OFFICIAL:   'Chính thức',
  TEMP:       'Thời vụ',
};
function viLabel(text) {
  if (typeof text === 'string' && VI_LABEL[text] != null) return VI_LABEL[text];
  return text;
}
const VI_ROLE = { PICKER: 'nhặt hàng', PACKER: 'đóng gói', CHECKER: 'kiểm hàng', LEAD: 'trưởng nhóm' };
function viRole(r) { return VI_ROLE[r] || (r || '').toLowerCase(); }

// ---------- Viewport hook ----------
function useViewport() {
  const [vw, setVw] = React.useState(typeof window !== 'undefined' ? window.innerWidth : 1280);
  React.useEffect(() => {
    let raf = 0;
    function onResize() { cancelAnimationFrame(raf); raf = requestAnimationFrame(() => setVw(window.innerWidth)); }
    window.addEventListener('resize', onResize);
    return () => { cancelAnimationFrame(raf); window.removeEventListener('resize', onResize); };
  }, []);
  return {
    vw,
    isMobile:  vw < 720,    // phone
    isTablet:  vw >= 720 && vw < 1024,
    isCompact: vw < 1100,   // hide non-essential top-nav chrome
    isNarrow:  vw < 1280,
  };
}

// ---------- Icon ----------
const FA_MAP = {
  // nav / actions
  home: 'fa-solid fa-house',
  warehouse: 'fa-solid fa-warehouse',
  users: 'fa-solid fa-users',
  sliders: 'fa-solid fa-sliders',
  forecast: 'fa-solid fa-chart-line',
  plan: 'fa-solid fa-list-check',
  hiring: 'fa-solid fa-user-plus',
  upload: 'fa-solid fa-file-arrow-up',
  audit: 'fa-solid fa-clipboard-check',
  search: 'fa-solid fa-magnifying-glass',
  command: 'fa-solid fa-square-caret-up',
  bell: 'fa-regular fa-bell',
  user: 'fa-regular fa-circle-user',
  logout: 'fa-solid fa-right-from-bracket',
  globe: 'fa-solid fa-globe',
  building: 'fa-solid fa-building',
  'chevron-down': 'fa-solid fa-chevron-down',
  'chevron-right': 'fa-solid fa-chevron-right',
  'chevron-left': 'fa-solid fa-chevron-left',
  check: 'fa-solid fa-check',
  x: 'fa-solid fa-xmark',
  plus: 'fa-solid fa-plus',
  minus: 'fa-solid fa-minus',
  edit: 'fa-regular fa-pen-to-square',
  trash: 'fa-regular fa-trash-can',
  download: 'fa-solid fa-download',
  print: 'fa-solid fa-print',
  pdf: 'fa-regular fa-file-pdf',
  excel: 'fa-regular fa-file-excel',
  filter: 'fa-solid fa-filter',
  refresh: 'fa-solid fa-rotate',
  rocket: 'fa-solid fa-rocket-launch',
  play: 'fa-solid fa-play',
  pause: 'fa-solid fa-pause',
  info: 'fa-solid fa-circle-info',
  warning: 'fa-solid fa-triangle-exclamation',
  critical: 'fa-solid fa-circle-exclamation',
  spark: 'fa-solid fa-bolt',
  history: 'fa-solid fa-clock-rotate-left',
  ellipsis: 'fa-solid fa-ellipsis',
  diff: 'fa-solid fa-code-branch',
  copy: 'fa-regular fa-copy',
  external: 'fa-solid fa-arrow-up-right-from-square',
  shield: 'fa-solid fa-shield-halved',
  lock: 'fa-solid fa-lock',
  mail: 'fa-regular fa-envelope',
  calendar: 'fa-regular fa-calendar',
  clock: 'fa-regular fa-clock',
  link: 'fa-solid fa-link',
  arrowUp: 'fa-solid fa-arrow-up',
  arrowDown: 'fa-solid fa-arrow-down',
  arrowRight: 'fa-solid fa-arrow-right',
  layers: 'fa-solid fa-layer-group',
  cube: 'fa-solid fa-cube',
  truck: 'fa-solid fa-truck',
  cog: 'fa-solid fa-gear',
  eye: 'fa-regular fa-eye',
  drag: 'fa-solid fa-grip-vertical',
};
function Icon({ name, size = 14, style, className }) {
  const cls = FA_MAP[name] || 'fa-regular fa-circle';
  return (
    <i className={(className ? className + ' ' : '') + cls} style={{
      fontSize: size, lineHeight: 1, display: 'inline-flex',
      alignItems: 'center', justifyContent: 'center',
      width: size + 2, height: size + 2, ...style,
    }}></i>
  );
}

// ---------- Button ----------
function Button({ variant = 'primary', size = 'md', children, icon, iconRight, onClick, disabled, type, style, title }) {
  const base = {
    fontFamily: 'var(--bx-font-sans)',
    fontWeight: 500,
    borderRadius: 10,
    border: '1px solid transparent',
    cursor: disabled ? 'not-allowed' : 'pointer',
    display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 8,
    height: size === 'sm' ? 30 : size === 'lg' ? 44 : 36,
    padding: size === 'sm' ? '0 12px' : size === 'lg' ? '0 22px' : '0 14px',
    fontSize: size === 'sm' ? 12.5 : 13.28,
    opacity: disabled ? 0.55 : 1,
    transition: 'background 120ms, border-color 120ms',
    whiteSpace: 'nowrap',
  };
  const v = {
    primary:   { background: 'var(--bx-primary)',   color: '#fff' },
    secondary: { background: 'var(--bx-surface-2)', color: 'var(--bx-text-dark)' },
    outline:   { background: '#fff', color: 'var(--bx-text-dark)', borderColor: 'var(--bx-border)' },
    ghost:     { background: 'transparent', color: 'var(--bx-text-dark)' },
    'ghost-primary': { background: 'transparent', color: 'var(--bx-primary)' },
    danger:    { background: 'var(--bx-error)',    color: '#fff' },
    'danger-outline': { background: '#fff', color: 'var(--bx-error)', borderColor: 'var(--bx-error)' },
  }[variant];
  return (
    <button type={type || 'button'} title={title} style={{ ...base, ...v, ...style }} onClick={disabled ? undefined : onClick}>
      {icon ? <Icon name={icon} size={size === 'sm' ? 13 : 14}/> : null}
      {children}
      {iconRight ? <Icon name={iconRight} size={size === 'sm' ? 13 : 14}/> : null}
    </button>
  );
}

// ---------- IconBtn (square, no label) ----------
function IconBtn({ name, onClick, title, active, size = 32, style }) {
  return (
    <button onClick={onClick} title={title} style={{
      width: size, height: size, border: '1px solid transparent',
      background: active ? 'var(--bx-surface-2)' : 'transparent',
      color: active ? 'var(--bx-primary)' : 'var(--bx-text-body)',
      borderRadius: 8, cursor: 'pointer', display: 'inline-flex',
      alignItems: 'center', justifyContent: 'center', ...style,
    }}>
      <Icon name={name} size={15}/>
    </button>
  );
}

// ---------- Input ----------
function Input({ value, onChange, placeholder, icon, iconRight, type = 'text', size = 'md', error, disabled, style, autoFocus, onKeyDown }) {
  const [focus, setFocus] = React.useState(false);
  const h = size === 'sm' ? 32 : 36;
  return (
    <div style={{
      display: 'inline-flex', alignItems: 'center', height: h, width: '100%',
      background: disabled ? 'var(--bx-surface-2)' : '#fff',
      border: '1px solid ' + (error ? 'var(--bx-error)' : focus ? 'var(--bx-primary)' : 'var(--bx-border)'),
      borderRadius: 10, padding: '0 10px', gap: 8,
      boxShadow: focus ? '0 0 0 3px rgba(11,67,234,0.16)' : 'none',
      transition: 'border-color 120ms, box-shadow 120ms',
      opacity: disabled ? 0.7 : 1, ...style,
    }}>
      {icon ? <Icon name={icon} size={13} style={{ color: 'var(--bx-text-muted)' }}/> : null}
      <input
        type={type} value={value ?? ''} onChange={onChange} placeholder={placeholder}
        onFocus={() => setFocus(true)} onBlur={() => setFocus(false)}
        autoFocus={autoFocus} disabled={disabled} onKeyDown={onKeyDown}
        style={{
          flex: 1, border: 'none', outline: 'none', background: 'transparent',
          fontFamily: 'var(--bx-font-sans)', fontSize: 13, color: 'var(--bx-text-dark)',
          minWidth: 0, fontVariantNumeric: type === 'number' ? 'tabular-nums' : 'normal',
        }}
      />
      {iconRight ? <Icon name={iconRight} size={13} style={{ color: 'var(--bx-text-muted)' }}/> : null}
    </div>
  );
}

// ---------- Field (label + input + error) ----------
function Field({ label, hint, error, required, children, style }) {
  return (
    <label style={{ display: 'flex', flexDirection: 'column', gap: 6, ...style }}>
      <span style={{ fontSize: 12.5, fontWeight: 500, color: 'var(--bx-text-dark)', display: 'flex', alignItems: 'center', gap: 4 }}>
        {label}
        {required ? <span style={{ color: 'var(--bx-error)' }}>*</span> : null}
      </span>
      {children}
      {error ? (
        <span style={{ fontSize: 11.5, color: 'var(--bx-error)', display: 'flex', alignItems: 'center', gap: 4 }}>
          <Icon name="warning" size={11}/>{error}
        </span>
      ) : hint ? (
        <span style={{ fontSize: 11.5, color: 'var(--bx-text-muted)' }}>{hint}</span>
      ) : null}
    </label>
  );
}

// ---------- Select (native, restyled) ----------
function Select({ value, onChange, options, placeholder, size = 'md', disabled, style }) {
  const h = size === 'sm' ? 32 : 36;
  return (
    <div style={{
      position: 'relative', display: 'inline-flex', alignItems: 'center', height: h, width: '100%',
      background: '#fff', border: '1px solid var(--bx-border)', borderRadius: 10, ...style,
    }}>
      <select value={value ?? ''} onChange={onChange} disabled={disabled}
        style={{
          flex: 1, height: '100%', border: 'none', outline: 'none', background: 'transparent',
          padding: '0 28px 0 10px', fontFamily: 'var(--bx-font-sans)', fontSize: 13,
          color: 'var(--bx-text-dark)', appearance: 'none', cursor: 'pointer',
        }}>
        {placeholder ? <option value="">{placeholder}</option> : null}
        {options.map(o => typeof o === 'string'
          ? <option key={o} value={o}>{o}</option>
          : <option key={o.value} value={o.value}>{o.label}</option>)}
      </select>
      <Icon name="chevron-down" size={11} style={{ position: 'absolute', right: 10, top: '50%', marginTop: -6, color: 'var(--bx-text-muted)', pointerEvents: 'none' }}/>
    </div>
  );
}

// ---------- Badge / Chip ----------
const STATUS_COLORS = {
  DRAFT:     { bg: '#f5f5f5', fg: '#4a4a4a', dot: '#99a1af' },
  APPROVED:  { bg: '#eef2ff', fg: '#0b43ea', dot: '#0b43ea' },
  SENT:      { bg: '#eff6ff', fg: '#1e40af', dot: '#1e40af' },
  FULFILLED: { bg: '#f0fdf4', fg: '#15803d', dot: '#00af4e' },
  CANCELLED: { bg: '#f5f5f5', fg: '#525252', dot: '#a3a3a3' },
  ACTIVE:    { bg: '#f0fdf4', fg: '#15803d', dot: '#00af4e' },
  INACTIVE:  { bg: '#f5f5f5', fg: '#737373', dot: '#a3a3a3' },
  PROMOTED:  { bg: '#f0fdf4', fg: '#15803d', dot: '#00af4e' },
  new:       { bg: '#eef2ff', fg: '#0b43ea', dot: '#0b43ea' },
  update:    { bg: '#fefce8', fg: '#92400e', dot: '#d97706' },
  unchanged: { bg: '#f5f5f5', fg: '#737373', dot: '#a3a3a3' },
  INFO:      { bg: '#eff6ff', fg: '#1e40af', dot: '#2563eb' },
  WARNING:   { bg: '#fefce8', fg: '#92400e', dot: '#d97706' },
  CRITICAL:  { bg: '#fef2f2', fg: '#b91c1c', dot: '#dc2626' },
};
function Badge({ kind, children, dot, style, size = 'md' }) {
  const c = STATUS_COLORS[kind] || { bg: 'var(--bx-surface-2)', fg: 'var(--bx-text-body)', dot: '#a3a3a3' };
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'center', gap: 6,
      height: size === 'sm' ? 18 : 22, padding: size === 'sm' ? '0 8px' : '0 10px',
      borderRadius: 9999,
      fontSize: size === 'sm' ? 10.5 : 11, fontWeight: 600,
      letterSpacing: '0.01em', textTransform: typeof children === 'string' && children.length < 12 ? 'uppercase' : 'none',
      background: c.bg, color: c.fg, whiteSpace: 'nowrap', ...style,
    }}>
      {dot !== false ? <span style={{ width: 6, height: 6, borderRadius: '50%', background: c.dot }}></span> : null}
      {viLabel(children) || viLabel(kind)}
    </span>
  );
}

// ---------- Card ----------
function Card({ title, subtitle, actions, padding = 16, children, style, bodyStyle, noBody }) {
  return (
    <div style={{
      background: 'var(--bx-card-bg)', border: '1px solid var(--bx-border-muted)',
      borderRadius: 'var(--bx-radius-lg)', boxShadow: 'var(--bx-shadow-card)',
      display: 'flex', flexDirection: 'column', minWidth: 0, ...style,
    }}>
      {(title || actions) && (
        <div style={{
          padding: '14px 16px', borderBottom: noBody ? 'none' : '1px solid var(--bx-border-muted)',
          display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12,
        }}>
          <div style={{ minWidth: 0 }}>
            {title && <div style={{ fontSize: 14, fontWeight: 600, color: 'var(--bx-text-heading)' }}>{title}</div>}
            {subtitle && <div style={{ fontSize: 11.5, color: 'var(--bx-text-muted)', marginTop: 2 }}>{subtitle}</div>}
          </div>
          {actions && <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>{actions}</div>}
        </div>
      )}
      {!noBody && <div style={{ padding, ...bodyStyle }}>{children}</div>}
    </div>
  );
}

// ---------- Tabs ----------
function Tabs({ value, onChange, tabs, dense, style }) {
  return (
    <div role="tablist" style={{
      display: 'inline-flex', gap: 4, padding: 4,
      background: 'var(--bx-surface-2)', borderRadius: 10, ...style,
    }}>
      {tabs.map(t => {
        const active = (t.id || t.value) === value;
        return (
          <button key={t.id || t.value} role="tab" aria-selected={active}
            onClick={() => onChange(t.id || t.value)} style={{
              display: 'inline-flex', alignItems: 'center', gap: 6,
              height: dense ? 26 : 30, padding: '0 12px',
              border: 'none', borderRadius: 8, cursor: 'pointer',
              background: active ? '#fff' : 'transparent',
              color: active ? 'var(--bx-text-dark)' : 'var(--bx-text-body)',
              fontWeight: active ? 600 : 500, fontSize: 12.5,
              boxShadow: active ? 'var(--bx-shadow-pill)' : 'none',
              transition: 'background 150ms var(--bx-ease)',
              fontFamily: 'var(--bx-font-sans)',
            }}>
            {t.icon ? <Icon name={t.icon} size={12}/> : null}
            <span style={{ whiteSpace: 'nowrap' }}>{t.label}</span>
            {typeof t.count === 'number' ? (
              <span style={{ fontVariantNumeric: 'tabular-nums', fontSize: 11, color: active ? 'var(--bx-primary)' : 'var(--bx-text-muted)' }}>
                {t.count}
              </span>
            ) : null}
          </button>
        );
      })}
    </div>
  );
}

// ---------- Section tabs (underlined for hero page) ----------
function SectionTabs({ value, onChange, tabs }) {
  return (
    <div role="tablist" style={{
      display: 'flex', gap: 0, borderBottom: '1px solid var(--bx-border-muted)',
    }}>
      {tabs.map(t => {
        const active = t.id === value;
        return (
          <button key={t.id} onClick={() => onChange(t.id)} style={{
            position: 'relative', padding: '10px 14px', border: 'none',
            background: 'transparent', cursor: 'pointer',
            color: active ? 'var(--bx-primary)' : 'var(--bx-text-body)',
            fontWeight: active ? 600 : 500, fontSize: 13,
            fontFamily: 'var(--bx-font-sans)',
            display: 'inline-flex', alignItems: 'center', gap: 8,
            marginBottom: -1,
          }}>
            {t.idx ? <span style={{
              display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
              width: 18, height: 18, borderRadius: 9999,
              background: active ? 'var(--bx-primary)' : 'var(--bx-surface-2)',
              color: active ? '#fff' : 'var(--bx-text-muted)',
              fontSize: 10, fontWeight: 700,
            }}>{t.idx}</span> : null}
            <span>{t.label}</span>
            {t.count ? <span style={{ fontSize: 11, color: 'var(--bx-text-muted)', fontVariantNumeric: 'tabular-nums' }}>{t.count}</span> : null}
            {active ? <span style={{
              position: 'absolute', bottom: -1, left: 8, right: 8, height: 2,
              background: 'var(--bx-primary)', borderRadius: 2,
            }}></span> : null}
          </button>
        );
      })}
    </div>
  );
}

// ---------- Table ----------
// Column.priority: 1 = always show, 2 = hide on mobile, 3 = hide on mobile + tablet
function Table({ columns, rows, getRowKey, dense, onRowClick, emptyText = 'Chưa có dữ liệu', stickyHeader }) {
  const { isMobile, isTablet } = useViewport();
  columns = columns.filter(c => {
    const p = c.priority || 1;
    if (isMobile  && p >= 2) return false;
    if (isTablet  && p >= 3) return false;
    return true;
  });
  if (!rows || rows.length === 0) {
    return (
      <div style={{ padding: 32, textAlign: 'center', color: 'var(--bx-text-muted)', fontSize: 12.5 }}>
        {emptyText === 'No rows' ? 'Chưa có dữ liệu' : emptyText}
      </div>
    );
  }
  return (
    <div style={{ width: '100%', overflowX: 'auto' }}>
      <table style={{
        width: '100%', borderCollapse: 'separate', borderSpacing: 0,
        fontSize: 13, color: 'var(--bx-text-body)',
      }}>
        <thead>
          <tr>
            {columns.map((c, i) => (
              <th key={c.key || i} style={{
                position: stickyHeader ? 'sticky' : undefined, top: stickyHeader ? 0 : undefined,
                background: 'var(--bx-surface-2)',
                textAlign: c.align || 'left', padding: dense ? '8px 10px' : '10px 12px',
                fontSize: 11, fontWeight: 600, color: 'var(--bx-text-muted)',
                letterSpacing: '0.04em', textTransform: 'uppercase',
                borderBottom: '1px solid var(--bx-border-muted)',
                whiteSpace: 'nowrap',
                width: c.width,
              }}>{c.header}</th>
            ))}
          </tr>
        </thead>
        <tbody>
          {rows.map((r, i) => (
            <tr key={getRowKey ? getRowKey(r) : i} onClick={onRowClick ? () => onRowClick(r) : undefined}
              style={{
                background: i % 2 === 1 ? 'rgba(245,245,245,0.4)' : '#fff',
                cursor: onRowClick ? 'pointer' : 'default',
              }}>
              {columns.map((c, ci) => (
                <td key={c.key || ci} style={{
                  padding: dense ? '8px 10px' : '11px 12px',
                  textAlign: c.align || 'left',
                  borderBottom: '1px solid var(--bx-border-muted)',
                  color: c.muted ? 'var(--bx-text-muted)' : 'var(--bx-text-dark)',
                  fontFamily: c.mono ? 'var(--bx-font-mono)' : 'inherit',
                  fontVariantNumeric: (c.mono || c.numeric) ? 'tabular-nums' : 'normal',
                  whiteSpace: c.wrap ? 'normal' : 'nowrap',
                }}>{c.render ? c.render(r, i) : r[c.key]}</td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

// ---------- Dialog ----------
function Dialog({ open, onClose, title, subtitle, footer, children, width = 560, destructive }) {
  const { isMobile } = useViewport();
  if (!open) return null;
  return (
    <div style={{
      position: 'fixed', inset: 0, background: 'var(--bx-scrim)',
      display: 'flex', alignItems: isMobile ? 'flex-end' : 'center', justifyContent: 'center', zIndex: 100,
      padding: isMobile ? 0 : 24,
    }} onClick={onClose}>
      <div onClick={e => e.stopPropagation()} style={{
        width: '100%', maxWidth: isMobile ? '100%' : width,
        maxHeight: isMobile ? '92vh' : 'calc(100vh - 48px)',
        borderTopLeftRadius: isMobile ? 'var(--bx-radius-lg)' : 'var(--bx-radius-lg)',
        borderTopRightRadius: isMobile ? 'var(--bx-radius-lg)' : 'var(--bx-radius-lg)',
        borderBottomLeftRadius: isMobile ? 0 : 'var(--bx-radius-lg)',
        borderBottomRightRadius: isMobile ? 0 : 'var(--bx-radius-lg)',
        background: '#fff', borderRadius: 'var(--bx-radius-lg)',
        boxShadow: 'var(--bx-shadow-pop)', display: 'flex', flexDirection: 'column',
        overflow: 'hidden',
      }}>
        {isMobile && <div style={{ width: 36, height: 4, background: 'var(--bx-border)', borderRadius: 9999, margin: '8px auto 0' }}></div>}
        <div style={{
          padding: '16px 20px', borderBottom: '1px solid var(--bx-border-muted)',
          display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16,
        }}>
          <div style={{ minWidth: 0, flex: 1 }}>
            <div style={{ fontSize: 16, fontWeight: 600, color: destructive ? 'var(--bx-error)' : 'var(--bx-text-heading)' }}>{title}</div>
            {subtitle && <div style={{ fontSize: 12.5, color: 'var(--bx-text-muted)', marginTop: 4 }}>{subtitle}</div>}
          </div>
          <IconBtn name="x" onClick={onClose} title="Đóng"/>
        </div>
        <div style={{ padding: 20, overflowY: 'auto', flex: 1, minHeight: 0 }}>{children}</div>
        {footer && (
          <div style={{
            padding: '12px 20px', borderTop: '1px solid var(--bx-border-muted)',
            display: 'flex', justifyContent: 'flex-end', gap: 8, background: 'var(--bx-surface-2)',
            flexWrap: 'wrap',
          }}>{footer}</div>
        )}
      </div>
    </div>
  );
}

// ---------- Skeleton ----------
function Skeleton({ width = '100%', height = 12, radius = 6, style }) {
  return (
    <div style={{
      width, height, borderRadius: radius,
      background: 'linear-gradient(90deg, #ececec 0%, #f5f5f5 50%, #ececec 100%)',
      backgroundSize: '200% 100%', animation: 'wfpShimmer 1.4s linear infinite',
      ...style,
    }}/>
  );
}

// ---------- Empty state ----------
function Empty({ icon = 'info', title, subtitle, action }) {
  return (
    <div style={{
      display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
      padding: '48px 24px', textAlign: 'center', gap: 8,
    }}>
      <div style={{
        width: 44, height: 44, borderRadius: 999,
        background: 'var(--bx-surface-2)', color: 'var(--bx-text-muted)',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
      }}>
        <Icon name={icon} size={18}/>
      </div>
      <div style={{ fontSize: 14, fontWeight: 600, color: 'var(--bx-text-heading)', marginTop: 4 }}>{title}</div>
      {subtitle ? <div style={{ fontSize: 12.5, color: 'var(--bx-text-muted)', maxWidth: 360 }}>{subtitle}</div> : null}
      {action ? <div style={{ marginTop: 8 }}>{action}</div> : null}
    </div>
  );
}

// ---------- Toast ----------
function Toast({ kind = 'INFO', title, body, onClose }) {
  const c = STATUS_COLORS[kind] || STATUS_COLORS.INFO;
  const ic = kind === 'CRITICAL' ? 'critical' : kind === 'WARNING' ? 'warning' : kind === 'FULFILLED' ? 'check' : 'info';
  return (
    <div style={{
      display: 'flex', gap: 10, padding: '12px 14px',
      background: '#fff', border: '1px solid var(--bx-border-muted)',
      borderRadius: 'var(--bx-radius-md)', boxShadow: 'var(--bx-shadow-pop)',
      minWidth: 280, maxWidth: 380, alignItems: 'flex-start',
    }}>
      <div style={{
        width: 24, height: 24, flexShrink: 0, borderRadius: 6,
        background: c.bg, color: c.fg, display: 'flex', alignItems: 'center', justifyContent: 'center',
      }}><Icon name={ic} size={13}/></div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--bx-text-heading)' }}>{title}</div>
        {body && <div style={{ fontSize: 12, color: 'var(--bx-text-body)', marginTop: 2 }}>{body}</div>}
      </div>
      <IconBtn name="x" onClick={onClose} size={24}/>
    </div>
  );
}

// ---------- Metric tile ----------
function Metric({ label, value, sublabel, hint, delta, deltaTone, icon, style, mono = true }) {
  return (
    <div style={{
      flex: 1, minWidth: 0,
      background: 'var(--bx-card-bg)', border: '1px solid var(--bx-border-muted)',
      borderRadius: 'var(--bx-radius-lg)', padding: '14px 16px',
      display: 'flex', flexDirection: 'column', gap: 4, ...style,
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        {icon ? <Icon name={icon} size={12} style={{ color: 'var(--bx-text-muted)' }}/> : null}
        <span style={{ fontSize: 11.5, color: 'var(--bx-text-muted)', textTransform: 'uppercase', letterSpacing: '0.04em', fontWeight: 600 }}>{label}</span>
      </div>
      <div style={{
        fontSize: 22, fontWeight: 600, color: 'var(--bx-text-heading)',
        fontVariantNumeric: mono ? 'tabular-nums' : 'normal', lineHeight: 1.1,
      }}>{value}</div>
      <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginTop: 2, minHeight: 16 }}>
        {sublabel ? <span style={{ fontSize: 11.5, color: 'var(--bx-text-muted)' }}>{sublabel}</span> : null}
        {delta != null ? (
          <span style={{
            fontSize: 11, fontWeight: 600,
            color: deltaTone === 'down' ? 'var(--bx-error)' : deltaTone === 'flat' ? 'var(--bx-text-muted)' : 'var(--bx-success)',
            fontVariantNumeric: 'tabular-nums',
            display: 'inline-flex', alignItems: 'center', gap: 2,
          }}>
            <Icon name={deltaTone === 'down' ? 'arrowDown' : deltaTone === 'flat' ? 'minus' : 'arrowUp'} size={9}/>
            {delta}
          </span>
        ) : null}
      </div>
      {hint ? <div style={{ fontSize: 11, color: 'var(--bx-text-muted)', marginTop: 4 }}>{hint}</div> : null}
    </div>
  );
}

// ---------- Number / fmt helpers ----------
const fmt = {
  int: (n) => (n == null ? '—' : Number(n).toLocaleString('vi-VN')),
  pct: (n) => (n == null ? '—' : (n <= 1 ? (n * 100).toFixed(1) + '%' : n.toFixed(1) + '%')),
  signed: (n) => (n == null ? '—' : (n > 0 ? '+' : '') + Number(n).toLocaleString('vi-VN')),
  hrs: (n) => (n == null ? '—' : Number(n).toFixed(1) + 'h'),
  date: (iso) => {
    if (!iso) return '—';
    const d = new Date(iso);
    const dd = String(d.getDate()).padStart(2,'0');
    const mm = String(d.getMonth()+1).padStart(2,'0');
    return dd + '/' + mm + '/' + d.getFullYear();
  },
  shortDate: (iso) => {
    if (!iso) return '—';
    const d = new Date(iso);
    const dd = String(d.getDate()).padStart(2,'0');
    const mm = String(d.getMonth()+1).padStart(2,'0');
    return dd + '/' + mm;
  },
  dateTime: (iso) => {
    if (!iso) return '—';
    const d = new Date(iso);
    const dd = String(d.getDate()).padStart(2,'0');
    const mm = String(d.getMonth()+1).padStart(2,'0');
    const hh = String(d.getHours()).padStart(2,'0');
    const mi = String(d.getMinutes()).padStart(2,'0');
    return dd + '/' + mm + ' ' + hh + ':' + mi;
  },
  vnd: (n) => (n == null ? '—' : Number(n).toLocaleString('vi-VN') + 'đ'),
};

// Vietnamese weekday helper
function viWeekday(iso, short = true) {
  if (!iso) return '—';
  const d = new Date(iso);
  const map = short
    ? ['CN','T2','T3','T4','T5','T6','T7']
    : ['Chủ nhật','Thứ 2','Thứ 3','Thứ 4','Thứ 5','Thứ 6','Thứ 7'];
  return map[d.getDay()];
}

// ---------- Avatar ----------
function Avatar({ name, size = 28, color }) {
  const initials = (name || '?').split(' ').slice(-2).map(s => s[0]).join('').toUpperCase();
  const bg = color || '#eef2ff';
  const fg = color ? '#fff' : '#0b43ea';
  return (
    <div style={{
      width: size, height: size, borderRadius: '50%',
      background: bg, color: fg, fontWeight: 600,
      fontSize: size < 28 ? 10 : 11.5,
      display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
      flexShrink: 0,
    }}>{initials}</div>
  );
}

// expose
Object.assign(window, {
  Icon, FA_MAP, Button, IconBtn, Input, Field, Select,
  Badge, Card, Tabs, SectionTabs, Table, Dialog, Skeleton, Empty,
  Toast, Metric, fmt, Avatar, STATUS_COLORS, useViewport,
  VI_LABEL, viLabel, VI_ROLE, viRole, viWeekday,
});
