// ============ AcademIA8 · Consola — Organizador de contenido (2 paneles + DnD) ============
// Reemplaza el editor de unidad. Trabaja sobre el BORRADOR del curso via `actions`.
const TRAY_LOC = '__tray__';
// En cursos ya publicados no se puede reordenar / borrar / insertar unidades (correría el progreso del alumno).
const UNIT_LOCK_MSG = 'No disponible en cursos publicados: correría el progreso de los alumnos.';

// ---- validación de archivos (reglas de WhatsApp / Meta) ----
const MB = 1048576;
// video: límite de SUBIDA (500MB, no el de entrega — el compresor achica
// cualquier video >16MB a ~15.5MB antes de que llegue a publicarse, C4bis
// compresor). El resto son límites de entrega, sin compresor, sin cambios.
const LIMITS = { video:500, audio:16, imagen:5, documento:100 }; // MB
// Umbral desde el que un video dispara compresión automática al subir —
// mismo límite de entrega de siempre (WhatsApp), no el de subida de arriba.
const VIDEO_COMPRESS_THRESHOLD_MB = 16;
const EXT_TYPE = {
  mp4:'video', mov:'video', mkv:'video', webm:'video', avi:'video',
  jpg:'imagen', jpeg:'imagen', png:'imagen', gif:'imagen', webp:'imagen', heic:'imagen',
  mp3:'audio', aac:'audio', ogg:'audio', opus:'audio', m4a:'audio', wav:'audio',
  pdf:'documento', doc:'documento', docx:'documento', ppt:'documento', pptx:'documento', xls:'documento', xlsx:'documento', txt:'documento',
};
const ALLOWED = { video:['mp4'], imagen:['jpg','jpeg','png'], audio:['aac','mp3','ogg','opus'], documento:['pdf','doc','docx','ppt','pptx','xls','xlsx'] };
function humanSize(bytes){ return bytes>=MB ? (bytes/MB).toFixed(1)+' MB' : Math.max(1,Math.round(bytes/1024))+' KB'; }
function classify(file){
  const ext=(file.name.split('.').pop()||'').toLowerCase();
  const type=EXT_TYPE[ext];
  const sizeMB=file.size/MB;
  if(!type) return { type:null, ok:false, reason:'Formato no soportado' };
  if(!ALLOWED[type].includes(ext)) return { type, ok:false, reason:`Formato no permitido · usá ${ALLOWED[type].slice(0,3).join('/').toUpperCase()}` };
  const lim=LIMITS[type];
  if(sizeMB>lim) return { type, ok:false, reason: type==='video'?'Supera 500 MB · exportá en 1080p e intentá de nuevo':`Supera ${lim} MB` };
  return { type, ok:true, reason:'Listo para agregar' };
}

// Etiqueta de display de un ítem: los ítems de texto NO tienen título (el modelo
// no lo tiene), así que se deriva del propio texto. Las preguntas usan su enunciado.
function itemLabel(item){
  if (item.kind==='chk') return item.question || '(pregunta)';
  if (item.type && item.type!=='texto') return item.title || FRAG_LABEL[item.type] || '(adjunto)';
  const t = (item.text||'').trim();
  return t ? (t.length>90 ? t.slice(0,90)+'…' : t) : '(texto vacío)';
}

// título editable inline
function OgEditable({ value, onChange, className, inpClass }){
  const [ed,setEd]=useState(false); const [v,setV]=useState(value);
  useEffect(()=>setV(value),[value]);
  const commit=()=>{ setEd(false); const t=v.trim(); if(t&&t!==value) onChange(t); else setV(value); };
  if(ed) return <input className={`edit-inp ${inpClass||''}`} autoFocus value={v} onChange={e=>setV(e.target.value)} onBlur={commit}
    onKeyDown={e=>{ if(e.key==='Enter') commit(); if(e.key==='Escape'){ setEd(false); setV(value); } }}
    onClick={e=>e.stopPropagation()} draggable={false} onDragStart={e=>e.preventDefault()} />;
  return <span className={className} onClick={e=>{ e.stopPropagation(); setEd(true); }} title="Clic para renombrar">{value}</span>;
}

function OgItem({ item, loc, index, over, dnd, onRename, onEdit, onDelete, unitReadonly, breakdown }){
  const isFrag = item.kind==='frag';
  const cls = isFrag ? `ft-${item.type||'texto'}` : 'ft-chk';
  const insert = over && over.loc===loc && over.index===index;
  const label = itemLabel(item);
  const ro = !!item._readonly;
  return (
    <div className={`item${insert?' insert-before':''}`} draggable={!ro}
      onDragStart={(e)=>{ if(ro) return; e.stopPropagation(); dnd.startItem(loc,item.id); }}
      onDragEnd={dnd.end}
      onDragOver={(e)=>{ e.preventDefault(); e.stopPropagation(); dnd.onOver(loc,index); }}
      onDrop={(e)=>{ e.preventDefault(); e.stopPropagation(); dnd.onDrop(loc,index); }}>
      <span className="ihandle" style={ro?{opacity:.25}:null}><Icon name="drag" /></span>
      <span className={`iic ${cls}`}><Icon name={isFrag?FRAG_ICON[item.type||'texto']:'question'} /></span>
      <div className="ibody">
        {(!isFrag && !ro)
          ? <OgEditable className="ititle" value={label} onChange={(v)=>onRename({question:v})} />
          : <span className="ititle" title={label}>{label}{ro && !unitReadonly && <span className="opt-tag" style={{marginLeft:'6px'}}>solo lectura</span>}</span>}
        {breakdown && <span className="imsg">{breakdown}</span>}
      </div>
      {!ro && <button className="icon-btn" title="Editar contenido" onClick={onEdit}><Icon name="edit" style={{width:15,height:15}} /></button>}
      {!ro && <button className="icon-btn danger" title="Borrar" onClick={onDelete}><Icon name="x" style={{width:15,height:15}} /></button>}
    </div>
  );
}

