/* ============================================================
   Eliminación MASIVA de eventos por archivo (wizard de la cola de acción).
   Sube un CSV/XLSX (hostname,url,sku,marca), valida server-side, congela
   candidatos de URL con score, umbral AJUSTABLE con re-selección local
   (histograma congelado, sin más requests), resumen y materialización en
   lotes REANUDABLES (/impl/bulk-*). Historial con reanudar.
   Patrón visual del RetailsAIWizard (overlay inline, chips de paso).
   ============================================================ */
const { useState:bS, useEffect:bE, useRef:bR } = React;
const BK_P='#4D4A9D', BK_RED='#B91C1C', BK_AMBER='#B45309', BK_GREEN='#166534';
const bkInp={padding:'6px 9px',border:'1px solid #E5E5E5',borderRadius:8,fontSize:12.5,boxSizing:'border-box'};
const bkBtn=(bg,fg,bd)=>({padding:'7px 12px',border:'1px solid '+(bd||'#E5E5E5'),borderRadius:8,fontSize:12.5,
  fontWeight:700,background:bg||'#fff',color:fg||'#525252',cursor:'pointer'});
const bkNum=(n)=>Number(n||0).toLocaleString();

/* ---- template CSV (Excel lo abre nativo; BOM para tildes) ---- */
function bkTemplate(es){
  const rows=[
    ['hostname','url','sku','marca'],
    ['www.tienda.cl','https://www.tienda.cl/p/zapato-modelo-x?utm_source=campana','AB-123','MiMarca'],
    ['simple.ripley.cl','/parrilla-carbon-ejemplo-mpm000401','MPM000401','Bosca'],
  ];
  const csv='﻿'+rows.map(r=>r.map(c=>'"'+String(c).replace(/"/g,'""')+'"').join(',')).join('\r\n');
  const a=document.createElement('a');
  a.href=URL.createObjectURL(new Blob([csv],{type:'text/csv;charset=utf-8'}));
  a.download=es?'template_eliminacion_masiva.csv':'bulk_delete_template.csv';
  document.body.appendChild(a); a.click(); a.remove(); setTimeout(()=>URL.revokeObjectURL(a.href),1000);
}

/* ---- parseo del archivo ---- */
function bkParseCsv(text){
  const t=text.replace(/^﻿/,'');
  const lines=t.split(/\r?\n/).filter(l=>l.trim());
  if(!lines.length) return [];
  const delim=(lines[0].match(/;/g)||[]).length>(lines[0].match(/,/g)||[]).length?';':',';
  const cells=(l)=>{ const out=[]; let cur='',q=false;
    for(let i=0;i<l.length;i++){ const ch=l[i];
      if(q){ if(ch==='"'){ if(l[i+1]==='"'){cur+='"';i++;} else q=false; } else cur+=ch; }
      else if(ch==='"') q=true;
      else if(ch===delim){ out.push(cur); cur=''; }
      else cur+=ch; }
    out.push(cur); return out.map(c=>c.trim()); };
  return lines.map(cells);
}
function bkRowsFromMatrix(m){
  if(!m.length) return {error:'archivo vacío'};
  const hdr=m[0].map(h=>String(h||'').trim().toLowerCase());
  const col=(names)=>hdr.findIndex(h=>names.includes(h));
  const ih=col(['hostname','host','retail','retailer']), iu=col(['url','url pdp','pdp']),
        is=col(['sku','sku_key']), ib=col(['marca','brand']);
  if(ih<0||iu<0||is<0) return {error:'faltan columnas: se requieren hostname, url y sku (marca opcional)'};
  const rows=[];
  for(let i=1;i<m.length;i++){ const r=m[i]; if(!r||!r.length) continue;
    const host=String(r[ih]||'').trim(), url=String(r[iu]||'').trim(), sku=String(r[is]||'').trim();
    if(!host&&!url&&!sku) continue;
    rows.push({idx:rows.length,host,url,sku,brand:ib>=0?String(r[ib]||'').trim():''}); }
  return {rows};
}
let _bkXlsx=null;
function bkLoadXlsx(){
  if(window.XLSX) return Promise.resolve();
  if(_bkXlsx) return _bkXlsx;
  _bkXlsx=new Promise((res,rej)=>{ const s=document.createElement('script');
    s.src='assets/xlsx.full.min.js'; s.onload=()=>res(); s.onerror=()=>rej(new Error('no se pudo cargar el lector XLSX'));
    document.head.appendChild(s); });
  return _bkXlsx;
}
async function bkReadFile(file){
  const name=(file.name||'').toLowerCase();
  if(name.endsWith('.csv')||file.type==='text/csv'){
    return bkRowsFromMatrix(bkParseCsv(await file.text()));
  }
  await bkLoadXlsx();
  const wb=window.XLSX.read(await file.arrayBuffer(),{type:'array'});
  const ws=wb.Sheets[wb.SheetNames[0]];
  const m=window.XLSX.utils.sheet_to_json(ws,{header:1,raw:false,defval:''});
  return bkRowsFromMatrix(m);
}

/* ---- API (todo POST + X-API-Key vía OA.implPost) ---- */
const bkApi=(op,body)=>OA.implPost('/impl/bulk-'+op,{},body||{});

/* ---- candado de la X-API-Key admin: el Monitor no tiene el keyBox de los
   mantenedores, así que el wizard/historial la piden y guardan aquí mismo
   (localStorage oa_admin_key, la misma llave del candado del Catálogo). ---- */
function BkKeyBox({onSaved}){
  const es=OA.lang==='es';
  const [v,setV]=bS('');
  const H=React.createElement;
  return H('div',{style:{background:'#FFFBEB',border:'1px solid #FDE68A',borderRadius:8,
    padding:'10px 12px',marginBottom:10,fontSize:12.5}},
    H('div',{style:{fontWeight:700,color:BK_AMBER,marginBottom:6}},'🔑 ',
      es?'Se requiere la X-API-Key de administrador (la misma del candado de los mantenedores).':'Admin X-API-Key required (same as the maintainers header lock).'),
    H('div',{style:{display:'flex',gap:8,alignItems:'center'}},
      H('input',{type:'password',placeholder:'X-API-Key',value:v,style:{...bkInp,flex:1},
        onChange:e=>setV(e.target.value),
        onKeyDown:e=>{ if(e.key==='Enter'&&v.trim()){ OA.setAdminKey(v.trim()); setV(''); if(onSaved) onSaved(); } }}),
      H('button',{style:bkBtn(BK_P,'#fff',BK_P),disabled:!v.trim(),
        onClick:()=>{ OA.setAdminKey(v.trim()); setV(''); if(onSaved) onSaved(); }},
        es?'Guardar y continuar':'Save & continue')));
}
const bkIs401=(e)=>e&&(e.status===401||/impl 401/.test(String(e&&e.message||e)));
const bkErrMsg=(e,es)=>bkIs401(e)
  ?(es?'X-API-Key inválida o no configurada: ingrésala aquí abajo.':'Invalid or missing X-API-Key: enter it below.')
  :String(e&&e.message||e);

/* ============================ WIZARD ============================ */
window.BulkSuppressWizard=function BulkSuppressWizard({onClose,onDone,resumeJob}){
  const es=OA.lang==='es';
  const [step,setStep]=bS(1);
  const [fileRows,setFileRows]=bS(null); const [fileName,setFileName]=bS('');
  const [job,setJob]=bS(resumeJob||null);          // {job_id,status,...}
  const [upProg,setUpProg]=bS(null);                // subida de chunks
  const [valid,setValid]=bS(null);                  // {rows_matched,rows_unmatched,score_histogram,fuse_current}
  const [thr,setThr]=bS(Number(localStorage.getItem('oa_bulk_thr')||90));
  const [applyProg,setApplyProg]=bS(null);
  const [running,setRunning]=bS(false);
  const [err,setErr]=bS('');
  const [needKey,setNeedKey]=bS(!OA.adminKey());
  const cancelRef=bR(false);
  const leaseRef=bR('w-'+Math.random().toString(16).slice(2,10));
  const setThreshold=(v)=>{ setThr(v); try{localStorage.setItem('oa_bulk_thr',String(v));}catch(_){} };

  /* reanudación: aterrizar en el paso correcto según el estado del job */
  bE(()=>{ if(!resumeJob) return;
    const st=resumeJob.status;
    if(st==='uploading'){ setStep(1); setErr(es?'Este job quedó a medio subir: vuelve a elegir el MISMO archivo para continuar.':'This job was mid-upload: pick the SAME file again to continue.'); }
    else if(st==='draft'){ setStep(3);
      bkApi('job',{job_id:resumeJob.job_id,histogram:true}).then(d=>{
        setValid({rows_matched:d.job.total_rows-d.job.rows_unmatched,rows_unmatched:d.job.rows_unmatched,
          score_histogram:d.score_histogram,fuse_current:d.fuse_current});
      }).catch(e=>{ setErr(bkErrMsg(e,es)); if(bkIs401(e)) setNeedKey(true); }); }
    else { setStep(5); }
  },[]);

  const hist=(valid&&valid.score_histogram)||[];
  const bucket=hist.length?(hist.find(h=>h.ge===Math.round(thr/5)*5)||hist[hist.length-1]):null;
  const fuseProj=bucket?Number(valid.fuse_current||0)+bucket.urls:null;

  /* paso 1: archivo -> create + add-rows en chunks (idempotente = reanudable) */
  const pickFile=async(f)=>{
    setErr('');
    try{
      const parsed=await bkReadFile(f);
      if(parsed.error){ setErr(parsed.error); return; }
      if(!parsed.rows.length){ setErr(es?'el archivo no tiene filas de datos':'no data rows in file'); return; }
      if(parsed.rows.length>20000){ setErr(es?'máximo 20.000 filas por archivo':'max 20,000 rows per file'); return; }
      setFileRows(parsed.rows); setFileName(f.name||'archivo');
    }catch(e){ setErr(String(e&&e.message||e)); }
  };
  const upload=async()=>{
    if(!fileRows||running) return;
    setRunning(true); setErr(''); cancelRef.current=false;
    try{
      let j=job;
      // reanudar 'uploading' reusa el job; si no, crear
      if(!j||j.status!=='uploading'){
        j=await bkApi('create',{filename:fileName,total_rows:fileRows.length,created_by:'monitor'});
        setJob(j);
      }
      let last=null;
      for(let i=0;i<fileRows.length;i+=200){
        if(cancelRef.current) break;
        last=await bkApi('add-rows',{job_id:j.job_id,rows:fileRows.slice(i,i+200)});
        setUpProg({done:Math.min(i+200,fileRows.length),total:fileRows.length});
      }
      if(last&&last.status==='draft'){ setValid(last); setJob({...j,status:'draft'}); setStep(2); }
    }catch(e){ setErr(bkErrMsg(e,es)); if(bkIs401(e)) setNeedKey(true); }
    setRunning(false);
  };

  /* paso 5: apply en loop reanudable */
  const runApply=async(retryErrors)=>{
    if(running) return;
    setRunning(true); setErr(''); cancelRef.current=false;
    try{
      for(;;){
        if(cancelRef.current) break;
        const r=await bkApi('apply',{job_id:job.job_id,lease_owner:leaseRef.current,max_rows:40,
          ...(retryErrors?{retry_errors:true}:{})});
        retryErrors=false;
        if(r.locked){ await new Promise(res=>setTimeout(res,r.retry_after_ms||5000)); continue; }
        setApplyProg(r);
        if(r.done){ if(onDone) onDone(); break; }
      }
    }catch(e){ setErr(bkErrMsg(e,es)); if(bkIs401(e)) setNeedKey(true); }
    setRunning(false);
  };

  const stepChip=(n,l)=>React.createElement('span',{key:n,style:{padding:'3px 10px',borderRadius:99,fontSize:11.5,
    fontWeight:700,background:step===n?BK_P:'#F5F5F5',color:step===n?'#fff':'#737373'}},l);
  const H=React.createElement;
  const pct=(a,b)=>b?Math.round(100*a/b):0;
  const bar=(p)=>H('div',{style:{height:8,background:'#EEE',borderRadius:99,overflow:'hidden',margin:'8px 0'}},
    H('div',{style:{width:p+'%',height:'100%',background:BK_P,transition:'width .3s'}}));

  return H('div',{style:{position:'fixed',inset:0,background:'rgba(20,18,40,.45)',zIndex:60,display:'flex',
    alignItems:'flex-start',justifyContent:'center',padding:'40px 16px',overflow:'auto'},
    onClick:e=>{ if(e.target===e.currentTarget&&!running) onClose(); }},
    H('div',{style:{background:'#fff',borderRadius:14,maxWidth:900,width:'100%',padding:'16px 20px',
      boxShadow:'0 20px 60px rgba(0,0,0,.25)'}},
      H('div',{style:{display:'flex',alignItems:'center',gap:8,marginBottom:12,flexWrap:'wrap'}},
        H('b',{style:{fontSize:15}},'📥 ',es?'Eliminación masiva de eventos':'Bulk event deletion'),
        H('span',{style:{flex:1}}),
        stepChip(1,'1 · '+(es?'Archivo':'File')),H('span',{style:{color:'#D4D4D4'}},'→'),
        stepChip(2,'2 · '+(es?'Validación':'Validation')),H('span',{style:{color:'#D4D4D4'}},'→'),
        stepChip(3,'3 · '+(es?'Umbral':'Threshold')),H('span',{style:{color:'#D4D4D4'}},'→'),
        stepChip(4,'4 · '+(es?'Resumen':'Summary')),H('span',{style:{color:'#D4D4D4'}},'→'),
        stepChip(5,'5 · '+(es?'Materializar':'Apply')),
        H('button',{style:bkBtn(),onClick:()=>{ if(!running) onClose(); }},'×')),
      err&&H('div',{style:{background:'#FEF2F2',border:'1px solid #FECACA',color:BK_RED,padding:'8px 12px',
        borderRadius:8,fontSize:12.5,marginBottom:10}},'⚠ ',err),
      needKey&&H(BkKeyBox,{onSaved:()=>{ setNeedKey(false); setErr(''); }}),

      /* ---------- paso 1 ---------- */
      step===1&&H('div',null,
        H('p',{style:{fontSize:13,color:'#525252'}},es
          ?'Sube un archivo con las columnas hostname, url (PDP), sku y marca. Los eventos históricos de cada par se eliminarán de TODA la analítica; si llega un evento nuevo, solo lo nuevo vuelve a contar.'
          :'Upload a file with hostname, url (PDP), sku and brand columns. Historical events of each pair are deleted from ALL analytics; only new events count again.'),
        H('div',{style:{display:'flex',gap:10,alignItems:'center',flexWrap:'wrap',margin:'12px 0'}},
          H('button',{style:bkBtn('#F5F3FF',BK_P,BK_P),onClick:()=>bkTemplate(es)},'⬇ ',es?'Descargar template':'Download template'),
          H('label',{style:{...bkBtn(BK_P,'#fff',BK_P),display:'inline-block'}},
            es?'Elegir archivo (.csv / .xlsx)':'Pick file (.csv / .xlsx)',
            H('input',{type:'file',accept:'.csv,.xlsx,.xls',style:{display:'none'},
              onChange:e=>{ const f=e.target.files&&e.target.files[0]; if(f) pickFile(f); e.target.value=''; }})),
          fileRows&&H('span',{style:{fontSize:12.5,color:'#166534',fontWeight:700}},
            '✓ '+fileName+' · '+bkNum(fileRows.length)+(es?' filas':' rows'))),
        upProg&&H('div',null,bar(pct(upProg.done,upProg.total)),
          H('span',{style:{fontSize:12,color:'#737373'}},bkNum(upProg.done)+' / '+bkNum(upProg.total))),
        H('div',{style:{display:'flex',justifyContent:'flex-end',gap:8,marginTop:10}},
          H('button',{style:bkBtn(BK_P,'#fff',BK_P),disabled:!fileRows||running,onClick:upload},
            running?(es?'Subiendo…':'Uploading…'):(es?'Validar archivo →':'Validate file →')))),

      /* ---------- paso 2 ---------- */
      step===2&&valid&&H('div',null,
        H('p',{style:{fontSize:13}},
          H('b',{style:{color:BK_GREEN}},bkNum(valid.rows_matched)),' ',es?'filas válidas (par encontrado)':'valid rows (pair found)',' · ',
          H('b',{style:{color:BK_AMBER}},bkNum(valid.rows_unmatched)),' ',es?'excluidas (host desconocido, par inexistente, marca no coincide o duplicadas) — no se tocarán':'excluded — untouched'),
        H('p',{style:{fontSize:12,color:'#737373'}},es
          ?'El detalle de las excluidas queda en el historial del job.'
          :'Excluded rows are listed in the job history.'),
        H('div',{style:{display:'flex',justifyContent:'space-between',marginTop:10}},
          H('button',{style:bkBtn(),onClick:()=>setStep(1)},'←'),
          H('button',{style:bkBtn(BK_P,'#fff',BK_P),disabled:!valid.rows_matched,onClick:()=>setStep(3)},
            es?'Elegir umbral →':'Pick threshold →'))),

      /* ---------- paso 3 ---------- */
      step===3&&H('div',null,
        H('p',{style:{fontSize:13,color:'#525252'}},es
          ?'Umbral de coincidencia de la URL: 100 = exactamente la misma PDP (los query params de campaña ya se ignoran); más bajo tolera variantes del path.'
          :'URL match threshold: 100 = exact same PDP (campaign query params already ignored); lower tolerates path variants.'),
        H('div',{style:{display:'flex',alignItems:'center',gap:10,margin:'10px 0'}},
          H('span',{style:{fontSize:12.5,fontWeight:700}},es?'Umbral':'Threshold'),
          H('input',{type:'range',min:50,max:100,step:5,value:thr,style:{accentColor:BK_P,width:220},
            onChange:e=>setThreshold(Number(e.target.value))}),
          H('b',{style:{fontSize:14,color:BK_P}},thr+'%'),
          H('span',{style:{fontSize:10.5,color:'#8B87A3'}},es?'(re-selección local, sin requests)':'(local re-select)')),
        bucket?H('div',{style:{display:'flex',gap:18,flexWrap:'wrap',background:'#FAFAFA',border:'1px solid #E5E5E5',
          borderRadius:10,padding:'10px 14px',fontSize:13}},
          H('span',null,H('b',null,bkNum(bucket.rows)),' ',es?'filas con selección':'rows selected'),
          H('span',null,H('b',null,bkNum((valid.rows_matched||0)-bucket.rows)),' ',es?'quedarán saltadas (sin URL sobre el umbral)':'skipped'),
          H('span',null,H('b',null,bkNum(bucket.urls)),' ',es?'entradas de supresión (':'suppression entries (',bkNum(bucket.wildcards),es?' pares completos)':' full pairs)'),
          H('span',null,H('b',null,bkNum(bucket.views)),' ',es?'vistas (90d) afectadas':'views (90d) affected'))
        :H('span',{className:'mon-cc'},es?'Cargando histograma…':'Loading…'),
        fuseProj!=null&&fuseProj>700&&H('div',{style:{marginTop:8,background:'#FFFBEB',border:'1px solid #FDE68A',
          color:BK_AMBER,padding:'8px 12px',borderRadius:8,fontSize:12.5}},
          '⚠ ',es?`Presupuesto de supresiones: ${bkNum(valid.fuse_current)} activas + ${bkNum(bucket.urls)} nuevas = ${bkNum(fuseProj)} (tope efectivo 800: sobre eso, las MÁS ANTIGUAS dejan de aplicar). Considera un umbral más alto o depurar la vista Eliminados.`
            :`Suppression budget: ${bkNum(fuseProj)} of 800 effective — oldest stop applying beyond that.`),
        H('div',{style:{display:'flex',justifyContent:'space-between',marginTop:12}},
          H('button',{style:bkBtn(),onClick:()=>setStep(2)},'←'),
          H('button',{style:bkBtn(BK_P,'#fff',BK_P),disabled:!bucket||!bucket.rows,onClick:()=>setStep(4)},
            es?'Ver resumen →':'Summary →'))),

      /* ---------- paso 4 ---------- */
      step===4&&bucket&&H('div',null,
        H('p',{style:{fontSize:13.5}},es
          ?`Se eliminarán los eventos históricos de ${bkNum(bucket.rows)} pares (${bkNum(bucket.urls)} entradas, ${bkNum(bucket.views)} vistas de 90d) con umbral ${thr}%. ${bkNum((valid.rows_matched||0)-bucket.rows)} filas quedarán saltadas y ${bkNum(valid.rows_unmatched)} excluidas. Los pares saldrán del Monitor al instante; la analítica converge en minutos (live) y ≤1 h (materializado). Reversible desde la vista Eliminados.`
          :`Will delete historical events of ${bkNum(bucket.rows)} pairs at threshold ${thr}%.`),
        H('div',{style:{display:'flex',justifyContent:'space-between',marginTop:12}},
          H('button',{style:bkBtn(),onClick:()=>setStep(3)},'←'),
          H('button',{style:bkBtn(BK_RED,'#fff',BK_RED),disabled:running,onClick:async()=>{
            setErr('');
            try{ const c=await bkApi('confirm',{job_id:job.job_id,threshold:thr});
              setJob({...job,status:c.status}); setStep(5); runApply(false);
            }catch(e){ setErr(bkErrMsg(e,es)); if(bkIs401(e)) setNeedKey(true); }
          }},'🗑 ',es?'Materializar eliminación':'Apply deletion'))),

      /* ---------- paso 5 ---------- */
      step===5&&H('div',null,
        applyProg&&applyProg.progress?H('div',null,
          bar(pct(applyProg.progress.applied+applyProg.progress.skipped+applyProg.progress.errors,
                  Math.max(1,applyProg.progress.total-applyProg.progress.excluded))),
          H('div',{style:{display:'flex',gap:16,flexWrap:'wrap',fontSize:13}},
            H('span',null,H('b',{style:{color:BK_GREEN}},bkNum(applyProg.progress.applied)),' ',es?'aplicadas':'applied'),
            H('span',null,H('b',null,bkNum(applyProg.progress.skipped)),' ',es?'saltadas':'skipped'),
            H('span',null,H('b',{style:{color:BK_AMBER}},bkNum(applyProg.progress.excluded)),' ',es?'excluidas':'excluded'),
            H('span',null,H('b',{style:{color:BK_RED}},bkNum(applyProg.progress.errors)),' ',es?'errores':'errors'),
            H('span',null,H('b',null,bkNum(applyProg.progress.pending)),' ',es?'pendientes':'pending'),
            H('span',null,H('b',null,bkNum(applyProg.progress.urls_suppressed)),' ',es?'entradas escritas':'entries written')))
        :H('p',{style:{fontSize:13,color:'#737373'}},es?'Listo para aplicar (reanudable en cualquier momento).':'Ready to apply (resumable).'),
        H('div',{style:{display:'flex',justifyContent:'flex-end',gap:8,marginTop:12}},
          running
            ?H('button',{style:bkBtn('#FFF','#B45309','#FDE68A'),onClick:()=>{cancelRef.current=true;}},
               es?'Pausar (reanudable desde el historial)':'Pause (resumable)')
            :[
              applyProg&&applyProg.done&&applyProg.progress&&applyProg.progress.errors>0&&
                H('button',{key:'r',style:bkBtn('#FEF2F2',BK_RED,BK_RED),onClick:()=>runApply(true)},
                  es?'Reintentar errores':'Retry errors'),
              (!applyProg||!applyProg.done)&&
                H('button',{key:'a',style:bkBtn(BK_P,'#fff',BK_P),onClick:()=>runApply(false)},
                  applyProg?(es?'Reanudar':'Resume'):(es?'Aplicar':'Apply')),
              H('button',{key:'c',style:bkBtn(),onClick:onClose},
                applyProg&&applyProg.done?(es?'Cerrar':'Close'):(es?'Cerrar (sigue reanudable)':'Close (still resumable)')),
            ]),
        applyProg&&applyProg.done&&H('p',{style:{fontSize:13,color:BK_GREEN,fontWeight:700,marginTop:8}},
          '✓ ',es?'Eliminación materializada.':'Deletion applied.'))));
};

/* ============================ HISTORIAL ============================ */
window.BulkHistory=function BulkHistory({onClose,onResume}){
  const es=OA.lang==='es';
  const [items,setItems]=bS(null); const [err,setErr]=bS('');
  const [needKey,setNeedKey]=bS(!OA.adminKey());
  const [tick,setTick]=bS(0);
  bE(()=>{ bkApi('jobs',{limit:30}).then(d=>{ setItems(d.items||[]); setErr(''); })
    .catch(e=>{ setErr(bkErrMsg(e,es)); if(bkIs401(e)) setNeedKey(true); }); },[tick]);
  const H=React.createElement;
  const stLbl={uploading:es?'Subida incompleta':'Upload incomplete',draft:es?'Validado (sin confirmar)':'Validated',
    ready:es?'Listo para aplicar':'Ready',applying:es?'Aplicando (interrumpido)':'Applying',
    done:es?'Completado':'Done',done_errors:es?'Completado con errores':'Done w/ errors',canceled:es?'Cancelado':'Canceled'};
  return H('div',{style:{position:'fixed',inset:0,zIndex:65},onClick:onClose},
    H('div',{style:{position:'fixed',top:0,right:0,width:600,maxWidth:'94vw',height:'100vh',background:'#fff',
      borderLeft:'1px solid #E5E5E5',boxShadow:'-12px 0 40px rgba(0,0,0,.12)',padding:16,overflow:'auto'},
      onClick:e=>e.stopPropagation()},
      H('div',{style:{display:'flex',alignItems:'center',marginBottom:12}},
        H('b',{style:{fontSize:14.5}},'🕘 ',es?'Historial de eliminaciones masivas':'Bulk deletion history'),
        H('button',{style:{...bkBtn(),marginLeft:'auto'},onClick:onClose},'×')),
      err&&H('div',{style:{color:BK_RED,fontSize:12.5,marginBottom:8}},'⚠ ',err),
      needKey&&H(BkKeyBox,{onSaved:()=>{ setNeedKey(false); setErr(''); setItems(null); setTick(t=>t+1); }}),
      items===null?H('span',{className:'mon-cc'},es?'Cargando…':'Loading…')
      :!items.length?H('span',{className:'mon-cc'},es?'Sin eliminaciones masivas aún.':'No bulk deletions yet.')
      :items.map(j=>H('div',{key:j.job_id,style:{border:'1px solid #E5E5E5',borderRadius:10,padding:'10px 12px',marginBottom:8}},
        H('div',{style:{display:'flex',alignItems:'center',gap:8,flexWrap:'wrap'}},
          H('b',{style:{fontSize:13}},j.filename||j.job_id.slice(0,10)),
          H('span',{style:{fontSize:11,fontWeight:700,padding:'2px 8px',borderRadius:99,
            background:j.status==='done'?'#F0FDF4':j.resumable?'#FFFBEB':'#F5F5F5',
            color:j.status==='done'?BK_GREEN:j.resumable?BK_AMBER:'#737373'}},stLbl[j.status]||j.status),
          H('span',{style:{marginLeft:'auto',fontSize:11.5,color:'#737373'}},new Date(j.created_at).toLocaleString())),
        H('div',{style:{fontSize:12,color:'#525252',marginTop:4}},
          bkNum(j.total_rows)+(es?' filas · ':' rows · ')+bkNum(j.rows_applied)+(es?' aplicadas · ':' applied · ')
          +bkNum(j.rows_skipped)+(es?' saltadas · ':' skipped · ')+bkNum(j.rows_unmatched)+(es?' excluidas · ':' excluded · ')
          +bkNum(j.rows_error)+(es?' errores · ':' errors · ')+bkNum(j.urls_suppressed)+(es?' entradas':' entries')
          +(j.threshold!=null?(' · '+(es?'umbral ':'thr ')+j.threshold+'%'):'')),
        j.resumable&&H('div',{style:{marginTop:6}},
          H('button',{style:bkBtn('#F5F3FF',BK_P,BK_P),onClick:()=>onResume(j)},'▶ ',es?'Reanudar':'Resume'))))));
};