// ============ AcademIA8 · Consola — Reglas de material + Vista de publicación ============
// El editor de unidad clásico (fragmentos/checkpoints separados) fue reemplazado
// por el organizador (organizer.jsx), coherente con el mapeo a pasos del backend.
// Este archivo conserva las reglas de material (compartidas con el organizador) y
// la vista de publicación, ahora cableada a los endpoints reales del backend
// (GET /diff, GET /disclaimer) — sin diff local ni copia publicada en el front.

// ---------- Reglas por tipo de material (contenido de las ayudas contextuales) ----------
const MEDIA_RULES = {
  video: { types:['MP4'], help:(<>
    <span className="ht-p"><b>Subida directa (hasta 500 MB):</b> se envía como archivo por WhatsApp. Si pesa más de 16 MB se comprime automáticamente al subir (podés ver el progreso en el editor) — el alumno siempre recibe un MP4 dentro del límite de WhatsApp.</span>
    <span className="ht-p"><b>Video largo por link:</b> alternativa si preferís pegar un link (YouTube, Vimeo) en vez de subir el archivo. El alumno recibe el link, no el archivo.</span>
  </>) },
  imagen: { types:['JPEG','PNG'], help:(<span className="ht-p">Hasta <b>5 MB</b>. Formatos: JPEG, PNG.</span>) },
  audio: { types:['AAC','MP3','OGG','Opus'], help:(<span className="ht-p">Hasta <b>16 MB</b>. Formatos: AAC, MP3, OGG, Opus.</span>) },
  documento: { types:['PDF','DOC','DOCX'], help:(<span className="ht-p">Hasta <b>100 MB</b>. Formatos: PDF, DOC, DOCX, etc.</span>) },
};

