/* ============================================================
   Omnitok Analytics — Yoda, asistente conversacional (widget)
   Cliente del bot worker (omnitok-analytics-bot): identidad por
   nombre (localStorage + match difuso server-side) y chat por
   POST /chat/:slug con respuesta SSE (progress|tool|delta|done).
   Cada respuesta con datos se publica en window.OAYoda.results y
   dispara 'oa-yoda-result' -> pestaña "Resultados de Yoda" del
   Monitor. Oculto mientras OA.BOT_BASE sea null (feature flag).
   ============================================================ */
const { useState:cS, useEffect:cE, useRef:cR } = React;
const hc = React.createElement;

/* registro global de resultados de Yoda (sobrevive al cambio de vista) */
window.OAYoda = window.OAYoda || { results: [] };
function yodaPublish(question, text, blocks){
  if(!blocks || !blocks.length) return;
  window.OAYoda.results.unshift({ id: Date.now(), at: new Date(), question, text, blocks });
  if(window.OAYoda.results.length > 3) window.OAYoda.results.length = 3;  // solo los últimos 3 (sin apilar)
  try{ window.dispatchEvent(new CustomEvent('oa-yoda-result')); }catch(e){}
}

/* ---- markdown ligero y SEGURO (elementos React, nunca HTML crudo) ----
   Soporta: **negrita**, `código`, [texto](url), títulos #, viñetas, y tablas
   markdown (|…|) que se convierten a tablas HTML con scroll PROPIO — la red de
   seguridad para cuando el modelo igual emite una tabla en el texto. */
