// =====================================================
// Odinote — Text format sidebar v2
// Uses document.execCommand on the currently-focused contentEditable
// so formatting applies only to selected text.
// =====================================================

function TextFormatSidebar({ item, lang, onUpdate, onClose, variant }) {
  if (!item) return null;
  const isCaption = variant === 'caption'; // captions get only Color/H1/B/I/S/U (no lists, quote, code)
  const isBigTitle = item.type === 'bigtitle';
  const isFrame = item.type === 'frame';
  const isMap = item.type === 'map';
  // El bloque de codigo lleva su titulo aparte del cuerpo, igual que el marco
  // y el mapa: su color va en item.titleColor y no dentro del texto.
  const isCode = item.type === 'code';
  // El reloj lleva su título en la barra de arriba, igual que el bloque de
  // código: en item.timerTitle, con el formato como propiedades del nodo.
  const isTimer = item.type === 'timer';
  // El nombre de la carpeta: texto plano en su propio campo, con el formato
  // como propiedades del nodo y el color en item.textColor.
  const isCarpeta = item.type === 'carpeta';
  const isTodo = item.type === 'todo';
  const isBoard = item.type === 'board';
  // Estos tipos guardan el formato como propiedades del nodo en vez de HTML, así
  // que NUNCA deben llegar a execCommand (findEditor haría fallback al primer
  // editor de nota del documento y formatearía un nodo ajeno).
  //
  // El to-do ya NO está en esa lista: sus tareas son texto con formato, así que
  // la negrita y el resto se aplican a lo seleccionado como en una nota. Antes
  // eran propiedades de la fila entera y por eso teñían la línea completa.
  // El tablero se trata como el título grande: color/negrita/cursiva son
  // propiedades del NODO entero, no de la selección —un pie de tablero con
  // dos tamaños de letra en dos palabras se vería mal—, pero sin alineación,
  // que ahí no significa nada (es una barra icono+texto+contador, no un bloque).
  const isCustomPropType = isBigTitle || isFrame || isMap || isBoard || isCode || isTimer || isCarpeta;
  // Ambos guardan el color en el mismo campo (item.textColor); frame y map
  // usan uno propio (item.titleColor) porque su titulo vive aparte del cuerpo.
  const usaTextColor = isBigTitle || isBoard || isCarpeta;

  // ── To-do: el formato se aplica a la TAREA enfocada; si no hay ninguna
  // enfocada, se aplica a todas las tareas del nodo. ──
  const todoRows = isTodo ? (item.items || []) : [];
  const focusedTodoRowId = isTodo && window._focusedTodoRow && window._focusedTodoRow.todoId === item.id
    ? window._focusedTodoRow.rowId : null;
  const activeTodoRow = focusedTodoRowId
    ? todoRows.find(r => r.id === focusedTodoRowId)
    : todoRows[0];
  const applyTodoStyle = (patch) => {
    onUpdate({
      items: todoRows.map(r => (focusedTodoRowId ? r.id === focusedTodoRowId : true) ? { ...r, ...patch } : r)
    });
  };
  const toggleTodoStyle = (key) => applyTodoStyle({ [key]: !(activeTodoRow && activeTodoRow[key]) });
  const [active, setActive] = React.useState({});
  const [colorOpen, setColorOpen] = React.useState(false);
  // El tamaño del texto del nodo entero (notas, comentarios, figuras…), el
  // mismo que en la barra del nodo: mientras se escribe, esta barra la tapa.
  const [tamanoOpen, setTamanoOpen] = React.useState(false);
  // La tipografía de la nota o del título entero (Fuentes.jsx).
  const [fuenteOpen, setFuenteOpen] = React.useState(false);
  const llevaFuente = !isCaption && (item.type === 'note' || isBigTitle) && !!window.PanelFuente;
  const cambiaFuente = (fuente) => {
    const patch = { fuente: fuente || null };
    // Un título se ajusta a su caja: se le recalcula para que con la nueva
    // tipografía siga teniendo la misma letra.
    if (isBigTitle && window.cajaDeTitulo) {
      Object.assign(patch, window.cajaDeTitulo({ ...item, fuente: patch.fuente }, window.letraDeTitulo(item)));
    }
    onUpdate(patch);
  };
  const specTamano = !isCaption && window.specTamanoTexto ? window.specTamanoTexto(item, onUpdate) : null;
  // «Listo» del panel de color: se cierra el panel que lo contiene.
  React.useEffect(() => {
    const cierra = () => setColorOpen(false);
    window.addEventListener('odi-color-listo', cierra);
    return () => window.removeEventListener('odi-color-listo', cierra);
  }, []);



  // ── Qué botones salen encendidos ──
  //
  // Hay dos clases de nodo y no se pueden mirar igual:
  //
  //  · Título, marco y mapa guardan el formato como PROPIEDADES del nodo. Ahí
  //    el estado se lee de las props en cada render y ya está. Antes se metía
  //    en un estado que refrescaba un setInterval, y ese intervalo se quedaba
  //    con el `item` del render en que se creó: quitarle la negrita al título
  //    cambiaba el texto pero el botón seguía verde para siempre, así que
  //    parecía averiado. (Solo `align` estaba en las dependencias, por eso la
  //    alineación era lo único que respondía.)
  //
  //  · Notas, comentarios y tareas son HTML editable: ahí el formato depende
  //    de dónde esté el cursor, y eso solo se puede preguntar al navegador.
  //    Para eso sigue el sondeo — pero llamando siempre a la última versión.
  const propActive = React.useMemo(() => {
    if (item.type === 'bigtitle') {
      return {
        bold: item.bold !== false,
        italic: !!item.italic,
        strike: !!item.strike,
        underline: !!item.underline,
        alignLeft: (item.align || 'center') === 'left',
        alignCenter: (item.align || 'center') === 'center',
        alignRight: (item.align || 'center') === 'right',
        alignJustify: (item.align || 'center') === 'justify',
      };
    }
    // El tablero también guarda su formato como propiedades: su título es un
    // campo de texto plano, no HTML editable, así que B/I/S/U valen para el
    // título entero. Se lee igual que el del Título grande.
    if (item.type === 'board' || item.type === 'carpeta') {
      return {
        bold: item.bold !== false,
        italic: !!item.italic,
        strike: !!item.strike,
        underline: !!item.underline,
      };
    }
    // El bloque de código y el reloj guardan TODO el formato de su título
    // como propiedades del nodo: negrita y compañía, y también la
    // alineación. La alineación ya se escribía en item.titleAlign pero no
    // se leía de vuelta, así que su botón no se encendía nunca aunque
    // estuviera puesta.
    if (item.type === 'code' || item.type === 'timer') {
      return {
        bold: item.bold !== false,
        italic: !!item.italic,
        strike: !!item.strike,
        underline: !!item.underline,
        alignLeft: (item.titleAlign || 'left') === 'left',
        alignCenter: (item.titleAlign || 'left') === 'center',
        alignRight: (item.titleAlign || 'left') === 'right',
        alignJustify: (item.titleAlign || 'left') === 'justify',
      };
    }
    if (item.type === 'frame' || item.type === 'map') {
      return {
        alignLeft: (item.titleAlign || 'left') === 'left',
        alignCenter: (item.titleAlign || 'left') === 'center',
        alignRight: (item.titleAlign || 'left') === 'right',
        alignJustify: (item.titleAlign || 'left') === 'justify',
      };
    }
    return null;
  }, [item.type, item.bold, item.italic, item.strike, item.underline, item.align, item.titleAlign]);

  const refresh = () => {
    if (propActive) return; // se lee de las props, no hay nada que sondear
    try {
      setActive({
        bold: document.queryCommandState('bold'),
        italic: document.queryCommandState('italic'),
        underline: document.queryCommandState('underline'),
        strike: document.queryCommandState('strikeThrough'),
        ul: document.queryCommandState('insertUnorderedList'),
        ol: document.queryCommandState('insertOrderedList'),
        alignLeft: document.queryCommandState('justifyLeft'),
        alignCenter: document.queryCommandState('justifyCenter'),
        alignRight: document.queryCommandState('justifyRight'),
        alignJustify: document.queryCommandState('justifyFull'),
      });
    } catch {}
  };

  // Lo que se pinta: las propiedades del nodo cuando las hay, y si no lo que
  // diga el navegador sobre la selección actual.
  const shown = propActive || active;

  // El intervalo llama a través de una ref: así siempre ejecuta la función del
  // último render y no la que existía cuando se montó el temporizador.
  const refreshRef = React.useRef(refresh);
  refreshRef.current = refresh;
  React.useEffect(() => {
    const iv = setInterval(() => refreshRef.current(), 250);
    return () => clearInterval(iv);
  }, []);

  // Helper: find the active contenteditable; if not focused, focus the note's editor
  const findEditor = () => {
    let el = document.activeElement;
    if (el && el.isContentEditable) return el;
    // fallback to first .note-edit.rich
    el = document.querySelector(isCaption ? '.rich-caption' : '.note-edit.rich');
    if (el) { el.focus(); return el; }
    return null;
  };

  const exec = (cmd, val) => {
    const ed = findEditor();
    if (!ed) return;
    if (cmd === 'formatBlock') {
      const cleanTag = val.replace(/[<>]/g, '').toLowerCase();
      const changed = window.changeBlockTag && window.changeBlockTag(ed, cleanTag);
      if (changed) {
        ed.dispatchEvent(new Event('input', { bubbles: true }));
        refresh();
        return;
      }
      document.execCommand('removeFormat', false, null);
    }
    document.execCommand(cmd, false, val);
    // notify the note to persist via input event
    ed.dispatchEvent(new Event('input', { bubbles: true }));
    refresh();
  };

  return (
    <>
    <div className="ctx-side text-format" onMouseDown={(e)=>e.preventDefault()}>
      <button className="ctx-close" onClick={onClose} title={window.t('Cerrar', 'Close')}>
        <span className="material-symbols-rounded">arrow_back</span>
      </button>

      <button
        className={`ctx-btn ${colorOpen ? 'active' : ''}`}
        onClick={()=>{ setColorOpen(o=>!o); setFuenteOpen(false); setTamanoOpen(false); }}
        title={window.t('Color del texto', 'Text color')}
      >
        <div className="ctx-letter" style={{
          fontWeight: 800,
          fontSize: 14,
          color: usaTextColor ? (item.textColor || '#1A1A1A') : ((isFrame || isMap || isCode || isTimer) ? (item.titleColor || '#1A1A1A') : '#E6544F'),
          background: isCustomPropType && (usaTextColor ? item.textColor : item.titleColor) && (usaTextColor ? item.textColor : item.titleColor) !== 'inherit' ? 'none' : 'linear-gradient(90deg, #1A1A1A, #E6544F, #90B968)',
          // 'none' no es un valor que acepten estas tres: el navegador lo ignoraba y
          // se quedaba el de antes. Con un color elegido después de abrir la barra,
          // la A seguía transparente y el botón salía vacío.
          WebkitBackgroundClip: isCustomPropType && (usaTextColor ? item.textColor : item.titleColor) && (usaTextColor ? item.textColor : item.titleColor) !== 'inherit' ? 'border-box' : 'text',
          WebkitTextFillColor: isCustomPropType && (usaTextColor ? item.textColor : item.titleColor) && (usaTextColor ? item.textColor : item.titleColor) !== 'inherit' ? 'currentcolor' : 'transparent',
          backgroundClip: isCustomPropType && (usaTextColor ? item.textColor : item.titleColor) && (usaTextColor ? item.textColor : item.titleColor) !== 'inherit' ? 'border-box' : 'text',
        }}>
          {isCustomPropType && (usaTextColor ? item.textColor : item.titleColor) && (usaTextColor ? item.textColor : item.titleColor) !== 'inherit' ? <span style={{color: usaTextColor ? item.textColor : item.titleColor}}>A</span> : 'A'}
        </div>
        <span>{window.t('Color', 'Color')}</span>
      </button>

      {llevaFuente && (
        <button
          className={`ctx-btn ${fuenteOpen ? 'active' : ''}`}
          onClick={() => { setFuenteOpen(o => !o); setTamanoOpen(false); setColorOpen(false); }}
          title={window.t('Tipografía', 'Font')}
        >
          <span className="material-symbols-rounded">font_download</span>
          <span>{window.t('Fuente', 'Font')}</span>
        </button>
      )}

      {specTamano && window.PanelTamano && (
        <button
          className={`ctx-btn ctx-tamano ${tamanoOpen ? 'active' : ''}`}
          onClick={() => { setTamanoOpen(o => !o); setColorOpen(false); setFuenteOpen(false); }}
          title={specTamano.titulo}
        >
          <span className="ctx-tamano-num" aria-hidden="true">
            {Math.round(specTamano.valor * 10) / 10}
            <span className="material-symbols-rounded">expand_more</span>
          </span>
          <span>{specTamano.etiqueta}</span>
        </button>
      )}

      {/* Bloques (titulos, listas, cita, codigo): no en una tarea ni en una
          leyenda. Una tarea es UNA linea; meterle una lista dentro no significa
          nada y descuadra la fila. Lo que si tiene sentido —color, negrita,
          cursiva, subrayado, tachado y enlazar— queda disponible. */}
      {!isCaption && !isTodo && !isCustomPropType && (
        <>
          <button className="ctx-btn" onClick={()=>exec('formatBlock', '<h1>')}>
            <div className="ctx-letter" style={{fontWeight: 800, fontSize: 14}}>H1</div>
            <span>{window.t('Título', 'Heading')}</span>
          </button>
          <button className="ctx-btn" onClick={()=>exec('formatBlock', '<h2>')}>
            <div className="ctx-letter" style={{fontWeight: 700, fontSize: 12}}>H2</div>
            <span>{window.t('Subtítulo', 'Subheading')}</span>
          </button>
          <button className="ctx-btn" onClick={()=>exec('formatBlock', '<p>')}>
            <span className="material-symbols-rounded">notes</span>
            <span>{window.t('Texto normal', 'Paragraph')}</span>
          </button>
        </>
      )}

      {(!isCustomPropType || isBigTitle || isBoard || isCode || isTimer || isCarpeta) && (
        <>
          {/* En el título y en las tareas, B/I/S/U son toggles de propiedad
              (texto plano); en las notas usan execCommand por selección. */}
          <button className={`ctx-btn ${shown.bold ? 'active' : ''}`} onClick={()=> (isBigTitle || isBoard || isCode || isTimer || isCarpeta) ? onUpdate({ bold: item.bold === false }) : exec('bold')}>
            <div className="ctx-letter" style={{fontWeight: 800}}>B</div>
            <span>{window.t('Negrita', 'Bold')}</span>
          </button>

          <button className={`ctx-btn ${shown.italic ? 'active' : ''}`} onClick={()=> (isBigTitle || isBoard || isCode || isTimer || isCarpeta) ? onUpdate({ italic: !item.italic }) : exec('italic')}>
            <div className="ctx-letter" style={{fontStyle: 'italic', fontWeight: 700}}>I</div>
            <span>{window.t('Cursiva', 'Italic')}</span>
          </button>

          <button className={`ctx-btn ${shown.strike ? 'active' : ''}`} onClick={()=> (isBigTitle || isBoard || isCode || isTimer || isCarpeta) ? onUpdate({ strike: !item.strike }) : exec('strikeThrough')}>
            <div className="ctx-letter" style={{textDecoration: 'line-through', fontWeight: 700}}>S</div>
            <span>{window.t('Tachado', 'Strike')}</span>
          </button>

          <button className={`ctx-btn ${shown.underline ? 'active' : ''}`} onClick={()=> (isBigTitle || isBoard || isCode || isTimer || isCarpeta) ? onUpdate({ underline: !item.underline }) : exec('underline')}>
            <div className="ctx-letter" style={{textDecoration: 'underline', fontWeight: 700}}>U</div>
            <span>{window.t('Subrayado', 'Underline')}</span>
          </button>
        </>
      )}

      {/* Alineación: no aplica a las tareas (y evitaría caer en execCommand) */}
      {!isTodo && !isBoard && !isCarpeta && (isCustomPropType || !isCaption) && (
        <>
          <div className="ctx-sep-h"/>

          <button
            className={`ctx-btn ${shown.alignLeft ? 'active' : ''}`}
            onClick={()=>{
              if (isBigTitle) {
                onUpdate({ align: 'left' });
              } else if (isFrame || isMap || isCode || isTimer) {
                onUpdate({ titleAlign: 'left' });
              } else {
                exec('justifyLeft');
              }
            }}
            title={window.t('Alinear a la izquierda', 'Align left')}
          >
            <span className="material-symbols-rounded">format_align_left</span>
            <span>{window.t('Izquierda', 'Left')}</span>
          </button>

          <button
            className={`ctx-btn ${shown.alignCenter ? 'active' : ''}`}
            onClick={()=>{
              if (isBigTitle) {
                onUpdate({ align: 'center' });
              } else if (isFrame || isMap || isCode || isTimer) {
                onUpdate({ titleAlign: 'center' });
              } else {
                exec('justifyCenter');
              }
            }}
            title={window.t('Centrar', 'Align center')}
          >
            <span className="material-symbols-rounded">format_align_center</span>
            <span>{window.t('Centrar', 'Center')}</span>
          </button>

          <button
            className={`ctx-btn ${shown.alignRight ? 'active' : ''}`}
            onClick={()=>{
              if (isBigTitle) {
                onUpdate({ align: 'right' });
              } else if (isFrame || isMap || isCode || isTimer) {
                onUpdate({ titleAlign: 'right' });
              } else {
                exec('justifyRight');
              }
            }}
            title={window.t('Alinear a la derecha', 'Align right')}
          >
            <span className="material-symbols-rounded">format_align_right</span>
            <span>{window.t('Derecha', 'Right')}</span>
          </button>

          <button
            className={`ctx-btn ${shown.alignJustify ? 'active' : ''}`}
            onClick={()=>{
              if (isBigTitle) {
                onUpdate({ align: 'justify' });
              } else if (isFrame || isMap || isCode || isTimer) {
                onUpdate({ titleAlign: 'justify' });
              } else {
                exec('justifyFull');
              }
            }}
            title={window.t('Justificar', 'Justify')}
          >
            <span className="material-symbols-rounded">format_align_justify</span>
            <span>{window.t('Justificar', 'Justify')}</span>
          </button>
        </>
      )}

      {!isCaption && !isTodo && !isCustomPropType && (
        <>
          <div className="ctx-sep-h"/>

          <button className={`ctx-btn ${shown.ul ? 'active' : ''}`} onClick={()=>exec('insertUnorderedList')}>
            <span className="material-symbols-rounded">format_list_bulleted</span>
            <span>{window.t('Lista', 'Bullets')}</span>
          </button>

          <button className={`ctx-btn ${shown.ol ? 'active' : ''}`} onClick={()=>exec('insertOrderedList')}>
            <span className="material-symbols-rounded">format_list_numbered</span>
            <span>{window.t('Numerada', 'Numbered')}</span>
          </button>

          {/* Aquí estaban Cita y Código. El código no funcionaba —envolvía la
              selección en <pre> y el resaltado se comía el texto al volver a
              editarlo— y la cita se usaba de adorno. Se quedan Lista y
              Numerada, que sí hacen lo que dicen. Para escribir código está el
              nodo Documento. */}


        </>
      )}

      {/* Enlazar con otro nodo. FUERA del bloque de arriba a propósito: ese
          está cerrado para títulos y leyendas porque usan execCommand, que en
          esos tipos formatearía otro nodo. Enlazar no usa execCommand —solo
          envuelve la selección—, así que puede estar en todos los textos. */}
      {/* El nombre de la carpeta es un campo de texto plano: enlazar un trozo
          no significa nada ahí. */}
      {!isCarpeta && <div className="ctx-sep-h"/>}
      {!isCarpeta && <button
        className="ctx-btn"
        onClick={()=>{
          const sel = window.getSelection();
          if (!sel || sel.isCollapsed || !sel.rangeCount) {
            window.showToast && window.showToast(
              window.t('Selecciona antes el texto que quieres enlazar.', 'Select the text you want to link first.')
            );
            return;
          }
          window.odiStartLinkFromSelection && window.odiStartLinkFromSelection(sel.getRangeAt(0).cloneRange());
        }}
        title={window.t('Enlazar la selección con otro nodo', 'Link selection to another node')}
      >
        <span className="material-symbols-rounded">add_link</span>
        <span>{window.t('Enlazar', 'Link')}</span>
      </button>}
    </div>

    {fuenteOpen && llevaFuente && (
      // Igual que el del tamaño: pulsar una tipografía no le quita el foco al
      // texto; el buscador sí puede enfocarse.
      <div onMouseDownCapture={(e) => { if (e.target.tagName !== 'INPUT') e.preventDefault(); }}>
        <window.PanelFuente valor={item.fuente || null} onCambio={cambiaFuente}/>
      </div>
    )}

    {tamanoOpen && specTamano && window.PanelTamano && (
      // En captura, antes que el panel: que pulsar un tamaño no le quite el
      // foco al texto (y con él la edición). El campo sí puede enfocarse.
      <div onMouseDownCapture={(e) => { if (e.target.tagName !== 'INPUT') e.preventDefault(); }}>
        <window.PanelTamano spec={specTamano} itemId={item.id} onCerrar={() => setTamanoOpen(false)}/>
      </div>
    )}

    {colorOpen && (
      <div className="ctx-popout text-color-pop" onMouseDown={(e)=>e.preventDefault()}>
        <div className="ctx-pop-section">
          <div className="ctx-pop-title">{window.t('Color del texto', 'Text color')}</div>
          {/* Sin `text-color-grid`: esa clase es una rejilla de siete columnas
              de cuando aquí había siete casillas sueltas. Metiendo dentro el
              selector entero, lo encajaba en una columna de treinta píxeles y
              el panel salía en vertical, estirado hacia abajo, cuando en todas
              las demás pantallas va en horizontal. */}
          <div>
            <window.SelectorColor
              entero
              valor={usaTextColor ? (item.textColor || '#1A1A1A') : ((isFrame || isMap || isCode || isTimer) ? (item.titleColor || '#1A1A1A') : '#1A1A1A')}
              onCambio={(c) => {
                if (usaTextColor) {
                  onUpdate({ textColor: c });
                } else if (isFrame || isMap || isCode || isTimer) {
                  onUpdate({ titleColor: c });
                } else {
                  exec('foreColor', c);
                }
                // No se cierra al cambiar: aquí ya solo está el panel grande, y
                // la gente lo mueve y mira. Antes se cerraba con los colores de
                // la paleta de siempre, y como el negro del texto es uno de
                // ellos, el primer toque del aro de tono (que con el texto negro
                // sigue dando negro) cerraba el panel sin dejar elegir. Se
                // cierra con «Listo» (odi-color-listo) o pulsando fuera.
              }}
            />
          </div>
          <button
            className="btn"
            style={{marginTop: 8, width: '100%', justifyContent: 'center', fontSize: 11.5}}
            onClick={()=>{
              if (isTodo) {
                // En una tarea hay dos formatos que limpiar: el de lo
                // seleccionado y las propiedades antiguas de la fila entera,
                // que si no seguirian tiniendo la linea completa.
                exec('removeFormat');
                applyTodoStyle({ color: null, bold: false, italic: false, underline: false, strike: false });
              } else if (usaTextColor) {
                onUpdate({ textColor: 'inherit' });
              } else if (isFrame || isMap || isCode || isTimer) {
                onUpdate({ titleColor: 'inherit' });
              } else {
                exec('removeFormat');
              }
              setColorOpen(false);
            }}
          >
            <span className="material-symbols-rounded" style={{fontSize:14}}>format_clear</span>
            {window.t('Limpiar formato', 'Clear formatting')}
          </button>
        </div>
      </div>
    )}
    </>
  );
}

window.TextFormatSidebar = TextFormatSidebar;