// Agrupación visual por burst de WhatsApp (vista, no cambia entrega):
// `groupBursts`/`itemMessageCount`/`FRENO_LABEL` vienen de console/burst.js
// (lógica pura sin JSX, testeada en tests/front/burst_grouping.test.mjs —
// espeja compute_segment en src/orchestrator/guion.py:130-158).
function OgItemList({ items, loc, empty, over, dnd, onRename, onEdit, onDelete, unitReadonly }){
  const zoneOver = over && over.loc===loc;
  // La agrupación solo tiene sentido sobre el guion real de una unidad: la
  // bandeja no tiene orden de entrega (ítems sin ubicar), y las unidades
  // importadas no traen esperaRespuesta/textoComoCaption (ver Fase A).
  const canGroup = loc!==TRAY_LOC && !unitReadonly;
  const bursts = canGroup ? groupBursts(items) : null;

  const renderItem = (it, i) => (
    <OgItem key={it.id} item={it} loc={loc} index={i} over={over} dnd={dnd} unitReadonly={unitReadonly}
      breakdown={canGroup ? itemMessageBreakdown(it) : null}
      onRename={(patch)=>onRename(loc,it.id,patch)} onEdit={()=>onEdit(loc,it)} onDelete={()=>onDelete(loc,it.id)} />
  );

  let body;
  if (bursts){
    let idx = 0;
    body = bursts.map((g,gi)=>{
      const rendered = g.items.map((it)=>renderItem(it, idx++));
      const n = g.items.reduce((s,it)=>s+itemMessageCount(it), 0);
      return (
        <div key={'burst-'+gi} className="burst-group">
          <div className="burst-count">{n} mensaje{n===1?'':'s'} juntos</div>
          {rendered}
          <div className="burst-freno">{FRENO_LABEL[g.freno]}</div>
        </div>
      );
    });
  } else {
    body = items.map((it,i)=>renderItem(it,i));
  }

  return (
    <div className={`${loc===TRAY_LOC?'tray-items':'items'}${items.length===0?' empty':''}${zoneOver?' zone-over':''}`}
      onDragOver={(e)=>{ e.preventDefault(); dnd.onOver(loc, items.length); }}
      onDrop={(e)=>{ e.preventDefault(); dnd.onDrop(loc, items.length); }}>
      {items.length===0 && <div className="drop-empty">{empty}</div>}
      {unitReadonly && loc!==TRAY_LOC && items.length>0 && (
        <div className="burst-noapply">Agrupación de mensajes: no aplica (contenido importado)</div>
      )}
      {body}
    </div>
  );
}