function mdInline(s,kb){
  const out=[]; let rest=String(s==null?'':s); let k=0;
  const re=/(\*\*([^*]+)\*\*)|(`([^`]+)`)|(\[([^\]]+)\]\((https?:[^\s)]+)\))/;
  while(rest){
    const m=rest.match(re);
    if(!m){ out.push(rest); break; }
    if(m.index>0) out.push(rest.slice(0,m.index));
    if(m[1]) out.push(hc('b',{key:kb+'-'+(k++)},m[2]));
    else if(m[3]) out.push(hc('code',{key:kb+'-'+(k++)},m[4]));
    else out.push(hc('a',{key:kb+'-'+(k++),href:m[7],target:'_blank',rel:'noopener'},m[6]));
    rest=rest.slice(m.index+m[0].length);
  }
  return out;
}
function MdLite({text}){
  const lines=String(text||'').split('\n');
  const blocks=[]; let i=0;
  while(i<lines.length){
    if(/^\s*\|.*\|\s*$/.test(lines[i])){
      const tbl=[]; while(i<lines.length&&/^\s*\|.*\|\s*$/.test(lines[i])){ tbl.push(lines[i]); i++; }
      const rows=tbl.map(r=>r.trim().replace(/^\|/,'').replace(/\|$/,'').split('|').map(c=>c.trim()))
        .filter(cs=>!cs.every(c=>/^:?-{2,}:?$/.test(c)||c===''));
      if(rows.length) blocks.push({t:'table',cols:rows[0],rows:rows.slice(1)});
      continue;
    }
    blocks.push({t:'line',x:lines[i]}); i++;
  }
  return hc('div',{className:'bw-md'},blocks.map((b,bi)=>{
    if(b.t==='table') return hc('div',{key:bi,className:'bw-md-scroll'},
      hc('table',{className:'bw-tbl'},
        hc('thead',null,hc('tr',null,b.cols.map((c,j)=>hc('th',{key:j},mdInline(c,bi+'h'+j))))),
        hc('tbody',null,b.rows.map((r,j)=>hc('tr',{key:j},
          r.map((c,k2)=>hc('td',{key:k2},mdInline(c,bi+'-'+j+'-'+k2))))))));
    const t=b.x;
    if(!t.trim()) return hc('div',{key:bi,style:{height:6}});
    if(/^\s*#{1,4}\s+/.test(t)) return hc('div',{key:bi,style:{fontWeight:800,margin:'7px 0 2px'}},mdInline(t.replace(/^\s*#{1,4}\s+/,''),bi));
    if(/^\s*([-*•]|\d+[.)])\s+/.test(t)) return hc('div',{key:bi,style:{paddingLeft:16,textIndent:-10,margin:'1px 0'}},'• ',mdInline(t.replace(/^\s*([-*•]|\d+[.)])\s+/,''),bi));
    return hc('div',{key:bi,style:{margin:'1px 0'}},mdInline(t,bi));
  }));
}
window.MdLite=MdLite;

/* renderer COMPARTIDO de bloques (compact = dentro del chat; full = panel) */
function YodaBlocks({blocks, full}){
  return hc('div',null,(blocks||[]).map((b,i)=>{
    if(b.type==='kpis') return hc('div',{key:i,className:'bw-kpis'},(b.items||[]).map((k,j)=>
      hc('div',{key:j,className:'bw-kpi'+(full?' full':'')},hc('b',null,Number(k.value).toLocaleString()),hc('span',null,k.label))));
    if(b.type==='bars'){ const mx=Math.max(1,...(b.items||[]).map(x=>x.value));
      return hc('div',{key:i,className:'bw-bars'},b.title&&hc('div',{className:'bw-lbl'},b.title),
        (b.items||[]).map((x,j)=>hc('div',{key:j,className:'bw-bar'+(full?' full':'')},
          hc('span',{className:'bw-bk',title:x.label},x.label),
          hc('span',{className:'bw-track'},hc('i',{style:{width:Math.max(4,x.value/mx*100)+'%'}})),
          hc('span',{className:'bw-bv'},Number(x.value).toLocaleString())))); }
    if(b.type==='image') return hc('figure',{key:i,className:'bw-shot'+(full?' full':'')},
      hc('img',{src:b.src,alt:b.caption||'screenshot',loading:'lazy'}),
      b.caption&&hc('figcaption',null,b.caption));
    if(b.type==='table'){ const rows=(b.rows||[]); const shown=full?rows:rows.slice(0,8);
      return hc('div',{key:i,style:full?{overflowX:'auto'}:null},
        hc('table',{className:'bw-tbl'+(full?' full':'')},
          hc('thead',null,hc('tr',null,(b.columns||[]).map((c,j)=>hc('th',{key:j},c)))),
          hc('tbody',null,shown.map((r,j)=>hc('tr',{key:j},
            r.map((v,k2)=>{ const s=String(v==null?'—':v);
              if(s.startsWith('http')) return hc('td',{key:k2},hc('a',{href:s,target:'_blank',rel:'noopener',className:'mon-btn-xs'},full?(OA.lang==='es'?'Abrir ↗':'Open ↗'):'↗'));
              const sev=['ok','warn','crit','critdark','noimpl'].includes(s)?s:null;
              if(sev&&full) return hc('td',{key:k2},hc('span',{className:'mon-chipst mon-'+sev},s));
              return hc('td',{key:k2},s); }))))),
        !full&&rows.length>8&&hc('div',{className:'mon-cc',style:{marginTop:4}},'+'+(rows.length-8)+' filas en el panel de resultados'));
    }
    return null; }));
}
window.YodaBlocks=YodaBlocks;

/* ---- panel de entrenamiento (CRUD de bot_knowledge; requiere BOT_ADMIN_TOKEN) ---- */
function KnowledgePanel({onClose}){
  const es=OA.lang==='es';
  const [token,setToken]=cS(sessionStorage.getItem('oa_bot_admin')||'');
  const [docs,setDocs]=cS(null);
  const [err,setErr]=cS(null);
  const [edit,setEdit]=cS(null); // doc en edición o {} para nuevo
  const hdr=()=>({'Authorization':'Bearer '+token,'Content-Type':'application/json'});
  const load=()=>{ setErr(null);
    fetch(OA.BOT_BASE+'/api/knowledge',{headers:hdr()}).then(r=>{
      if(r.status===401) throw new Error(es?'Token inválido':'Invalid token');
      return r.json(); }).then(j=>{ sessionStorage.setItem('oa_bot_admin',token); setDocs(j.docs||[]); })
      .catch(e=>setErr(e.message)); };
  const save=()=>{ setErr(null);
    fetch(OA.BOT_BASE+'/api/knowledge'+(edit.id?'/'+edit.id:''),{method:edit.id?'PUT':'POST',headers:hdr(),
      body:JSON.stringify({kind:edit.kind||'instruction',title:edit.title,content:edit.content})})
      .then(r=>{ if(!r.ok) throw new Error('HTTP '+r.status); setEdit(null); load(); })
      .catch(e=>setErr(e.message)); };
  const del=(id)=>{ if(!confirm(es?'¿Eliminar este documento?':'Delete this doc?')) return;
    fetch(OA.BOT_BASE+'/api/knowledge/'+id,{method:'DELETE',headers:hdr()}).then(load); };
  return hc('div',{className:'bw-know'},
    hc('div',{style:{display:'flex',alignItems:'center',gap:8,marginBottom:8}},
      hc('b',{style:{fontSize:13}},es?'🎓 Entrenamiento del asistente':'🎓 Assistant training'),
      hc('button',{className:'bw-x',style:{marginLeft:'auto',background:'var(--g100)',color:'var(--g600)'},onClick:onClose},'×')),
    docs==null
      ?hc('div',{style:{display:'flex',gap:8}},
        hc('input',{className:'mon-input',style:{flex:1},type:'password',value:token,placeholder:'BOT_ADMIN_TOKEN',onChange:e=>setToken(e.target.value)}),
        hc('button',{className:'btn',onClick:load},es?'Entrar':'Enter'))
      :(edit
        ?hc('div',{style:{display:'flex',flexDirection:'column',gap:7}},
          hc('select',{className:'mon-input',value:edit.kind||'instruction',onChange:e=>setEdit({...edit,kind:e.target.value})},
            ['schema','recipe','instruction','source'].map(k=>hc('option',{key:k,value:k},k))),
          hc('input',{className:'mon-input',value:edit.title||'',placeholder:es?'Título':'Title',onChange:e=>setEdit({...edit,title:e.target.value})}),
          hc('textarea',{className:'mon-input',rows:7,value:edit.content||'',placeholder:es?'Contenido (qué debe saber/hacer el bot y de dónde obtener los datos)':'Content',onChange:e=>setEdit({...edit,content:e.target.value}),style:{resize:'vertical',fontFamily:'inherit'}}),
          hc('div',{style:{display:'flex',gap:8}},
            hc('button',{className:'btn',onClick:save,disabled:!(edit.title&&edit.content)},es?'Guardar':'Save'),
            hc('button',{className:'btn',onClick:()=>setEdit(null)},es?'Cancelar':'Cancel')))
        :hc('div',null,
          hc('div',{style:{display:'flex',gap:8,marginBottom:8}},
            hc('button',{className:'btn',onClick:()=>setEdit({})},'＋ '+(es?'Nuevo documento':'New doc')),
            hc('span',{className:'mon-cc',style:{alignSelf:'center'}},(docs.length)+' docs')),
          hc('div',{style:{maxHeight:260,overflow:'auto',display:'flex',flexDirection:'column',gap:5}},
            docs.map(d=>hc('div',{key:d.id,style:{display:'flex',gap:8,alignItems:'center',border:'1px solid var(--g200)',borderRadius:8,padding:'6px 9px',fontSize:12}},
              hc('span',{className:'mon-pill'},d.kind),
              hc('span',{style:{flex:1,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap',fontWeight:600},title:d.title},d.title),
              hc('button',{className:'mon-btn-xs',onClick:()=>setEdit({id:d.id,kind:d.kind,title:d.title,content:d.content})},'✎'),
              hc('button',{className:'mon-btn-xs',onClick:()=>del(d.id)},'🗑')))))),
    err&&hc('div',{style:{color:'var(--error)',fontSize:12,marginTop:6}},'⚠ ',err));
}

function ChatWidget({account,isAdmin,gotoResults}){
  const es=OA.lang==='es';
  const [open,setOpen]=cS(false);
  const [showKnow,setShowKnow]=cS(false);
  const [uid,setUid]=cS(localStorage.getItem('oa_bot_uid')||null);
  const [uname,setUname]=cS(localStorage.getItem('oa_bot_name')||'');
  const [nameInput,setNameInput]=cS('');
  const [pendingMatch,setPendingMatch]=cS(null); // {user_id,display_name} a confirmar
  const [msgs,setMsgs]=cS([]);   // {who:'user'|'bot', text, blocks?, tools?}
  const [busy,setBusy]=cS(false);
  const [status,setStatus]=cS('');
  const [input,setInput]=cS('');
  const boxRef=cR(null);
  const scrollDown=()=>{ setTimeout(()=>{ if(boxRef.current) boxRef.current.scrollTop=boxRef.current.scrollHeight; },30); };

  /* API global para otras vistas (MonitorView "Investigar") */
  cE(()=>{ window.OABot={ investigate:(sku,host)=>{ setOpen(true);
      send(es
        ?`Corre el protocolo de diagnóstico Indexado para el SKU ${sku} en ${host}: obtén su URL del monitor, ejecuta inspect_indexado (pasa el sku para marcarlo en verde si verifica) e informa paso a paso el resultado.`
        :`Run the Indexado diagnostic protocol for SKU ${sku} at ${host}: get its URL from the monitor, run inspect_indexado (pass the sku so it turns green if verified) and report step by step.`); } };
    return ()=>{ delete window.OABot; }; },[uid,account]);

  /* pregunta en curso: si el usuario envía OTRA antes de la respuesta, se aborta
     el stream y se re-envía la CONCATENACIÓN de ambas como una sola pregunta.
     (Declarados ANTES del return condicional: isAdmin cambia dentro del mismo
     montaje al elegir marca desde el Monitor, y un hook después de un return
     temprano rompe React con "Rendered fewer hooks than expected".) */
  const abortRef=cR(null); const lastQRef=cR(null); const botIdRef=cR(null);

  if(!OA.BOT_BASE || !isAdmin) return null;

  const identify=async(name,confirmId)=>{
    const r=await fetch(OA.BOT_BASE+'/api/identify',{method:'POST',headers:{'Content-Type':'application/json'},
      body:JSON.stringify({name,account:account&&account!=='all'?account:null,confirm_user_id:confirmId||null})});
    if(!r.ok) throw new Error('identify '+r.status);
    const j=await r.json();
    if(j.confirm_required&&j.match&&!confirmId){ setPendingMatch({...j.match,name}); return null; }
    localStorage.setItem('oa_bot_uid',j.user_id); localStorage.setItem('oa_bot_name',j.display_name||name);
    setUid(j.user_id); setUname(j.display_name||name); setPendingMatch(null);
    if(j.conversations&&j.conversations.length){
      setMsgs(m=>[...m,{who:'bot',text:(es?'¡Hola de nuevo, ':'Welcome back, ')+(j.display_name||name)+'! '+
        (es?'Soy Yoda. Retomo el contexto de tus conversaciones anteriores.':'I am Yoda. Resuming context from your previous conversations.')}]);
    } else {
      setMsgs(m=>[...m,{who:'bot',text:(es?'Hola ':'Hi ')+(j.display_name||name)+' 👋 '+
        (es?'Soy Yoda, el asistente de implementación. Pregúntame por SKUs, retailers, alertas, o pídeme un diagnóstico. Los resultados con datos los dejo organizados en la pestaña "Resultados de Yoda" del Monitor.':'I am Yoda, the implementation assistant. Ask me about SKUs, retailers or alerts; data results are organized in the Monitor\'s "Yoda results" tab.')}]);
    }
    return j.user_id;
  };

  const send=async(text)=>{
    if(!text) return;
    let u=uid;
    if(!u){ setMsgs(m=>[...m,{who:'bot',text:es?'Primero dime tu nombre (arriba) para poder recordarte.':'First tell me your name (above) so I can remember you.'}]); return; }
    let question=text;
    const staleBotId=botIdRef.current;
    if(busy&&lastQRef.current){
      try{ abortRef.current&&abortRef.current.abort(); }catch(e){}
      question=lastQRef.current+'\n\n'+(es?'Además: ':'Also: ')+text;
    }
    const mid='b'+Date.now()+Math.random().toString(36).slice(2,6);
    setMsgs(m=>{
      // quita la respuesta parcial interrumpida (se re-responde todo junto)
      const n=(busy&&staleBotId)?m.filter(x=>x.id!==staleBotId):[...m];
      return [...n,{who:'user',text},{id:mid,who:'bot',text:'',tools:[],blocks:null}];
    });
    lastQRef.current=question; botIdRef.current=mid;
    setBusy(true); setStatus(es?'Pensando…':'Thinking…'); scrollDown();
    const ctl=new AbortController(); abortRef.current=ctl;
    // apply SEGURO por id (nunca "el último mensaje": puede haber sido reemplazado)
    const apply=(fn)=>setMsgs(m=>m.map(x=>{ if(x.id!==mid) return x; const b={...x}; fn(b); return b; }));
    let acc='';   // texto final acumulado (para publicar en el panel de resultados)
    try{
      const r=await fetch(OA.BOT_BASE+'/chat/'+encodeURIComponent(u),{method:'POST',
        headers:{'Content-Type':'application/json'}, signal:ctl.signal,
        body:JSON.stringify({message:question,user:{user_id:u,name:uname},scope:{account:account&&account!=='all'?account:null}})});
      if(!r.ok||!r.body) throw new Error('chat '+r.status);
      const reader=r.body.getReader(); const dec=new TextDecoder();
      let buf='';
      while(true){
        const {done,value}=await reader.read(); if(done) break;
        buf+=dec.decode(value,{stream:true});
        let idx;
        while((idx=buf.indexOf('\n\n'))>=0){
          const chunk=buf.slice(0,idx); buf=buf.slice(idx+2);
          let ev='message', data='';
          for(const line of chunk.split('\n')){
            if(line.startsWith('event:')) ev=line.slice(6).trim();
            else if(line.startsWith('data:')) data+=line.slice(5).trim();
          }
          if(!data) continue;
          let d; try{ d=JSON.parse(data); }catch(e){ continue; }
          if(ev==='progress'){ setStatus(d.text||''); }
          else if(ev==='tool'){ apply(b=>{ b.tools=[...(b.tools||[]),d]; }); setStatus(d.label||d.tool||''); scrollDown(); }
          else if(ev==='delta'){ acc+=(d.text||''); apply(b=>{ b.text=(b.text||'')+(d.text||''); }); scrollDown(); }
          else if(ev==='done'){ if(d.blocks) apply(b=>{ b.blocks=d.blocks; });
            if(d.text) apply(b=>{ if(!b.text) b.text=d.text; }); setStatus(''); scrollDown();
            yodaPublish(question, acc||d.text||'', d.blocks||[]); }
          else if(ev==='error'){ apply(b=>{ b.text=(b.text||'')+'\n⚠ '+(d.error||'error'); }); setStatus(''); }
        }
      }
    }catch(e){
      if(!(e&&e.name==='AbortError')){
        apply(b=>{ b.text=(b.text||'')+'⚠ '+(e&&e.message||e); });
      }
    } finally {
      // solo el stream ACTIVO limpia el estado (uno abortado no pisa al nuevo)
      if(abortRef.current===ctl){
        setBusy(false); setStatus(''); lastQRef.current=null; botIdRef.current=null; abortRef.current=null;
      }
      scrollDown();
    }
  };

  const SUGGEST= es
    ?['¿Dónde están los problemas críticos?','¿Qué retailers requieren más atención?','¿Qué SKUs no están implementados?']
    :['Where are the critical issues?','Which retailers need attention?','Which SKUs are not implemented?'];

  if(!open) return hc('button',{className:'bw-fab',onClick:()=>setOpen(true),'aria-label':'Yoda'},
    '🧙 Yoda');

  return hc('div',{className:'bw-win',role:'dialog'},
    hc('div',{className:'bw-head'},
      hc('span',{className:'bw-ava'},'🧙'),
      hc('div',{style:{minWidth:0}},
        hc('b',null,'Yoda · ',es?'Asistente de implementación':'Implementation assistant'),
        hc('div',{className:'bw-st'},status||(uname?uname:(es?'Sin identificar':'Not identified')))),
      hc('button',{className:'bw-x',title:es?'Entrenamiento':'Training',style:{marginLeft:'auto'},onClick:()=>setShowKnow(k=>!k)},'⚙'),
      hc('button',{className:'bw-x',onClick:()=>setOpen(false)},'×')),
    showKnow&&hc(KnowledgePanel,{onClose:()=>setShowKnow(false)}),
    /* identidad por nombre */
    !uid&&hc('div',{className:'bw-ident'},
      pendingMatch
        ?hc('div',null,
          hc('div',{style:{marginBottom:8}},es?`¿Eres ${pendingMatch.display_name}? Puedo retomar tu contexto.`:`Are you ${pendingMatch.display_name}? I can resume your context.`),
          hc('div',{style:{display:'flex',gap:8}},
            hc('button',{className:'btn',onClick:()=>identify(pendingMatch.name,pendingMatch.user_id)},es?'Sí, soy yo':'Yes, that is me'),
            hc('button',{className:'btn',onClick:()=>identify(pendingMatch.name,'new')},es?'No, crear nuevo':'No, create new')))
        :hc('form',{onSubmit:(e)=>{e.preventDefault(); if(nameInput.trim()) identify(nameInput.trim()).catch(err=>setMsgs(m=>[...m,{who:'bot',text:'⚠ '+err.message}]));}},
          hc('div',{style:{marginBottom:6,fontWeight:700}},es?'¿Cómo te llamas?':'What is your name?'),
          hc('div',{style:{display:'flex',gap:8}},
            hc('input',{className:'mon-input',style:{flex:1},value:nameInput,placeholder:es?'Tu nombre…':'Your name…',onChange:e=>setNameInput(e.target.value)}),
            hc('button',{className:'btn',type:'submit'},'OK')))),
    hc('div',{className:'bw-msgs',ref:boxRef},
      msgs.map((m,i)=>hc('div',{key:i,className:'bw-msg '+m.who},
        (m.tools||[]).map((t,j)=>hc('div',{key:j,className:'bw-tool'},'⚙ ',t.label||t.tool)),
        m.text&&(m.who==='bot'
          ?hc(MdLite,{text:m.text})
          :hc('div',{style:{whiteSpace:'pre-wrap'}},m.text)),
        m.blocks&&hc(YodaBlocks,{blocks:m.blocks}),
        m.blocks&&m.blocks.length>0&&gotoResults&&hc('div',{style:{marginTop:8}},
          hc('button',{className:'bw-chip',onClick:gotoResults},'📊 ',
            es?'Ver organizado en "Resultados de Yoda"':'View organized in "Yoda results"')))),
      busy&&hc('div',{className:'bw-msg bot'},hc('span',{className:'spinner',style:{width:14,height:14}}),' ',status||'…')),
    uid&&hc('div',{className:'bw-suggest'},SUGGEST.map((s,i)=>hc('button',{key:i,className:'bw-chip',onClick:()=>send(s)},s))),
    hc('form',{className:'bw-input',onSubmit:(e)=>{e.preventDefault(); const v=input.trim(); if(v){ send(v); setInput(''); }}},
      hc('input',{value:input,placeholder:busy
        ?(es?'Puedes agregar otra pregunta: se responden juntas':'You can add another question: answered together')
        :(es?'Ej: ¿qué SKUs de HP fallan en Falabella?':'E.g.: which HP SKUs fail at Falabella?'),
        onChange:e=>setInput(e.target.value)}),
      hc('button',{type:'submit'},es?'Enviar':'Send')));
}
window.ChatWidget=ChatWidget;
