const { useState, useEffect, useRef, useCallback } = React;
const socket = io();

// ─── API / Auth ──────────────────────────────────────────────────
let authToken = localStorage.getItem('songcraft_token');
const api = async (url, opts = {}) => {
  const headers = { 'Content-Type': 'application/json', ...(authToken ? { 'Authorization': 'Bearer ' + authToken } : {}) };
  const res = await fetch(url, { ...opts, headers });
  if (res.status === 401) { authToken = null; localStorage.removeItem('songcraft_token'); window.location.reload(); }
  return res.json();
};
const setToken = (t) => { authToken = t; localStorage.setItem('songcraft_token', t); };

// ─── E2E Crypto ──────────────────────────────────────────────────
async function deriveKey(pw) { const e = new TextEncoder(); const km = await crypto.subtle.importKey('raw', e.encode(pw), 'PBKDF2', false, ['deriveKey']); return crypto.subtle.deriveKey({ name: 'PBKDF2', salt: e.encode('songcraft-salt'), iterations: 100000, hash: 'SHA-256' }, km, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']); }
async function encryptMsg(text, pw) { const k = await deriveKey(pw); const iv = crypto.getRandomValues(new Uint8Array(12)); const ct = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, k, new TextEncoder().encode(text)); return { encrypted: btoa(String.fromCharCode(...new Uint8Array(ct))), iv: btoa(String.fromCharCode(...iv)) }; }
async function decryptMsg(enc, ivB64, pw) { try { const k = await deriveKey(pw); const iv = Uint8Array.from(atob(ivB64), c => c.charCodeAt(0)); const ct = Uint8Array.from(atob(enc), c => c.charCodeAt(0)); return new TextDecoder().decode(await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, k, ct)); } catch { return '[Déchiffrement impossible]'; } }

// ─── Constants & Styles ──────────────────────────────────────────
const COLOR_PALETTE = ["#E8A838","#38BDE8","#E85B8A","#8BE85B","#B85BE8","#E8D838","#38E8C4","#E87838","#7888E8","#55CC88","#CC5577","#88BBDD","#DDAA55","#AA55DD","#55DDAA","#DD5555"];
const REACTION_EMOJIS = ["👍","❤️","🔥","🎵","✨","🤔","👏","💡"];
const DEFAULT_SECTION_TYPES = ["Intro","Couplet","Refrain","Pont","Outro","Pré-refrain","Ad-lib","Instrumental"];
const uid = () => Math.random().toString(36).slice(2, 10);
const authorBg = (h) => h + "1a";
const font = "'JetBrains Mono',monospace";
const fontD = "'Playfair Display',serif";
const UI = { accent: "#E8A838", dim: "#555", faint: "#333", surface: "rgba(255,255,255,0.04)", border: "rgba(255,255,255,0.08)" };
const inpS = { width: "100%", padding: "7px 10px", background: "rgba(0,0,0,0.3)", border: `1px solid ${UI.border}`, borderRadius: 4, color: "#ccc", fontFamily: font, fontSize: 12, outline: "none", boxSizing: "border-box" };
const labS = { fontSize: 10, color: "#888", letterSpacing: 1, textTransform: "uppercase", display: "block", marginBottom: 4 };

// ─── Shared UI ───────────────────────────────────────────────────
function Badge({ children, style, onClick }) { return <span onClick={onClick} style={{ display: "inline-block", padding: "2px 10px", borderRadius: 3, fontSize: 10, fontWeight: 700, letterSpacing: 1, textTransform: "uppercase", background: "rgba(255,255,255,0.06)", color: "#aaa", cursor: onClick ? "pointer" : "default", ...style }}>{children}</span>; }
function Btn({ children, onClick, primary, danger, small, disabled, style }) {
  const bg = danger ? "rgba(220,50,50,0.15)" : primary ? "rgba(255,255,255,0.06)" : UI.surface;
  return <button style={{ padding: small ? "5px 12px" : "8px 20px", border: `1px solid ${UI.border}`, borderRadius: 4, background: bg, color: danger ? "#E84B38" : primary ? "#ccc" : "#999", fontFamily: font, fontSize: small ? 11 : 12, cursor: disabled ? "not-allowed" : "pointer", opacity: disabled ? 0.4 : 1, fontWeight: 600, transition: "all .15s", ...style }} onClick={disabled ? undefined : onClick} onMouseEnter={e => { if (!disabled) e.target.style.background = "rgba(255,255,255,0.1)"; }} onMouseLeave={e => { e.target.style.background = bg; }}>{children}</button>;
}
function TabBar({ tabs, active, onChange }) { return <div style={{ display: "flex", borderBottom: `1px solid ${UI.border}`, marginBottom: 24 }}>{tabs.map(t => <button key={t.key} onClick={() => onChange(t.key)} style={{ padding: "12px 24px", background: "none", border: "none", borderBottom: active === t.key ? `2px solid ${UI.accent}` : "2px solid transparent", color: active === t.key ? "#ddd" : "#666", fontFamily: font, fontSize: 12, cursor: "pointer", fontWeight: active === t.key ? 700 : 400, letterSpacing: 1, textTransform: "uppercase" }}>{t.icon} {t.label}</button>)}</div>; }

// ─── ReactionBar ─────────────────────────────────────────────────
function ReactionBar({ reactions = [], userId, onChange, small }) {
  const [sp, setSp] = useState(false); const ref = useRef();
  useEffect(() => { if (!sp) return; const h = e => { if (ref.current && !ref.current.contains(e.target)) setSp(false); }; document.addEventListener("mousedown", h); return () => document.removeEventListener("mousedown", h); }, [sp]);
  const grouped = {}; (reactions||[]).forEach(r => { if (!grouped[r.emoji]) grouped[r.emoji] = []; grouped[r.emoji].push(r.author); });
  const toggle = e => { const ex = (reactions||[]).find(r => r.emoji === e && r.author === userId); onChange(ex ? reactions.filter(r => !(r.emoji === e && r.author === userId)) : [...(reactions||[]), { emoji: e, author: userId }]); setSp(false); };
  return <div style={{ display: "flex", alignItems: "center", gap: 3, flexWrap: "wrap", position: "relative" }}>
    {Object.entries(grouped).map(([em, authors]) => <button key={em} onClick={() => toggle(em)} style={{ display: "inline-flex", alignItems: "center", gap: 2, padding: "1px 4px", borderRadius: 10, border: `1px solid ${authors.includes(userId) ? "rgba(255,255,255,0.15)" : "rgba(255,255,255,0.06)"}`, background: authors.includes(userId) ? "rgba(255,255,255,0.06)" : "rgba(255,255,255,0.02)", cursor: "pointer", fontSize: small ? 11 : 13 }}><span>{em}</span>{authors.length > 1 && <span style={{ fontSize: 9, color: "#888" }}>{authors.length}</span>}</button>)}
    <div ref={ref} style={{ position: "relative" }}><button onClick={() => setSp(!sp)} style={{ width: 16, height: 16, borderRadius: "50%", border: "1px solid rgba(255,255,255,0.06)", background: "none", color: "#555", fontSize: 8, cursor: "pointer" }}>+</button>
      {sp && <div style={{ position: "absolute", bottom: "100%", left: 0, marginBottom: 4, zIndex: 60, background: "#181C26", border: `1px solid ${UI.border}`, borderRadius: 8, padding: 6, boxShadow: "0 4px 16px rgba(0,0,0,0.6)", display: "flex", gap: 2 }}>{REACTION_EMOJIS.map(e => <button key={e} onClick={() => toggle(e)} style={{ width: 28, height: 28, borderRadius: 6, border: "none", background: "none", cursor: "pointer", fontSize: 15 }}>{e}</button>)}</div>}
    </div></div>;
}

// ─── Color Picker ────────────────────────────────────────────────
function ColorPickerPopup({ current, onPick, onClose }) { const ref = useRef(); useEffect(() => { const h = e => { if (ref.current && !ref.current.contains(e.target)) onClose(); }; document.addEventListener("mousedown", h); return () => document.removeEventListener("mousedown", h); }, []); return <div ref={ref} style={{ position: "absolute", top: "100%", right: 0, marginTop: 8, zIndex: 60, background: "#181C26", border: `1px solid ${UI.border}`, borderRadius: 10, padding: 14, boxShadow: "0 8px 28px rgba(0,0,0,0.7)", width: 210 }}><div style={labS}>Couleur d'écriture</div><div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>{COLOR_PALETTE.map(c => <button key={c} onClick={() => onPick(c)} style={{ width: 28, height: 28, borderRadius: 6, border: c === current ? "2px solid #fff" : "2px solid transparent", background: c, cursor: "pointer", transform: c === current ? "scale(1.15)" : "none" }} />)}</div></div>; }

function AuthorBadge({ user, onChangeColor }) {
  const [sp, setSp] = useState(false);
  return <div style={{ position: "relative" }}>
    <Badge onClick={() => setSp(!sp)} style={{ cursor: "pointer", display: "flex", alignItems: "center", gap: 6 }}><span style={{ width: 8, height: 8, borderRadius: "50%", background: user.color, display: "inline-block" }} />{user.name}</Badge>
    {sp && <ColorPickerPopup current={user.color} onPick={c => { onChangeColor(c); setSp(false); }} onClose={() => setSp(false)} />}
  </div>;
}

// ─── Section Info Bubble ─────────────────────────────────────────
function SectionInfoBubble({ info, onUpdate }) {
  const [ed, setEd] = useState(false); const [txt, setTxt] = useState(info || ""); const ref = useRef(); const iRef = useRef();
  useEffect(() => { if (ed) { setTxt(info || ""); setTimeout(() => iRef.current?.focus(), 30); } }, [ed]);
  useEffect(() => { if (!ed) return; const h = e => { if (ref.current && !ref.current.contains(e.target)) { onUpdate(txt.trim()); setEd(false); } }; document.addEventListener("mousedown", h); return () => document.removeEventListener("mousedown", h); }, [ed, txt]);
  if (ed) return <div ref={ref} style={{ position: "absolute", top: "100%", left: 40, marginTop: 4, zIndex: 55, background: "#1A1E28", border: "1px solid rgba(255,255,255,0.12)", borderRadius: 8, padding: 10, boxShadow: "0 6px 20px rgba(0,0,0,0.6)", minWidth: 260 }}>
    <div style={labS}>Info section</div>
    <textarea ref={iRef} value={txt} onChange={e => setTxt(e.target.value)} onKeyDown={e => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); onUpdate(txt.trim()); setEd(false); } if (e.key === "Escape") setEd(false); }} rows={2} placeholder="Objectif, ambiance..." style={{ ...inpS, resize: "vertical" }} />
    <div style={{ display: "flex", gap: 4, marginTop: 6 }}><Btn small primary onClick={() => { onUpdate(txt.trim()); setEd(false); }}>OK</Btn><Btn small onClick={() => setEd(false)}>✕</Btn></div>
  </div>;
  return <button onClick={e => { e.stopPropagation(); setEd(true); }} title={info || "Ajouter une info"} style={{ background: "none", border: "none", cursor: "pointer", padding: "0 3px", opacity: info ? 0.85 : 0.35, fontSize: 13 }}>ℹ️</button>;
}

