/* ============================================================
   Omnitok Analytics — Monitor de Implementación (admin)
   Matriz SKU×Retail con semáforo por días sin visita, panel de
   alertas, cola de acción y export Excel. Datos: /impl/* del
   materializer (D1 impl_pairs, refresco cada 2h).
   ============================================================ */
const { useState:iS, useMemo:iM, useEffect:iE, useRef:iR, useCallback:iC } = React;
const h = React.createElement;

/* ---- severidad ---- */
const MON_SEV_LBL = ()=>({
  ok: OA.lang==='es'?'Fresco':'Fresh',
  warn: OA.lang==='es'?'Atención':'Attention',
  crit: OA.lang==='es'?'Crítico':'Critical',
  critdark: OA.lang==='es'?'Vencido':'Overdue',
  noimpl: OA.lang==='es'?'No implementado':'Not implemented',
});
const MON_FMT_DH = (hours)=> hours==null?'—':(hours<24?hours+' h':Math.floor(hours/24)+'d '+(hours%24)+'h');
/* motivo dominante de "no implementado" por HOST (fase 1 Cobertura): misma
   precedencia server-side de /impl/coverage. Filtro VISUAL: no toca números. */
const MON_MOTIVO_LBL=(es)=>({
  all: es?'Todos':'All',
  no_events: es?'Sin entregas registradas':'No deliveries recorded',
  no_url: es?'Sin URL detectada':'No URL detected',
  no_script: es?'No acepta script':'No script accepted',
  host_pending: es?'Host pendiente':'Host pending',
  out_of_scope: es?'Fuera de alcance':'Out of scope',
});
const monHostMotivo=(rt)=> rt.in_scope===false?'out_of_scope'
  : rt.accepts_script==='no'?'no_script'
  : rt.pending?'host_pending':'no_events';
/* marca de disponibilidad (impl_overrides, escrita por la extensión/Yoda):
   badge APARTE del semáforo — la severidad sigue midiendo días sin visita */
const MON_OVR_LBL=(st,es)=> st==='oos'?(es?'Fuera de stock':'Out of stock'):(es?'No disponible':'Unavailable');
const monOvrBadge=(ovr,es)=> ovr?h('span',{className:'mon-ovr'+(ovr.status==='unavailable'?' mon-ovr-unav':''),
  title:MON_OVR_LBL(ovr.status,es)+' — '+(es?'marcado por':'marked by')+' '+(ovr.noted_by||'?')+
    (ovr.noted_at?' · '+new Date(ovr.noted_at).toLocaleString():'')+(ovr.source?' ('+ovr.source+')':'')},
  ovr.status==='oos'?'⛔':'∅'):null;
const monChkIcon=(chk,es)=> chk?h('span',{className:'mon-chk',
  title:(es?'Verificado por ':'Checked by ')+(chk.source==='extension'?(es?'la extensión':'the extension'):'inspector')+
    (chk.verified?' — OK':(es?' — con problemas':' — failed'))+
    (chk.checked_at?' · '+new Date(chk.checked_at).toLocaleString():'')},'☑'):null;
/* ficha multi-producto: misma ficha (brandSku) mostrada para N productos del
   retail (decisión de contenido). Badge ⧉N -> panel con las páginas. Con
   fam.urls (sin familia coherente) el badge es "el producto se ve en N urls
   del retail" — mismo panel, listado de todas las páginas del par. */
const monFamBadge=(fam,es,onOpen)=> fam?h('button',{className:'mon-fambdg',
  title:fam.urls
    ?(es?('El producto se muestra en '+fam.n+' URLs de este retail — clic para verlas')
        :('The product shows at '+fam.n+' URLs at this retailer — click to view'))
    :(es?('Esta ficha se muestra en '+fam.n+' productos de este retail — clic para ver el detalle')
        :('This content shows on '+fam.n+' products at this retailer — click for detail')),
  onClick:(e)=>{e.stopPropagation();onOpen();}},'⧉ ',fam.n):null;
/* badge del par: familia coherente si existe; si no, >=2 urls del pool */
const monPairBadge=(c)=> c.family || ((c.urls_n||0)>=2 ? {n:c.urls_n,urls:true} : null);
const monDispBadge=(es)=>h('span',{className:'mon-dispbdg',
  title:es?'Atribución dispersa: la ficha dispara en páginas sin coherencia con el producto — revisar el tag/embed de este retailer'
          :'Scattered attribution: the content fires on pages unrelated to the product — check this retailer’s tag'},'⚠ tag');
/* La url del par es el path dominante donde se desplegó la ficha (regla
   2026-07-30: la verificación textual del SKU es ranking, no gate). Para pares
   SIN eventos (noimpl) Yoda puede INFERIR la PDP por nombre (1er resultado de
   búsqueda) — esa url se muestra marcada como INFERIDA (estilo distinto). */
/* colores para el export Excel (bg, texto) — mismos del diseño aprobado */
const MON_XLS = { ok:['#E7F7EE','#12A150'], warn:['#FDF1DF','#B45309'], crit:['#FDEAEA','#DC2626'],
  critdark:['#8B1A1A','#FFFFFF'], noimpl:['#F3F2F8','#8B87A3'] };

