// ── Customizable KPI block — каждый сотрудник может настроить свой набор метрик ──
// Каталог KPI релевантных для отдела закупок (cross-functional)
// Сохранение конфигурации per-user в localStorage
const {useState: useStateK, useMemo: useMemoK, useEffect: useEffectK, useRef: useRefK} = React;

// ── KPI Catalog ──────────────────────────────────────────────
// Каждый виджет: id, title, group, calc(S, ctx) → {value, unit, delta, deltaTone, points, navTo}
// group: 'orders' | 'logistics' | 'finance' | 'inventory' | 'quality' | 'team'
const KPI_CATALOG = [
  // ─ ORDERS / ЗАКУПЩИК ─
  { id:'orders_active', title:'Активных заказов', group:'orders', color:'#2563eb',
    desc:'Открытые заявки в работе',
    calc:(S)=>{
      const inv = (S.inv||[]).filter(i=>!['rcvd','todo'].includes(i.status));
      return {value: inv.length, delta:'+5.02%', deltaTone:'up',
        points:[{v:42},{v:51},{v:57},{v:inv.length||60}], navTo:'inv'};
    }},
  { id:'orders_new_sku', title:'Новых SKU', group:'orders', color:'#16a34a',
    desc:'Добавлено в номенклатуру за период',
    calc:(S, ctx)=> ({value: ctx?.stats?.newSku||14, delta:'+8', deltaTone:'up',
      points:[{v:4},{v:8},{v:11},{v:ctx?.stats?.newSku||14}], navTo:'nom'})},
  { id:'orders_avg_lead', title:'Цикл SKU', group:'orders', color:'#0ea5e9', unit:' дн',
    desc:'Среднее время от заказа до приёма',
    calc:()=>({value:42, delta:'−3 дн', deltaTone:'up',
      points:[{v:48},{v:46},{v:44},{v:42}], navTo:'nom'})},
  { id:'orders_suppliers', title:'Поставщиков', group:'orders', color:'#8b5cf6',
    desc:'Активных фабрик в работе',
    calc:()=>({value:18, delta:'+2', deltaTone:'up',
      points:[{v:14},{v:16},{v:17},{v:18}], navTo:'sup'})},

  // ─ LOGISTICS / ЛОГИСТ ─
  { id:'log_in_transit', title:'Фур в пути', group:'logistics', color:'#0d9488', unit:' шт',
    desc:'Транзит / таможня / распределение',
    calc:(S)=>{
      const v = (S.fur||[]).filter(f=>['transit','customs','distrib','В пути до границы с РФ','В пути на склад РФ'].includes(f.status)).length;
      return {value: v||9, delta:'+1', deltaTone:'flat',
        points:[{v:7},{v:8},{v:9},{v:v||9}], navTo:'fur'};
    }},
  { id:'log_delays', title:'Задержки фур', group:'logistics', color:'#db2777', unit:' шт',
    desc:'Превышение планового срока',
    calc:(S, ctx)=>({value: ctx?.stats?.delays||1, delta:'−3.58%', deltaTone:'up',
      points:[{v:3},{v:2},{v:2},{v:ctx?.stats?.delays||1}], navTo:'fur'})},
  { id:'log_eta_acc', title:'Точность ETA', group:'logistics', color:'#10b981', unit:'%',
    desc:'Доля прибывших в срок',
    calc:()=>({value:87, delta:'+4.2%', deltaTone:'up',
      points:[{v:79},{v:82},{v:85},{v:87}], navTo:'fur'})},
  { id:'log_cost', title:'Логистика, ₽', group:'logistics', color:'#ea580c', unit:' млн',
    desc:'Затраты на перевозку',
    calc:(S)=>{
      const c = (S.fur||[]).reduce((a,f)=>a+(f.cost||0),0);
      return {value: +(c/1e6).toFixed(1)||14.2, delta:'−1.8%', deltaTone:'up',
        points:[{v:16.1},{v:15.3},{v:14.8},{v:+(c/1e6).toFixed(1)||14.2}], navTo:'fur'};
    }},
  { id:'log_kg_avg', title:'₽/кг средн.', group:'logistics', color:'#7c3aed',
    desc:'Стоимость доставки за килограмм',
    calc:()=>({value:98, delta:'−4 ₽', deltaTone:'up',
      points:[{v:108},{v:104},{v:101},{v:98}], navTo:'fur'})},

  // ─ FINANCE / ФИНАНСИСТ ─
  { id:'fin_turnover', title:'Оборот', group:'finance', color:'#7c3aed', unit:' ₽ млн',
    desc:'Сумма закупок за период',
    calc:(S)=>{
      const totalRub = (S.inv||[]).reduce((sum, i)=>{
        const cur = i.cur || '¥';
        const rate = typeof window.zkRate === 'function' ? window.zkRate(cur) : 1;
        return sum + ((i.price||0)*(i.qty||0)*rate);
      },0);
      const m = +(totalRub/1e6).toFixed(1);
      return {value:m, delta:`${(S.inv||[]).length} инв.`, deltaTone:'up',
        points:[{v:Math.max(0,m*0.7)},{v:Math.max(0,m*0.82)},{v:Math.max(0,m*0.93)},{v:m}], navTo:'fin'};
    }},
  { id:'fin_in_transit_money', title:'Деньги в пути', group:'finance', color:'#a78bfa', unit:' ₽ млн',
    desc:'Передоплаты + транзит',
    calc:(S)=>{
      const active = (S.inv||[]).filter(i=>!['rcvd','todo'].includes(i.status));
      const money = active.reduce((sum, i)=>{
        const cur = i.cur || '¥';
        const rate = typeof window.zkRate === 'function' ? window.zkRate(cur) : 1;
        const total = (i.price||0)*(i.qty||0)*rate;
        const paid = ((i.adv||0)+(i.sur||0)*rate);
        return sum + Math.max(0, total - paid);
      },0);
      const m = +(money/1e6).toFixed(1);
      return {value:m, delta:`${active.length} в работе`, deltaTone:'flat',
        points:[{v:Math.max(0,m*0.72)},{v:Math.max(0,m*0.84)},{v:Math.max(0,m*0.92)},{v:m}], navTo:'fin'};
    }},
  { id:'fin_overdue', title:'Просрочено к оплате', group:'finance', color:'#ef4444', unit:' ₽ млн',
    desc:'Долг по счетам поставщиков',
    calc:(S)=>{
      const overdue = (S.inv||[]).filter(i=>{
        const total=(i.price||0)*(i.qty||0); const paid=(i.adv||0)+(i.sur||0);
        return total>0 && paid<total && i.status!=='rcvd';
      }).length;
      return {value: 3.2, delta:`${overdue||2} счета`, deltaTone:'down',
        points:[{v:1.8},{v:2.4},{v:2.9},{v:3.2}], navTo:'fin'};
    }},
  { id:'fin_deposit', title:'Депозит свободно', group:'finance', color:'#14b8a6', unit:' ₽ млн',
    desc:'Остаток на расчётах',
    calc:()=>({value:18.6, delta:'−2.1 млн', deltaTone:'flat',
      points:[{v:24},{v:22},{v:20.7},{v:18.6}], navTo:'fin'})},
  { id:'fin_avg_invoice', title:'Средний инвойс', group:'finance', color:'#f59e0b', unit:' тыс ₽',
    desc:'Средняя сумма счёта поставщика',
    calc:()=>({value:485, delta:'+12%', deltaTone:'up',
      points:[{v:420},{v:445},{v:468},{v:485}], navTo:'inv'})},

  // ─ INVENTORY / СКЛАД ─
  { id:'inv_total_sku', title:'Всего SKU', group:'inventory', color:'#6366f1',
    desc:'Активные позиции в номенклатуре',
    calc:(S)=>({value:(S.nom||[]).length||60, delta:'+6', deltaTone:'up',
      points:[{v:48},{v:54},{v:58},{v:(S.nom||[]).length||60}], navTo:'nom'})},
  { id:'inv_red_zone', title:'SKU красная зона', group:'inventory', color:'#dc2626',
    desc:'Низкий остаток / дефицит',
    calc:(S)=>{
      const r = (S.nom||[]).filter(n=>{
        const b = window.nomBuffer?.(n.sku);
        return b && b.level==='red' && b.total>0;
      }).length;
      return {value: r||7, delta:'+2', deltaTone:'down',
        points:[{v:3},{v:5},{v:6},{v:r||7}], navTo:'nom'};
    }},
  { id:'inv_coverage', title:'Покрытие, дн', group:'inventory', color:'#0891b2', unit:' дн',
    desc:'Среднее покрытие склада спросом',
    calc:()=>({value:34, delta:'−2 дн', deltaTone:'down',
      points:[{v:38},{v:36},{v:35},{v:34}], navTo:'nom'})},
  { id:'inv_top_sku', title:'Топ SKU оборота', group:'inventory', color:'#fb923c',
    desc:'Доля топ-10 в обороте',
    calc:()=>({value:64, unit:'%', delta:'+2%', deltaTone:'up',
      points:[{v:58},{v:61},{v:62},{v:64}], navTo:'nom'})},

  // ─ QUALITY / ИНСПЕКТОР ─
  { id:'qa_defect_rate', title:'Брак при приёмке', group:'quality', color:'#e11d48', unit:'%',
    desc:'Доля забракованного товара',
    calc:()=>({value:1.4, delta:'−0.6%', deltaTone:'up',
      points:[{v:2.4},{v:2.0},{v:1.7},{v:1.4}], navTo:'fur'})},
  { id:'qa_inspections', title:'Инспекций КНР', group:'quality', color:'#0891b2',
    desc:'Проведено за период',
    calc:()=>({value:23, delta:'+5', deltaTone:'up',
      points:[{v:14},{v:18},{v:21},{v:23}], navTo:'fur'})},
  { id:'qa_rejected', title:'Возвратов фабрике', group:'quality', color:'#9333ea',
    desc:'Партий не принято',
    calc:()=>({value:2, delta:'−1', deltaTone:'up',
      points:[{v:5},{v:4},{v:3},{v:2}], navTo:'fur'})},

  // ─ TEAM / КОМАНДА ─
  { id:'team_active_tasks', title:'Открытых задач', group:'team', color:'#8b5cf6',
    desc:'У команды на сейчас',
    calc:()=>({value:31, delta:'+4', deltaTone:'flat',
      points:[{v:24},{v:27},{v:29},{v:31}], navTo:'team'})},
  { id:'team_workload', title:'Загрузка команды', group:'team', color:'#22c55e', unit:'%',
    desc:'Средний % утилизации',
    calc:()=>({value:78, delta:'+6%', deltaTone:'up',
      points:[{v:68},{v:72},{v:75},{v:78}], navTo:'team'})},
];