/* ===================================================================
   VISTA DE PUBLICACIÓN  (diff borrador vs publicado — backend Camino A)
   - estado derivado: solo-borrador / publicado-limpio / publicado-con-cambios
   - diff: unidades `editadas` y `nuevas` (no hay movida ni borrada, Camino A)
   - disclaimer de alcance: por unidad editada, cuántos alumnos ya la cursaron
=================================================================== */
function PublishView({ course, actions }){
  const [diff, setDiff] = useState(null);
  const [disc, setDisc] = useState({});
  const [loading, setLoading] = useState(true);
  const [err, setErr] = useState(null);
  const [publishing, setPublishing] = useState(false);
  const [expanded, setExpanded] = useState(()=>new Set());

  const estado = course.derivedEstado;
  const firstPub = estado === 'solo-borrador';
  const clean = estado === 'publicado-limpio';

  // mapa unidad_id → {title, pos} desde el borrador cargado
  const unitById = {};
  (course.draft?.units || []).forEach((u,i)=>{ unitById[u.id] = { title:u.title, pos:i+1, u }; });

  useEffect(()=>{
    let alive = true;
    (async ()=>{
      setLoading(true); setErr(null);
      try {
        // detalle=true: el backend ya viene con el diff item por item y campo
        // por campo de cada unidad editada (DIFF-1) — el front solo pinta.
        const d = await A8Api.diff(course.id, { detalle:true });
        let dc = {};
        if (d.editadas && d.editadas.length){
          try { dc = await A8Api.disclaimer(course.id); } catch(e){ dc = {}; }
        }
        if (alive){ setDiff(d); setDisc(dc || {}); }
      } catch(e){ if (alive) setErr(e.message || String(e)); }
      finally { if (alive) setLoading(false); }
    })();
    return ()=>{ alive = false; };
  }, [course.id]);

  function toggle(key){
    setExpanded(prev=>{
      const next = new Set(prev);
      if (next.has(key)) next.delete(key); else next.add(key);
      return next;
    });
  }
  function expandAll(keys){ setExpanded(new Set(keys)); }
  function collapseAll(){ setExpanded(new Set()); }

  // Motivos de bloqueo de Camino A que ya vienen calculados en el diff
  // (`bloquea_publicacion`/`motivo_bloqueo` por item o checkpoint, `violaciones`
  // por unidad) — front avisa, `camino_a_violations` en el backend sigue siendo
  // la única puerta autoritativa (ver draft_diff.py).
  const motivosBloqueo = [];
  (diff?.editadas || []).forEach(e=>{
    (e.violaciones || []).forEach(v=>motivosBloqueo.push(v));
    (e.items || []).forEach(it=>{
      if (it.bloquea_publicacion && it.motivo_bloqueo) motivosBloqueo.push(it.motivo_bloqueo);
      (it.campos || []).forEach(c=>{
        if (c.campo==='checkpoint' && c.bloquea_publicacion && c.motivo_bloqueo) motivosBloqueo.push(c.motivo_bloqueo);
      });
    });
  });
  const bloqueaPublicacion = motivosBloqueo.length > 0;

  async function doPublish(){
    setPublishing(true);
    const ok = await actions.publish(course.id);   // recarga + toast; devuelve bool
    setPublishing(false);
    if (ok) actions.openCourse(course.id);
  }

  const nuevas = diff ? diff.nuevas : [];
  const editadas = diff ? diff.editadas : [];
  const metaCambiada = diff ? !!diff.meta_cambiada : false;
  const totalChanges = nuevas.length + editadas.length + (metaCambiada ? 1 : 0);
  const affectedTotal = Object.values(disc).reduce((s,n)=>s+n, 0);

  return (
    <div className="wrap">
      <div className="flex between center">
        <div>
          <div className="page-kick">Vista de publicación</div>
          <h1 className="page-title" style={{marginTop:'8px'}}>{course.meta_draft?.name}</h1>
        </div>
        <Btn variant="ghost" onClick={()=>actions.openCourse(course.id)}>Volver al editor</Btn>
      </div>

      {/* hero resumen */}
      <div className="pub-hero" style={{marginTop:'22px'}}>
        <div className="pg" style={{width:'220px',height:'220px',background:'#01C599',opacity:.3,left:'-60px',top:'-80px'}}></div>
        <div className="pg" style={{width:'200px',height:'200px',background:'#CE1E4E',opacity:.3,right:'-60px',bottom:'-90px'}}></div>
        <div className="pub-hero-in">
          <div className="flex between center">
            <div style={{maxWidth:'560px'}}>
              <div style={{fontFamily:'var(--mono)',fontSize:'10px',letterSpacing:'.2em',textTransform:'uppercase',color:'var(--teal)',fontWeight:700}}>
                {firstPub ? 'Primera publicación' : clean ? 'Sin cambios' : 'Confirmá los cambios'}
              </div>
              <h2 style={{fontFamily:'var(--serif)',fontWeight:700,fontSize:'21px',marginTop:'8px',lineHeight:1.25}}>
                {firstPub
                  ? 'Vas a hacer visible este curso al alumno por primera vez.'
                  : clean
                    ? 'El borrador ya está publicado tal cual. No hay cambios para enviar.'
                    : <>Al publicar, el alumno pasará a ver <span style={{color:'var(--ambar)'}}>tu borrador actual</span>.</>}
              </h2>
            </div>
          </div>
          {!firstPub && !clean && (
            <div className="pub-summary">
              <div className="pub-stat add"><span className="n">+{nuevas.length}</span><span className="l">unidades nuevas</span></div>
              <div className="pub-stat edit"><span className="n">{editadas.length}</span><span className="l">unidades editadas</span></div>
            </div>
          )}
        </div>
      </div>

      {/* alcance del Camino A (informativo, no bloquea) */}
      <div className="readonly-note" style={{marginTop:'18px'}}>
        <Icon name="eye" /> <span>Republicar no reordena ni borra unidades ya publicadas: solo se editan las existentes y se agregan al final. El progreso de cada alumno se conserva.</span>
      </div>

      {loading && (
        <div className="empty-state" style={{padding:'40px 20px'}}>
          <div className="es-ic"><Icon name="refresh" /></div>
          <h3>Calculando diferencias…</h3>
        </div>
      )}

      {err && (
        <div className="readonly-note" style={{marginTop:'18px',background:'var(--dirty-bg)',borderColor:'rgba(206,30,78,.35)',color:'var(--magenta)'}}>
          <Icon name="alert" /> No se pudo calcular el diff: {err}
        </div>
      )}

      {!loading && !err && (
        <>
          {/* diff por unidad */}
          <div className="section-gap"></div>
          <div className="flex between center" style={{marginBottom:'12px'}}>
            <div className="sec-title">
              {firstPub ? 'Unidades a publicar' : 'Cambios respecto de lo publicado'}
            </div>
            {!firstPub && editadas.length>0 && (
              <span className="diff-expand-all" onClick={()=>{
                const allOpen = editadas.every(e=>expanded.has(e.unidad_id)) && (!metaCambiada || expanded.has('meta'));
                if (allOpen) collapseAll();
                else expandAll([...editadas.map(e=>e.unidad_id), ...(metaCambiada?['meta']:[])]);
              }}>
                {editadas.every(e=>expanded.has(e.unidad_id)) && (!metaCambiada || expanded.has('meta')) ? 'Colapsar todo' : 'Expandir todo'}
              </span>
            )}
          </div>

          {totalChanges===0 ? (
            <div className="empty-state" style={{padding:'40px 20px'}}>
              <div className="es-ic"><Icon name="check" /></div>
              <h3>{firstPub ? 'El borrador está vacío' : 'Sin cambios'}</h3>
              <p>{firstPub ? 'Agregá unidades y contenido antes de publicar.' : 'El borrador coincide con lo publicado.'}</p>
            </div>
          ) : (
            <div style={{display:'flex',flexDirection:'column',gap:'10px'}}>
              {metaCambiada && (
                <div className="drow is-edit">
                  <div className="drow-top is-toggle" onClick={()=>toggle('meta')}>
                    <span className="drow-pos"><Icon name="settings" style={{width:13,height:13}} /></span>
                    <span className="drow-title">Metadata del curso</span>
                    <span className="tag-diff tag-edit">editada</span>
                    {diff.meta_diff && <span className="drow-toggle-ic"><Icon name={expanded.has('meta')?'up':'down'} /></span>}
                  </div>
                  <div className="drow-meta"><span>Nombre, descripción y/o mensaje de bienvenida</span></div>
                  {expanded.has('meta') && diff.meta_diff && (
                    <div className="drow-detalle">
                      <MetaTwoColumnDetalle campos={diff.meta_diff.campos} />
                    </div>
                  )}
                </div>
              )}
              {nuevas.map(e=>{
                const u = unitById[e.unidad_id];
                return <DiffRow key={e.unidad_id} title={u?u.title:e.unidad_id} pos={u?u.pos:e.posicion}
                  items={u?u.u.items:[]} tone="add" label={firstPub?'a publicar':'nueva al final'} />;
              })}
              {editadas.map(e=>{
                const u = unitById[e.unidad_id];
                const n = disc[e.unidad_id] || 0;
                const note = n>0 ? `${n} ${n===1?'inscripción ya cursó':'inscripciones ya cursaron'} esta unidad y no ${n===1?'verá':'verán'} esta edición` : null;
                return <DiffRow key={e.unidad_id} unidadId={e.unidad_id} title={u?u.title:e.unidad_id} pos={u?u.pos:e.posicion}
                  items={u?u.u.items:[]} tone="edit" label="editada" note={note}
                  detalle={e.items} violaciones={e.violaciones}
                  expanded={expanded.has(e.unidad_id)} onToggle={()=>toggle(e.unidad_id)} />;
              })}
            </div>
          )}

          {/* disclaimer de alcance */}
          {!firstPub && affectedTotal>0 && (
            <>
              <div className="section-gap"></div>
              <div className="publish-disclaimer">
                <Icon name="alert" />
                <span>Hay <b>{affectedTotal} {affectedTotal===1?'alumno':'alumnos'}</b> que no {affectedTotal===1?'verá':'verán'} {editadas.length===1?'este cambio':'estos cambios'} porque ya {affectedTotal===1?'pasó':'pasaron'} por {editadas.length===1?'esa unidad':'esas unidades'}.</span>
              </div>
            </>
          )}

          {/* bloqueo de Camino A (front avisa, backend sigue siendo la puerta) */}
          {bloqueaPublicacion && (
            <>
              <div className="section-gap"></div>
              <div className="publish-block-note">
                <Icon name="alert" />
                <span><b>No se puede publicar:</b> {motivosBloqueo.join('; ')}.</span>
              </div>
            </>
          )}

          {/* barra publicar */}
          <div className="section-gap"></div>
          <div className="card card-pad" style={{display:'flex',alignItems:'center',gap:'16px'}}>
            <div className="frag-type ft-imagen" style={{background:'var(--pub-bg)',color:'var(--verde-prof)',width:'40px',height:'40px'}}><Icon name="publish" /></div>
            <div>
              <div style={{fontWeight:700,fontSize:'14px',color:'var(--tinta)'}}>{firstPub?'Publicar por primera vez':clean?'Nada para publicar':`Publicar ${totalChanges} ${totalChanges===1?'cambio':'cambios'}`}</div>
              <div style={{fontSize:'12.5px',color:'var(--gris)',marginTop:'2px'}}>Esto reemplaza lo que ve el alumno por tu borrador actual. La acción es inmediata.</div>
            </div>
            <div className="grow"></div>
            <Btn variant="subtle" onClick={()=>actions.openCourse(course.id)}>Cancelar</Btn>
            <Btn variant="pub" icon="publish" disabled={publishing || clean || bloqueaPublicacion || (firstPub && totalChanges===0)} onClick={doPublish}>
              {publishing ? 'Publicando…' : firstPub?'Publicar curso':'Publicar cambios'}
            </Btn>
          </div>
        </>
      )}
    </div>
  );
}