// ================= Panel Agregar / Editar contenido =================
// - NUEVO: importador MÚLTIPLE de archivos con validación (sí/no) por cada uno.
// - Texto y Cuestionario se agregan por separado (alta manual).
// - EDITAR: formulario simple del item existente.
function ContentForm({ data, cursoId, unidadId, onClose, onSaveItem, onAddItems }){
  const editing = !!data;
  const mediaOn = window.FEATURES.mediaNonText;   // C4: media no-texto (video/audio/imagen/doc)
  const editType = editing ? (data.kind==='chk'?'cuestionario':data.type) : null;
  // modo: 'choose' (selector texto/pregunta) | 'bulk' (importar archivos, solo con C4)
  //       | 'texto' | 'cuestionario' | 'media' (editar archivo, solo con C4)
  const initMode = editing
    ? (data.kind==='chk' ? 'cuestionario' : (data.type==='texto' || !mediaOn ? 'texto' : 'media'))
    : (mediaOn ? 'bulk' : 'choose');
  const [mode,setMode] = useState(initMode);
  const backMode = mediaOn ? 'bulk' : 'choose';   // a dónde vuelve el back-link

  // --- estados alta manual (texto / cuestionario / editar media) ---
  const [title,setTitle] = useState(data?.title || '');
  const [text,setText] = useState(data?.text || '');
  // Uploader v2: subida real (presigned PUT). `s3Key` sigue siendo la fuente de
  // verdad que se guarda; se completa sola al terminar de subir, o a mano si el
  // autor usa el fallback ("pegar la key a mano").
  const [s3Key,setS3Key] = useState(data?.s3Key || '');
  const [uploadPct,setUploadPct] = useState(data?.s3Key ? 100 : 0);
  const [uploadErr,setUploadErr] = useState(null);
  const [manualMode,setManualMode] = useState(false);
  const [compressionStatus,setCompressionStatus] = useState(null);
  const [compressionInfo,setCompressionInfo] = useState(null);
  const editFileRef = useRef(null);
  const [caption,setCaption] = useState(data?.caption || '');
  // C4bis (arq_06bis): flags de paso, default false = comportamiento de hoy.
  const [textoComoCaption,setTextoComoCaption] = useState(data?.textoComoCaption || false);
  const [esperaRespuesta,setEsperaRespuesta] = useState(data?.esperaRespuesta || false);
  const [q,setQ] = useState(data?.question || '');
  const [tipoPregunta,setTipoPregunta] = useState(data?.tipoPregunta || 'opcion_multiple');
  const [opts,setOpts] = useState(data?.options ? [...data.options] : ['Opción 1','Opción 2']);
  const [correct,setCorrect] = useState(data?.correct ?? 0);
  const [rubrica,setRubrica] = useState(data?.rubrica || '');
  // f) checkpoint: bloqueante (el alumno no avanza hasta responder bien) + reorientación
  // por rúbrica en sus tres ramas (si_correcto/si_parcial/si_incorrecto — el motor
  // las lee así, ver orchestrator/agent/tools.py:_reorientacion_hint).
  const [blocking,setBlocking] = useState(data?.blocking ?? false);
  const [reorientCorrecto,setReorientCorrecto] = useState(data?.reorientCorrecto || '');
  const [reorientParcial,setReorientParcial] = useState(data?.reorientParcial || '');
  const [reorientIncorrecto,setReorientIncorrecto] = useState(data?.reorientIncorrecto || '');

  // --- estados importador ---
  const [files,setFiles] = useState([]);
  const [over,setOver] = useState(false);
  const inputRef = useRef(null);

  // Uploader v2: cada archivo válido arranca a subir solo (presigned PUT) apenas
  // se elige/arrastra. `s3Key` se completa cuando termina; `manualKey` marca los
  // que el autor prefirió pegar a mano (fallback) en vez de subir.
  async function startUpload(fileObj, rawFile){
    setFiles(prev=>prev.map(x=>x.id===fileObj.id?{...x,uploading:true,error:null}:x));
    try {
      const { upload_url, s3_key, content_type } = await A8Api.getMediaUploadUrl(cursoId, {
        filename: rawFile.name, sizeBytes: rawFile.size, unidadId,
      });
      await A8Api.uploadFileToS3(upload_url, rawFile, content_type, (pct)=>
        setFiles(prev=>prev.map(x=>x.id===fileObj.id?{...x,progress:pct}:x)));
      setFiles(prev=>prev.map(x=>x.id===fileObj.id
        ? {...x,uploading:false,uploaded:true,progress:100,s3Key:s3_key,error:null} : x));
      // Video >16MB dispara compresión automática (S3 event, C4bis compresor).
      // El sidecar puede tardar un toque en aparecer recién subido el archivo.
      if(fileObj.type==='video' && rawFile.size > VIDEO_COMPRESS_THRESHOLD_MB*MB){
        setFiles(prev=>prev.map(x=>x.id===fileObj.id?{...x,compressionStatus:'procesando'}:x));
        A8Api.pollMediaStatus(cursoId, s3_key, (st)=>
          setFiles(prev=>prev.map(x=>x.id===fileObj.id?{...x,compressionStatus:st.status,compressionInfo:st}:x)));
      }
    } catch(e){
      setFiles(prev=>prev.map(x=>x.id===fileObj.id
        ? {...x,uploading:false,uploaded:false,error:(e&&e.message)||'Error al subir'} : x));
    }
  }
  function addFiles(fileList){
    const raws = Array.from(fileList||[]);
    const arr = raws.map(f=>{
      const c=classify(f);
      return { id:A8.uid('f'), name:f.name, human:humanSize(f.size), s3Key:'', progress:0,
        uploading:false, uploaded:false, error:null, manualKey:false, _raw:f, ...c };
    });
    if(arr.length) setFiles(prev=>[...prev, ...arr]);
    arr.forEach((fo,i)=>{ if(fo.ok) startUpload(fo, raws[i]); });
  }
  const removeFile=(id)=> setFiles(files.filter(f=>f.id!==id));
  const setFileKey=(id,v)=> setFiles(files.map(f=>f.id===id?{...f,s3Key:v}:f));
  const toggleManual=(id)=> setFiles(files.map(f=>f.id===id?{...f,manualKey:!f.manualKey}:f));
  const retryUpload=(id)=>{
    const fo = files.find(f=>f.id===id);
    if (fo && fo._raw) startUpload(fo, fo._raw);
  };
  const validFiles = files.filter(f=>f.ok && f.s3Key.trim() && (f.uploaded || f.manualKey));

  function saveBulk(){
    const items = validFiles.map(f=>({ id:A8.uid('it'), kind:'frag', type:f.type, title:f.name.replace(/\.[^.]+$/,''), s3Key:f.s3Key.trim() }));
    onAddItems(items);
  }

  // --- alta/edición manual ---
  const setOpt=(i,v)=>{ const n=[...opts]; n[i]=v; setOpts(n); };
  const addOpt=()=> opts.length<6 && setOpts([...opts,'']);
  const delOpt=(i)=>{ if(opts.length<=2) return; const n=opts.filter((_,j)=>j!==i); setOpts(n); if(correct>=n.length||correct===i) setCorrect(0); };

  function saveText(){ onSaveItem({ id:data?.id||A8.uid('it'), kind:'frag', type:'texto', title:title.trim(), text:text.trim(), esperaRespuesta }); }
  function saveMedia(){ const fr={ id:data?.id||A8.uid('it'), kind:'frag', type:editType, title:title.trim(), s3Key:s3Key.trim(), text:text.trim(), textoComoCaption, esperaRespuesta }; if(caption.trim()) fr.caption=caption.trim(); onSaveItem(fr); }
  function saveChk(){
    const reorientFields = {
      reorientCorrecto: reorientCorrecto.trim(),
      reorientParcial: reorientParcial.trim(),
      reorientIncorrecto: reorientIncorrecto.trim(),
    };
    if (tipoPregunta==='abierta'){
      onSaveItem({ id:data?.id||A8.uid('it'), kind:'chk', tipoPregunta:'abierta', question:q.trim(), rubrica:rubrica.trim(), blocking, ...reorientFields });
      return;
    }
    const clean=opts.map(o=>o.trim()).filter(Boolean);
    onSaveItem({ id:data?.id||A8.uid('it'), kind:'chk', tipoPregunta:'opcion_multiple', question:q.trim(), options:clean, correct:Math.min(correct,clean.length-1), blocking, ...reorientFields });
  }

  const modalTitle = editing ? 'Editar contenido' : (mode==='bulk'?'Agregar contenido':(mode==='cuestionario'?'Agregar cuestionario':'Agregar texto'));

  // ---------------- MODO SELECTOR (sin media, C4 apagado) ----------------
  if(mode==='choose'){
    return (
      <Modal wide icon="plus" title="Agregar contenido"
        subtitle="Elegí qué querés agregar a la unidad. Se guarda en el borrador."
        onClose={onClose}
        foot={<><div className="spacer"></div><Btn variant="subtle" onClick={onClose}>Cancelar</Btn></>}>
        <div className="type-picker" style={{gridTemplateColumns:'1fr 1fr'}}>
          <button className="type-opt" onClick={()=>{ setMode('texto'); setTitle(''); setText(''); }}>
            <Icon name="text" /><span>Texto</span>
          </button>
          <button className="type-opt" onClick={()=>{ setMode('cuestionario'); setQ(''); setOpts(['Opción 1','Opción 2']); setCorrect(0); }}>
            <Icon name="question" /><span>Cuestionario</span>
          </button>
        </div>
        <div className="hint" style={{marginTop:'12px'}}>La carga de video, audio, imagen y documentos estará disponible próximamente.</div>
      </Modal>
    );
  }

  // ---------------- MODO IMPORTAR (múltiple) ----------------
  if(mode==='bulk'){
    return (
      <Modal wide icon="upload" title="Agregar contenido"
        subtitle="Arrastrá o elegí los archivos: se suben solos. Se guardan en el borrador."
        onClose={onClose}
        foot={<><div className="spacer"></div><Btn variant="subtle" onClick={onClose}>Cancelar</Btn><Btn variant="primary" icon="check" disabled={!validFiles.length} onClick={saveBulk}>{validFiles.length?`Agregar ${validFiles.length} archivo${validFiles.length>1?'s':''}`:'Agregar archivos'}</Btn></>}>
        <input ref={inputRef} type="file" multiple style={{display:'none'}} onChange={e=>{ addFiles(e.target.files); e.target.value=''; }} />
        <div className={`dropzone${over?' over':''}`}
          onDragOver={e=>{e.preventDefault();setOver(true);}} onDragLeave={()=>setOver(false)}
          onDrop={e=>{e.preventDefault();setOver(false);addFiles(e.dataTransfer.files);}}
          onClick={()=>inputRef.current&&inputRef.current.click()}>
          <div className="dz-ic"><Icon name="upload" /></div>
          <div className="dz-t">Arrastrá varios archivos o hacé clic para elegirlos</div>
          <div className="dz-s">Video hasta 500 MB (se comprime solo si pesa más de 16 MB) · Audio hasta 16 MB · Imagen 5 MB · Documento 100 MB.</div>
        </div>

        {files.length>0 && (
          <div className="bulk-list">
            {files.map(f=>(
              <div key={f.id} className={`bulk-row${f.ok?'':' bad'}`}>
                <span className={`iic ${f.type?`ft-${f.type}`:'ft-chk'}`} style={!f.type?{background:'var(--dirty-bg)',color:'var(--magenta)'}:null}><Icon name={f.type?FRAG_ICON[f.type]:'x'} /></span>
                <div style={{flex:1,minWidth:0}}>
                  <div className="bulk-name">{f.name}</div>
                  <div className={`bulk-status ${f.ok?'ok':'bad'}`}>
                    <Icon name={f.ok?'check':'x'} style={{width:12,height:12}} />{f.reason}
                  </div>
                  {f.ok && f.uploading && (
                    <div className="upload-progress" style={{marginTop:'4px'}}>
                      <progress value={f.progress} max="100" style={{width:'100%'}} />
                      <span className="hint">Subiendo… {f.progress}%</span>
                    </div>
                  )}
                  {f.ok && f.uploaded && (
                    <div className="bulk-status ok" style={{marginTop:'4px'}}>
                      <Icon name="check" style={{width:12,height:12}} /> Subido
                    </div>
                  )}
                  {f.compressionStatus && <CompressionStatusLine status={f.compressionStatus} info={f.compressionInfo} />}
                  {f.ok && f.error && (
                    <div style={{marginTop:'4px',display:'flex',alignItems:'center',gap:'8px'}}>
                      <span className="bulk-status bad"><Icon name="x" style={{width:12,height:12}} />{f.error}</span>
                      <button className="link-btn" onClick={()=>retryUpload(f.id)}>Reintentar</button>
                    </div>
                  )}
                  {f.ok && !f.uploading && !f.uploaded && (
                    <button className="link-btn" style={{marginTop:'4px'}} onClick={()=>toggleManual(f.id)}>
                      {f.manualKey ? 'Ocultar' : 'O pegá la S3 key a mano'}
                    </button>
                  )}
                  {f.ok && f.manualKey && (
                    <input type="text" className="bulk-s3key" placeholder="S3 key (ya subido por otro medio)"
                      value={f.s3Key} onChange={e=>setFileKey(f.id, e.target.value)}
                      style={{marginTop:'4px',width:'100%'}} />
                  )}
                </div>
                <span className="bulk-size">{f.human}</span>
                <button className="icon-btn danger" title="Quitar" onClick={()=>removeFile(f.id)}><Icon name="x" style={{width:14,height:14}} /></button>
              </div>
            ))}
          </div>
        )}

        <div className="mode-links">
          <span className="ml-label">¿No es un archivo?</span>
          <button className="chip-btn" onClick={()=>{ setMode('texto'); setTitle(''); setText(''); }}><Icon name="text" style={{width:14,height:14}} /> Agregar texto</button>
          <button className="chip-btn" onClick={()=>{ setMode('cuestionario'); setQ(''); setOpts(['Opción 1','Opción 2']); setCorrect(0); }}><Icon name="question" style={{width:14,height:14}} /> Agregar cuestionario</button>
        </div>
      </Modal>
    );
  }

  // ---------------- MODO TEXTO ----------------
  if(mode==='texto'){
    return (
      <Modal wide icon={editing?'edit':'text'} title={editing?'Editar texto':'Agregar texto'}
        subtitle="Bloque de texto que se le enseña al alumno. Se guarda en el borrador."
        onClose={onClose}
        foot={<><div className="spacer"></div><Btn variant="subtle" onClick={onClose}>Cancelar</Btn><Btn variant="primary" icon="check" disabled={!text.trim()} onClick={saveText}>Guardar en borrador</Btn></>}>
        {!editing && <button className="back-link" onClick={()=>setMode(backMode)}><Icon name="chevright" style={{width:14,height:14,transform:'rotate(180deg)'}} /> {mediaOn?'Volver a importar archivos':'Volver'}</button>}
        <Field label="Texto del contenido" hint="Lo que se le enseña al alumno. Es lo que se envía por WhatsApp."><textarea style={{minHeight:'160px'}} autoFocus value={text} onChange={e=>setText(e.target.value)} placeholder="Escribí el material que se le enseña al alumno…" /></Field>
        <Field label={<>Espera libre <HelpTip title="Espera libre"><span className="ht-p">El guion frena después de este paso y espera cualquier mensaje del alumno para seguir — sin pregunta, sin evaluación. Útil para bienvenidas o cierres parciales.</span></HelpTip></>}>
          <label style={{display:'flex',alignItems:'center',gap:'10px',cursor:'pointer',padding:'4px 0'}}>
            <input type="checkbox" checked={esperaRespuesta} onChange={e=>setEsperaRespuesta(e.target.checked)} />
            <span>Frenar acá y esperar cualquier respuesta antes de seguir</span>
          </label>
        </Field>
      </Modal>
    );
  }

  // ---------------- MODO CUESTIONARIO ----------------
  if(mode==='cuestionario'){
    const canSave = tipoPregunta==='abierta'
      ? (q.trim() && rubrica.trim())
      : (q.trim() && opts.filter(o=>o.trim()).length>=2);
    return (
      <Modal wide icon={editing?'edit':'question'} title={editing?'Editar cuestionario':'Agregar cuestionario'}
        subtitle="Una pregunta de recall: opción múltiple (con opciones) o abierta (con rúbrica, el tutor evalúa la respuesta libre)."
        onClose={onClose}
        foot={<><div className="spacer"></div><Btn variant="subtle" onClick={onClose}>Cancelar</Btn><Btn variant="primary" icon="check" disabled={!canSave} onClick={saveChk}>Guardar en borrador</Btn></>}>
        {!editing && <button className="back-link" onClick={()=>setMode(backMode)}><Icon name="chevright" style={{width:14,height:14,transform:'rotate(180deg)'}} /> {mediaOn?'Volver a importar archivos':'Volver'}</button>}
        <Field label="Tipo de pregunta">
          <div className="rag-seg">
            <button type="button" className={tipoPregunta==='opcion_multiple'?'on':''} onClick={()=>setTipoPregunta('opcion_multiple')}>Opción múltiple</button>
            <button type="button" className={tipoPregunta==='abierta'?'on':''} onClick={()=>setTipoPregunta('abierta')}>Abierta</button>
          </div>
        </Field>
        <Field label="Pregunta"><textarea style={{minHeight:'64px'}} autoFocus value={q} onChange={e=>setQ(e.target.value)} placeholder="Escribí la pregunta de evaluación…" /></Field>
        {tipoPregunta==='opcion_multiple' ? (
        <Field label="Opciones" hint="Marcá con el círculo cuál es la correcta. Mínimo 2 opciones.">
          {opts.map((o,i)=>(
            <div key={i} className="opt-edit">
              <div className={`radio${correct===i?' on':''}`} title="Marcar como correcta" onClick={()=>setCorrect(i)}></div>
              <input type="text" value={o} onChange={e=>setOpt(i,e.target.value)} placeholder={`Opción ${i+1}`} />
              {correct===i && <span className="correct-lbl">correcta</span>}
              <button className="btn-icon danger" disabled={opts.length<=2} title="Quitar opción" onClick={()=>delOpt(i)} style={opts.length<=2?{opacity:.3}:null}><Icon name="x" /></button>
            </div>
          ))}
          {opts.length<6 && <button className="add-row" style={{padding:'9px',marginTop:'4px'}} onClick={addOpt}><Icon name="plus" /> Agregar opción</button>}
        </Field>
        ) : (
        <Field label="Rúbrica de evaluación" hint="Cómo el tutor evalúa la respuesta libre del alumno (el LLM la compara contra esto). Sin opciones.">
          <textarea style={{minHeight:'80px'}} value={rubrica} onChange={e=>setRubrica(e.target.value)} placeholder="Ej: la respuesta debe mencionar que un sesgo de datos ocurre cuando…" />
        </Field>
        )}
        <Field label="Bloqueante">
          <label style={{display:'flex',alignItems:'center',gap:'10px',cursor:'pointer',padding:'4px 0'}}>
            <input type="checkbox" checked={blocking} onChange={e=>setBlocking(e.target.checked)} />
            <span>Bloqueante: el alumno no avanza hasta responder bien</span>
          </label>
          <div className="hint">{blocking ? 'Marcado: esta pregunta bloquea el avance hasta responder bien.' : 'Sin marcar: el alumno avanza igual, responda lo que responda.'}</div>
        </Field>
        <Field label="Reorientación por resultado (opcional)" hint="Guías para el tutor: no son texto que el alumno ve literal; el tutor las lee y responde con sus palabras. Escriba cada caso directo en su campo, sin prefijos ni títulos.">
          <div style={{display:'flex',flexDirection:'column',gap:'10px'}}>
            <div>
              <div className="hint" style={{marginBottom:'4px'}}>Si responde correcto</div>
              <div className="hint" style={{marginBottom:'4px'}}>Qué hacer cuando el alumno acierta (validar, qué adelantar o no revelar todavía).</div>
              <textarea style={{minHeight:'48px'}} value={reorientCorrecto} onChange={e=>setReorientCorrecto(e.target.value)} placeholder="Ej: Confirmá y avanzá al siguiente punto." />
            </div>
            <div>
              <div className="hint" style={{marginBottom:'4px'}}>Si responde parcial</div>
              <div className="hint" style={{marginBottom:'4px'}}>Qué hacer cuando está cerca pero incompleto (qué pedirle o repreguntar).</div>
              <textarea style={{minHeight:'48px'}} value={reorientParcial} onChange={e=>setReorientParcial(e.target.value)} placeholder="Ej: Pedile que profundice tal punto." />
            </div>
            <div>
              <div className="hint" style={{marginBottom:'4px'}}>Si responde incorrecto</div>
              <div className="hint" style={{marginBottom:'4px'}}>Qué hacer cuando erra (cómo reorientarlo sin regalarle la respuesta).</div>
              <textarea style={{minHeight:'48px'}} value={reorientIncorrecto} onChange={e=>setReorientIncorrecto(e.target.value)} placeholder="Ej: Repasá la sección sobre… y volvé a intentar." />
            </div>
          </div>
        </Field>
      </Modal>
    );
  }

  // ---------------- MODO EDITAR ARCHIVO (media) ----------------
  const rules = MEDIA_RULES[editType] || { types:[], help:null };

  async function uploadEditFile(file){
    const c = classify(file);
    if (!c.ok) { setUploadErr(c.reason); return; }
    setUploadErr(null); setUploadPct(0);
    try {
      const { upload_url, s3_key, content_type } = await A8Api.getMediaUploadUrl(cursoId, {
        filename: file.name, sizeBytes: file.size, unidadId,
      });
      await A8Api.uploadFileToS3(upload_url, file, content_type, setUploadPct);
      setS3Key(s3_key);
      setUploadPct(100);
      setCompressionStatus(null); setCompressionInfo(null);
      if(editType==='video' && file.size > VIDEO_COMPRESS_THRESHOLD_MB*MB){
        setCompressionStatus('procesando');
        A8Api.pollMediaStatus(cursoId, s3_key, (st)=>{ setCompressionStatus(st.status); setCompressionInfo(st); });
      }
    } catch(e){
      setUploadErr((e&&e.message)||'Error al subir');
    }
  }

  return (
    <Modal wide icon="edit" title="Editar contenido"
      subtitle="Reemplazá el archivo, el título o el caption. Se guarda en el borrador."
      onClose={onClose}
      foot={<><div className="spacer"></div><Btn variant="subtle" onClick={onClose}>Cancelar</Btn><Btn variant="primary" icon="check" disabled={!title.trim()||!s3Key.trim()} onClick={saveMedia}>Guardar en borrador</Btn></>}>
      <Field label="Título"><input type="text" value={title} onChange={e=>setTitle(e.target.value)} placeholder="Título del contenido" /></Field>
      <Field label={<>Archivo de {FRAG_LABEL[editType].toLowerCase()} <HelpTip title={`Reglas · ${FRAG_LABEL[editType]}`}><span className="ht-p">{rules.help}</span></HelpTip></>}>
        <input ref={editFileRef} type="file" style={{display:'none'}}
          onChange={e=>{ if(e.target.files[0]) uploadEditFile(e.target.files[0]); e.target.value=''; }} />
        {!manualMode ? (
          <>
            <div className="dropzone" onClick={()=>editFileRef.current&&editFileRef.current.click()}>
              <div className="dz-ic"><Icon name="upload" /></div>
              <div className="dz-t">{s3Key ? 'Reemplazar archivo' : 'Elegir o arrastrar archivo'}</div>
              <div className="dz-types">{rules.types.map(t=><span key={t}>{t}</span>)}</div>
            </div>
            {uploadPct>0 && uploadPct<100 && (
              <div className="upload-progress" style={{marginTop:'6px'}}>
                <progress value={uploadPct} max="100" style={{width:'100%'}} />
                <span className="hint">Subiendo… {uploadPct}%</span>
              </div>
            )}
            {s3Key && uploadPct>=100 && !uploadErr && (
              <div className="bulk-status ok" style={{marginTop:'6px'}}>
                <Icon name="check" style={{width:12,height:12}} /> Archivo subido ({s3Key.split('/').pop()})
              </div>
            )}
            {compressionStatus && (
              <div style={{marginTop:'6px'}}><CompressionStatusLine status={compressionStatus} info={compressionInfo} /></div>
            )}
            {uploadErr && (
              <div className="bulk-status bad" style={{marginTop:'6px'}}>
                <Icon name="x" style={{width:12,height:12}} />{uploadErr}
              </div>
            )}
            <button className="link-btn" style={{marginTop:'6px'}} onClick={()=>setManualMode(true)}>
              O pegá la S3 key a mano
            </button>
          </>
        ) : (
          <>
            <input type="text" value={s3Key} onChange={e=>setS3Key(e.target.value)} placeholder="curso/unidad/asset-….ext" />
            <button className="link-btn" style={{marginTop:'6px'}} onClick={()=>setManualMode(false)}>Volver a subir un archivo</button>
          </>
        )}
      </Field>
      <Field label={<>Texto del paso <span className="opt-tag">opcional</span> <HelpTip title="Texto del paso"><span className="ht-p">Lo que se le enseña al alumno junto con el adjunto. Con "Usar como caption" activo, este texto viaja pegado al adjunto en un solo mensaje en vez de mandarse aparte.</span></HelpTip></>}>
        <textarea style={{minHeight:'80px'}} value={text} onChange={e=>setText(e.target.value)} placeholder="Texto que acompaña al adjunto (opcional)…" />
      </Field>
      <Field label={<>Caption del adjunto <span className="opt-tag">opcional</span> <HelpTip title="Caption"><span className="ht-p">Texto que acompaña al adjunto cuando se envía por WhatsApp. Máximo <b>1024 caracteres</b>. No usar junto con "Usar el texto del paso como caption" — son dos fuentes en conflicto.</span></HelpTip></>}>
        <textarea maxLength={1024} style={{minHeight:'56px'}} value={caption} onChange={e=>setCaption(e.target.value)} placeholder="Texto que acompaña al archivo…" />
        <div className="hint" style={{textAlign:'right'}}>{caption.length}/1024</div>
      </Field>
      {editType!=='audio' && (
        <Field label={<>Usar el texto del paso como caption <HelpTip title="Texto como caption"><span className="ht-p">En vez de un mensaje de texto y después el adjunto por separado, se manda UN solo mensaje: el adjunto con el texto del paso como caption. Requiere texto ≤1024 caracteres y sin caption propio (elegí una sola fuente).</span></HelpTip></>}>
          <label style={{display:'flex',alignItems:'center',gap:'10px',cursor:'pointer',padding:'4px 0'}}>
            <input type="checkbox" checked={textoComoCaption} onChange={e=>setTextoComoCaption(e.target.checked)} />
            <span>Fusionar en un solo mensaje (texto del paso como caption)</span>
          </label>
          {textoComoCaption && text.trim() && caption.trim() && (
            <div className="bulk-status bad" style={{marginTop:'6px'}}><Icon name="x" style={{width:12,height:12}} /> Tiene texto Y caption propio: al publicar se va a rechazar, dejá solo uno.</div>
          )}
          {textoComoCaption && text.length>1024 && (
            <div className="bulk-status bad" style={{marginTop:'6px'}}><Icon name="x" style={{width:12,height:12}} /> El texto tiene {text.length} caracteres, supera el límite de 1024 para caption.</div>
          )}
        </Field>
      )}
      <Field label={<>Espera libre <HelpTip title="Espera libre"><span className="ht-p">El guion frena después de este paso y espera cualquier mensaje del alumno para seguir — sin pregunta, sin evaluación.</span></HelpTip></>}>
        <label style={{display:'flex',alignItems:'center',gap:'10px',cursor:'pointer',padding:'4px 0'}}>
          <input type="checkbox" checked={esperaRespuesta} onChange={e=>setEsperaRespuesta(e.target.checked)} />
          <span>Frenar acá y esperar cualquier respuesta antes de seguir</span>
        </label>
      </Field>
    </Modal>
  );
}