const KPI_GROUPS = {
  orders:    {label:'Заказы',     color:'#2563eb', icon:'box'},
  logistics: {label:'Логистика',  color:'#0d9488', icon:'truck'},
  finance:   {label:'Финансы',    color:'#7c3aed', icon:'wallet'},
  inventory: {label:'Склад',      color:'#6366f1', icon:'package'},
  quality:   {label:'Качество',   color:'#e11d48', icon:'shield'},
  team:      {label:'Команда',    color:'#8b5cf6', icon:'users'},
};

// ── Пресеты по ролям ──
const ROLE_PRESETS = {
  owner:    ['fin_turnover','orders_active','inv_red_zone','log_delays'],
  manager:  ['fin_turnover','orders_active','inv_red_zone','log_delays'],
  buyer:    ['orders_active','orders_new_sku','orders_avg_lead','inv_red_zone'],
  logist:   ['log_in_transit','log_delays','log_eta_acc','log_cost'],
  finance:  ['fin_turnover','fin_overdue','fin_in_transit_money','fin_avg_invoice'],
  warehouse:['inv_total_sku','inv_red_zone','inv_coverage','log_in_transit'],
  inspector:['qa_defect_rate','qa_inspections','qa_rejected','log_in_transit'],
};

// ── Storage ──
const KPI_STORE_KEY = 'zk_dash_kpis_v1';
function loadKpiCfg(userId, role){
  try{
    const all = JSON.parse(localStorage.getItem(KPI_STORE_KEY)||'{}');
    if(all[userId]) return all[userId];
  }catch(e){}
  return ROLE_PRESETS[role] || ROLE_PRESETS.owner;
}
function saveKpiCfg(userId, ids){
  try{
    const all = JSON.parse(localStorage.getItem(KPI_STORE_KEY)||'{}');
    all[userId] = ids;
    localStorage.setItem(KPI_STORE_KEY, JSON.stringify(all));
  }catch(e){}
}