function DiffRow({ unidadId, title, pos, items, tone, label, note, detalle, violaciones, expanded, onToggle }){
  const cnt = A8.countUnitContent({ items: items || [] });
  const cls = tone==='add'?'is-add':tone==='edit'?'is-edit':'';
  const tagCls = tone==='add'?'tag-new':tone==='edit'?'tag-edit':'tag-move';
  const clickable = !!detalle;
  const bloqueada = !!(violaciones && violaciones.length);
  return (
    <div className={`drow ${cls}${bloqueada?' is-block':''}`}>
      <div className={`drow-top${clickable?' is-toggle':''}`} onClick={clickable?onToggle:undefined}>
        <span className="drow-pos">#{pos}</span>
        <span className="drow-title">{title}</span>
        {label && <span className={`tag-diff ${tagCls}`}>{label}</span>}
        {bloqueada && <span className="tag-diff tag-block">bloquea la publicación</span>}
        {clickable && <span className="drow-toggle-ic"><Icon name={expanded?'up':'down'} /></span>}
      </div>
      <div className="drow-meta">
        <span><Icon name="text" /> {cnt.frags} cont.</span>
        <span><Icon name="question" /> {cnt.chks} cuest.</span>
      </div>
      {note && <div className="drow-seen"><Icon name="alert" /> {note}</div>}
      {bloqueada && (
        <div className="drow-block-note"><Icon name="alert" /> <span>{violaciones.join('; ')}</span></div>
      )}
      {expanded && detalle && (
        <div className="drow-detalle">
          <TwoColumnDetalle items={detalle} />
        </div>
      )}
    </div>
  );
}