// ─── Comment Popover ─────────────────────────────────────────────
function CommentPopover({ segment, userId, onUpdateComment, onUpdateCR, onClose }) {
  const [txt, setTxt] = useState(segment.comment || ""); const ref = useRef(); const iRef = useRef();
  useEffect(() => { setTimeout(() => iRef.current?.focus(), 30); }, []);
  useEffect(() => { const h = e => { if (ref.current && !ref.current.contains(e.target)) { onUpdateComment(txt.trim() || null); onClose(); } }; document.addEventListener("mousedown", h); return () => document.removeEventListener("mousedown", h); }, [txt]);
  return <div ref={ref} style={{ position: "absolute", top: "100%", left: 0, marginTop: 4, zIndex: 55, background: "#1A1E28", border: "1px solid rgba(255,255,255,0.12)", borderRadius: 8, padding: 10, boxShadow: "0 6px 20px rgba(0,0,0,0.6)", minWidth: 260, maxWidth: 320 }}>
    <div style={labS}>Commentaire</div>
    <textarea ref={iRef} value={txt} onChange={e => setTxt(e.target.value)} onKeyDown={e => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); onUpdateComment(txt.trim() || null); onClose(); } if (e.key === "Escape") onClose(); }} rows={2} placeholder="Commentaire..." style={{ ...inpS, resize: "vertical" }} />
    <div style={{ marginTop: 6 }}><ReactionBar reactions={segment.commentReactions || []} userId={userId} onChange={onUpdateCR} small /></div>
    <div style={{ display: "flex", justifyContent: "space-between", marginTop: 6 }}>
      <div style={{ display: "flex", gap: 4 }}><Btn small primary onClick={() => { onUpdateComment(txt.trim() || null); onClose(); }}>OK</Btn><Btn small onClick={onClose}>✕</Btn></div>
      {segment.comment && <button onClick={() => { onUpdateComment(null); onClose(); }} style={{ background: "none", border: "none", cursor: "pointer", opacity: 0.5, padding: 2 }}><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#999" strokeWidth="2" strokeLinecap="round"><path d="M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/></svg></button>}
    </div>
  </div>;
}

// ═══════════════════════════════════════════════════════════════════
// EDITABLE LINE — select text → replace or comment
// ═══════════════════════════════════════════════════════════════════
function EditableLine({ line, userId, authorColors, onUpdate, onDelete }) {
  const lr = useRef(null); const [ed, setEd] = useState(false); const [sel, setSel] = useState(null);
  const [pm, setPm] = useState("replace"); const [rep, setRep] = useState(""); const [ct, setCt] = useState("");
  const rr = useRef(null); const cr = useRef(null); const [pp, setPp] = useState({ top: 0, left: 0 });
  const [csid, setCsid] = useState(null);
  const [wordSel, setWordSel] = useState(null);
  const isMobile = 'ontouchstart' in window;

  // Desktop: detect partial text selection on mouseup
  const hmu = () => {
    if (!ed || isMobile || !lr.current) return;
    const s = window.getSelection(); if (!s || s.isCollapsed) return;
    const rng = s.getRangeAt(0); let ss = rng.startContainer;
    while (ss && (!ss.getAttribute || !ss.getAttribute("data-si"))) ss = ss.parentNode;
    if (!ss || !lr.current.contains(ss)) { s.removeAllRanges(); return; }
    const si = +ss.getAttribute("data-si"); const sd = line.segments[si];
    if (!sd || sd.strikethrough) { s.removeAllRanges(); return; }
    const st = sd.text; let so = rng.startContainer.nodeType === 3 ? rng.startOffset : 0;
    let es = rng.endContainer; while (es && (!es.getAttribute || !es.getAttribute("data-si"))) es = es.parentNode;
    let eo = es === ss ? (rng.endContainer.nodeType === 3 ? rng.endOffset : st.length) : st.length;
    const a = Math.max(0, Math.min(so, st.length)), b = Math.max(a, Math.min(eo, st.length));
    if (a !== b) { const r = rng.getBoundingClientRect(), l = lr.current.getBoundingClientRect(); setPp({ top: r.bottom - l.top + 6, left: Math.max(0, r.left - l.left) }); setSel({ si, a, b }); setRep(""); setCt(""); setPm("replace"); setTimeout(() => { s.removeAllRanges(); rr.current?.focus(); }, 50); }
  };

  // Mobile: tap a word — first tap = start, second tap = end, then open popup
  const tapWord = (segIdx, wStart, wEnd, evt) => {
    evt.stopPropagation(); evt.preventDefault();
    const sd = line.segments[segIdx];
    if (!sd || sd.strikethrough) return;
    if (!ed) setEd(true);
    if (!wordSel || wordSel.si !== segIdx) {
      setWordSel({ si: segIdx, a: wStart, b: wEnd });
    } else {
      const a = Math.min(wordSel.a, wStart);
      const b = Math.max(wordSel.b, wEnd);
      setPp({ top: 40, left: 8 });
      setSel({ si: segIdx, a: a, b: b });
      setRep(""); setCt(""); setPm("replace");
      setWordSel(null);
      setTimeout(() => rr.current?.focus(), 100);
    }
  };

  // Split segment text into tappable words for mobile
  const renderWords = (text, segIdx) => {
    const parts = text.split(/(\s+)/);
    let pos = 0;
    return parts.map((w, wi) => {
      const ws = pos; pos += w.length; const we = pos;
      if (!w.trim()) return React.createElement("span", { key: "sp" + wi }, w);
      const hi = wordSel && wordSel.si === segIdx && ws >= wordSel.a && ws < wordSel.b;
      return React.createElement("span", {
        key: "w" + wi,
        onTouchEnd: function(e) { tapWord(segIdx, ws, we, e); },
        style: { padding: "3px 5px", borderRadius: 4, cursor: "pointer",
          background: hi ? "rgba(232,168,56,0.35)" : "rgba(255,255,255,0.06)",
          border: hi ? "1px solid rgba(232,168,56,0.5)" : "1px solid rgba(255,255,255,0.1)",
          margin: "2px 1px", display: "inline-block", lineHeight: "1.8" }
      }, w);
    });
  };

  const doReplace = () => { if (!sel) return; const { si, a, b } = sel; const o = line.segments[si]; const ns = []; for (let i = 0; i < si; i++) ns.push(line.segments[i]); if (a > 0) ns.push({ id: uid(), text: o.text.slice(0, a), author: o.author, strikethrough: false, comment: o.comment, commentReactions: [] }); ns.push({ id: uid(), text: o.text.slice(a, b), author: o.author, strikethrough: true, comment: null, commentReactions: [] }); if (rep.trim()) ns.push({ id: uid(), text: rep.trim(), author: userId, strikethrough: false, comment: null, commentReactions: [] }); if (b < o.text.length) ns.push({ id: uid(), text: o.text.slice(b), author: o.author, strikethrough: false, comment: null, commentReactions: [] }); for (let i = si + 1; i < line.segments.length; i++) ns.push(line.segments[i]); onUpdate({ ...line, segments: ns }); close(); };
  const doComment = () => { if (!sel) return; const { si, a, b } = sel; const o = line.segments[si]; const c = ct.trim() || null; const ns = []; for (let i = 0; i < si; i++) ns.push(line.segments[i]); if (a > 0) ns.push({ id: uid(), text: o.text.slice(0, a), author: o.author, strikethrough: false, comment: o.comment, commentReactions: [] }); ns.push({ id: uid(), text: o.text.slice(a, b), author: o.author, strikethrough: false, comment: c, commentReactions: [] }); if (b < o.text.length) ns.push({ id: uid(), text: o.text.slice(b), author: o.author, strikethrough: false, comment: null, commentReactions: [] }); for (let i = si + 1; i < line.segments.length; i++) ns.push(line.segments[i]); onUpdate({ ...line, segments: ns }); close(); };
  const close = () => { setSel(null); setRep(""); setCt(""); setWordSel(null); setEd(false); };

  const mobileHint = !sel && ed && isMobile ? (wordSel ? "Touchez le dernier mot" : "Touchez le premier mot") : null;

  return <div style={{ position: "relative", padding: "2px 0" }}>
    <div style={{ display: "flex", alignItems: "flex-start", gap: 4 }}>
      <div style={{ flexShrink: 0, marginTop: 3 }}><ReactionBar reactions={line.reactions || []} userId={userId} onChange={r => onUpdate({ ...line, reactions: r })} small /></div>
      <div ref={lr} onMouseUp={hmu} onClick={() => { if (!ed && !sel) setEd(true); }} style={{ flex: 1, fontSize: 13, lineHeight: 1.4, cursor: ed ? "text" : "pointer", padding: "3px 8px", borderRadius: 4, background: ed ? "rgba(255,255,255,0.03)" : "transparent", border: ed ? "1px dashed rgba(255,255,255,0.12)" : "1px solid transparent", userSelect: (!isMobile && ed) ? "text" : "none", WebkitUserSelect: (!isMobile && ed) ? "text" : "none" }}>
        {line.segments.map((s, i) => { const col = authorColors[s.author] || "#ccc"; const hc = !!s.comment;
          return <span key={s.id || i} style={{ position: "relative", display: "inline" }}>
            <span data-si={i} style={{
                color: s.strikethrough ? "#555" : col,
                textDecoration: s.strikethrough ? "line-through" : "none",
                fontStyle: (s.strikethrough || hc) ? "italic" : "normal",
                background: s.strikethrough ? "rgba(220,50,50,0.06)" : (hc ? "rgba(255,200,50,0.06)" : authorBg(col)),
                opacity: s.strikethrough ? 0.65 : 1, borderRadius: 2, padding: "0 1px",
                borderBottom: hc && !s.strikethrough ? "1px dotted rgba(255,200,50,0.4)" : "none",
              }}>{(ed && isMobile && !s.strikethrough) ? renderWords(s.text, i) : s.text}</span>
            {hc && !s.strikethrough && <span onClick={e => { e.stopPropagation(); setCsid(csid === s.id ? null : s.id); }} style={{ cursor: "pointer", fontSize: 9, opacity: 0.7, verticalAlign: "super", userSelect: "none" }}>💬</span>}
            {csid === s.id && hc && <CommentPopover segment={s} userId={userId} onUpdateComment={c => onUpdate({ ...line, segments: line.segments.map(x => x.id === s.id ? { ...x, comment: c } : x) })} onUpdateCR={r => onUpdate({ ...line, segments: line.segments.map(x => x.id === s.id ? { ...x, commentReactions: r } : x) })} onClose={() => setCsid(null)} />}
          </span>; })}
        {mobileHint && <div style={{ fontSize: 9, color: wordSel ? UI.accent : "#555", marginTop: 4, fontStyle: "italic" }}>{mobileHint}</div>}
      </div>
      <div style={{ display: "flex", gap: 2, flexShrink: 0, marginTop: 4 }}>{ed && !sel && <button onClick={close} style={{ background: "none", border: "none", color: "#4a4", cursor: "pointer", fontSize: 13 }}>✓</button>}<button onClick={onDelete} style={{ background: "none", border: "none", color: "#442", cursor: "pointer", fontSize: 10 }}>✕</button></div>
    </div>

    {sel && <div style={{ position: "absolute", top: pp.top + 30, left: 8, right: 8, zIndex: 50, background: "#181C26", border: "1px solid rgba(255,255,255,0.15)", borderRadius: 8, boxShadow: "0 8px 32px rgba(0,0,0,0.7)", overflow: "hidden" }}>
      <div style={{ display: "flex", borderBottom: "1px solid rgba(255,255,255,0.06)" }}>{[["replace","✏ Remplacer"],["comment","💬 Commenter"]].map(([k,l]) => <button key={k} onClick={() => { setPm(k); setTimeout(() => (k === "replace" ? rr : cr).current?.focus(), 30); }} style={{ flex: 1, padding: "10px 0", background: pm === k ? "rgba(255,255,255,0.04)" : "none", border: "none", borderBottom: pm === k ? `2px solid ${UI.accent}` : "2px solid transparent", color: pm === k ? "#ccc" : "#666", fontFamily: font, fontSize: 11, cursor: "pointer", fontWeight: pm === k ? 700 : 400 }}>{l}</button>)}</div>
      <div style={{ padding: 12 }}><div style={{ fontSize: 11, color: "#666", marginBottom: 8 }}>« <span style={{ color: "#bbb" }}>{line.segments[sel.si]?.text.slice(sel.a, sel.b)}</span> »</div>
        {pm === "replace" ? <><div style={{ display: "flex", gap: 6 }}><input ref={rr} value={rep} onChange={e => setRep(e.target.value)} onKeyDown={e => { if (e.key === "Enter") doReplace(); if (e.key === "Escape") close(); }} placeholder="Remplacement..." style={{ flex: 1, ...inpS, fontSize: 14, padding: 10 }} /><Btn small primary onClick={doReplace}>✓</Btn></div><div style={{ display: "flex", justifyContent: "space-between", marginTop: 8 }}><span style={{ fontSize: 9, color: "#444" }}>Vide = barrer</span><Btn small onClick={close}>✕ Annuler</Btn></div></> : <><div style={{ display: "flex", gap: 6 }}><textarea ref={cr} value={ct} onChange={e => setCt(e.target.value)} placeholder="Commentaire..." rows={2} style={{ flex: 1, ...inpS, fontSize: 14, padding: 10, resize: "vertical" }} /><Btn small primary onClick={doComment} style={{ alignSelf: "flex-end" }}>✓</Btn></div><div style={{ display: "flex", justifyContent: "flex-end", marginTop: 8 }}><Btn small onClick={close}>✕ Annuler</Btn></div></>}
      </div></div>}
  </div>;
}


