// ============ AcademIA8 · Consola — App root (estado, routing, acciones) ============
// El backend (arq_05) es la fuente de verdad. El estado local es un espejo que se
// refresca desde el API tras cada mutación. Nada se persiste en localStorage: se
// relee del servidor. `window.A8Api` traduce el modelo del front (lista mixta de
// ítems) a los contratos del backend (pasos + checkpoints).

const TRAY = '__tray__';

// Estado derivado del backend → status para el pill de la UI.
function statusOf(course){ return A8.estadoToStatus(course && course.derivedEstado); }

// Guía consolidada de reglas de carga de materiales (mismo contenido que las ayudas ?)
const GUIDE_ORDER = ['video','audio','imagen','documento'];
function MaterialGuide({ onClose }){
  return (
    <Modal wide icon="help" title="Guía de carga de materiales"
      subtitle="Reglas de cada tipo de material que podés cargar en una unidad. Los mismos límites aparecen en el ícono ? de cada campo."
      onClose={onClose}
      foot={<><div className="spacer"></div><Btn variant="primary" icon="check" onClick={onClose}>Entendido</Btn></>}>
      <div className="guide-list">
        {GUIDE_ORDER.map(k=>(
          <div key={k} className="guide-row">
            <span className={`iic ft-${k}`}><Icon name={FRAG_ICON[k]} /></span>
            <div className="guide-main">
              <div className="guide-t">{FRAG_LABEL[k]}</div>
              <div className="guide-rules">{A8_RULES[k]?.help}</div>
            </div>
          </div>
        ))}
        <div className="guide-row">
          <span className="iic ft-texto"><Icon name="text" /></span>
          <div className="guide-main">
            <div className="guide-t">Caption del adjunto</div>
            <div className="guide-rules"><span className="ht-p">Texto opcional que acompaña a un adjunto cuando se envía por WhatsApp. Máximo <b>1024 caracteres</b>.</span></div>
          </div>
        </div>
      </div>
    </Modal>
  );
}
const A8_RULES = (typeof MEDIA_RULES!=='undefined') ? MEDIA_RULES : {};