/* ===================================================================
   DIFF-1C · vista de dos columnas (Publicado | Borrador). El backend ya
   calculó todo el diff — palabra por palabra, campo por campo, y snapshots
   verbatim de cada copia (`contenido_publicado`/`contenido_borrador`) — este
   código solo pinta. Alineación por item_id, fila por fila: un reemplazo
   (paso eliminado + paso nuevo con el mismo `orden`) se lee como tal porque
   quedan adyacentes en la secuencia que ya ordena el backend — no hay
   heurística de detección de reemplazo acá.
=================================================================== */

function DiffTokens({ tokens }){
  if (!tokens) return null;
  return tokens.map((t,i)=>{
    if (t.tipo==='borrado') return <span key={i} className="diff-tok-del">{t.valor}</span>;
    if (t.tipo==='agregado') return <span key={i} className="diff-tok-add">{t.valor}</span>;
    return <span key={i}>{t.valor}</span>;
  });
}

function fmtCampoValor(v){
  if (v===null || v===undefined) return '(vacío)';
  if (typeof v === 'object') return v.s3_key || v.url || JSON.stringify(v);
  if (typeof v === 'boolean') return v ? 'sí' : 'no';
  return String(v);
}

// De un array de tokens de word-diff (igual/borrado/agregado), el subset que
// le toca pintar a cada columna: Publicado conserva igual+borrado (lo que se
// va, tachado), Borrador conserva igual+agregado (lo que llega).
function ladoTokens(tokens, lado){
  if (!tokens) return null;
  return tokens.filter(t => t.tipo==='igual' || (lado==='publicado' ? t.tipo==='borrado' : t.tipo==='agregado'));
}