// ─── Section Block (collapsible, draggable, with info + reactions) ──
function SectionBlock({ section, sectionIndex, sectionTypes, userId, authorColors, project, updateSections, onDelete, onMoveUp, onMoveDown, isFirst, isLast, collapsed, onToggleCollapse, onDragStart, onDragOver, onDragEnd, onDrop, isDragOver, dragOverSide }) {
  const [newLine, setNewLine] = useState("");
  const addLine = () => { if (!newLine.trim()) return; const secs = JSON.parse(JSON.stringify(project.sections)); secs[sectionIndex].lines.push({ id: uid(), segments: [{ id: uid(), text: newLine.trim(), author: userId, strikethrough: false, comment: null, commentReactions: [] }], reactions: [] }); updateSections(secs); setNewLine(""); };
  const updateLine = (li, updated) => { const secs = JSON.parse(JSON.stringify(project.sections)); secs[sectionIndex].lines[li] = updated; updateSections(secs); };
  const deleteLine = (li) => { const secs = JSON.parse(JSON.stringify(project.sections)); secs[sectionIndex].lines.splice(li, 1); updateSections(secs); };
  const splitAt = (li) => {
    const secs = JSON.parse(JSON.stringify(project.sections));
    const current = secs[sectionIndex];
    const linesBelow = current.lines.splice(li);
    const newSec = { id: uid(), type: "Couplet", lines: linesBelow, reactions: [], info: "" };
    secs.splice(sectionIndex + 1, 0, newSec);
    updateSections(secs);
  };
  const updateInfo = (info) => { const secs = JSON.parse(JSON.stringify(project.sections)); secs[sectionIndex].info = info; updateSections(secs); };
  const updateReactions = (r) => { const secs = JSON.parse(JSON.stringify(project.sections)); secs[sectionIndex].reactions = r; updateSections(secs); };
  const previewText = collapsed ? (section.lines[0]?.segments.filter(s => !s.strikethrough).map(s => s.text).join("") || "") : "";

  return <div onDragOver={e => { e.preventDefault(); if (onDragOver) onDragOver(e, sectionIndex); }} onDrop={e => { e.preventDefault(); if (onDrop) onDrop(e, sectionIndex); }}
    style={{ marginBottom: 2, background: "rgba(255,255,255,0.015)", border: "1px solid rgba(255,255,255,0.06)", borderRadius: 6, overflow: "visible", borderTop: isDragOver && dragOverSide === "top" ? `2px solid ${UI.accent}` : undefined, borderBottom: isDragOver && dragOverSide === "bottom" ? `2px solid ${UI.accent}` : undefined }}>
    <div onClick={e => { if (e.target.tagName === "SELECT" || e.target.tagName === "OPTION" || e.target.closest?.("button")) return; onToggleCollapse(); }}
      style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "8px 14px", background: "rgba(255,255,255,0.02)", cursor: "pointer", borderBottom: collapsed ? "none" : "1px solid rgba(255,255,255,0.04)", position: "relative" }}>
      <div style={{ display: "flex", alignItems: "center", gap: 6, flex: 1, minWidth: 0 }}>
        <span draggable onDragStart={e => { e.stopPropagation(); e.dataTransfer.effectAllowed = "move"; e.dataTransfer.setData("text/plain", String(sectionIndex)); if (onDragStart) onDragStart(sectionIndex); }} onDragEnd={() => { if (onDragEnd) onDragEnd(); }} style={{ cursor: "grab", color: "#444", fontSize: 14, userSelect: "none" }}>⠿</span>
        <span style={{ color: "#555", fontSize: 10, transform: collapsed ? "rotate(-90deg)" : "rotate(0)", display: "inline-block", transition: "transform .15s" }}>▼</span>
        <select value={section.type} onChange={e => { const secs = JSON.parse(JSON.stringify(project.sections)); secs[sectionIndex].type = e.target.value; updateSections(secs); }} onClick={e => e.stopPropagation()} style={{ background: "rgba(0,0,0,0.3)", border: `1px solid ${UI.border}`, borderRadius: 3, color: "#ccc", fontFamily: font, fontSize: 11, padding: "3px 6px", fontWeight: 700, cursor: "pointer" }}>{sectionTypes.map(t => <option key={t} value={t}>{t}</option>)}</select>
        <span style={{ fontSize: 9, color: "#444" }}>{section.lines.length}</span>
        {collapsed && previewText && <span style={{ fontSize: 11, color: "#555", fontStyle: "italic", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", maxWidth: 300 }}>{previewText}</span>}
        {!collapsed && <SectionInfoBubble info={section.info || ""} onUpdate={updateInfo} />}
        {!collapsed && <ReactionBar reactions={section.reactions || []} userId={userId} onChange={updateReactions} small />}
      </div>
      <div style={{ display: "flex", gap: 4, flexShrink: 0 }}>
        <button onClick={e => { e.stopPropagation(); onMoveUp(); }} disabled={isFirst} style={{ background: "none", border: "none", color: isFirst ? "#222" : "#666", cursor: "pointer", fontSize: 14 }}>↑</button>
        <button onClick={e => { e.stopPropagation(); onMoveDown(); }} disabled={isLast} style={{ background: "none", border: "none", color: isLast ? "#222" : "#666", cursor: "pointer", fontSize: 14 }}>↓</button>
        <button onClick={e => { e.stopPropagation(); onDelete(); }} style={{ background: "none", border: "none", color: "#E84B38", cursor: "pointer", fontSize: 12 }}>✕</button>
      </div>
    </div>
    {!collapsed && section.info && <div style={{ padding: "5px 14px", background: "rgba(100,180,255,0.04)", borderBottom: "1px solid rgba(100,180,255,0.08)", fontSize: 11, color: "#8ab", fontStyle: "italic" }}>ℹ️ {section.info}</div>}
    {!collapsed && <div style={{ padding: "4px 14px" }}>
      {section.lines.map((l, li) => <div key={l.id || li} style={{ position: "relative", display: "flex", alignItems: "flex-start" }}>
            <div style={{ flex: 1 }}><EditableLine line={l} userId={userId} authorColors={authorColors} onUpdate={u => updateLine(li, u)} onDelete={() => deleteLine(li)} /></div>
            {li > 0 && <button onClick={() => splitAt(li)} title="Insérer une section ici (lignes suivantes déplacées)" style={{ background: "none", border: "none", color: "#555", cursor: "pointer", fontSize: 10, padding: "4px 2px", flexShrink: 0, opacity: 0.4 }} onMouseEnter={e => e.target.style.opacity=1} onMouseLeave={e => e.target.style.opacity=0.4}>✂</button>}
          </div>)}
      <div style={{ display: "flex", gap: 8, marginTop: 6 }}><input value={newLine} onChange={e => setNewLine(e.target.value)} onKeyDown={e => e.key === "Enter" && addLine()} placeholder="Ajouter une ligne..." style={{ flex: 1, ...inpS }} /><Btn small onClick={addLine}>+</Btn></div>
    </div>}
  </div>;
}

// ─── Section Types Manager ───────────────────────────────────────
function SectionTypesManager({ types, onAdd, onRemove }) {
  const [sp, setSp] = useState(false); const [nt, setNt] = useState("");
  const doAdd = () => { const t = nt.trim(); if (t && !types.includes(t)) { onAdd(t); setNt(""); } };
  return <div style={{ position: "relative", display: "inline-block" }}><button onClick={() => setSp(!sp)} style={{ background: "none", border: `1px solid ${UI.border}`, borderRadius: 4, color: "#888", cursor: "pointer", fontSize: 11, padding: "4px 10px", fontFamily: font }}>⚙ Sections</button>
    {sp && <div style={{ position: "absolute", top: "100%", left: 0, marginTop: 6, zIndex: 40, background: "#181C26", border: `1px solid ${UI.border}`, borderRadius: 8, padding: 14, boxShadow: "0 8px 24px rgba(0,0,0,0.6)", minWidth: 240 }}><div style={labS}>Types</div><div style={{ display: "flex", flexWrap: "wrap", gap: 4, marginBottom: 10 }}>{types.map((t, i) => <span key={t} style={{ display: "inline-flex", alignItems: "center", gap: 4, padding: "3px 8px", background: i < DEFAULT_SECTION_TYPES.length ? "rgba(255,255,255,0.04)" : "rgba(139,187,88,0.1)", border: "1px solid rgba(255,255,255,0.06)", borderRadius: 3, fontSize: 11, color: "#ccc" }}>{t}{i >= DEFAULT_SECTION_TYPES.length && <button onClick={() => onRemove(t)} style={{ background: "none", border: "none", color: "#E84B38", cursor: "pointer", fontSize: 10 }}>✕</button>}</span>)}</div><div style={{ display: "flex", gap: 6 }}><input value={nt} onChange={e => setNt(e.target.value)} onKeyDown={e => e.key === "Enter" && doAdd()} placeholder="Nouveau type..." style={{ flex: 1, ...inpS }} /><Btn small onClick={doAdd}>+</Btn></div></div>}
  </div>;
}

// ═══════════════════════════════════════════════════════════════════
// CHAT WIDGET (E2E encrypted, SecureChat)
// ═══════════════════════════════════════════════════════════════════
function ChatWidget({ projectId, projectTitle, user, allUsers }) {
  const [open, setOpen] = useState(false); const [messages, setMessages] = useState([]); const [input, setInput] = useState(""); const [typing, setTyping] = useState(null); const msgsEnd = useRef(); const pw = projectTitle;
  useEffect(() => { if (!projectId || !open) return; api(`/api/projects/${projectId}/chat`).then(async msgs => { setMessages(await Promise.all(msgs.map(async m => ({ ...m, text: await decryptMsg(m.encrypted_text, m.iv, pw) })))); }); }, [projectId, open]);
  useEffect(() => { if (!projectId) return; const h = async msg => { if (msg.project_id !== projectId) return; setMessages(p => [...p, { ...msg, text: '...' }]); const text = await decryptMsg(msg.encrypted_text, msg.iv, pw); setMessages(p => p.map(m => m.id === msg.id ? { ...m, text } : m)); }; const th = ({ userId: uid }) => { if (uid !== user.id) { setTyping(uid); setTimeout(() => setTyping(null), 2000); } }; socket.on('chat:message', h); socket.on('chat:typing', th); return () => { socket.off('chat:message', h); socket.off('chat:typing', th); }; }, [projectId, pw]);
  useEffect(() => { if (open && msgsEnd.current) msgsEnd.current.scrollIntoView({ behavior: 'smooth' }); }, [messages, open]);
  const send = async () => { if (!input.trim()) return; const { encrypted, iv } = await encryptMsg(input.trim(), pw); socket.emit('chat:message', { projectId, encryptedText: encrypted, iv }); setInput(""); };
  const getName = id => allUsers.find(u => u.id === id)?.name || id;
  const getColor = id => allUsers.find(u => u.id === id)?.color || "#888";
  return <><button onClick={() => setOpen(!open)} style={{ position: "fixed", bottom: 24, right: 24, width: 56, height: 56, borderRadius: "50%", border: "none", background: "linear-gradient(135deg,#666,#444)", color: "#fff", fontSize: 22, cursor: "pointer", boxShadow: "0 4px 20px rgba(0,0,0,0.5)", zIndex: 1000, display: "flex", alignItems: "center", justifyContent: "center", transform: open ? "rotate(45deg)" : "none" }}>💬</button>
    {open && <div style={{ position: "fixed", bottom: 92, right: 24, width: 340, height: 440, background: "#12161E", border: `1px solid ${UI.border}`, borderRadius: 12, zIndex: 999, display: "flex", flexDirection: "column", boxShadow: "0 12px 40px rgba(0,0,0,0.6)", overflow: "hidden" }}>
      <div style={{ padding: "12px 16px", background: "rgba(255,255,255,0.03)", borderBottom: `1px solid ${UI.border}`, display: "flex", alignItems: "center", gap: 8 }}><span>🔒</span><div><div style={{ fontSize: 12, fontWeight: 700, color: "#ccc" }}>{projectTitle}</div><div style={{ fontSize: 9, color: "#555" }}>E2E chiffré · clé = projet</div></div></div>
      <div style={{ flex: 1, overflowY: "auto", padding: 12, display: "flex", flexDirection: "column", gap: 8 }}>{messages.map(m => { const me = m.author_id === user.id; return <div key={m.id} style={{ alignSelf: me ? "flex-end" : "flex-start", maxWidth: "80%" }}><div style={{ fontSize: 9, color: getColor(m.author_id), marginBottom: 2, textAlign: me ? "right" : "left" }}>{getName(m.author_id)} · {new Date(m.created_at).toLocaleTimeString("fr",{hour:"2-digit",minute:"2-digit"})}</div><div style={{ padding: "8px 12px", borderRadius: 8, background: "rgba(255,255,255,0.05)", fontSize: 12, lineHeight: 1.5, color: "#ddd" }}>{m.text}</div></div>; })}{typing && <div style={{ fontSize: 10, color: "#555", fontStyle: "italic" }}>{getName(typing)} écrit...</div>}<div ref={msgsEnd} /></div>
      <div style={{ padding: 8, borderTop: `1px solid ${UI.border}`, display: "flex", gap: 6 }}><input value={input} onChange={e => { setInput(e.target.value); socket.emit('chat:typing', { projectId }); }} onKeyDown={e => e.key === "Enter" && send()} placeholder="Message chiffré..." style={{ flex: 1, ...inpS, borderRadius: 6 }} /><Btn small primary onClick={send}>↑</Btn></div>
    </div>}</>
}

// ─── Login Screen (login + mot de passe + JWT) ───────────────────
function LoginScreen({ onLogin }) {
  const [name, setName] = useState(""); const [password, setPassword] = useState("");
  const [error, setError] = useState(""); const [loading, setLoading] = useState(false);
  const submit = async (path) => {
    setLoading(true); setError("");
    try {
      const v = await api(path, { method: 'POST', body: JSON.stringify({ name: name.trim(), password }) });
      if (v.error) throw new Error(v.error);
      if (v.verified && v.token) { setToken(v.token); socket.emit('auth', v.token); onLogin(v.user); }
      else setError("Échec");
    } catch (e) { setError(e.message); }
    setLoading(false);
  };
  const doLogin = () => submit('/api/auth/login');
  const doRegister = () => submit('/api/auth/register');
  return <div style={{ display: "flex", alignItems: "center", justifyContent: "center", minHeight: "100vh" }}><div style={{ textAlign: "center", maxWidth: 360, padding: 24 }}>
    <div style={{ fontSize: 48, marginBottom: 8 }}>🎵</div><h1 style={{ fontFamily: fontD, fontSize: 36, fontWeight: 700, color: UI.accent, margin: "0 0 4px" }}>SongCraft</h1><p style={{ color: UI.dim, fontSize: 12, letterSpacing: 2, textTransform: "uppercase", marginBottom: 40 }}>Co-création musicale</p>
    <input value={name} onChange={e => setName(e.target.value)} placeholder="Votre nom d'auteur" style={{ ...inpS, textAlign: "center", fontSize: 14, padding: 12, marginBottom: 12 }} />
    <input value={password} onChange={e => setPassword(e.target.value)} type="password" placeholder="Mot de passe" onKeyDown={e => e.key === "Enter" && doLogin()} style={{ ...inpS, textAlign: "center", fontSize: 14, padding: 12, marginBottom: 16 }} />
    <div style={{ display: "flex", gap: 8, justifyContent: "center", marginBottom: 16 }}><Btn primary onClick={doLogin} disabled={!name.trim() || !password || loading}>🔐 Connexion</Btn></div>
    <div style={{ display: "flex", gap: 8, justifyContent: "center" }}><Btn small onClick={doRegister} disabled={!name.trim() || !password || loading}>+ Créer un compte</Btn></div>
    {error && <p style={{ color: "#E84B38", fontSize: 11, marginTop: 8 }}>{error}</p>}
    {loading && <p style={{ color: "#888", fontSize: 11, marginTop: 8 }}>Connexion…</p>}
  </div></div>;
}

// ─── Dashboard ───────────────────────────────────────────────────
function Dashboard({ user, allUsers, onOpen, onChangeColor }) {
  const [projects, setProjects] = useState([]); const [sn, setSn] = useState(false); const [nt, setNt] = useState(""); const [ng, setNg] = useState("");
  const [genCoverId, setGenCoverId] = useState(null);
  const [zoomImg, setZoomImg] = useState(null); // which project is generating cover
  const load = () => api('/api/projects').then(setProjects);
  useEffect(() => { load(); socket.on('project:created', load); socket.on('project:deleted', load); socket.on('project:coverUpdated', load); return () => { socket.off('project:created', load); socket.off('project:deleted', load); socket.off('project:coverUpdated', load); }; }, []);
  const create = async () => { if (!nt.trim()) return; await api('/api/projects', { method: 'POST', body: JSON.stringify({ title: nt.trim(), genre: ng.trim(), memberIds: allUsers.map(u => u.id) }) }); setNt(""); setNg(""); setSn(false); load(); };
  const voteDel = async (pid) => { await api(`/api/projects/${pid}/vote`, { method: 'DELETE' }); load(); };
  const genCover = async (pid, e) => { e.stopPropagation(); setGenCoverId(pid); try { const r = await api(`/api/projects/${pid}/generate-cover`, { method: 'POST' }); if (r.error) alert(r.error); else load(); } catch(err) { alert(err.message); } setGenCoverId(null); };

  return <div style={{ maxWidth: 1100, margin: "0 auto", padding: "0 24px" }}>
    <header style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "28px 0 32px", borderBottom: "1px solid rgba(255,255,255,0.05)" }}>
      <div style={{ display: "flex", alignItems: "center", gap: 14 }}><span style={{ fontSize: 26 }}>🎵</span><h1 style={{ fontFamily: fontD, fontSize: 22, fontWeight: 700, color: UI.accent, margin: 0 }}>SongCraft</h1></div>
      <div style={{ display: "flex", alignItems: "center", gap: 12 }}><SunoAdmin /><AuthorBadge user={user} onChangeColor={onChangeColor} /><Btn primary small onClick={() => setSn(true)}>+ Nouveau</Btn></div>
    </header>
    {sn && <div style={{ margin: "24px 0", padding: 20, background: "rgba(255,255,255,0.02)", border: `1px solid ${UI.border}`, borderRadius: 8 }}><div style={{ display: "flex", gap: 12, flexWrap: "wrap", alignItems: "flex-end" }}><div style={{ flex: 1, minWidth: 200 }}><label style={labS}>Titre</label><input value={nt} onChange={e => setNt(e.target.value)} placeholder="Nom du projet..." style={inpS} /></div><div style={{ flex: 1, minWidth: 200 }}><label style={labS}>Genre</label><input value={ng} onChange={e => setNg(e.target.value)} placeholder="Pop, Rock, Electro..." style={inpS} /></div><div style={{ display: "flex", gap: 8 }}><Btn primary onClick={create}>Créer</Btn><Btn onClick={() => setSn(false)}>✕</Btn></div></div></div>}
    <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))", gap: 16, marginTop: 28 }}>{projects.map(p => {
      const isGenning = genCoverId === p.id;
      return <div key={p.id} onClick={() => onOpen(p.id)} style={{ background: "rgba(255,255,255,0.02)", border: "1px solid rgba(255,255,255,0.06)", borderRadius: 10, cursor: "pointer", overflow: "hidden", transition: "all .2s", display: "flex", flexDirection: "row" }} onMouseEnter={e => e.currentTarget.style.borderColor = "rgba(255,255,255,0.15)"} onMouseLeave={e => e.currentTarget.style.borderColor = "rgba(255,255,255,0.06)"}>
        {/* Cover image — square left */}
        <div style={{ width: 120, minHeight: 120, flexShrink: 0, background: "#0D1018", position: "relative", overflow: "hidden" }}>
          {p.hasCover ? <img src={`/api/projects/${p.id}/cover?t=${Date.now()}`} onClick={e => { e.stopPropagation(); setZoomImg(p.id); }} style={{ width: "100%", height: "100%", objectFit: "cover", cursor: "zoom-in" }} alt="" />
            : <div style={{ width: "100%", height: "100%", display: "flex", alignItems: "center", justifyContent: "center", background: "linear-gradient(135deg, rgba(255,255,255,0.02), rgba(255,255,255,0.05))" }}><span style={{ fontSize: 36, opacity: 0.2 }}>🎵</span></div>}
          <button onClick={e => genCover(p.id, e)} disabled={isGenning} title={p.hasCover ? "Régénérer" : "Générer"} style={{ position: "absolute", bottom: 4, right: 4, width: 26, height: 26, borderRadius: "50%", border: "none", background: "rgba(0,0,0,0.6)", color: "#ccc", fontSize: 11, cursor: isGenning ? "wait" : "pointer", display: "flex", alignItems: "center", justifyContent: "center" }}>{isGenning ? "⏳" : p.hasCover ? "🔄" : "🎨"}</button>
        </div>
        {/* Card body — right */}
        <div style={{ padding: "12px 14px", flex: 1, display: "flex", flexDirection: "column", justifyContent: "space-between", minWidth: 0 }}>
          <div>
            <h3 style={{ fontFamily: fontD, fontSize: 16, color: "#eee", margin: "0 0 3px", fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{p.title}</h3>
            <span style={{ fontSize: 10, color: UI.dim }}>{p.genre}</span>
            <div style={{ display: "flex", gap: 6, marginTop: 8 }}>{(p.authors||[]).map(a => { const u = allUsers.find(x => x.id === a); return <div key={a} style={{ display: "flex", alignItems: "center", gap: 3 }}><span style={{ width: 7, height: 7, borderRadius: 2, background: u?.color || "#888" }} /><span style={{ fontSize: 9, color: "#999" }}>{u?.name}</span></div>; })}</div>
          </div>
          <div style={{ display: "flex", justifyContent: "flex-end", marginTop: 6 }}><Btn danger small onClick={e => { e.stopPropagation(); voteDel(p.id); }}>🗑</Btn></div>
        </div>
      </div>; })}</div>
    {zoomImg && <div onClick={() => setZoomImg(null)} style={{ position: "fixed", inset: 0, zIndex: 2000, background: "rgba(0,0,0,0.85)", display: "flex", alignItems: "center", justifyContent: "center", cursor: "zoom-out", backdropFilter: "blur(8px)" }}><img src={`/api/projects/${zoomImg}/cover?t=${Date.now()}`} style={{ maxWidth: "90vw", maxHeight: "90vh", borderRadius: 12, boxShadow: "0 20px 60px rgba(0,0,0,0.8)" }} alt="" /></div>}
  </div>;
}