/* ---- slider de 3 umbrales (g<r<rr, días) ---- */
function ThresholdSlider({th,setTh,onSave,dirty,saving}){
  const MAX=21; const ref=iR(null); const drag=iR(null);
  const pct=v=>v/MAX*100;
  const clamp=(t)=>{ const g=Math.max(1,Math.min(t.g,MAX)); const r=Math.max(g+1,Math.min(t.r,MAX)); const rr=Math.max(r+1,Math.min(t.rr,MAX)); return {g,r,rr}; };
  const fromX=(x)=>{ const rect=ref.current.getBoundingClientRect(); return Math.round(Math.max(0,Math.min(MAX,(x-rect.left)/rect.width*MAX))); };
  const onDown=(k)=>(e)=>{ drag.current=k; e.target.setPointerCapture(e.pointerId); e.preventDefault(); };
  const onMove=(e)=>{ if(!drag.current) return; setTh(t=>clamp({...t,[drag.current]:fromX(e.clientX)})); };
  const onUp=()=>{ drag.current=null; };
  const onKey=(k)=>(e)=>{ let d=0; if(e.key==='ArrowLeft'||e.key==='ArrowDown')d=-1; else if(e.key==='ArrowRight'||e.key==='ArrowUp')d=1; else return; e.preventDefault(); setTh(t=>clamp({...t,[k]:t[k]+d})); };
  const track=`linear-gradient(to right, var(--sev-ok) 0 ${pct(th.g)}%, var(--sev-warn) ${pct(th.g)}% ${pct(th.r)}%, var(--sev-crit) ${pct(th.r)}% ${pct(th.rr)}%, var(--sev-critdark) ${pct(th.rr)}% 100%)`;
  const handle=(k,cls)=>h('div',{key:k,className:'mon-th-handle '+cls,tabIndex:0,role:'slider','aria-valuenow':th[k],
    style:{left:pct(th[k])+'%'},onPointerDown:onDown(k),onKeyDown:onKey(k)},
    h('span',{className:'mon-th-bub'},th[k]+'d'));
  return h('div',{className:'mon-thctl'},
    h('div',{className:'mon-th-top'},
      h('span',{className:'filt-label'},OA.lang==='es'?'Umbrales de color · días sin visita':'Color thresholds · days without visit'),
      h('button',{className:'btn'+(dirty?' mon-dirty':''),style:{padding:'4px 12px',fontSize:12},onClick:onSave,disabled:!dirty||saving},
        saving?'…':(dirty?(OA.lang==='es'?'Guardar cambios':'Save changes'):(OA.lang==='es'?'Guardado':'Saved')))),
    h('div',{className:'mon-th-slider',ref,onPointerMove:onMove,onPointerUp:onUp,onPointerCancel:onUp},
      h('div',{className:'mon-th-track',style:{background:track}}),
      handle('g','g'),handle('r','r'),handle('rr','rr')),
    h('div',{className:'mon-legend'},
      [['ok',`< ${th.g}d`],['warn',`${th.g}–${th.r}d`],['crit',`${th.r}–${th.rr}d`],['critdark',`≥ ${th.rr}d`],['noimpl','']].map(([s,t])=>
        h('span',{key:s},h('span',{className:'mon-dot mon-bg-'+s}),MON_SEV_LBL()[s]+(t?' '+t:'')))));
}

/* ---- popover multiselect (retailers / categorías) ---- */
function MonMsel({label,all,items,sel,setSel}){
  const [open,setOpen]=iS(false); const ref=iR(null);
  iE(()=>{ const fn=(e)=>{ if(ref.current&&!ref.current.contains(e.target)) setOpen(false); }; document.addEventListener('mousedown',fn); return ()=>document.removeEventListener('mousedown',fn); },[]);
  const toggle=(v)=>{ const n=new Set(sel); n.has(v)?n.delete(v):n.add(v); setSel(n); };
  return h('div',{className:'mon-msel',ref},
    h('button',{className:'mon-msel-btn'+(sel.size?' has':''),onClick:()=>setOpen(o=>!o)},
      sel.size?`${sel.size} ${label.toLowerCase()}`:all),
    open&&h('div',{className:'mon-msel-pop'},
      items.map(it=>h('label',{key:it.v},
        h('input',{type:'checkbox',checked:sel.has(it.v),onChange:()=>toggle(it.v)}),
        ' ',it.t, it.meta?h('span',{className:'mon-cc'},it.meta):null))));
}