// Texto libre de una columna: si el campo cambió, resaltado palabra por
// palabra (solo el lado que le corresponde); si no, el valor plano — pero
// SIEMPRE el contenido verbatim del autor, nunca reescrito.
function TextoLado({ valor, campo, lado, className }){
  if (campo && campo.estado==='editado' && campo.granularidad==='palabra'){
    return <div className={className}><DiffTokens tokens={ladoTokens(campo.diff, lado)} /></div>;
  }
  if (!valor) return null;
  return <div className={className}>{valor}</div>;
}

// Valor discreto de una columna (tipo/bloqueante/media ref): solo se pinta
// cuando el campo efectivamente cambió (si no cambió, quien llama muestra el
// contenido plano directo, sin pasar por acá).
function ValorLado({ campo, lado }){
  const v = lado==='publicado' ? campo.antes : campo.despues;
  const cls = lado==='publicado' ? 'diff-campo-antes' : 'diff-campo-despues';
  return <span className={cls}>{fmtCampoValor(v)}</span>;
}

// Nota discreta (tipo/bloqueante de un checkpoint o item_recall): solo
// aparece si ese campo puntual cambió, aunque el resto del item no.
function CampoDiscretoLado({ label, campo, lado }){
  if (!campo || campo.estado !== 'editado') return null;
  return (
    <div className="diff-campo">
      <span className="diff-campo-label">{label}</span>
      <ValorLado campo={campo} lado={lado} />
    </div>
  );
}

// Opciones de opcion_multiple, una columna. Sin diff de fila (campoOpciones
// ausente, caso común: item sin cambios) se pintan directo. Con diff de fila
// (item editado), cada columna se queda con las filas que le tocan por
// posición — Publicado se salta las `nuevo`, Borrador se salta las
// `eliminado` — para que la fila alineada siga leyéndose como una lista.
function OpcionesLado({ opciones, respuesta, campoOpciones, lado }){
  const filas = campoOpciones && campoOpciones.granularidad==='fila' ? campoOpciones.filas : null;
  if (!filas){
    return (
      <div className="idiff-opciones">
        {opciones.map((o,i)=>(
          <div key={i} className={o===respuesta ? 'diff-fila is-correct' : 'diff-fila'}>
            {o}{o===respuesta ? ' ✓' : ''}
          </div>
        ))}
      </div>
    );
  }
  const propias = lado==='publicado' ? ['igual','editado','eliminado'] : ['igual','editado','nuevo'];
  return (
    <div className="idiff-opciones">
      {filas.filter(f=>propias.includes(f.estado)).map((f,i)=>{
        if (f.estado==='igual') return <div key={i} className={f.valor===respuesta?'diff-fila is-correct':'diff-fila'}>{f.valor}{f.valor===respuesta?' ✓':''}</div>;
        if (f.estado==='nuevo') return <div key={i} className={f.valor===respuesta?'diff-fila is-add is-correct':'diff-fila is-add'}>{f.valor}{f.valor===respuesta?' ✓':''}</div>;
        if (f.estado==='eliminado') return <div key={i} className="diff-fila is-del">{f.valor}</div>;
        return <div key={i} className="diff-fila"><DiffTokens tokens={ladoTokens(f.diff, lado)} /></div>;
      })}
    </div>
  );
}

// Checkpoint embebido en un paso, una columna. `campoCheckpoint` solo existe
// si el paso está `editado` — trae el sub-diff campo por campo; si el paso
// está `igual`/`nuevo`/`eliminado` se pinta el contenido directo.
function CheckpointLado({ checkpoint, campoCheckpoint, lado }){
  const camposCk = campoCheckpoint && campoCheckpoint.checkpoint ? campoCheckpoint.checkpoint.campos : null;
  const campo = (nombre) => camposCk && camposCk.find(c=>c.campo===nombre);
  const reorientacionEntries = Object.entries(checkpoint.reorientacion || {}).filter(([,v])=>v);
  return (
    <div className="diff-checkpoint">
      <div className="diff-checkpoint-label">Checkpoint</div>
      <TextoLado valor={checkpoint.enunciado} campo={campo('enunciado')} lado={lado} className="idiff-contenido-texto" />
      {(checkpoint.opciones || []).length > 0 && (
        <OpcionesLado opciones={checkpoint.opciones} respuesta={checkpoint.respuesta_referencia} campoOpciones={campo('opciones')} lado={lado} />
      )}
      <CampoDiscretoLado label="tipo" campo={campo('tipo')} lado={lado} />
      <CampoDiscretoLado label="bloqueante" campo={campo('bloqueante')} lado={lado} />
      {checkpoint.rubrica && (
        <div className="diff-campo"><span className="diff-campo-label">rúbrica</span><TextoLado valor={checkpoint.rubrica} campo={campo('rubrica')} lado={lado} /></div>
      )}
      {reorientacionEntries.map(([k,v])=>(
        <div className="diff-campo" key={k}>
          <span className="diff-campo-label">reorientación · {k.replace('si_','si ')}</span>
          <TextoLado valor={v} campo={campo(`reorientacion.${k}`)} lado={lado} />
        </div>
      ))}
    </div>
  );
}