function App(){
  const [courses, setCourses] = useState([]);
  const [nav, setNav] = useState({ screen:'list', courseId:null, unitId:null });
  const [toast, setToast] = useState(null);
  const [guide, setGuide] = useState(false);
  const [sideOpen, setSideOpen] = useState(true);
  const [busy, setBusy] = useState(false);
  const [loadError, setLoadError] = useState(null);
  const [view, setView] = useState('borrador'); // 'borrador' | 'publicado' (toggle del header)

  // Usuario logueado: se lee al render (después de que boot autenticó), no al
  // parse del módulo — así el nombre del ID token ya está disponible.
  const sessionUser = (window.A8Auth && window.A8Auth.user)
    ? window.A8Auth.user()
    : { name:'Autor', email:'', initials:'A8' };

  // reveal suave una vez montado (evita el pop-in del compilado de Babel)
  useEffect(()=>{
    const r = document.getElementById('root');
    if (r){ requestAnimationFrame(()=>requestAnimationFrame(()=>r.classList.add('ready'))); }
  },[]);

  const toastMsg = (m)=>{ setToast(m); setTimeout(()=>setToast(null), 2600); };
  const findCourse = (id)=> courses.find(c=>c.id===id);
  function contOf(c, loc){ return loc===TRAY ? (c.draft.tray||{items:[]}) : c.draft.units.find(x=>x.id===loc); }

  function handleErr(e){
    const msg = (e && e.message) ? e.message : 'Ocurrió un error';
    toastMsg(msg);
    // 401 ya dispara logout desde el cliente API.
  }

  // ---- carga de datos ----
  function upsertCourse(full){
    setCourses(cs=>{ const i=cs.findIndex(c=>c.id===full.id); const copy=cs.slice(); if(i>=0) copy[i]=full; else copy.unshift(full); return copy; });
  }

  // Carga (o recarga) un curso. El editor solo necesita el BORRADOR (getCourse):
  // se renderiza apenas eso resuelve, SIN bloquear en /estado y /diff (que en un
  // curso publicado suman ~2 round-trips extra y hacían que el editor "no
  // apareciera" por varios segundos). El estado derivado se siembra de lo ya
  // conocido (la lista lo trae) y se refina en segundo plano con refreshStatus.
  async function reload(id){
    const full = await A8Api.getCourse(id);
    const prev = findCourse(id);
    full.derivedEstado = prev ? prev.derivedEstado : undefined;
    full.published = prev ? !!prev.published : false;
    full.nuevasUnitIds = (prev && prev.nuevasUnitIds) || [];
    full.editadasUnitIds = (prev && prev.editadasUnitIds) || [];
    upsertCourse(full);        // el editor ya puede renderizar el borrador editable
    refreshStatus(id);         // /estado + /diff en segundo plano (no se espera)
    return full;
  }

  // Estado derivado (pill / nota de publicar) y diff (lock de unidades publicadas)
  // en segundo plano; cuando llegan, actualizan el curso sin bloquear la edición.
  async function refreshStatus(id){
    try {
      const est = await A8Api.estado(id);
      const published = est !== 'solo-borrador';
      let nuevas = [], editadas = [];
      if (published){
        try {
          const d = await A8Api.diff(id);
          nuevas = (d.nuevas||[]).map(x=>x.unidad_id);
          editadas = (d.editadas||[]).map(x=>x.unidad_id);
        } catch(e){ /* diff best-effort */ }
      }
      setCourses(cs=>cs.map(c=> c.id===id
        ? { ...c, derivedEstado:est, published, nuevasUnitIds:nuevas, editadasUnitIds:editadas }
        : c));
    } catch(e){ /* el editor ya está; el estado se refina cuando se pueda */ }
  }

  async function loadList(){
    setLoadError(null);
    try {
      const list = await A8Api.listCourses();
      setCourses(cs=> list.map(r=>{
        const ex = cs.find(c=>c.id===r.curso_id);
        const patch = { derivedEstado:r.estado_derivado, published:r.estado_derivado!=='solo-borrador' };
        if (ex && ex._loaded) return { ...ex, ...patch, meta_draft:{ ...ex.meta_draft, name:r.titulo } };
        return { id:r.curso_id, meta_draft:{ name:r.titulo }, ...patch, _light:true };
      }));
    } catch(e){ setLoadError(e.message||String(e)); }
  }

  // carga inicial de la lista
  useEffect(()=>{ loadList(); },[]);

  // navegación con carga perezosa del borrador completo
  async function openCourse(id){
    setView('borrador');   // al abrir un curso siempre arrancamos en borrador editable
    setNav({ screen:'course', courseId:id, unitId:null });
    const c = findCourse(id);
    if (!c || !c._loaded){
      setBusy(true);
      try { await reload(id); } catch(e){ handleErr(e); setNav({ screen:'list', courseId:null, unitId:null }); }
      finally { setBusy(false); }
    }
  }

  // Carga la copia PUBLICADO (snapshot solo-lectura) bajo demanda para el toggle.
  async function loadPublished(id){
    const c = findCourse(id);
    if (c && c.publishedFront) return;   // ya cargada
    setBusy(true);
    try {
      const pub = await A8Api.getCourse(id, 'publicado');
      setCourses(cs=>cs.map(x=> x.id===id ? { ...x, publishedFront:pub } : x));
    } catch(e){ handleErr(e); setView('borrador'); }
    finally { setBusy(false); }
  }

  // Cambio de vista del header (Borrador / Publicado).
  function changeView(v){
    setView(v);
    if (v === 'publicado' && current && current.published) loadPublished(current.id);
  }

  // helper para envolver una mutación: corre la llamada, recarga y avisa.
  async function run(id, fn, okMsg){
    setBusy(true);
    try { await fn(); if (id) await reload(id); if (okMsg) toastMsg(okMsg); return true; }
    catch(e){ if (id){ try{ await reload(id); }catch(_){} } handleErr(e); return false; }
    finally { setBusy(false); }
  }

  const actions = {
    // navegación
    goList: ()=>{ setNav({ screen:'list', courseId:null, unitId:null }); loadList(); },
    notify: (m)=>toastMsg(m),
    openCourse,
    openUnit: (id,uid)=>setNav({ screen:'unit', courseId:id, unitId:uid }),
    openPublish: (id)=>setNav({ screen:'publish', courseId:id, unitId:null }),

    // ---- Curso ----
    createCourse: async ()=>{
      const id = A8.uid('curso');
      setBusy(true);
      try {
        await A8Api.createCourse(id, { name:'Curso nuevo', description:'', welcome:'', schedule:'Entrega diaria de contenido' });
        await reload(id);
        setNav({ screen:'course', courseId:id, unitId:null });
        toastMsg('Curso creado en borrador');
      } catch(e){ handleErr(e); }
      finally { setBusy(false); }
    },
    duplicateCourse: async (origenId, destinoId, titulo)=>{
      setBusy(true);
      try {
        const r = await A8Api.duplicarCurso(origenId, destinoId, titulo);
        await reload(r.curso_id);
        setNav({ screen:'course', courseId:r.curso_id, unitId:null });
        toastMsg('Curso duplicado como nuevo borrador');
        return true;
      } catch(e){ handleErr(e); return false; }
      finally { setBusy(false); }
    },
    deleteCourse: async (id)=>{
      const ok = await run(null, ()=>A8Api.deleteCourse(id), 'Curso borrado');
      if (ok){ setCourses(cs=>cs.filter(c=>c.id!==id)); setNav({ screen:'list', courseId:null, unitId:null }); loadList(); }
    },
    updateMeta: (id, meta)=> run(id, ()=>A8Api.updateMeta(id, meta), 'Metadata guardada en borrador'),

    // ---- Unidades ----
    addUnit: (id, title)=>{
      const c = findCourse(id);
      const unitId = A8.uid('u');
      const posicion = (c.draft.units.length) + 1;
      return run(id, ()=>A8Api.createUnit(id, unitId, title, posicion), 'Unidad agregada');
    },
    renameUnit: (id, uid, title)=> run(id, ()=>A8Api.renameUnit(id, uid, title)),
    deleteUnit: (id, uid)=> run(id, ()=>A8Api.deleteUnit(id, uid), 'Unidad borrada'),
    reorderUnit: (id, from, to)=>{
      const c = findCourse(id);
      const units = c.draft.units.slice();
      if (to<0 || to>=units.length) return;
      const [m] = units.splice(from,1); units.splice(to,0,m);
      return run(id, ()=>A8Api.reorderUnits(id, units.map(u=>u.id)));
    },

    // ---- Ítems (texto / pregunta) ----
    // loc = id de unidad o '__tray__' (bandeja).
    addItem: (id, loc, item)=>{
      const c = findCourse(id);
      const cont = contOf(c, loc);
      const unidad_id = loc===TRAY ? null : loc;
      const orden = loc===TRAY ? 1 : (cont.items.filter(x=>!x._readonly).length + 1);
      return run(id, ()=>A8Api.saveItem(id, unidad_id, item, orden), loc===TRAY?'Agregado a la bandeja':'Contenido agregado');
    },
    addItems: (id, loc, items)=>{
      if (!items || !items.length) return;
      const c = findCourse(id);
      const cont = contOf(c, loc);
      const unidad_id = loc===TRAY ? null : loc;
      let base = loc===TRAY ? 1 : (cont.items.filter(x=>!x._readonly).length + 1);
      return run(id, async ()=>{ for (let i=0;i<items.length;i++){ await A8Api.saveItem(id, unidad_id, items[i], base+i); } },
        `${items.length} elemento${items.length>1?'s':''} agregado${items.length>1?'s':''}`);
    },
    saveItem: (id, loc, item)=>{
      const c = findCourse(id);
      const cont = contOf(c, loc);
      const unidad_id = loc===TRAY ? null : loc;
      const idx = cont ? cont.items.findIndex(x=>x.id===item.id) : -1;
      const orden = loc===TRAY ? 1 : (idx>=0 ? idx+1 : cont.items.filter(x=>!x._readonly).length+1);
      return run(id, ()=>A8Api.saveItem(id, unidad_id, item, orden), 'Contenido guardado');
    },
    renameItem: (id, loc, itemId, patch)=>{
      const c = findCourse(id);
      const cont = contOf(c, loc);
      const it = cont && cont.items.find(x=>x.id===itemId);
      if (!it || it._readonly) return;
      const merged = { ...it, ...patch };
      const unidad_id = loc===TRAY ? null : loc;
      const idx = cont.items.findIndex(x=>x.id===itemId);
      const orden = loc===TRAY ? 1 : idx+1;
      return run(id, ()=>A8Api.saveItem(id, unidad_id, merged, orden));
    },
    deleteItem: (id, loc, itemId)=>{
      const c = findCourse(id);
      const cont = contOf(c, loc);
      const remaining = (loc===TRAY || !cont) ? [] : cont.items.filter(x=>x.id!==itemId && !x._readonly);
      return run(id, async ()=>{
        await A8Api.deleteItem(id, itemId);
        if (loc!==TRAY && remaining.length) await A8Api.reorderItems(id, loc, remaining); // renumera orden contiguo
      }, 'Contenido borrado');
    },

    // mover un ítem entre contenedores o reordenar dentro de uno.
    moveItem: (id, fromLoc, fromIx, toLoc, toIx)=>{
      const c = findCourse(id);
      const fromCont = contOf(c, fromLoc), toCont = contOf(c, toLoc);
      if (!fromCont || !toCont) return;
      const moving = fromCont.items[fromIx];
      if (!moving) return;
      if (moving._readonly){ toastMsg('Ese contenido es de solo lectura'); return; }
      return run(id, async ()=>{
        if (fromLoc===toLoc){
          if (fromLoc===TRAY) return; // la bandeja no tiene orden persistente
          const items = fromCont.items.slice();
          items.splice(fromIx,1);
          let ti = toIx; if (fromIx<toIx) ti--;
          items.splice(Math.max(0,Math.min(ti,items.length)),0,moving);
          await A8Api.reorderItems(id, fromLoc, items.filter(x=>!x._readonly));
        } else {
          const toUnit = toLoc===TRAY ? null : toLoc;
          await A8Api.moveItemToUnit(id, moving.id, toUnit);
          if (moving.kind==='chk') await A8Api.moveConcepto(id, moving.id, toUnit); // el concepto viaja con la pregunta
          if (toUnit){
            const items = toCont.items.filter(x=>!x._readonly);
            items.splice(Math.max(0,Math.min(toIx,items.length)),0,moving);
            await A8Api.reorderItems(id, toUnit, items);
          }
          if (fromLoc!==TRAY){
            const rest = fromCont.items.filter(x=>x.id!==moving.id && !x._readonly);
            if (rest.length) await A8Api.reorderItems(id, fromLoc, rest);
          }
        }
      });
    },

    // publicar
    publish: (id)=> run(id, ()=>A8Api.publish(id), 'Curso publicado'),
  };

  const current = findCourse(nav.courseId);
  // Solo los cursos publicados tienen copia PUBLICADO; si no, la vista es siempre borrador.
  const effectiveView = (current && current.published) ? view : 'borrador';
  const showToggle = nav.screen === 'course' && current && current._loaded;

  // navegación con crumbs
  const crumbs = [];
  const crumbLabel = nav.screen==='campanias' ? 'Campañas' : (nav.screen==='costos' ? 'Costos' : 'Cursos WhatsApp');
  crumbs.push({ label: crumbLabel, onClick:actions.goList, cur: nav.screen==='list' || nav.screen==='campanias' || nav.screen==='costos' });
  if (current){
    const nm = current.meta_draft?.name || 'Curso';
    crumbs.push({ label:nm, onClick:()=>actions.openCourse(current.id), cur: nav.screen==='course' });
    if (nav.screen==='publish') crumbs.push({ label:'Publicar', cur:true });
  }

  return (
    <div className={`app${sideOpen?'':' side-collapsed'}`}>
      {/* SIDEBAR */}
      <aside className="side">
        <div className="side-glow" style={{background:'#01C599',left:'-70px',top:'-40px'}}></div>
        <div className="side-glow" style={{background:'#CE1E4E',right:'-80px',bottom:'40px',opacity:.25}}></div>
        <button className="side-collapse" title="Contraer barra lateral" onClick={()=>setSideOpen(false)} style={{width:'40px',height:'38px'}}><Icon name="panelclose" style={{width:'31px',height:'22px'}} /></button>
        <div className="side-brand">
          <BrandMark />
          <div>
            <div className="wm">Academ<span className="ia">IA</span>8</div>
            <div className="tag">Consola de autoría</div>
          </div>
        </div>
        <nav className="nav">
          <div className="nav-lbl">Contenido</div>
          <button className={`nav-item${nav.screen==='list'?' active':''}`} onClick={actions.goList}><Icon name="courses" />Cursos WhatsApp</button>
          <button className={`nav-item${nav.screen==='campanias'?' active':''}`} onClick={()=>setNav({ screen:'campanias', courseId:null, unitId:null })}><Icon name="send" />Campañas</button>
          {window.FEATURES.costCalc && (
            <button className={`nav-item${nav.screen==='costos'?' active':''}`} onClick={()=>setNav({ screen:'costos', courseId:null, unitId:null })}><Icon name="money" />Costos</button>
          )}
        </nav>
        <div className="side-foot">
          <div className="who">
            <div className="av">{sessionUser.initials}</div>
            <div>
              <div className="nm">{sessionUser.name}</div>
              <div className="rl">Equipo AcademIA8</div>
            </div>
          </div>
        </div>
      </aside>

      {/* MAIN */}
      <div className="main">
        <div className="topbar">
          {!sideOpen && (
            <button className="topbar-open" title="Mostrar barra lateral" onClick={()=>setSideOpen(true)}><Icon name="panelopen" /></button>
          )}
          <div className="crumbs">
            {crumbs.map((c,i)=>(
              <React.Fragment key={i}>
                {i>0 && <span className="sep"><Icon name="chevright" style={{width:14,height:14}} /></span>}
                {c.cur ? <span className="cur">{c.label}</span> : <a onClick={c.onClick}>{c.label}</a>}
              </React.Fragment>
            ))}
          </div>
          <div className="topbar-right">
            {showToggle && <ViewToggle value={effectiveView} onChange={changeView} disabled={!current.published} />}
            {busy && <span className="busy-dot" title="Sincronizando con el servidor…"><Icon name="refresh" /></span>}
            <button className="topbar-info" title="Guía de carga de materiales" onClick={()=>setGuide(true)}><Icon name="help" /><span>Guía</span></button>
            <SessionMenu user={sessionUser} onLogout={()=>window.A8Auth.logout()} />
          </div>
        </div>

        <div className="main-scroll">
          <ErrorBoundary key={nav.screen + ':' + (current ? current.id : '')}>
            {nav.screen==='list' && <CourseList courses={courses} actions={actions} loadError={loadError} onRetry={loadList} />}
            {nav.screen==='course' && current && current._loaded && <CourseEditor key={current.id} course={current} view={effectiveView} actions={actions} />}
            {nav.screen==='course' && (!current || !current._loaded) && <LoadingScreen />}
            {nav.screen==='publish' && current && current._loaded && <PublishView key={current.id} course={current} actions={actions} />}
            {nav.screen==='campanias' && <CampaignsModule notify={toastMsg} />}
            {nav.screen==='costos' && window.FEATURES.costCalc && <CostosModule />}
          </ErrorBoundary>
        </div>
      </div>

      {toast && <div className="toast-wrap"><Toast msg={toast} /></div>}
      {guide && <MaterialGuide onClose={()=>setGuide(false)} />}
    </div>
  );
}