function CourseOrganizer({ course, actions }){
  const cid = course.id;
  const locked = !!course.published; // curso ya publicado → estructura de unidades publicadas bloqueada
  const nuevas = new Set(course.nuevasUnitIds||[]); // unidades del borrador aún no publicadas
  const unitLocked = (u)=> locked && !nuevas.has(u.id); // unidad ya publicada: no se borra ni reordena
  const units = course.draft.units;
  const tray = course.draft.tray || { items:[] };
  const [open,setOpen]=useState(()=>new Set(units.map(u=>u.id)));
  const [over,setOver]=useState(null);
  const [uover,setUover]=useState(null);
  const [modal,setModal]=useState(null);   // {type:'add'|'edit'|'del-unit', loc, item?}
  const dragRef=useRef(null);

  const isOpen=(id)=>open.has(id);
  const toggle=(id)=>setOpen(s=>{ const n=new Set(s); n.has(id)?n.delete(id):n.add(id); return n; });
  const allOpen = units.length>0 && units.every(u=>open.has(u.id));
  const toggleAll = ()=>setOpen(()=> allOpen ? new Set() : new Set(units.map(u=>u.id)));

  const dnd = {
    startItem:(loc,id)=>{ dragRef.current={mode:'item',loc,id}; },
    end:()=>{ dragRef.current=null; setOver(null); setUover(null); },
    onOver:(loc,index)=>{ const d=dragRef.current; if(d&&d.mode==='item') setOver({loc,index}); },
    onDrop:(toLoc,toIndex)=>{
      const d=dragRef.current; if(!d||d.mode!=='item'){ setOver(null); return; }
      const fromCont = d.loc===TRAY_LOC ? tray : units.find(u=>u.id===d.loc);
      const fromIx = fromCont ? fromCont.items.findIndex(x=>x.id===d.id) : -1;
      if(fromIx>=0) actions.moveItem(cid, d.loc, fromIx, toLoc, toIndex);
      dragRef.current=null; setOver(null);
    },
  };
  const startUnit=(id)=>{ dragRef.current={mode:'unit',id}; };
  const onUnitOver=(i)=>{ const d=dragRef.current; if(d&&d.mode==='unit') setUover(i); };
  const onUnitDrop=(toIndex)=>{
    const d=dragRef.current; if(!d||d.mode!=='unit'){ setUover(null); return; }
    // Camino A: en un curso publicado no se reordenan unidades (correría el progreso).
    // Dejamos que el intento ocurra y damos un rechazo legible (defensa en el front;
    // el backend lo rechaza igual con CaminoAError).
    if(locked){ dragRef.current=null; setUover(null); actions.notify(UNIT_LOCK_MSG); return; }
    const idx=units.findIndex(u=>u.id===d.id);
    if(idx>=0){ const to = idx<toIndex ? toIndex-1 : toIndex; if(to!==idx) actions.reorderUnit(cid, idx, to); }
    dragRef.current=null; setUover(null);
  };

  const renameItem=(loc,id,patch)=> actions.renameItem(cid, loc, id, patch);
  const delItem=(loc,id)=> actions.deleteItem(cid, loc, id);
  const editItem=(loc,item)=> setModal({ type:'edit', loc, item });
  const addContent=(loc)=> setModal({ type:'add', loc });

  const trayCount=tray.items.length;

  return (
    <div className="org-grid">
      {/* IZQUIERDA · unidades */}
      <div>
        <div className="col-title">
          <Icon name="layers" style={{width:18,height:18,color:'var(--tinta)'}} />
          <span className="ct">Unidades del curso</span>
          <span className="cc">{units.length}</span>
          {units.length>0 && <button className="link-btn" style={{marginLeft:'auto'}} onClick={toggleAll}>{allOpen?'Contraer todas':'Expandir todas'}</button>}
        </div>
        {locked && (
          <div className="units-locked-note"><Icon name="eye" /> <span>Este curso ya está publicado. Podés agregar unidades nuevas al final y editar el contenido de las existentes, pero no reordenar ni borrar las ya publicadas: eso correría el progreso de los alumnos.</span></div>
        )}
        <div className="units">
          {units.map((u,ui)=>{
            const cnt=A8.countUnitContent(u);
            const uLock = unitLocked(u);
            const uRO = !!u.readonly; // unidad importada del formato original: solo lectura en v1
            return (
              <div key={u.id} className={`unit${uover===ui?' uover':''}`}
                onDragOver={(e)=>{ if(dragRef.current?.mode==='unit'){ e.preventDefault(); onUnitOver(ui); } }}
                onDrop={(e)=>{ if(dragRef.current?.mode==='unit'){ e.preventDefault(); onUnitDrop(ui); } }}>
                <div className="unit-head">
                  <span className="uhandle" draggable onDragStart={()=>startUnit(u.id)} onDragEnd={dnd.end} title={locked?'Reordenar no está disponible en cursos publicados':'Arrastrar unidad'} style={locked?{opacity:.4}:null}><Icon name="drag" /></span>
                  <button className={`ucaret${isOpen(u.id)?' open':''}`} onClick={()=>toggle(u.id)}><Icon name="chevright" /></button>
                  <span className="upos">{ui+1}</span>
                  <OgEditable className="utitle" inpClass="utitle-inp" value={u.title} onChange={(v)=>actions.renameUnit(cid,u.id,v)} />
                  <span className="ucount">{cnt.frags} cont. · {cnt.chks} cuest.</span>
                  {uLock ? (
                    <button className="icon-btn locked" title={UNIT_LOCK_MSG} onClick={()=>actions.notify(UNIT_LOCK_MSG)}><Icon name="trash" style={{width:15,height:15}} /></button>
                  ) : (
                    <button className="icon-btn danger" title="Borrar unidad" onClick={()=>setModal({type:'del-unit', unit:u})}><Icon name="trash" style={{width:15,height:15}} /></button>
                  )}
                </div>
                {isOpen(u.id) && (
                  <>
                    {uRO && (
                      <div className="units-locked-note" style={{margin:'0 12px 10px'}}>
                        <Icon name="eye" /> <span>Contenido importado del formato original. Podés verlo, pero todavía no editarlo desde acá: la edición llega en una versión próxima.</span>
                      </div>
                    )}
                    <OgItemList items={u.items} loc={u.id} empty="Arrastrá contenido acá o agregá abajo" over={over} dnd={dnd} unitReadonly={uRO}
                      onRename={renameItem} onEdit={editItem} onDelete={delItem} />
                    {!uRO && <div style={{padding:'0 12px 12px'}}><button className="add-trigger" onClick={()=>addContent(u.id)}><Icon name="plus" /> Agregar contenido</button></div>}
                  </>
                )}
              </div>
            );
          })}
          <div style={{height:'2px'}}
            onDragOver={(e)=>{ if(dragRef.current?.mode==='unit'){ e.preventDefault(); onUnitOver(units.length); } }}
            onDrop={(e)=>{ if(dragRef.current?.mode==='unit'){ e.preventDefault(); onUnitDrop(units.length); } }}></div>
          <button className="add-unit" onClick={()=>actions.addUnit(cid, 'Unidad '+(units.length+1))}><Icon name="plus" /> Agregar unidad</button>
        </div>
      </div>

      {/* DERECHA · bandeja */}
      <div className="tray">
        <div className="tray-head">
          <div className="th-k"><Icon name="layers" /> Sin ubicar</div>
          <div className="th-t">Contenido para reubicar</div>
          <div className="th-s">Agregá material acá y arrastralo a la unidad que corresponda. Lo que quede sin ubicar no se publica.</div>
          <span className="count-pill">{trayCount}</span>
        </div>
        <div className={`tray-body${over&&over.loc===TRAY_LOC?' zone-over':''}`}>
          {trayCount===0 ? (
            <div className="tray-empty">
              <div className="te-ic"><Icon name="check" /></div>
              <p>Nada sin ubicar. Todo el contenido está en una unidad.</p>
            </div>
          ) : (
            <OgItemList items={tray.items} loc={TRAY_LOC} empty="Bandeja vacía" over={over} dnd={dnd}
              onRename={renameItem} onEdit={editItem} onDelete={delItem} />
          )}
          <div className="tray-add"><button className="add-trigger" onClick={()=>addContent(TRAY_LOC)}><Icon name="plus" /> Agregar contenido</button></div>
        </div>
      </div>

      {/* panel agregar / editar contenido */}
      {modal?.type==='add' && (
        <ContentForm
          cursoId={cid} unidadId={modal.loc===TRAY_LOC?null:modal.loc}
          onClose={()=>setModal(null)}
          onAddItems={(items)=>{ actions.addItems(cid, modal.loc, items); setModal(null); }}
          onSaveItem={(item)=>{ actions.addItem(cid, modal.loc, item); setModal(null); }} />
      )}
      {modal?.type==='edit' && (
        <ContentForm data={modal.item} cursoId={cid} unidadId={modal.loc===TRAY_LOC?null:modal.loc}
          onClose={()=>setModal(null)}
          onSaveItem={(item)=>{ actions.saveItem(cid, modal.loc, item); setModal(null); }} />
      )}
      {modal?.type==='del-unit' && (
        <ConfirmDelete what="unidad" detail={<>Se va a borrar <b>“{modal.unit.title}”</b> con todo su contenido del borrador. Las demás unidades se renumeran.</>}
          onClose={()=>setModal(null)} onConfirm={()=>{ actions.deleteUnit(cid, modal.unit.id); setModal(null); }} />
      )}
    </div>
  );
}

window.CourseOrganizer = CourseOrganizer;