// Contenido completo de un paso, una columna: texto, material, checkpoint —
// siempre verbatim, resaltado palabra por palabra solo donde cambió.
function PasoContenidoLado({ contenido, campos, lado }){
  if (!contenido) return null;
  const campo = (nombre) => campos && campos.find(c=>c.campo===nombre);
  const campoMedia = campo('media');
  const campoCaption = campo('media_caption');
  return (
    <div className="idiff-contenido">
      <TextoLado valor={contenido.texto} campo={campo('texto')} lado={lado} className="idiff-contenido-texto" />
      {contenido.media && (
        <div className="diff-campo">
          <span className="diff-campo-label">material</span>
          <div>
            {campoMedia && campoMedia.estado==='editado' ? <ValorLado campo={campoMedia} lado={lado} /> : fmtCampoValor(contenido.media)}
            {contenido.media.caption ? ' — ' : ''}
            <TextoLado valor={contenido.media.caption} campo={campoCaption} lado={lado} />
          </div>
        </div>
      )}
      {contenido.checkpoint && (
        <CheckpointLado checkpoint={contenido.checkpoint} campoCheckpoint={campo('checkpoint')} lado={lado} />
      )}
    </div>
  );
}

// Contenido completo de un item_recall clásico, una columna.
function RecallContenidoLado({ contenido, campos, lado }){
  if (!contenido) return null;
  const campo = (nombre) => campos && campos.find(c=>c.campo===nombre);
  return (
    <div className="idiff-contenido">
      <TextoLado valor={contenido.enunciado} campo={campo('enunciado')} lado={lado} className="idiff-contenido-texto" />
      {(contenido.opciones || []).length > 0 && (
        <OpcionesLado opciones={contenido.opciones} respuesta={contenido.respuesta_referencia} campoOpciones={campo('opciones')} lado={lado} />
      )}
      <CampoDiscretoLado label="tipo" campo={campo('tipo')} lado={lado} />
      {contenido.rubrica && (
        <div className="diff-campo"><span className="diff-campo-label">rúbrica</span><TextoLado valor={contenido.rubrica} campo={campo('rubrica')} lado={lado} /></div>
      )}
    </div>
  );
}

// Una fila del diff de dos columnas: un paso o item_recall, alineado por
// item_id. `igual` → ambos lados en gris, contenido visible sin desplegable
// (nada que ocultar). `nuevo`/`eliminado` → hueco de un lado, contenido
// entero del otro. `editado` → ambos lados con contenido, resaltado palabra
// por palabra solo en lo que cambió.
function ItemDiffTwoCol({ item }){
  const estadoCls = item.estado==='nuevo' ? 'is-add'
    : item.estado==='eliminado' ? 'is-del'
    : item.estado==='editado' ? 'is-edit'
    : 'is-equal';
  const campos = item.campos || null;
  const checkpointCampo = campos && campos.find(c=>c.campo==='checkpoint');
  const bloqueo = (item.bloquea_publicacion && item.motivo_bloqueo)
    || (checkpointCampo && checkpointCampo.bloquea_publicacion && checkpointCampo.motivo_bloqueo)
    || null;
  const respuestaCampo = campos && campos.find(c=>c.campo==='respuesta_referencia');
  const opcionesCampo = campos && campos.find(c=>c.campo==='opciones');
  const soloRespuestaCambio = item.tipo==='item_recall' && respuestaCampo && respuestaCampo.estado==='editado'
    && !(opcionesCampo && opcionesCampo.estado==='editado');
  const tipoLabel = item.tipo==='paso' ? 'Paso' : 'Pregunta';
  const tag = item.estado==='nuevo' ? {texto:'nuevo', cls:'tag-new'}
    : item.estado==='eliminado' ? {texto:'se elimina', cls:'tag-move'}
    : item.estado==='editado' ? {texto:'editado', cls:'tag-edit'}
    : null;
  const Contenido = item.tipo==='paso' ? PasoContenidoLado : RecallContenidoLado;
  return (
    <div className={`dcol-row ${estadoCls}${bloqueo?' is-block':''}`}>
      <div className="dcol-rowhead">
        <span className="idiff-tipo">{tipoLabel}</span>
        {tag && <span className={`tag-diff ${tag.cls}`}>{tag.texto}</span>}
        {bloqueo && <span className="tag-diff tag-block">bloquea la publicación</span>}
      </div>
      <div className="dcol-cell pub">
        {item.contenido_publicado ? <Contenido contenido={item.contenido_publicado} campos={campos} lado="publicado" /> : <span className="dcol-empty-mark">—</span>}
      </div>
      <div className="dcol-cell draft">
        {item.contenido_borrador ? <Contenido contenido={item.contenido_borrador} campos={campos} lado="borrador" /> : <span className="dcol-empty-mark">—</span>}
      </div>
      {soloRespuestaCambio && <div className="dcol-note">La respuesta correcta cambió</div>}
      {bloqueo && <div className="dcol-block-note"><Icon name="alert" /> <span>{bloqueo}</span></div>}
    </div>
  );
}