// ── Customizable KPI Row ──────────────────────────────────────
function CustomKpiRow({onNavigate, stats}){
  const user = window.AUTH_USER || {id:'u1', role:'owner'};
  const userId = user.id || 'u1';
  const role = user.role || 'owner';

  const [ids,setIds] = useStateK(()=>loadKpiCfg(userId, role));
  const [editOpen,setEditOpen] = useStateK(false);
  const S = window.ZK_STORE?.get?.() || window.S || {};

  const widgets = useMemoK(()=>{
    return ids.map(id => KPI_CATALOG.find(k=>k.id===id)).filter(Boolean);
  },[ids]);

  const apply = (newIds) => {
    setIds(newIds);
    saveKpiCfg(userId, newIds);
  };

  return (
    <>
      {/* Header strip with edit button */}
      <div className="d2-kpi-strip">
        <div className="d2-kpi-strip-l">
          <span className="d2-kpi-strip-title">Мои показатели</span>
          <span className="d2-kpi-strip-role">· {roleLabel(role)}</span>
        </div>
        <button className="fl-btn d2-kpi-strip-btn" onClick={()=>setEditOpen(true)}>
          <Icon name="settings" size={12}/> Настроить ({widgets.length})
        </button>
      </div>

      {/* KPI cards grid */}
      <div className={`d2-mks d2-mks-${Math.min(widgets.length,6)}`}>
        {widgets.map(w => {
          const r = w.calc(S, {stats}) || {};
          const unit = r.unit || w.unit || '';
          return (
            <MiniKpi
              key={w.id}
              title={w.title}
              unit={unit}
              delta={r.delta}
              deltaTone={r.deltaTone||'flat'}
              color={w.color}
              points={r.points || [{v:1},{v:2},{v:3},{v:4}]}
              onClick={()=>r.navTo && onNavigate(r.navTo)}
              forcedValue={r.value}
            />
          );
        })}
        {widgets.length===0 && (
          <div className="d2-kpi-empty">
            <Icon name="layers" size={26} style={{color:'var(--ink4)'}}/>
            <div style={{fontWeight:600,fontSize:13,marginTop:8}}>Нет выбранных показателей</div>
            <div style={{fontSize:12,color:'var(--ink4)',margin:'2px 0 10px'}}>Выберите 3–6 виджетов из каталога</div>
            <button className="fl-btn primary" onClick={()=>setEditOpen(true)}>
              <Icon name="plus" size={12}/> Настроить
            </button>
          </div>
        )}
      </div>

      {editOpen && (
        <KpiEditModal
          currentIds={ids}
          role={role}
          onClose={()=>setEditOpen(false)}
          onApply={(next)=>{ apply(next); setEditOpen(false); window.pushToast?.({icon:'check', text:'Дашборд обновлён'}); }}
          onResetRole={()=>{ apply(ROLE_PRESETS[role]||ROLE_PRESETS.owner); setEditOpen(false); window.pushToast?.({icon:'refresh', text:'Применён пресет роли'}); }}
        />
      )}
    </>
  );
}