// ═══════════════════════════════════════════════════════════════════
// WORKSPACE (project view with tabs)
// ═══════════════════════════════════════════════════════════════════
function Workspace({ projectId, user, allUsers, onBack, onChangeColor }) {
  const [project, setProject] = useState(null); const [tab, setTab] = useState("lyrics");
  const [sectionTypes, setSectionTypes] = useState(DEFAULT_SECTION_TYPES);
  const load = useCallback(() => api(`/api/projects/${projectId}`).then(setProject), [projectId]);
  useEffect(() => { load(); api('/api/section-types').then(r => { if (Array.isArray(r)) setSectionTypes(r.map(t => t.name)); }); socket.emit('auth', authToken); socket.emit('join:project', projectId); const h = d => { if (d.projectId === projectId) load(); }; socket.on('sections:updated', h); socket.on('version:created', h); socket.on('proposal:created', h); return () => { socket.emit('leave:project', projectId); socket.off('sections:updated', h); socket.off('version:created', h); socket.off('proposal:created', h); }; }, [projectId]);
  const saveSections = async (secs) => { setProject(p => p ? { ...p, sections: secs } : p); await api(`/api/projects/${projectId}/sections`, { method: 'PUT', body: JSON.stringify({ sections: secs }) }); };
  const authorColors = Object.fromEntries(allUsers.map(u => [u.id, u.color]));
  const addST = async (t) => { setSectionTypes(p => [...p, t]); await api('/api/section-types', { method: 'POST', body: JSON.stringify({ name: t }) }); };
  const rmST = async (t) => { setSectionTypes(p => p.filter(x => x !== t)); await api(`/api/section-types/${t}`, { method: 'DELETE' }); };
  if (!project) return <div style={{ textAlign: "center", padding: 60, color: "#555" }}>Chargement...</div>;
  return <div style={{ maxWidth: 1100, margin: "0 auto", padding: "0 24px" }}>
    <header style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "20px 0 16px" }}>
      <div style={{ display: "flex", alignItems: "center", gap: 14 }}><button onClick={onBack} style={{ background: "none", border: "none", color: "#666", cursor: "pointer", fontSize: 18 }}>←</button><div><h2 style={{ fontFamily: fontD, fontSize: 20, color: "#eee", margin: 0 }}>{project.title}</h2><span style={{ fontSize: 10, color: "#444" }}>{project.genre}</span></div></div>
      <AuthorBadge user={user} onChangeColor={onChangeColor} />
    </header>
    <TabBar tabs={[{ key: "lyrics", label: "Paroles", icon: "✍" }, { key: "music", label: "Musique", icon: "🎹" }]} active={tab} onChange={setTab} />
    {tab === "lyrics" && <LyricsTab project={project} userId={user.id} authorColors={authorColors} allUsers={allUsers} updateSections={saveSections} sectionTypes={sectionTypes} addST={addST} rmST={rmST} onReload={load} />}
    {tab === "music" && <MusicTab project={project} userId={user.id} authorColors={authorColors} allUsers={allUsers} onReload={load} />}
    <ChatWidget projectId={projectId} projectTitle={project.title} user={user} allUsers={allUsers} />
  </div>;
}