/* ---- export Excel (HTML .xls con colores; Excel respeta los estilos) ---- */
function monExportXls(lines, filename, matrixMode, nameOf){
  const esc=(s)=>String(s==null?'':s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
  const head=matrixMode
    ? ['Estado','SKU','Producto','Marca','Categoría','Retailer','País','Días sin visita','Vistas 7d (168h)','URL producto','Stock','Ficha']
    : ['Estado','SKU','Producto','Marca','Categoría','Retailer','País','Días sin visita','Vistas 7d','URL producto','Stock','Ficha'];
  let t='<table border="1" cellspacing="0"><tr>'+head.map(x=>`<th style="background:#4D4A9D;color:#fff;padding:4px">${esc(x)}</th>`).join('')+'</tr>';
  for(const p of lines){
    const [bg,fg]=MON_XLS[p.sev]||MON_XLS.noimpl;
    const cells=[MON_SEV_LBL()[p.sev]||p.sev,p.sku,p.name,p.brand,p.category,(nameOf?nameOf(p.host):p.host),p.country,
      p.sev==='noimpl'?'—':(p.days!=null?p.days:''),p.views_7d!=null?p.views_7d:'',
      // verificada > inferida (marcada) > vacío
      p.url||(p.url_inferred?p.url_inferred+' (INFERIDA)':''),
      p.override_status==='oos'?'Fuera de stock'+(p.override_by?' (por '+p.override_by+')':'')
        :p.override_status==='unavailable'?'No disponible':'',
      p.family_n>=2?('Multi-producto ('+p.family_n+' páginas)'):(p.dispersed?'Atribución dispersa':'')];
    t+='<tr>'+cells.map((v,i)=>`<td style="padding:3px;${i===0?`background:${bg};color:${fg};font-weight:bold`:''}">${esc(v)}</td>`).join('')+'</tr>';
  }
  t+='</table>';
  const blob=new Blob(['﻿<html xmlns:x="urn:schemas-microsoft-com:office:excel"><head><meta charset="utf-8"></head><body>'+t+'</body></html>'],{type:'application/vnd.ms-excel'});
  const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download=filename;
  document.body.appendChild(a); a.click(); a.remove(); setTimeout(()=>URL.revokeObjectURL(a.href),1000);
}

/* ============================ VISTA ============================ */
function MonitorView({account}){
  const es=OA.lang==='es';
  const acct = account && account!=='all' ? account : 'all';
  /* umbrales */
  const [th,setTh]=iS({g:1,r:4,rr:8});
  const [thSaved,setThSaved]=iS({g:1,r:4,rr:8});
  const [thSaving,setThSaving]=iS(false);
  const dirty = th.g!==thSaved.g||th.r!==thSaved.r||th.rr!==thSaved.rr;
  /* datos */
  const [data,setData]=iS(null);      // respuesta de /impl/matrix
  const [alerts,setAlerts]=iS(null);  // respuesta de /impl/alerts
  const [loading,setLoading]=iS(true);
  const [err,setErr]=iS(null);
  /* filtros/ui */
  const [tab,setTab]=iS('matrix');    // matrix | alerts | yoda
  const [yodaTick,setYodaTick]=iS(0); // re-render al llegar resultados de Yoda
  const [q,setQ]=iS(''); const [qLive,setQLive]=iS('');
  const [selRet,setSelRet]=iS(new Set());
  const [ovrOnly,setOvrOnly]=iS(false); // solo pares marcados Fuera de stock / No disp.
  const [motivo,setMotivo]=iS('all');   // chip de motivo noimpl (filtro visual client-side)
  const [page,setPage]=iS(1);
  const [alPage,setAlPage]=iS(1);
  const [alSev,setAlSev]=iS(new Set(['warn','crit','critdark']));
  const [apQueue,setApQueue]=iS(null);   // cola de acción: página propia de /impl/alerts
  const [apPage,setApPage]=iS(1);
  const [colsShown,setColsShown]=iS(8);
  const [apOpen,setApOpen]=iS(false);
  const [exporting,setExporting]=iS(false);
  const [famOpen,setFamOpen]=iS(null);   // {sku_key,sku,name,host} del panel ⧉
  const [famData,setFamData]=iS(null);   // respuesta de /impl/pair
  const scrollRef=iR(null);
  const PER=12, AL_PER=50;

  /* debounce de la búsqueda */
  iE(()=>{ const t=setTimeout(()=>{ setQ(qLive); setPage(1); },350); return ()=>clearTimeout(t); },[qLive]);

  /* resultados de Yoda: refrescar + cambiar a la pestaña al llegar uno nuevo;
     si se entró desde el botón del widget (openResults), abrir directo */
  iE(()=>{ const fn=()=>{ setYodaTick(t=>t+1); setTab('yoda'); };
    window.addEventListener('oa-yoda-result',fn);
    if(window.OAYoda&&window.OAYoda.openResults){ window.OAYoda.openResults=false; setTab('yoda'); }
    return ()=>window.removeEventListener('oa-yoda-result',fn); },[]);

  /* panel de ficha multi-producto: fetch del detalle al abrir */
  iE(()=>{ if(!famOpen){ setFamData(null); return; }
    let cancel=false;
    OA.impl('/impl/pair',{sku_key:famOpen.sku_key,host:famOpen.host})
      .then(d=>{ if(!cancel) setFamData(d); })
      .catch(()=>{ if(!cancel) setFamData({error:true}); });
    return ()=>{ cancel=true; }; },[famOpen&&famOpen.sku_key,famOpen&&famOpen.host]);

  /* cargar umbrales guardados al cambiar de cuenta */
  iE(()=>{ OA.impl('/impl/thresholds',{account:acct}).then(t=>{
      if(t&&t.g){ setTh({g:t.g,r:t.r,rr:t.rr}); setThSaved({g:t.g,r:t.r,rr:t.rr}); }
    }).catch(()=>{}); },[acct]);

  const saveTh=()=>{ setThSaving(true);
    OA.implPut('/impl/thresholds',{account:acct},th)
      .then(()=>{ setThSaved({...th}); }).catch(e=>setErr(String(e))).finally(()=>setThSaving(false)); };

  /* fetch de matrix + alerts (umbral con debounce propio para no spamear al arrastrar) */
  const thSig=`${th.g}|${th.r}|${th.rr}`;
  iE(()=>{
    const ctl={cancel:false};
    const t=setTimeout(()=>{
      setLoading(true); setErr(null);
      const base={account:acct,g:th.g,r:th.r,rr:th.rr};
      if(q) base.q=q;
      if(selRet.size) base.retailers=[...selRet].join(',');
      if(ovrOnly) base.override='oos,unavailable';
      Promise.all([
        OA.impl('/impl/matrix',{...base,page,per:PER,...(ovrOnly?{include:'present'}:{})}),
        // filtro de severidad SERVER-SIDE: paginación coherente con los chips
        OA.impl('/impl/alerts',{...base,page:alPage,per:AL_PER,severity:[...alSev].join(',')}),
        OA.impl('/impl/alerts',{...base,page:apPage,per:AL_PER}),
      ]).then(([m,a,q])=>{ if(ctl.cancel) return;
        setData(m); setAlerts(a); setApQueue(q); setLoading(false); setColsShown(8);
      }).catch(e=>{ if(ctl.cancel) return; setErr(String(e&&e.message||e)); setLoading(false); });
    },q!==qLive?0:250);
    return ()=>{ ctl.cancel=true; clearTimeout(t); };
  },[acct,thSig,q,[...selRet].join(','),ovrOnly,page,alPage,apPage,[...alSev].sort().join(',')]);

  /* lazy load lateral de columnas */
  const onScroll=iC(()=>{ const el=scrollRef.current; if(!el||!data) return;
    const max=(motivo==='all'||motivo==='no_url')?data.retailers.length
      :data.retailers.filter(rt=>monHostMotivo(rt)===motivo).length;
    if(el.scrollLeft+el.clientWidth>el.scrollWidth-260&&colsShown<max){
      setColsShown(n=>Math.min(n+6,max)); }
  },[data,colsShown,motivo]);

  const sevLbl=MON_SEV_LBL();
  const motivoLbl=MON_MOTIVO_LBL(es);
  const retailers=data?data.retailers:[];
  /* chips de motivo: filtran COLUMNAS por motivo del host; 'no_url' mantiene
     las columnas y atenúa los pares que SÍ tienen URL. Solo visual. */
  const retailersF=(motivo==='all'||motivo==='no_url')?retailers
    :retailers.filter(rt=>monHostMotivo(rt)===motivo);
  const shown=retailersF.slice(0,colsShown);
  // nombre del retail: primero el del mantenedor (viene en /impl/matrix),
  // fallback al summary (OA.rName solo conoce el top-N) y al host crudo
  const nameBy=iM(()=>{ const m={}; for(const r of retailers) if(r.name) m[r.host]=r.name; return m; },[retailers]);
  const rName=(hst)=> nameBy[hst] || (OA.rName?OA.rName(hst):hst);

  /* stats agregadas (suma sobre todos los hosts del scope) */
  const stats=iM(()=>{ const s={ok:0,warn:0,crit:0,critdark:0,noimpl:0,overrides:0};
    for(const r of retailers){ s.ok+=r.counts.ok; s.warn+=r.counts.warn; s.crit+=r.counts.crit; s.critdark+=r.counts.critdark; s.noimpl+=r.counts.noimpl; s.overrides+=r.counts.overrides||0; }
    return s; },[retailers]);

  /* export: baja el NDJSON del scope y arma el .xls */
  const doExport=(mode)=>{ if(exporting) return; setExporting(true);
    const params={account:acct,g:th.g,r:th.r,rr:th.rr,include:mode==='alerts'?'present':'all'};
    if(selRet.size) params.retailers=[...selRet].join(',');
    if(ovrOnly) params.override='oos,unavailable';
    OA.implStream('/impl/export',params,60000).then(lines=>{
      let rows=lines;
      if(mode==='alerts') rows=lines.filter(l=>['warn','crit','critdark'].includes(l.sev));
      rows=rows.slice(0,50000);
      monExportXls(rows,`omnitok_monitor_${mode}_${acct}.xls`,mode!=='alerts',rName);
    }).catch(e=>setErr(String(e))).finally(()=>setExporting(false));
  };

  const investigate=(sku,hst)=>{ if(window.OABot&&window.OABot.investigate) window.OABot.investigate(sku,hst);
    else alert(es?'El asistente estará disponible pronto':'Assistant coming soon'); };

  /* --------- render --------- */
  if(err&&!data) return h('div',{className:'card'},h('div',{className:'empty'},'⚠ ',String(err)));

  const retItems=retailers.map(r=>({v:r.host,t:rName(r.host),meta:(r.host||'').replace('www.','')+(r.country?' · '+r.country:'')}));

  /* fila de alerta / cola de acción */
  const alertRow=(a,i)=>h('tr',{key:a.sku+a.host+i},
    h('td',null,h('span',{className:'mon-chipst mon-'+a.sev},sevLbl[a.sev]),a.override?' ':null,monOvrBadge(a.override,es),
      monPairBadge(a)?' ':null,monFamBadge(monPairBadge(a),es,()=>setFamOpen({sku_key:a.sku_key,sku:a.sku,name:a.name,host:a.host})),
      a.dispersed?' ':null,a.dispersed?monDispBadge(es):null),
    h('td',{className:'mon-num',style:{fontWeight:800}},a.sku),
    h('td',null,h('span',{className:'mon-name',title:a.name||a.sku},a.name||a.sku)),
    h('td',null,h('span',{className:'mon-pill'},a.brand||'—')),
    h('td',null,rName(a.host),' ',h('span',{className:'mon-cc'},a.country||'')),
    h('td',{className:'mon-num'},MON_FMT_DH(a.hours)),
    h('td',null,a.url
      ?h('a',{className:'mon-btn-xs',href:a.url,target:'_blank',rel:'noopener'},es?'Abrir ↗':'Open ↗')
      :a.url_inferred
        ?h('a',{className:'mon-btn-xs mon-inf',href:a.url_inferred,target:'_blank',rel:'noopener',
            title:es?'URL INFERIDA por nombre — no verificada':'INFERRED URL by name — not verified'},es?'Inferida ≈↗':'Inferred ≈↗')
        :h('span',{className:'mon-cc',title:es?'Pídele a Yoda inferir la URL (botón Investigar)':'Ask Yoda to infer the URL'},es?'sin URL':'no URL')),
    h('td',null,h('button',{className:'mon-btn-xs',onClick:()=>investigate(a.sku,a.host)},es?'Investigar 🔎':'Investigate 🔎')));

  return h('div',null,
    /* aviso de alcance */
    h('div',{className:'note-bar',style:{marginBottom:12}},h(Icon.info,null),
      h('span',null,es
        ?`Universo: SKUs del catálogo con presencia en retail (90 días). "${sevLbl.noimpl}" = SKU activo sin visita en un host donde la marca sí aparece. Datos refrescados cada 2 h.`
        :`Universe: catalog SKUs with retail presence (90 days). "${sevLbl.noimpl}" = active SKU unseen at a host where the brand appears. Data refreshes every 2 h.`),
      data&&h('b',{style:{marginLeft:6,color:'#525252'}},
        (data.universe.active_skus||0).toLocaleString()+' SKUs con presencia · de '+(data.universe.skus||0).toLocaleString()+' del catálogo · '+(data.universe.hosts||0)+' retailers')),

    /* stats */
    h('div',{className:'mon-stats'},
      [['critdark',stats.critdark],['crit',stats.crit],['warn',stats.warn],['ok',stats.ok],['noimpl',stats.noimpl]].map(([s,n])=>
        h('div',{key:s,className:'mon-stat'},
          h('span',{className:'mon-dot mon-bg-'+s}),
          h('b',{className:'mon-num'},n.toLocaleString()),
          h('span',null,sevLbl[s]))),
      stats.overrides>0&&h('div',{className:'mon-stat'},
        h('span',{className:'mon-dot mon-bg-oos'}),
        h('b',{className:'mon-num'},stats.overrides.toLocaleString()),
        h('span',null,es?'Fuera de stock / No disp.':'OOS / Unavailable')),
      h('div',{style:{marginLeft:'auto',display:'flex',gap:8}},
        h('button',{className:'btn',onClick:()=>doExport('matrix'),disabled:exporting},exporting?'…':(es?'⬇ Excel completo':'⬇ Full Excel')),
        h('button',{className:'btn',onClick:()=>doExport('alerts'),disabled:exporting},es?'⬇ Excel de alertas':'⬇ Alerts Excel'))),

    /* controles */
    h('div',{className:'card',style:{padding:'12px 16px',marginBottom:12,display:'flex',gap:14,alignItems:'flex-end',flexWrap:'wrap'}},
      h('div',{className:'mon-ctl'},h('span',{className:'filt-label'},es?'Buscar SKU / nombre':'Search SKU / name'),
        h('input',{className:'mon-input',value:qLive,placeholder:'1F3Y2A…',onChange:e=>setQLive(e.target.value)})),
      h('div',{className:'mon-ctl'},h('span',{className:'filt-label'},'Retailers'),
        h(MonMsel,{label:'retailers',all:es?'Todos los retailers':'All retailers',items:retItems,sel:selRet,setSel:(s)=>{setSelRet(s);setPage(1);}})),
      h('div',{className:'mon-ctl'},h('span',{className:'filt-label'},es?'Disponibilidad':'Availability'),
        h('button',{className:'mon-chip'+(ovrOnly?' on':''),
          title:es?'Solo pares marcados "Fuera de stock" o "Producto no disponible" desde la extensión':'Only pairs marked "Out of stock" or "Unavailable" via the extension',
          onClick:()=>{setOvrOnly(v=>!v);setPage(1);setAlPage(1);setApPage(1);}},
          h('span',{className:'mon-dot mon-bg-oos'}),es?'Solo OOS / No disp.':'Only OOS / Unavail.')),
      h(ThresholdSlider,{th,setTh,onSave:saveTh,dirty,saving:thSaving}),
      /* chips de motivo noimpl (fase 1 Cobertura): filtro VISUAL, los números no cambian */
      h('div',{className:'mon-ctl',style:{flexBasis:'100%'}},
        h('span',{className:'filt-label'},es?'Motivo (no implementado)':'Reason (not implemented)'),
        h('div',{className:'mon-chips'},['all','no_events','no_url','no_script','host_pending','out_of_scope'].map(k=>{
          if(k==='out_of_scope'&&!retailers.some(rt=>rt.in_scope===false)) return null; // sin alcance configurado
          const cnt=(k==='all'||k==='no_url')?null
            :retailers.filter(rt=>monHostMotivo(rt)===k).reduce((a,rt)=>a+(rt.counts.noimpl||0),0);
          return h('button',{key:k,className:'mon-chip'+(motivo===k?' on':''),
            title:k==='no_url'
              ?(es?'Atenúa los pares que SÍ tienen URL: resalta presentes sin URL verificada ni inferida':'Dims pairs WITH a URL: highlights present pairs without any URL')
              :(es?'Filtra las columnas de retailers por motivo dominante de sus "no implementado"':'Filters retailer columns by their dominant not-implemented reason'),
            onClick:()=>setMotivo(m=>m===k?'all':k)},
            motivoLbl[k],(cnt!=null&&cnt>0)?h('span',{className:'mon-cc',style:{marginLeft:4}},cnt.toLocaleString()):null);
        })))),

    /* tabs */
    h('div',{style:{display:'flex',gap:10,alignItems:'center',margin:'0 0 10px'}},
      h('div',{className:'mon-tabs'},
        h('button',{className:'mon-tab'+(tab==='matrix'?' on':''),onClick:()=>setTab('matrix')},'▦ ',es?'Matriz':'Matrix'),
        h('button',{className:'mon-tab'+(tab==='alerts'?' on':''),onClick:()=>setTab('alerts')},'🚨 ',es?'Panel de alertas':'Alerts panel',
          alerts&&h('span',{className:'mon-cnt'},(alerts.totals.warn+alerts.totals.crit+alerts.totals.critdark).toLocaleString())),
        h('button',{className:'mon-tab'+(tab==='yoda'?' on':''),onClick:()=>setTab('yoda')},'🧙 ',
          es?'Resultados de Yoda':'Yoda results',
          (window.OAYoda&&window.OAYoda.results.length)?h('span',{className:'mon-cnt',style:{background:'var(--g100)',color:'var(--primary)'}},window.OAYoda.results.length):null))),

    loading&&h('div',{className:'card'},h('div',{className:'empty'},h('div',{className:'spinner'}),es?'Cargando…':'Loading…')),

    /* ============ MATRIZ ============ */
    !loading&&tab==='matrix'&&data&&h('div',{className:'card',style:{padding:0,overflow:'hidden'}},
      h('div',{className:'mon-scroll',ref:scrollRef,onScroll},
        h('table',{className:'mon-tbl'},
          h('thead',null,h('tr',null,
            h('th',{className:'mon-sticky mon-c1'},'SKU'),
            h('th',{className:'mon-sticky mon-c2'},es?'Marca':'Brand'),
            h('th',{className:'mon-sticky mon-c3'},es?'Nombre':'Name'),
            shown.map(rt=>h('th',{key:rt.host,className:'mon-rcol'},
              h('span',{className:'mon-rname'},rName(rt.host)),
              h('span',{className:'mon-rmeta'},(rt.host||'').replace('www.','')+(rt.country?' · '+rt.country:'')),
              (rt.counts.crit+rt.counts.critdark)>0&&h('span',{className:'mon-ralert'},rt.counts.crit+rt.counts.critdark))),
            colsShown<retailersF.length&&h('th',{className:'mon-lazy'},
              h('button',{className:'mon-lazy-btn',onClick:()=>setColsShown(retailersF.length)},'＋ '+(retailersF.length-colsShown)+' retailers')))),
          h('tbody',null,data.rows.map(r=>h('tr',{key:r.sku_key},
            h('td',{className:'mon-sticky mon-c1'},h('span',{className:'mon-num',style:{fontWeight:800,fontSize:12.5}},r.sku)),
            h('td',{className:'mon-sticky mon-c2'},h('span',{className:'mon-pill'},r.brand||'—')),
            h('td',{className:'mon-sticky mon-c3'},h('span',{className:'mon-name',title:r.name||r.sku},r.name||r.sku)),
            shown.map(rt=>{ const c=r.cells[rt.host];
              if(!c){
                // "No implementado" SOLO si la marca de la fila está presente en el
                // host (en 'all', un SKU Midea en un host solo-HP no es noimpl)
                const hb=(data.host_brands&&data.host_brands[rt.host])||[];
                const isNo=hb.includes((r.brand||'').toUpperCase());
                return h('td',{key:rt.host},isNo
                  ?h('div',{className:'mon-cell mon-noimpl',
                      title:(es?'No implementado — motivo dominante: ':'Not implemented — dominant reason: ')+motivoLbl[monHostMotivo(rt)]},
                      sevLbl.noimpl)
                  :h('div',{className:'mon-cell',style:{justifyContent:'center',color:'var(--g300)'}},'—'));
              }
              return h('td',{key:rt.host},h('div',{className:'mon-cell mon-'+c.sev,
                style:(motivo==='no_url'&&(c.url||c.url_inferred))?{opacity:.22}:null},
                h('span',{className:'mon-num'},MON_FMT_DH(c.hours)),
                monOvrBadge(c.override,es),
                monChkIcon(c.check,es),
                monFamBadge(monPairBadge(c),es,()=>setFamOpen({sku_key:r.sku_key,sku:r.sku,name:r.name,host:rt.host})),
                c.dispersed?monDispBadge(es):null,
                c.url
                  ?h('a',{href:c.url,target:'_blank',rel:'noopener',title:r.sku+' @ '+rt.host},'↗')
                  :c.url_inferred
                    ?h('a',{href:c.url_inferred,target:'_blank',rel:'noopener',className:'mon-inf',
                        title:es?'URL INFERIDA por nombre (1er resultado de búsqueda) — no verificada':'INFERRED URL by name (1st search result) — not verified'},'≈↗')
                    :null));
            }),
            colsShown<retailersF.length&&h('td',null))))),
        ),
      h('div',{className:'mon-foot'},
        h('span',{className:'mon-cc'},data.page.total_rows
          ?`${(data.page.page-1)*PER+1}–${Math.min(data.page.page*PER,data.page.total_rows)} de ${data.page.total_rows.toLocaleString()} SKUs · ${retailersF.length} retailers (${Math.min(colsShown,retailersF.length)} visibles)`+(motivo!=='all'&&motivo!=='no_url'?(es?' · filtro por motivo':' · reason filter'):'')
          :(es?'Sin resultados':'No results')),
        h('div',{style:{display:'flex',gap:6}},
          h('button',{className:'btn',disabled:page<=1,onClick:()=>setPage(p=>p-1)},'‹ '+(es?'Anterior':'Prev')),
          h('button',{className:'btn',disabled:data.page.page*PER>=data.page.total_rows,onClick:()=>setPage(p=>p+1)},(es?'Siguiente':'Next')+' ›')))),

    /* panel ⧉ ficha multi-producto: páginas del retail donde se muestra la ficha */
    famOpen&&(()=>{
      const prods=(famData&&famData.products)||[];
      const maxV=prods.reduce((m,p)=>Math.max(m,p.views_win||0),1);
      const fmtLast=(ts)=>ts?MON_FMT_DH(Math.floor((Date.now()-ts)/3600000)):'—';
      return h('div',{className:'card mon-famp',key:'famp'},
        h('div',{className:'mon-ap-head'},
          h('b',null,'⧉ ',famData&&famData.family
            ?(es?'Ficha multi-producto':'Multi-product content')
            :(es?'URLs del producto en el retail':'Product URLs at the retailer')),
          h('span',{className:'mon-cc'},h('b',{className:'mon-num'},famOpen.sku),' · ',rName(famOpen.host),
            famData&&famData.family?(' — '+(es?'se muestra en ':'shows on ')+famData.family.n+(es?' productos del retail':' retailer products'))
            :prods.length>=2?(' — '+(es?'se ve en ':'seen at ')+prods.length+(es?' URLs':' URLs')):''),
          h('button',{className:'mon-btn-xs',style:{marginLeft:'auto'},onClick:()=>setFamOpen(null)},'× '+(es?'Cerrar':'Close'))),
        !famData?h('div',{className:'empty',style:{padding:18}},h('div',{className:'spinner'})):
        famData.error?h('div',{className:'empty',style:{padding:18}},'⚠ ',es?'No se pudo cargar el detalle':'Could not load detail'):
        !prods.length?h('div',{className:'empty',style:{padding:18}},
          es?'Sin páginas registradas para este par (se materializan cada 2 h).':'No pages recorded for this pair yet (materialized every 2 h).'):
        h('div',null,
          h('div',{className:'mon-famp-row mon-famp-head'},
            h('span',null,es?'Página de producto':'Product page'),h('span',null,'retailSku'),
            h('span',null,es?'Vistas':'Views'),h('span',null,es?'Últ. visita':'Last visit'),h('span',null,'')),
          prods.map((p,i)=>h('div',{className:'mon-famp-row',key:i},
            h('span',{className:'mon-famp-p',title:p.url_path},p.url_path),
            h('span',{className:'mon-cc mon-num'},p.retail_sku||'—'),
            h('span',{className:'mon-famp-bar'},
              h('i',{style:{width:Math.max(4,Math.round(100*(p.views_win||0)/maxV))+'%'}}),
              h('b',{className:'mon-num'},(p.views_win||0).toLocaleString())),
            h('span',{className:'mon-num'},fmtLast(p.last_ts)),
            p.url?h('a',{className:'mon-btn-xs',href:p.url,target:'_blank',rel:'noopener'},es?'Abrir ↗':'Open ↗'):h('span',null)))));
    })(),

    /* cola de acción bajo la matriz: TODAS las alertas, paginadas server-side */
    !loading&&tab==='matrix'&&apQueue&&(()=>{
      const apTotal=apQueue.totals.warn+apQueue.totals.crit+apQueue.totals.critdark;
      const apFrom=(apPage-1)*AL_PER+1, apTo=Math.min(apPage*AL_PER,apTotal);
      return h('div',{className:'card',style:{marginTop:12,padding:0,overflow:'hidden'}},
        h('div',{className:'mon-ap-head'},
          h('b',null,'🔧 ',es?'Cola de acción':'Action queue'),
          h('span',{className:'mon-cc'},apTotal.toLocaleString(),' ',es?'pares por revisar':'pairs to review'),
          h('div',{style:{marginLeft:'auto'}},
            h('button',{className:'mon-btn-xs',onClick:()=>setApOpen(o=>!o)},apOpen?(es?'Ocultar':'Hide'):(es?'Mostrar':'Show')))),
        apOpen&&h('div',{className:'mon-ap-scroll'},
          h('table',{className:'mon-ap-tbl'},
            h('thead',null,h('tr',null,['Estado','SKU',es?'Producto':'Product',es?'Marca':'Brand','Retailer',es?'Sin visita':'No visit',es?'Arreglar':'Fix',es?'Diagnóstico':'Diagnose'].map((x,i)=>h('th',{key:i},x)))),
            h('tbody',null,(apQueue.alerts||[]).map(alertRow)))),
        apOpen&&h('div',{className:'mon-foot'},
          h('span',{className:'mon-cc'},apTotal
            ?`${apFrom.toLocaleString()}–${apTo.toLocaleString()} ${es?'de':'of'} ${apTotal.toLocaleString()}`
            :(es?'Sin pares en alerta':'No pairs in alert')),
          h('div',{style:{display:'flex',gap:6,alignItems:'center'}},
            h('button',{className:'btn',disabled:apPage<=1,onClick:()=>setApPage(1)},'«'),
            h('button',{className:'btn',disabled:apPage<=1,onClick:()=>setApPage(p=>p-1)},'‹ '+(es?'Anterior':'Prev')),
            h('span',{className:'mon-cc mon-num'},apPage+' / '+Math.max(1,Math.ceil(apTotal/AL_PER))),
            h('button',{className:'btn',disabled:apPage*AL_PER>=apTotal,onClick:()=>setApPage(p=>p+1)},(es?'Siguiente':'Next')+' ›'),
            h('button',{className:'btn',disabled:apPage*AL_PER>=apTotal,onClick:()=>setApPage(Math.ceil(apTotal/AL_PER))},'»'))));
    })(),

    /* ============ PANEL DE ALERTAS ============ */
    !loading&&tab==='alerts'&&alerts&&h('div',{className:'card',style:{padding:0,overflow:'hidden'}},
      h('div',{className:'mon-ap-head'},
        h('b',null,'🚨 ',es?'Panel de alertas':'Alerts panel'),
        h('div',{className:'mon-chips'},
          [['critdark','critdark'],['crit','crit'],['warn','warn']].map(([k])=>
            h('button',{key:k,className:'mon-chip'+(alSev.has(k)?' on':''),onClick:()=>{ const n=new Set(alSev); n.has(k)?n.delete(k):n.add(k); if(!n.size){n.add('warn');n.add('crit');n.add('critdark');} setAlSev(n); setAlPage(1); }},
              h('span',{className:'mon-dot mon-bg-'+k}),sevLbl[k],' ',h('b',{className:'mon-num'},(alerts.totals[k]||0).toLocaleString())))),
        h('div',{style:{marginLeft:'auto'}},
          h('button',{className:'btn',onClick:()=>doExport('alerts'),disabled:exporting},es?'⬇ Excel de alertas':'⬇ Alerts Excel'))),
      h('div',{className:'mon-ap-scroll',style:{maxHeight:560}},
        h('table',{className:'mon-ap-tbl'},
          h('thead',null,h('tr',null,['Estado','SKU',es?'Producto':'Product',es?'Marca':'Brand','Retailer',es?'Sin visita':'No visit',es?'Arreglar':'Fix',es?'Diagnóstico':'Diagnose'].map((x,i)=>h('th',{key:i},x)))),
          h('tbody',null,(alerts.alerts||[]).map(alertRow)))),  /* el filtro ya viene del servidor */
      (()=>{ const alTotal=[...alSev].reduce((s,k)=>s+(alerts.totals[k]||0),0);
        return h('div',{className:'mon-foot'},
          h('span',{className:'mon-cc'},
            `${((alPage-1)*AL_PER+1).toLocaleString()}–${Math.min(alPage*AL_PER,alTotal).toLocaleString()} ${es?'de':'of'} ${alTotal.toLocaleString()} · `,
            es?'ordenado por severidad y tiempo sin visita':'sorted by severity and time without visit'),
          h('div',{style:{display:'flex',gap:6,alignItems:'center'}},
            h('button',{className:'btn',disabled:alPage<=1,onClick:()=>setAlPage(1)},'«'),
            h('button',{className:'btn',disabled:alPage<=1,onClick:()=>setAlPage(p=>p-1)},'‹'),
            h('span',{className:'mon-cc mon-num'},alPage+' / '+Math.max(1,Math.ceil(alTotal/AL_PER))),
            h('button',{className:'btn',disabled:alPage*AL_PER>=alTotal,onClick:()=>setAlPage(p=>p+1)},'›'),
            h('button',{className:'btn',disabled:alPage*AL_PER>=alTotal,onClick:()=>setAlPage(Math.ceil(alTotal/AL_PER))},'»'))); })()),

    /* ============ RESULTADOS DE YODA ============ */
    tab==='yoda'&&h('div',{key:'yoda'+yodaTick},
      !(window.OAYoda&&window.OAYoda.results.length)
        ?h('div',{className:'card'},h('div',{className:'empty'},'🧙 ',
          es?'Aún no hay resultados. Pregúntale algo a Yoda (burbuja abajo a la derecha) y sus respuestas con datos aparecerán aquí, organizadas.':'No results yet. Ask Yoda something (bubble at bottom right) and data answers will appear here, organized.'))
        :h('div',null,
          h('div',{style:{display:'flex',alignItems:'center',gap:10,marginBottom:10}},
            h('span',{className:'mon-cc'},es?'Respuestas de Yoda con datos, más reciente primero (se muestran solo los últimos 3 resultados)':'Yoda data answers, newest first (only the last 3 results are kept)'),
            h('button',{className:'mon-btn-xs',style:{marginLeft:'auto'},onClick:()=>{window.OAYoda.results=[];setYodaTick(t=>t+1);}},
              es?'Limpiar':'Clear')),
          window.OAYoda.results.map(r=>h('div',{key:r.id,className:'card yoda-res'},
            h('div',{className:'yoda-res-head'},
              h('span',{className:'yoda-res-ava'},'🧙'),
              h('div',{style:{minWidth:0,flex:1}},
                h('div',{className:'yoda-res-title'},
                  (es?'Resultados de ':'Results for '),h('b',null,'«',r.question,'»'),
                  h('span',{className:'mon-cc'},' — ',es?'solicitud realizada a Yoda':'request made to Yoda')),
                h('div',{className:'mon-cc'},r.at.toLocaleTimeString())),
              h('button',{className:'mon-btn-xs',onClick:()=>{
                window.OAYoda.results=window.OAYoda.results.filter(x=>x.id!==r.id); setYodaTick(t=>t+1); }},'×')),
            r.text&&h('div',{className:'yoda-res-text'},
              window.MdLite?h(window.MdLite,{text:r.text}):r.text),
            h('div',{className:'yoda-res-blocks'},
              (window.YodaBlocks?h(window.YodaBlocks,{blocks:r.blocks,full:true}):null)))))));
}
window.MonitorView=MonitorView;