// Vista de dos columnas de una unidad editada: la unidad COMPLETA en orden,
// items sin cambios incluidos (contexto) — DIFF-1C.
function TwoColumnDetalle({ items }){
  if (!items || !items.length){
    return <div className="drow-detalle-empty">Sin contenido en esta unidad.</div>;
  }
  return (
    <div className="dcol-wrap">
      <div className="dcol-headrow">
        <div className="diff-shead pub-h"><span className="dh-t">Publicado</span></div>
        <div className="diff-shead draft-h"><span className="dh-t">Borrador</span></div>
      </div>
      {items.map(it=><ItemDiffTwoCol key={it.tipo+':'+it.item_id} item={it} />)}
    </div>
  );
}

// Valor de una columna de metadata del curso: mismo criterio que ValorLado/
// TextoLado pero sin exigir que el campo haya cambiado (la metadata siempre
// se muestra completa, dos columnas).
function MetaValorLado({ campo, lado }){
  const v = lado==='publicado' ? campo.antes : campo.despues;
  if (campo.granularidad === 'palabra'){
    if (campo.estado==='editado'){
      return <div className="idiff-contenido-texto"><DiffTokens tokens={ladoTokens(campo.diff, lado)} /></div>;
    }
    return v ? <div className="idiff-contenido-texto">{v}</div> : <span className="dcol-empty-mark">—</span>;
  }
  if (v===null || v===undefined) return <span className="dcol-empty-mark">—</span>;
  return <span className={campo.estado==='editado' ? (lado==='publicado'?'diff-campo-antes':'diff-campo-despues') : undefined}>{fmtCampoValor(v)}</span>;
}

function MetaCampoRow({ campo }){
  return (
    <div className={`dcol-row ${campo.estado==='editado'?'is-edit':'is-equal'}`}>
      <div className="dcol-rowhead"><span className="diff-campo-label">{campo.campo}</span></div>
      <div className="dcol-cell pub"><MetaValorLado campo={campo} lado="publicado" /></div>
      <div className="dcol-cell draft"><MetaValorLado campo={campo} lado="borrador" /></div>
    </div>
  );
}

// Metadata del curso, dos columnas: título/descripción/bienvenida y su media
// — siempre completa, no solo lo que cambió (DIFF-1C punto 3).
function MetaTwoColumnDetalle({ campos }){
  return (
    <div className="dcol-wrap">
      <div className="dcol-headrow">
        <div className="diff-shead pub-h"><span className="dh-t">Publicado</span></div>
        <div className="diff-shead draft-h"><span className="dh-t">Borrador</span></div>
      </div>
      {campos.map((c,i)=><MetaCampoRow key={i} campo={c} />)}
    </div>
  );
}

Object.assign(window, {
  PublishView, DiffRow, MEDIA_RULES,
  DiffTokens, TwoColumnDetalle, ItemDiffTwoCol, MetaTwoColumnDetalle,
});