// ─── Critique Panel ──────────────────────────────────────────────
function CritiquePanel({ critique, onClose }) {
  // Simple markdown-like rendering: **bold**, sections with ##
  const renderCritique = (text) => {
    return text.split('\n').map((line, i) => {
      const trimmed = line.trim();
      if (!trimmed) return <div key={i} style={{ height: 8 }} />;
      // Headers
      if (trimmed.startsWith('## ') || trimmed.startsWith('### ')) {
        const content = trimmed.replace(/^#{2,3}\s+/, '');
        return <div key={i} style={{ fontSize: 13, fontWeight: 700, color: "#ddd", margin: "14px 0 6px", borderBottom: "1px solid rgba(255,255,255,0.06)", paddingBottom: 4 }}>{content}</div>;
      }
      // Bold markers **text**
      const parts = trimmed.split(/(\*\*[^*]+\*\*)/g);
      return <div key={i} style={{ fontSize: 12, lineHeight: 1.7, color: "#bbb" }}>
        {parts.map((part, j) => {
          if (part.startsWith('**') && part.endsWith('**')) {
            return <span key={j} style={{ fontWeight: 700, color: "#ddd" }}>{part.slice(2, -2)}</span>;
          }
          return <span key={j}>{part}</span>;
        })}
      </div>;
    });
  };

  return <div style={{ marginBottom: 20, background: "rgba(200,160,60,0.04)", border: "1px solid rgba(200,160,60,0.15)", borderRadius: 8, overflow: "hidden" }}>
    <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "10px 16px", background: "rgba(200,160,60,0.06)", borderBottom: "1px solid rgba(200,160,60,0.1)" }}>
      <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
        <span style={{ fontSize: 18 }}>🎭</span>
        <span style={{ fontSize: 12, fontWeight: 700, color: "#ca8", letterSpacing: 1, textTransform: "uppercase" }}>Critique IA</span>
      </div>
      <button onClick={onClose} style={{ background: "none", border: "none", color: "#888", cursor: "pointer", fontSize: 14 }}>✕</button>
    </div>
    <div style={{ padding: "16px 20px", maxHeight: "75vh", overflowY: "auto", WebkitOverflowScrolling: "touch" }}>
      {renderCritique(critique)}
    </div>
  </div>;
}