function roleLabel(r){
  return ({owner:'Владелец',manager:'Руководитель',buyer:'Закупщик',logist:'Логист',finance:'Финансист',warehouse:'Склад',inspector:'Инспектор'}[r])||'Сотрудник';
}

// ── Edit Modal ──────────────────────────────────────────────
function KpiEditModal({currentIds, role, onClose, onApply, onResetRole}){
  const [picked,setPicked] = useStateK([...currentIds]);
  const [filter,setFilter] = useStateK('all'); // all | <group>
  const [dragId,setDragId] = useStateK(null);

  const filtered = useMemoK(()=>{
    if(filter==='all') return KPI_CATALOG;
    return KPI_CATALOG.filter(k=>k.group===filter);
  },[filter]);

  const toggle = (id) => {
    setPicked(p => p.includes(id) ? p.filter(x=>x!==id) : (p.length<6 ? [...p, id] : p));
  };
  const remove = (id) => setPicked(p=>p.filter(x=>x!==id));

  // drag-reorder
  const onDrop = (toId) => {
    if(!dragId || dragId===toId) return;
    const a = picked.indexOf(dragId), b = picked.indexOf(toId);
    if(a<0||b<0) return;
    const next = [...picked];
    next.splice(a,1);
    next.splice(b,0,dragId);
    setPicked(next);
    setDragId(null);
  };

  useEffectK(()=>{
    const onKey = e => { if(e.key==='Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return ()=>window.removeEventListener('keydown', onKey);
  },[onClose]);

  return (
    <div className="kpi-modal-bg" onClick={onClose}>
      <div className="kpi-modal" onClick={e=>e.stopPropagation()}>
        <div className="kpi-modal-hdr">
          <div>
            <h3>Настройка показателей</h3>
            <div className="sub">Выберите 3–6 KPI для главного экрана. Перетаскивайте, чтобы изменить порядок.</div>
          </div>
          <button className="kpi-x" onClick={onClose}><Icon name="x" size={16}/></button>
        </div>

        <div className="kpi-modal-body">
          {/* LEFT: selected list */}
          <div className="kpi-selected">
            <div className="kpi-sel-hdr">
              <span>Выбрано</span>
              <span className="kpi-count">{picked.length}/6</span>
            </div>
            {picked.length===0 && <div className="kpi-sel-empty">Пока ничего не выбрано — кликните на карточки справа.</div>}
            <div className="kpi-sel-list">
              {picked.map((id,ix) => {
                const w = KPI_CATALOG.find(k=>k.id===id);
                if(!w) return null;
                const g = KPI_GROUPS[w.group];
                return (
                  <div
                    key={id}
                    className={`kpi-sel-row ${dragId===id?'dragging':''}`}
                    draggable
                    onDragStart={()=>setDragId(id)}
                    onDragOver={e=>{e.preventDefault();}}
                    onDrop={()=>onDrop(id)}
                    onDragEnd={()=>setDragId(null)}
                  >
                    <span className="kpi-sel-grip"><Icon name="grip" size={11}/></span>
                    <span className="kpi-sel-num">{ix+1}</span>
                    <span className="kpi-sel-dot" style={{background:w.color}}/>
                    <span className="kpi-sel-name">
                      <b>{w.title}</b>
                      <span className="kpi-sel-grp" style={{color:g?.color}}>{g?.label}</span>
                    </span>
                    <button className="kpi-sel-rm" onClick={()=>remove(id)} title="Убрать">
                      <Icon name="x" size={12}/>
                    </button>
                  </div>
                );
              })}
            </div>
          </div>

          {/* RIGHT: catalog */}
          <div className="kpi-catalog">
            <div className="kpi-cat-tabs">
              <button className={filter==='all'?'on':''} onClick={()=>setFilter('all')}>Все <span>{KPI_CATALOG.length}</span></button>
              {Object.entries(KPI_GROUPS).map(([k,g])=>(
                <button key={k} className={filter===k?'on':''} onClick={()=>setFilter(k)}>
                  <span className="kpi-cat-dot" style={{background:g.color}}/>
                  {g.label}
                  <span>{KPI_CATALOG.filter(c=>c.group===k).length}</span>
                </button>
              ))}
            </div>

            <div className="kpi-cat-grid">
              {filtered.map(w => {
                const on = picked.includes(w.id);
                const disabled = !on && picked.length>=6;
                const g = KPI_GROUPS[w.group];
                return (
                  <div
                    key={w.id}
                    className={`kpi-cat-card ${on?'on':''} ${disabled?'disabled':''}`}
                    onClick={()=>!disabled && toggle(w.id)}
                  >
                    <div className="kpi-cat-card-hdr">
                      <span className="kpi-cat-dot" style={{background:w.color}}/>
                      <span className="kpi-cat-grp" style={{color:g.color,background:g.color+'18'}}>{g.label}</span>
                      <span className="kpi-cat-check">
                        {on ? <Icon name="check" size={12}/> : (disabled ? <span style={{fontSize:10,color:'var(--ink4)'}}>лимит</span> : <Icon name="plus" size={12}/>)}
                      </span>
                    </div>
                    <div className="kpi-cat-title">{w.title}</div>
                    <div className="kpi-cat-desc">{w.desc}</div>
                  </div>
                );
              })}
            </div>
          </div>
        </div>

        <div className="kpi-modal-foot">
          <button className="fl-btn" onClick={onResetRole}>
            <Icon name="refresh" size={12}/> Пресет роли «{roleLabel(role)}»
          </button>
          <div style={{flex:1}}/>
          <button className="fl-btn" onClick={onClose}>Отмена</button>
          <button
            className="fl-btn primary"
            disabled={picked.length<3}
            onClick={()=>onApply(picked)}
          >
            Применить ({picked.length})
          </button>
        </div>
      </div>
    </div>
  );
}

// ── Stylesheet ────────────────────────────────────
(function injectKpiStyles(){
  if(document.getElementById('kpi-cust-styles')) return;
  const s = document.createElement('style');
  s.id = 'kpi-cust-styles';
  s.textContent = `
.d2-kpi-strip{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:0 0 10px;padding:0 2px}
.d2-kpi-strip-l{display:flex;align-items:baseline;gap:6px;flex-wrap:wrap}
.d2-kpi-strip-title{font-size:12px;font-weight:600;color:var(--ink2);letter-spacing:.2px;text-transform:uppercase}
.d2-kpi-strip-role{font-size:11.5px;color:var(--ink4)}
.d2-kpi-strip-btn{font-size:11.5px;padding:5px 10px;display:inline-flex;align-items:center;gap:5px}

/* responsive grid for variable count */
.d2-mks-3{grid-template-columns:repeat(3,1fr)}
.d2-mks-4{grid-template-columns:repeat(4,1fr)}
.d2-mks-5{grid-template-columns:repeat(5,1fr)}
.d2-mks-6{grid-template-columns:repeat(6,1fr)}
@media(max-width:1280px){
  .d2-mks-5,.d2-mks-6{grid-template-columns:repeat(3,1fr)}
}
@media(max-width:1100px){
  .d2-mks-3,.d2-mks-4,.d2-mks-5,.d2-mks-6{grid-template-columns:repeat(2,1fr)}
}

.d2-kpi-empty{
  grid-column:1/-1;background:var(--panel);border:1.5px dashed var(--line);
  border-radius:12px;padding:28px;display:flex;flex-direction:column;align-items:center;justify-content:center;
}

/* ─ Modal ─ */
.kpi-modal-bg{
  position:fixed;inset:0;background:rgba(15,23,42,.45);backdrop-filter:blur(2px);
  z-index:200;display:flex;align-items:center;justify-content:center;padding:24px;
  animation:kpiFade .15s ease;
}
@keyframes kpiFade{from{opacity:0}to{opacity:1}}
.kpi-modal{
  background:var(--panel);border:1px solid var(--line);border-radius:14px;
  box-shadow:0 20px 60px rgba(15,23,42,.25);
  width:100%;max-width:1040px;max-height:88vh;display:flex;flex-direction:column;
  animation:kpiPop .2s cubic-bezier(.2,.9,.3,1.1);
}
@keyframes kpiPop{from{opacity:0;transform:scale(.96)}to{opacity:1;transform:scale(1)}}

.kpi-modal-hdr{display:flex;justify-content:space-between;align-items:flex-start;
  padding:16px 18px;border-bottom:1px solid var(--line);gap:12px}
.kpi-modal-hdr h3{margin:0;font-size:15px;font-weight:600}
.kpi-modal-hdr .sub{font-size:12px;color:var(--ink4);margin-top:2px}
.kpi-x{background:transparent;border:0;cursor:pointer;color:var(--ink3);padding:4px;border-radius:6px}
.kpi-x:hover{background:var(--panel2);color:var(--ink)}

.kpi-modal-body{display:grid;grid-template-columns:300px 1fr;gap:0;flex:1;min-height:0;overflow:hidden}

/* Selected pane */
.kpi-selected{padding:14px;border-right:1px solid var(--line);display:flex;flex-direction:column;min-height:0;background:var(--panel2)}
.kpi-sel-hdr{display:flex;justify-content:space-between;align-items:center;
  font-size:11px;color:var(--ink4);text-transform:uppercase;letter-spacing:.4px;
  font-weight:600;margin-bottom:8px;padding:0 2px}
.kpi-count{background:var(--panel);border:1px solid var(--line);border-radius:6px;padding:2px 7px;font-size:11px;color:var(--ink2)}
.kpi-sel-empty{font-size:12px;color:var(--ink4);padding:24px 8px;text-align:center;background:var(--panel);border:1px dashed var(--line);border-radius:8px}
.kpi-sel-list{display:flex;flex-direction:column;gap:6px;overflow:auto;padding-bottom:6px}
.kpi-sel-row{
  display:flex;align-items:center;gap:8px;padding:8px 8px 8px 4px;
  background:var(--panel);border:1px solid var(--line);border-radius:8px;
  cursor:grab;transition:.12s;font-size:12px;
}
.kpi-sel-row:hover{border-color:var(--brand);box-shadow:var(--sh1)}
.kpi-sel-row.dragging{opacity:.4;cursor:grabbing}
.kpi-sel-grip{color:var(--ink4);display:flex}
.kpi-sel-num{font-size:10px;color:var(--ink4);font-weight:700;width:14px;text-align:center}
.kpi-sel-dot{width:8px;height:8px;border-radius:2px;flex-shrink:0}
.kpi-sel-name{flex:1;min-width:0;display:flex;flex-direction:column;line-height:1.2}
.kpi-sel-name b{font-weight:500;color:var(--ink);font-size:12px}
.kpi-sel-grp{font-size:10.5px;font-weight:500}
.kpi-sel-rm{background:transparent;border:0;cursor:pointer;color:var(--ink4);padding:3px;border-radius:4px;display:flex}
.kpi-sel-rm:hover{background:var(--err);color:#fff}

/* Catalog pane */
.kpi-catalog{display:flex;flex-direction:column;min-height:0}
.kpi-cat-tabs{display:flex;gap:4px;padding:12px 14px 10px;border-bottom:1px solid var(--line);overflow-x:auto;flex-shrink:0}
.kpi-cat-tabs button{
  background:transparent;border:1px solid transparent;border-radius:7px;padding:5px 9px;
  font-size:11.5px;color:var(--ink3);cursor:pointer;display:inline-flex;align-items:center;gap:5px;
  white-space:nowrap;transition:.12s;
}
.kpi-cat-tabs button:hover{background:var(--panel2)}
.kpi-cat-tabs button.on{background:var(--panel2);border-color:var(--line);color:var(--ink);font-weight:500}
.kpi-cat-tabs button span:last-child{font-size:10.5px;color:var(--ink4);background:var(--panel);padding:1px 6px;border-radius:4px;border:1px solid var(--line)}
.kpi-cat-dot{width:7px;height:7px;border-radius:2px}

.kpi-cat-grid{
  display:grid;grid-template-columns:repeat(2,1fr);gap:10px;padding:14px;overflow:auto;
}
.kpi-cat-card{
  background:var(--panel);border:1.5px solid var(--line);border-radius:9px;padding:12px;
  cursor:pointer;transition:.12s;display:flex;flex-direction:column;gap:5px;
}
.kpi-cat-card:hover:not(.disabled){border-color:var(--brand);transform:translateY(-1px);box-shadow:var(--sh1)}
.kpi-cat-card.on{border-color:var(--brand);background:linear-gradient(180deg,#f5f3ff 0%,var(--panel) 100%)}
.kpi-cat-card.disabled{opacity:.45;cursor:not-allowed}
.kpi-cat-card-hdr{display:flex;align-items:center;gap:6px;margin-bottom:2px}
.kpi-cat-grp{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.3px;padding:2px 6px;border-radius:4px}
.kpi-cat-check{margin-left:auto;width:18px;height:18px;border-radius:50%;
  background:var(--panel2);border:1px solid var(--line);display:flex;align-items:center;justify-content:center;color:var(--ink4)}
.kpi-cat-card.on .kpi-cat-check{background:var(--brand);border-color:var(--brand);color:#fff}
.kpi-cat-title{font-size:13px;font-weight:600;color:var(--ink)}
.kpi-cat-desc{font-size:11.5px;color:var(--ink4);line-height:1.35}

.kpi-modal-foot{
  display:flex;align-items:center;gap:8px;padding:12px 16px;border-top:1px solid var(--line);
  background:var(--panel2);border-radius:0 0 14px 14px;
}
.fl-btn.primary{background:var(--brand);color:#fff;border-color:var(--brand)}
.fl-btn.primary:hover{background:var(--brand-d, #5b21b6)}
.fl-btn.primary:disabled{opacity:.4;cursor:not-allowed}

@media(max-width:880px){
  .kpi-modal-body{grid-template-columns:1fr}
  .kpi-selected{border-right:0;border-bottom:1px solid var(--line);max-height:30vh}
  .kpi-cat-grid{grid-template-columns:1fr}
}
  `;
  document.head.appendChild(s);
})();

// expose
window.CustomKpiRow = CustomKpiRow;
window.KPI_CATALOG = KPI_CATALOG;
window.KPI_GROUPS = KPI_GROUPS;
window.ROLE_PRESETS = ROLE_PRESETS;