// Isotipo de marca (fallback limpio en SVG inline; no depende de assets externos).
function BrandMark(){
  return (
    <svg width="38" height="38" viewBox="0 0 38 38" fill="none" xmlns="http://www.w3.org/2000/svg" aria-label="AcademIA8" role="img">
      <defs>
        <linearGradient id="a8grad" x1="0" y1="0" x2="38" y2="38" gradientUnits="userSpaceOnUse">
          <stop stopColor="#01C599" />
          <stop offset="1" stopColor="#CE1E4E" />
        </linearGradient>
      </defs>
      <rect width="38" height="38" rx="11" fill="url(#a8grad)" />
      <text x="19" y="20" textAnchor="middle" dominantBaseline="central" fill="#fff"
        fontFamily="Poppins, Manrope, sans-serif" fontSize="18" fontWeight="800">8</text>
    </svg>
  );
}

function LoadingScreen(){
  return (
    <div className="wrap">
      <div className="empty-state" style={{padding:'60px 20px'}}>
        <div className="es-ic"><Icon name="refresh" /></div>
        <h3>Cargando…</h3>
        <p>Trayendo el borrador del curso desde el servidor.</p>
      </div>
    </div>
  );
}

// El render lo dispara boot.jsx tras cargar config.json y autenticar (Cognito).
window.__A8_BOOT__ = () => {
  ReactDOM.createRoot(document.getElementById('root')).render(<App />);
};