// ─── Lyrics Tab ──────────────────────────────────────────────────
function LyricsTab({ project, userId, authorColors, allUsers, updateSections, sectionTypes, addST, rmST, onReload }) {
  const [preview, setPreview] = useState(false); const [viewVer, setViewVer] = useState(null);
  const [showFreeze, setShowFreeze] = useState(false); const [freezeNote, setFreezeNote] = useState("");
  const [showImport, setShowImport] = useState(false); const [importText, setImportText] = useState("");
  const [collapsedSet, setCollapsedSet] = useState(new Set());
  const [critique, setCritique] = useState(null); const [critiqueLoading, setCritiqueLoading] = useState(false);
  const [dragIdx, setDragIdx] = useState(null); const [dragOverIdx, setDragOverIdx] = useState(null); const [dragOverSide, setDragOverSide] = useState(null);

  const loadCritique = async (versionId) => {
    try {
      const r = await api('/api/projects/' + project.id + '/critique' + (versionId ? '?versionId=' + versionId : ''));
      if (r.critique) setCritique(r.critique);
    } catch(e) {}
  };
  const getCritique = async (sections, versionId) => {
    setCritiqueLoading(true); setCritique(null);
    try {
      const r = await api(`/api/projects/${project.id}/critique`, { method: 'POST', body: JSON.stringify({ sections: sections, versionId: versionId || null }) });
      if (r.error) { alert(r.error); } else { setCritique(r.critique); }
    } catch (e) { alert(e.message); }
    setCritiqueLoading(false);
  };
  const toggleC = id => setCollapsedSet(p => { const n = new Set(p); if (n.has(id)) n.delete(id); else n.add(id); return n; });
  const allC = project.sections.length > 0 && collapsedSet.size >= project.sections.length;
  const moveSection = (i, d) => { const a = JSON.parse(JSON.stringify(project.sections)); const n = i + d; if (n < 0 || n >= a.length) return; [a[i], a[n]] = [a[n], a[i]]; updateSections(a); };
  const delSection = (i) => { const a = JSON.parse(JSON.stringify(project.sections)); a.splice(i, 1); updateSections(a); };
  const addSection = () => updateSections([...project.sections, { id: uid(), type: "Couplet", lines: [], reactions: [], info: "" }]);
  const handleDragStart = i => setDragIdx(i); const handleDragEnd = () => { setDragIdx(null); setDragOverIdx(null); setDragOverSide(null); };
  const handleDragOver = (e, i) => { if (dragIdx === null || dragIdx === i) { setDragOverIdx(null); return; } const r = e.currentTarget.getBoundingClientRect(); setDragOverIdx(i); setDragOverSide(e.clientY < r.top + r.height / 2 ? "top" : "bottom"); };
  const handleDrop = (e, ti) => { if (dragIdx === null || dragIdx === ti) { handleDragEnd(); return; } const a = JSON.parse(JSON.stringify(project.sections)); const [m] = a.splice(dragIdx, 1); let ins = dragOverSide === "bottom" ? (dragIdx < ti ? ti : ti + 1) : (dragIdx < ti ? ti - 1 : ti); ins = Math.max(0, Math.min(ins, a.length)); a.splice(ins, 0, m); updateSections(a); handleDragEnd(); };
  const freeze = async () => { await api(`/api/projects/${project.id}/versions`, { method: 'POST', body: JSON.stringify({ name: `Version ${(project.versions?.length || 0) + 1}`, note: freezeNote.trim(), sections: project.sections }) }); setFreezeNote(""); setShowFreeze(false); onReload(); };
  const voteRestore = async (vid) => { await api(`/api/versions/${vid}/vote-restore`, { method: 'POST' }); onReload(); };
  // Import
  const ALIASES = {"intro":"Intro","couplet":"Couplet","verse":"Couplet","refrain":"Refrain","chorus":"Refrain","pont":"Pont","bridge":"Pont","outro":"Outro","pré-refrain":"Pré-refrain","pre-chorus":"Pré-refrain","ad-lib":"Ad-lib","instrumental":"Instrumental","interlude":"Instrumental"};
  const parseST = raw => { const c = raw.replace(/[\[\]]/g, "").trim().toLowerCase(); return ALIASES[c] || ALIASES[c.replace(/\s*\d+$/, "")] || sectionTypes.find(t => c.startsWith(t.toLowerCase())) || "Couplet"; };
  const doImport = () => { if (!importText.trim()) return; const raws = importText.split("\n"), news = []; let ct = null, cl = []; const flush = () => { const f = cl.filter(l => l.trim()); if (f.length > 0 || ct) news.push({ id: uid(), type: ct || "Couplet", lines: f.map(l => ({ id: uid(), segments: [{ id: uid(), text: l.trim(), author: userId, strikethrough: false, comment: null, commentReactions: [] }], reactions: [] })), reactions: [], info: "" }); cl = []; }; for (const r of raws) { const t = r.trim(); const m = t.match(/^\[([^\]]+)\]$/); if (m) { flush(); ct = parseST(m[0]); continue; } if (!t && cl.length === 0) continue; cl.push(t); } flush(); if (news.length === 0) { const al = importText.trim().split("\n").filter(l => l.trim()); if (al.length > 0) news.push({ id: uid(), type: "Couplet", lines: al.map(l => ({ id: uid(), segments: [{ id: uid(), text: l.trim(), author: userId, strikethrough: false, comment: null, commentReactions: [] }], reactions: [] })), reactions: [], info: "" }); } if (news.length > 0) updateSections([...project.sections, ...news]); setImportText(""); setShowImport(false); };

  if (viewVer) { const lv = project.versions?.find(v => v.id === viewVer.id) || viewVer; const vS = lv.sections || []; const votes = lv.restoreVotes || []; return <div>
    <div style={{ display: "flex", justifyContent: "space-between", flexWrap: "wrap", gap: 8, marginBottom: 16 }}><div><h3 style={{ fontFamily: fontD, fontSize: 20, color: "#eee", margin: 0 }}>📌 {lv.name}</h3><span style={{ fontSize: 10, color: "#555" }}>{lv.created_at?.slice(0, 10)}</span></div><div style={{ display: "flex", gap: 8 }}><Btn small onClick={() => getCritique(vS, lv.id)} disabled={critiqueLoading}>{critiqueLoading ? "⏳ Analyse..." : "🎭 Critique IA"}</Btn><Btn small primary onClick={() => voteRestore(lv.id)}>↩ Restaurer ({votes.length}/{project.authors?.length})</Btn><Btn small onClick={() => { setViewVer(null); setCritique(null); }}>← Retour</Btn></div></div>
    {lv.note && <div style={{ padding: "8px 14px", marginBottom: 12, background: "rgba(139,187,88,0.06)", border: "1px solid rgba(139,187,88,0.15)", borderRadius: 6, fontSize: 11, color: "#8b8" }}>📝 {lv.note}</div>}
    {!critique && <div style={{ marginBottom: 8 }}><Btn small onClick={() => loadCritique(viewVer ? viewVer.id : null)} style={{ fontSize: 10 }}>📄 Charger la dernière critique</Btn></div>}
    {critique && <CritiquePanel critique={critique} onClose={() => setCritique(null)} />}
    <div style={{ background: "rgba(255,255,255,0.02)", border: `1px solid ${UI.border}`, borderRadius: 8, padding: 20 }}>{vS.map(s => <div key={s.id} style={{ marginBottom: 20 }}><div style={{ fontSize: 10, color: "#999", letterSpacing: 2, textTransform: "uppercase", marginBottom: 8, fontWeight: 700 }}>[{s.type}]</div>{s.info && <div style={{ fontSize: 10, color: "#8ab", fontStyle: "italic", marginBottom: 4 }}>ℹ️ {s.info}</div>}{(s.lines||[]).map(l => <div key={l.id} style={{ fontSize: 13, lineHeight: 1.8 }}>{(l.segments||[]).map(sg => <span key={sg.id} style={{ color: sg.strikethrough ? "#555" : (authorColors[sg.author] || "#ccc"), textDecoration: sg.strikethrough ? "line-through" : "none", fontStyle: (sg.strikethrough || sg.comment) ? "italic" : "normal", background: sg.strikethrough ? "rgba(220,50,50,0.06)" : authorBg(authorColors[sg.author] || "#ccc"), opacity: sg.strikethrough ? 0.65 : 1, borderRadius: 2, padding: "0 1px" }}>{sg.text}</span>)}</div>)}</div>)}</div>
  </div>; }
  if (preview) return <div><div style={{ display: "flex", justifyContent: "space-between", flexWrap: "wrap", gap: 8, marginBottom: 20 }}><h3 style={{ fontFamily: fontD, fontSize: 20, color: "#eee" }}>Aperçu — {project.title}</h3><div style={{ display: "flex", gap: 8 }}><Btn small onClick={() => getCritique(project.sections, null)} disabled={critiqueLoading}>{critiqueLoading ? "⏳ Analyse..." : "🎭 Critique IA"}</Btn><Btn onClick={() => { setPreview(false); setCritique(null); }}>← Retour</Btn></div></div>
    {!critique && <div style={{ marginBottom: 8 }}><Btn small onClick={() => loadCritique(null)} style={{ fontSize: 10 }}>📄 Charger la dernière critique</Btn></div>}
    {critique && <CritiquePanel critique={critique} onClose={() => setCritique(null)} />}
    <div style={{ background: "rgba(255,255,255,0.02)", border: `1px solid ${UI.border}`, borderRadius: 8, padding: 28 }}>{project.sections.map(s => { const cls = s.lines.map(l => l.segments.filter(g => !g.strikethrough).map(g => g.text).join("")).filter(Boolean); return cls.length ? <div key={s.id} style={{ marginBottom: 24 }}><div style={{ fontSize: 10, color: "#999", letterSpacing: 2, textTransform: "uppercase", marginBottom: 8, fontWeight: 700 }}>[{s.type}]</div>{cls.map((t, i) => <div key={i} style={{ fontSize: 15, lineHeight: 1.9, color: "#ddd", fontFamily: fontD }}>{t}</div>)}</div> : null; })}</div></div>;

  return <div>
    <div style={{ display: "flex", justifyContent: "space-between", flexWrap: "wrap", gap: 8, marginBottom: 16 }}>
      <div style={{ display: "flex", gap: 12, alignItems: "center" }}>{allUsers.map(u => <div key={u.id} style={{ display: "flex", alignItems: "center", gap: 5 }}><span style={{ width: 10, height: 10, borderRadius: 2, background: u.color }} /><span style={{ fontSize: 10, color: "#999" }}>{u.name}</span></div>)}</div>
      <div style={{ display: "flex", gap: 6, alignItems: "center" }}>
        <SectionTypesManager types={sectionTypes} onAdd={addST} onRemove={rmST} />
        {project.sections.length > 1 && <Btn small onClick={() => { if (allC) setCollapsedSet(new Set()); else setCollapsedSet(new Set(project.sections.map(s => s.id))); }}>{allC ? "▼ Déplier" : "▶ Plier"}</Btn>}
        <Btn small onClick={() => setShowImport(true)}>📥</Btn>
        <Btn small onClick={() => setShowFreeze(true)}>📌 Figer</Btn>
        <Btn small primary onClick={() => setPreview(true)}>👁</Btn>
      </div>
    </div>
    {showFreeze && <div style={{ marginBottom: 16, padding: 16, background: "rgba(139,187,88,0.04)", border: "1px solid rgba(139,187,88,0.15)", borderRadius: 6 }}><label style={labS}>Note de version</label><textarea value={freezeNote} onChange={e => setFreezeNote(e.target.value)} rows={2} placeholder="Ce qui a changé..." style={{ ...inpS, resize: "vertical", marginBottom: 8 }} /><div style={{ display: "flex", gap: 8 }}><Btn primary small onClick={freeze}>📌 Figer</Btn><Btn small onClick={() => setShowFreeze(false)}>Annuler</Btn></div></div>}
    {showImport && <div style={{ marginBottom: 16, padding: 16, background: "rgba(255,255,255,0.02)", border: `1px solid ${UI.border}`, borderRadius: 6 }}><label style={labS}>Importer des paroles</label><textarea value={importText} onChange={e => setImportText(e.target.value)} rows={8} placeholder={"[Intro]\nLes feuilles tombent\n\n[Couplet]\nJe marche seul"} style={{ ...inpS, resize: "vertical" }} /><div style={{ display: "flex", gap: 8, marginTop: 8 }}><Btn small primary onClick={doImport}>Importer</Btn><Btn small onClick={() => setShowImport(false)}>Annuler</Btn></div></div>}
    {project.sections.map((s, i) => <SectionBlock key={s.id} section={s} sectionIndex={i} sectionTypes={sectionTypes} userId={userId} authorColors={authorColors} project={project} updateSections={updateSections} onDelete={() => delSection(i)} onMoveUp={() => moveSection(i, -1)} onMoveDown={() => moveSection(i, 1)} isFirst={i === 0} isLast={i === project.sections.length - 1} collapsed={collapsedSet.has(s.id)} onToggleCollapse={() => toggleC(s.id)} onDragStart={handleDragStart} onDragOver={handleDragOver} onDragEnd={handleDragEnd} onDrop={handleDrop} isDragOver={dragOverIdx === i} dragOverSide={dragOverSide} />)}
    <Btn onClick={addSection} style={{ width: "100%", textAlign: "center", borderStyle: "dashed" }}>+ Section</Btn>
    {project.versions?.length > 0 && <div style={{ marginTop: 24 }}><h4 style={{ fontSize: 11, color: UI.dim, letterSpacing: 1, textTransform: "uppercase", margin: "0 0 10px" }}>Versions figées</h4>{project.versions.map(v => <div key={v.id} style={{ padding: "8px 12px", background: "rgba(255,255,255,0.02)", borderRadius: 5, marginBottom: 4, border: "1px solid rgba(255,255,255,0.04)" }}><div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}><span style={{ color: "#aaa", fontSize: 12 }}>📌 {v.name} <span style={{ color: "#444", fontSize: 10 }}>{v.created_at?.slice(0, 10)}</span></span><div style={{ display: "flex", gap: 4 }}><Btn small onClick={() => setViewVer(v)}>👁</Btn><Btn small onClick={() => voteRestore(v.id)}>↩</Btn></div></div>{v.note && <div style={{ fontSize: 10, color: "#8b8", fontStyle: "italic", marginTop: 4 }}>📝 {v.note}</div>}</div>)}</div>}
  </div>;
}

// ─── Music Tab ───────────────────────────────────────────────────
function GenCard({ gen, projectId, onDelete }) {
  const [g, setG] = useState(gen);
  React.useEffect(() => {
    if (g.status !== 'generating') return;
    let alive = true;
    const poll = async () => {
      try {
        const r = await api('/api/generations/' + g.id + '/poll');
        if (alive) { setG(r); if (r.status === 'generating') setTimeout(poll, 5000); }
      } catch(e) { if (alive) setTimeout(poll, 10000); }
    };
    setTimeout(poll, 3000);
    return () => { alive = false; };
  }, [g.status]);

  return <div style={{ padding: "8px 0", borderTop: "1px solid rgba(255,255,255,0.04)" }}>
    <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 4 }}>
      <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
        <span>{g.status === "done" ? "🎵" : g.status === "error" ? "❌" : "⏳"}</span>
        <span style={{ fontSize: 11, color: "#aaa" }}>{g.title}</span>
      </div>
      <span style={{ fontSize: 9, padding: "2px 6px", borderRadius: 3,
        background: g.status === "done" ? "rgba(80,200,80,0.1)" : g.status === "error" ? "rgba(220,50,50,0.1)" : "rgba(232,168,56,0.1)",
        color: g.status === "done" ? "#6c6" : g.status === "error" ? "#E84B38" : "#E8A838"
      }}>{g.status === "done" ? "Prêt" : g.status === "error" ? "Erreur" : "En cours..."}</span>
      <button onClick={async (e) => { e.stopPropagation(); if (!confirm('Supprimer cette génération ?')) return; try { await api('/api/generations/' + g.id, { method: 'DELETE' }); if (onDelete) onDelete(g.id); } catch(e) { console.error(e); } }} style={{ background: "none", border: "none", color: "#553", cursor: "pointer", fontSize: 10, padding: "0 4px" }}>🗑</button>
    </div>
    {g.audio_url1 && <div style={{ marginTop: 4 }}>
      <audio controls preload="none" src={g.audio_url1} style={{ width: "100%", height: 32, borderRadius: 4 }} />
    </div>}
    {g.audio_url2 && <div style={{ marginTop: 4 }}>
      <audio controls preload="none" src={g.audio_url2} style={{ width: "100%", height: 32, borderRadius: 4 }} />
    </div>}
  </div>;
}

function MusicTab({ project, sunoGens, sunoGenerate, sunoLoading, sunoError, userId, authorColors, allUsers, onReload }) {
  const [editForm, setEditForm] = useState(null); const [generating, setGenerating] = useState(false);
  const proposals = project.musicProposals || [];
  const startNew = () => setEditForm({ id: null, ambiance: "", style: project.genre || "", mesure: "4/4", tempo: "120", instruments: "", evolution: "", description: "" });
  const startEdit = p => setEditForm({ id: p.id, ambiance: p.ambiance || p.form?.ambiance || "", style: p.style || p.form?.style || "", mesure: p.mesure || p.form?.mesure || "4/4", tempo: p.tempo || p.form?.tempo || "120", instruments: p.instruments || p.form?.instruments || "", evolution: p.evolution || p.form?.evolution || "", description: p.description || "" });
  const save = async () => { if (!editForm) return; const { id, description, ...form } = editForm; if (id) await api(`/api/proposals/${id}`, { method: 'PUT', body: JSON.stringify({ form, description }) }); else await api(`/api/projects/${project.id}/proposals`, { method: 'POST', body: JSON.stringify({ form, description }) }); setEditForm(null); onReload(); };
  const del = async id => { await api(`/api/proposals/${id}`, { method: 'DELETE' }); onReload(); };
  const gen = async (pid, title) => { await api(`/api/proposals/${pid}/generate`, { method: 'POST', body: JSON.stringify({ title }) }); onReload(); };
  const genPrompt = async () => { setGenerating(true); try { const r = await api(`/api/projects/${project.id}/generate-prompt`, { method: 'POST', body: JSON.stringify({ title: project.title, ambiance: editForm.ambiance, style: editForm.style, mesure: editForm.mesure, tempo: editForm.tempo, instruments: editForm.instruments, evolution: editForm.evolution }) }); if (r.error) { alert(r.error); } else if (r.description) { setEditForm(f => ({ ...f, description: r.description })); } } catch (e) { console.error(e); alert("Erreur: " + e.message); } setGenerating(false); };
  const F = (k, l, ph, multi) => <div style={{ marginBottom: 10 }}><label style={labS}>{l}</label>{multi ? <textarea value={editForm[k]} onChange={e => setEditForm(f => ({ ...f, [k]: e.target.value }))} placeholder={ph} rows={2} style={{ ...inpS, resize: "vertical" }} /> : <input value={editForm[k]} onChange={e => setEditForm(f => ({ ...f, [k]: e.target.value }))} placeholder={ph} style={inpS} />}</div>;
  return <div>
    <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 16 }}><h4 style={{ fontSize: 11, color: "#999", letterSpacing: 2, textTransform: "uppercase" }}>Propositions musicales</h4>{!editForm && <Btn primary small onClick={startNew}>+ Nouvelle</Btn>}</div>
    {editForm && <div style={{ padding: 20, background: "rgba(255,255,255,0.02)", border: `1px solid ${UI.border}`, borderRadius: 8, marginBottom: 20 }}>
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "0 16px" }}>{F("ambiance","Ambiance","Mélancolique, joyeux...")}{F("style","Style","Indie Folk, Synthwave...")}{F("mesure","Mesure","4/4, 3/4...")}{F("tempo","Tempo (BPM)","90, 120...")}</div>
      {F("instruments","Instruments","Guitare, piano, batterie...",true)}
      {F("evolution","Évolution musicale","Démarrage doux, montée au refrain, explosion finale...",true)}
      <div style={{ marginTop: 16, padding: 14, background: "rgba(255,255,255,0.02)", border: `1px solid ${UI.border}`, borderRadius: 6 }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 8 }}><label style={{ ...labS, margin: 0 }}>Description Suno (max 1000)</label><Btn small primary onClick={genPrompt} disabled={generating}>{generating ? "⏳..." : "🤖 IA"}</Btn></div>
        <textarea value={editForm.description} onChange={e => setEditForm(f => ({ ...f, description: e.target.value.slice(0, 1000) }))} rows={5} placeholder="Prompt Suno..." style={{ ...inpS, resize: "vertical" }} /><span style={{ fontSize: 9, color: "#444" }}>{(editForm.description||"").length}/1000</span>
      </div>
      <div style={{ display: "flex", gap: 8, marginTop: 16 }}><Btn primary onClick={save} disabled={!editForm.description?.trim()}>💾 Enregistrer</Btn><Btn onClick={() => setEditForm(null)}>Annuler</Btn></div>
    </div>}
    {proposals.map(p => { const au = allUsers.find(u => u.id === p.author_id); const gens = (project.generations || []).filter(g => g.proposal_id === p.id); return <div key={p.id} style={{ marginBottom: 12, background: "rgba(255,255,255,0.02)", border: "1px solid rgba(255,255,255,0.04)", borderRadius: 8 }}>
      <div style={{ padding: "12px 16px" }}>
        <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 8 }}><div><span style={{ fontSize: 9, color: authorColors[p.author_id], fontWeight: 700, textTransform: "uppercase" }}>{au?.name}</span><span style={{ fontSize: 9, color: "#444", marginLeft: 8 }}>{p.created_at?.slice(0, 10)}</span></div><div style={{ display: "flex", gap: 4 }}><Btn small onClick={() => startEdit(p)}>✏</Btn><Btn small danger onClick={() => del(p.id)}>🗑</Btn></div></div>
        <div style={{ display: "flex", gap: 12, flexWrap: "wrap", fontSize: 11, color: "#888", marginBottom: 8 }}>{(p.style || p.form?.style) && <span>🎸 {p.style || p.form?.style}</span>}{(p.ambiance || p.form?.ambiance) && <span>🌙 {p.ambiance || p.form?.ambiance}</span>}{(p.tempo || p.form?.tempo) && <span>⏱ {p.tempo || p.form?.tempo}</span>}{(p.mesure || p.form?.mesure) && <span>📐 {p.mesure || p.form?.mesure}</span>}</div>
        {(p.evolution || p.form?.evolution) && <div style={{ fontSize: 11, color: "#8ab", marginBottom: 8, fontStyle: "italic" }}>📈 {p.evolution || p.form?.evolution}</div>}
        <div style={{ fontSize: 12, color: "#bbb", fontStyle: "italic", padding: 10, background: "rgba(0,0,0,0.15)", borderRadius: 4, maxHeight: 80, overflowY: "auto" }}>{p.description}</div>
      </div>
      <div style={{ padding: "8px 16px", background: "rgba(0,0,0,0.1)" }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: gens.length ? 6 : 0 }}><span style={{ fontSize: 9, color: "#666", textTransform: "uppercase" }}>{gens.length} génération(s)</span><Btn small primary onClick={() => gen(p.id, `${project.title} - Take ${gens.length + 1}`)}>🚀 Générer</Btn></div>
        {gens.map(g => <GenCard key={g.id} gen={g} projectId={project.id} onDelete={() => onReload()} />)}
      </div>
    </div>; })}
    {proposals.length === 0 && !editForm && <p style={{ color: "#444", fontSize: 12, textAlign: "center", padding: 20 }}>Aucune proposition. Créez-en une !</p>}
  </div>;
}

// ─── App Root ────────────────────────────────────────────────────
function SunoAdmin() {
  const [show, setShow] = useState(false);
  const [status, setStatus] = useState(null);
  const [token, setToken] = useState('');
  const check = async () => { try { const r = await api('/api/admin/suno-status'); setStatus(r); } catch(e) {} };
  React.useEffect(() => { check(); const i = setInterval(check, 60000); return () => clearInterval(i); }, []);
  const save = async () => {
    if (!token.trim()) return;
    await api('/api/admin/suno-token', { method: 'POST', body: JSON.stringify({ token: token.trim() }) });
    setToken(''); check(); setShow(false);
  };
  const color = status?.status === 'valid' ? '#6c6' : status?.status === 'expired' ? '#E84B38' : '#888';
  return <div style={{ position: 'relative', display: 'inline-block' }}>
    <button onClick={() => setShow(!show)} title={'Suno: ' + (status?.status || '?')} style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '4px 8px', fontSize: 11, color: color }}>
      {status?.status === 'valid' ? '🟢' : '🔴'} Suno
    </button>
    {show && <div style={{ position: 'absolute', top: '100%', right: 0, zIndex: 100, background: '#181C26', border: '1px solid rgba(255,255,255,0.12)', borderRadius: 8, padding: 14, boxShadow: '0 8px 24px rgba(0,0,0,0.7)', width: 320 }}>
      <div style={{ fontSize: 10, color: '#888', marginBottom: 8 }}>Token Suno : <span style={{ color: color }}>{status?.status || '?'}</span>{status?.expires ? ' — expire ' + status.expires.slice(11, 16) : ''}</div>
      <div style={{ fontSize: 9, color: '#555', marginBottom: 8 }}>Sur suno.com, DevTools → Network → requete studio-api → header Authorization → coller le Bearer token</div>
      <textarea value={token} onChange={e => setToken(e.target.value)} placeholder="Bearer eyJ..." rows={3} style={{ width: '100%', padding: 8, background: 'rgba(0,0,0,0.3)', border: '1px solid rgba(255,255,255,0.08)', borderRadius: 4, color: '#ccc', fontFamily: 'monospace', fontSize: 10, resize: 'vertical', outline: 'none', boxSizing: 'border-box' }} />
      <div style={{ display: 'flex', gap: 6, marginTop: 8 }}><Btn small primary onClick={save}>Mettre à jour</Btn><Btn small onClick={() => setShow(false)}>Fermer</Btn></div>
    </div>}
  </div>;
}

function App() {
  const [user, setUser] = useState(null); const [allUsers, setAllUsers] = useState([]); const [openPid, setOpenPid] = useState(null); const [checking, setChecking] = useState(true);
  useEffect(() => { if (authToken) { api('/api/auth/me').then(d => { if (d.user) { setUser(d.user); socket.emit('auth', authToken); } setChecking(false); }).catch(() => setChecking(false)); } else setChecking(false); }, []);
  useEffect(() => { if (user) api('/api/users').then(setAllUsers); }, [user]);
  useEffect(() => { socket.on('user:color', ({ userId: uid, color }) => { setAllUsers(p => p.map(u => u.id === uid ? { ...u, color } : u)); if (user?.id === uid) setUser(u => ({ ...u, color })); }); return () => socket.off('user:color'); }, [user]);
  const changeColor = async (c) => { setUser(u => ({ ...u, color: c })); await api('/api/users/color', { method: 'PUT', body: JSON.stringify({ color: c }) }); };
  if (checking) return <div style={{ display: "flex", alignItems: "center", justifyContent: "center", minHeight: "100vh", color: "#555" }}>Chargement...</div>;
  if (!user) return <LoginScreen onLogin={u => { setUser(u); socket.emit('auth', authToken); }} />;
  if (openPid) return <Workspace projectId={openPid} user={user} allUsers={allUsers} onBack={() => setOpenPid(null)} onChangeColor={changeColor} />;
  return <Dashboard user={user} allUsers={allUsers} onOpen={setOpenPid} onChangeColor={changeColor} />;
}

ReactDOM.createRoot(document.getElementById('root')).render(React.createElement(App));
