/* Atelier - Chansons : ecoute ligne a ligne, shadowing, substitutions.
   Drill libre, sans effet SRS. Extrait d'atelier.jsx (decoupage 2026-06-12). */

function songSections(song) {
  return Array.isArray(song && song.sections) ? song.sections : [];
}

function songLines(song) {
  return songSections(song).flatMap((section) => (section.lines || []).map((line) => ({ ...line, section: section.label })));
}

function songLineCount(song) {
  return songLines(song).length;
}

function songSubstitutionCount(song) {
  return (Array.isArray(song && song.substitutionSets) ? song.substitutionSets : [])
    .reduce((total, set) => total + ((set.items && set.items.length) || 0), 0);
}

function songMasteredState(state) {
  if (!state || typeof state !== "object") return false;
  const box = Number(state.box || state.bucket || 0);
  const stability = Number(state.stability || 0);
  const reps = Number(state.reps || state.reviewCount || state.seenCount || 0);
  const lapses = Number(state.lapses || state.errors || 0);
  return box >= 3 || stability >= 2.5 || (reps >= 2 && lapses === 0);
}

function songItemMastered(item, cardStates) {
  const ids = Array.isArray(item && item.sourceIds) ? item.sourceIds : [];
  return ids.some((id) => songMasteredState(cardStates && cardStates[id]));
}

function songAvailableSubstitutions(set, cardStates) {
  const items = Array.isArray(set && set.items) ? set.items : [];
  const mastered = items.filter((item) => songItemMastered(item, cardStates));
  const fallback = items.filter((item) => item.fromSong);
  const out = [];
  const seen = Object.create(null);
  mastered.concat(fallback.length ? fallback : items).forEach((item) => {
    const key = item.kr;
    if (!key || seen[key]) return;
    seen[key] = true;
    out.push(item);
  });
  return out;
}

function songPatternText(set, item) {
  const pattern = String((set && set.pattern) || "");
  const slot = set && set.slot ? `{${set.slot}}` : "{}";
  return pattern.replace(slot, item && item.kr ? item.kr : "___");
}

function songExerciseFr(set, item) {
  if (!set || !item) return "";
  if (set.id === "where-is-place") return `Où est ${item.fr} ?`;
  if (set.id === "where-exists-object") return `Où est ${item.fr} ?`;
  if (set.id === "here-there-exists") return `Il y en a ${item.fr}.`;
  return item.fr;
}

function SongSpeakButton({ text, children, gloss, onSpeak, className, style, title = "Ecouter" }) {
  const [open, setOpen] = useState(false);
  const speak = (event) => {
    event.stopPropagation();
    if (typeof onSpeak === "function") onSpeak(text);
    else if (window.LangAudio) window.LangAudio.speak(text, { ref: text, rate: 0.94 });
    if (gloss) setOpen((value) => !value);
  };
  return (
    <span style={{ position: "relative", display: "inline-block" }}>
      <button type="button" onClick={speak} onMouseEnter={() => gloss && setOpen(true)} onMouseLeave={() => gloss && setOpen(false)}
        className={className} title={gloss ? `${text} - ${gloss}` : title} style={{
          border: "none",
          borderBottom: "1.5px dotted var(--accent-line)",
          background: "transparent",
          color: "inherit",
          cursor: "pointer",
          padding: "1px 2px",
          borderRadius: 5,
          font: "inherit",
          ...style,
        }}>
        {children || text}
      </button>
      {open && gloss && (
        <span role="tooltip" className="fade-up" style={{
          position: "absolute",
          bottom: "calc(100% + 6px)",
          left: "50%",
          transform: "translateX(-50%)",
          width: "max-content",
          maxWidth: 230,
          zIndex: 70,
          background: "var(--ink)",
          color: "var(--paper)",
          borderRadius: 8,
          padding: "5px 9px",
          fontFamily: "var(--sans)",
          fontSize: 12.5,
          fontWeight: 700,
          lineHeight: 1.25,
          textAlign: "center",
          boxShadow: "var(--shadow-pop)",
          pointerEvents: "none",
        }}>{gloss}</span>
      )}
    </span>
  );
}

function SongLineText({ line, onSpeak }) {
  const tokens = Array.isArray(line && line.tokens) && line.tokens.length
    ? line.tokens
    : String((line && line.kr) || "").split(/\s+/).filter(Boolean).map((kr) => ({ kr, fr: "" }));
  return (
    <span>
      {tokens.map((token, index) => (
        <React.Fragment key={`${line.id}:${token.kr}:${index}`}>
          {index > 0 && " "}
          <SongSpeakButton text={token.kr} gloss={token.fr} onSpeak={onSpeak} className="kr">
            {token.kr}
          </SongSpeakButton>
        </React.Fragment>
      ))}
    </span>
  );
}

function SongLineRow({ line, index, active, shadowed, onSelect, onSpeak }) {
  return (
    <div onClick={() => onSelect(line.id)} role="button" tabIndex={0}
      onKeyDown={(event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); onSelect(line.id); } }}
      style={{
        border: `1px solid ${active ? "var(--accent-line)" : "var(--rule)"}`,
        background: active ? "var(--accent-tint)" : "var(--card)",
        borderRadius: "var(--radius-sm)",
        padding: 14,
        display: "grid",
        gridTemplateColumns: "34px minmax(0, 1fr)",
        gap: 12,
        alignItems: "start",
        cursor: "pointer",
      }}>
      <div style={{ display: "grid", gap: 7, justifyItems: "center" }}>
        <span className="faint" style={{ fontFamily: "var(--mono)", fontSize: 12 }}>{String(index + 1).padStart(2, "0")}</span>
        <button type="button" onClick={(event) => { event.stopPropagation(); onSpeak(line.kr); }} title="Ecouter la ligne parlee" style={{
          width: 30,
          height: 30,
          borderRadius: 999,
          border: "none",
          background: active ? "var(--accent)" : "var(--accent-tint)",
          color: active ? "#fff" : "var(--accent-deep)",
          cursor: "pointer",
          display: "grid",
          placeItems: "center",
        }}>
          <Icon name="speaker" size={16} />
        </button>
      </div>
      <div style={{ minWidth: 0, display: "grid", gap: 5 }}>
        <div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
          <span className="chip" style={{ fontSize: 11 }}>{line.section}</span>
          {shadowed > 0 && <span className="chip" style={{ fontSize: 11, background: "var(--good-tint)", color: "var(--good)", borderColor: "transparent" }}>{shadowed}x</span>}
        </div>
        <div className="kr" style={{ fontSize: "clamp(20px, 4vw, 30px)", lineHeight: 1.45, color: "var(--ink)" }}>
          <SongLineText line={line} onSpeak={onSpeak} />
        </div>
        <div className="muted" style={{ fontSize: 14.5 }}>{line.fr}</div>
      </div>
    </div>
  );
}

function SongShadowingPanel({ lines, activeLineId, setActiveLineId, shadowed, markShadowed, onSpeak }) {
  const index = Math.max(0, lines.findIndex((line) => line.id === activeLineId));
  const line = lines[index] || lines[0];
  if (!line) return <EmptyState icon="speaker" title="Aucune ligne" body="Ce morceau n'a pas encore de paroles." />;

  const goLine = (delta) => {
    const next = lines[Math.max(0, Math.min(lines.length - 1, index + delta))];
    if (next) setActiveLineId(next.id);
  };

  return (
    <section className="card" style={{ padding: 18, display: "grid", gap: 14 }}>
      <div>
        <Eyebrow>Shadowing</Eyebrow>
        <h2 style={{ fontSize: 24, marginTop: 4 }}>Ligne active</h2>
      </div>
      <div style={{ border: "1px solid var(--rule)", borderRadius: "var(--radius-sm)", background: "var(--card-2)", padding: 14, display: "grid", gap: 8 }}>
        <span className="chip" style={{ justifySelf: "start", fontSize: 11 }}>{line.section} · {index + 1}/{lines.length}</span>
        <div className="kr" style={{ fontSize: 27, lineHeight: 1.45, color: "var(--accent-deep)" }}>{line.kr}</div>
        <div className="muted" style={{ fontSize: 14 }}>{line.fr}</div>
      </div>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(3, minmax(0, 1fr))", gap: 8 }}>
        <Btn kind="ghost" icon="prev" disabled={index <= 0} onClick={() => goLine(-1)}>Avant</Btn>
        <Btn kind="secondary" icon="speaker" onClick={() => onSpeak(line.kr)}>Parler</Btn>
        <Btn kind="ghost" iconR="next" disabled={index >= lines.length - 1} onClick={() => goLine(1)}>Suite</Btn>
      </div>
      <Btn kind="primary" icon="check" onClick={() => markShadowed(line.id)}>J'ai répété</Btn>
      <div className="faint" style={{ fontSize: 12 }}>Répétitions : {shadowed[line.id] || 0}</div>
    </section>
  );
}

function SongSubstitutionExercise({ song, cardStates, onSpeak }) {
  const sets = Array.isArray(song && song.substitutionSets) ? song.substitutionSets : [];
  const [setId, setSetId] = useState(() => (sets[0] && sets[0].id) || "");
  const [picked, setPicked] = useState("");
  const currentSet = sets.find((set) => set.id === setId) || sets[0];
  const items = songAvailableSubstitutions(currentSet, cardStates);
  const selected = items.find((item) => item.kr === picked) || items[0];
  const phrase = selected ? songPatternText(currentSet, selected) : "";
  const masteredCount = items.filter((item) => songItemMastered(item, cardStates)).length;

  useEffect(() => {
    if (currentSet && currentSet.id !== setId) setSetId(currentSet.id);
    if (items.length && !items.some((item) => item.kr === picked)) setPicked(items[0].kr);
  }, [setId, song && song.id]);

  if (!sets.length) return <EmptyState icon="spark" title="Aucune substitution" body="Ce morceau n'a pas encore d'exercices." />;

  return (
    <section className="card" style={{ padding: "var(--pad-card)", display: "grid", gap: 16 }}>
      <div style={{ display: "flex", justifyContent: "space-between", gap: 12, alignItems: "center", flexWrap: "wrap" }}>
        <div>
          <Eyebrow>Substitution</Eyebrow>
          <h2 style={{ fontSize: 30, marginTop: 4 }}>Remplacer sans casser la phrase</h2>
        </div>
        <Segmented value={currentSet && currentSet.id} options={sets.map((set) => ({ value: set.id, label: set.title }))} onChange={(value) => { setSetId(value); setPicked(""); }} size="sm" />
      </div>

      <div style={{ border: "1px solid var(--rule)", background: "var(--card-2)", borderRadius: "var(--radius-sm)", padding: 16, display: "grid", gap: 10 }}>
        <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
          <span className="chip" style={{ fontSize: 11 }}>{items.length} choix</span>
          <span className="chip" style={{ fontSize: 11 }}>{masteredCount} solides SRS</span>
        </div>
        <div className="kr" style={{ fontSize: "clamp(28px, 6vw, 44px)", lineHeight: 1.25, color: "var(--accent-deep)" }}>
          {phrase || "___"}
        </div>
        {selected && <div className="muted" style={{ fontSize: 15 }}>{songExerciseFr(currentSet, selected)}</div>}
        <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
          <Btn kind="primary" icon="speaker" disabled={!phrase} onClick={() => onSpeak(phrase)}>Parler</Btn>
          <Btn kind="secondary" icon="refresh" disabled={!items.length} onClick={() => {
            if (!items.length) return;
            const currentIndex = Math.max(0, items.findIndex((item) => item.kr === (selected && selected.kr)));
            const next = items[(currentIndex + 1) % items.length];
            setPicked(next.kr);
          }}>Changer</Btn>
        </div>
      </div>

      <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(145px, 1fr))", gap: 8 }}>
        {items.map((item) => {
          const on = selected && selected.kr === item.kr;
          const mastered = songItemMastered(item, cardStates);
          return (
            <button key={item.kr} type="button" onClick={() => setPicked(item.kr)} style={{
              border: `1px solid ${on ? "var(--accent-line)" : "var(--rule)"}`,
              background: on ? "var(--accent-tint)" : "var(--card)",
              color: on ? "var(--accent-deep)" : "var(--ink)",
              borderRadius: "var(--radius-sm)",
              padding: 12,
              cursor: "pointer",
              textAlign: "left",
              display: "grid",
              gap: 4,
            }}>
              <span className="kr" style={{ fontSize: 20, fontWeight: 800 }}>{item.kr}</span>
              <span className="muted" style={{ fontSize: 12.5 }}>{item.fr}</span>
              <span className="chip" style={{ fontSize: 10, justifySelf: "start", padding: "2px 7px" }}>{mastered ? "SRS" : "morceau"}</span>
            </button>
          );
        })}
      </div>
    </section>
  );
}

function SongLibrary({ songs, selectedId, onSelect }) {
  return (
    <section className="card" style={{ padding: 18, display: "grid", gap: 12 }}>
      <div>
        <Eyebrow>Bibliothèque</Eyebrow>
        <h2 style={{ fontSize: 24, marginTop: 4 }}>Morceaux</h2>
      </div>
      <div style={{ display: "grid", gap: 8 }}>
        {songs.map((song, index) => {
          const on = song.id === selectedId;
          return (
            <button key={song.id} type="button" onClick={() => onSelect(song.id)} style={{
              border: `1px solid ${on ? "var(--accent-line)" : "var(--rule)"}`,
              background: on ? "var(--accent-tint)" : "var(--card-2)",
              borderRadius: "var(--radius-sm)",
              padding: 12,
              textAlign: "left",
              cursor: "pointer",
              display: "grid",
              gap: 5,
            }}>
              <span className="faint" style={{ fontSize: 11, fontFamily: "var(--mono)" }}>Morceau {index + 1}</span>
              <span className="kr" style={{ fontWeight: 900, color: on ? "var(--accent-deep)" : "var(--ink)", fontSize: 18 }}>{song.title}</span>
              <span className="muted" style={{ fontSize: 12.5 }}>{song.titleFr}</span>
            </button>
          );
        })}
      </div>
    </section>
  );
}

function SongsWorkshop() {
  const [load, setLoad] = useState(() => ({ status: window.LangData && window.LangData.loadSongs ? "loading" : "error", payload: null, error: null }));
  const [songId, setSongId] = useState("");
  const [activeLineId, setActiveLineId] = useState("");
  const [shadowed, setShadowed] = useState({});
  const audioRef = useRef(null);

  useEffect(() => {
    let alive = true;
    if (!window.LangData || !window.LangData.loadSongs) {
      setLoad((state) => ({ ...state, status: "error", error: new Error("loadSongs missing") }));
      return () => { alive = false; };
    }
    window.LangData.loadSongs().then((payload) => {
      if (alive) setLoad({ status: "ready", payload, error: null });
    }).catch((error) => {
      if (alive) setLoad((state) => ({ ...state, status: "error", error }));
    });
    return () => { alive = false; };
  }, []);

  const songs = load.payload && Array.isArray(load.payload.songs) ? load.payload.songs : [];
  const selectedSong = songs.find((song) => song.id === songId) || songs[0];
  const lines = songLines(selectedSong);
  const activeLine = lines.find((line) => line.id === activeLineId) || lines[0];
  const cardStates = window.LangStore && window.LangStore.cards ? window.LangStore.cards.all() : {};

  useEffect(() => {
    if (!songId && songs[0]) setSongId(songs[0].id);
  }, [load.status, songs.length, songId]);

  useEffect(() => {
    if (selectedSong && lines[0] && !lines.some((line) => line.id === activeLineId)) setActiveLineId(lines[0].id);
  }, [selectedSong && selectedSong.id, activeLineId]);

  function pauseSongAudio() {
    if (!audioRef.current) return;
    try { audioRef.current.pause(); } catch (e) {}
  }

  function speakSpoken(text, rate) {
    pauseSongAudio();
    if (window.LangAudio) window.LangAudio.speak(text, { ref: text, rate: rate || 0.92 });
  }

  function markShadowed(lineId) {
    setShadowed((state) => ({ ...state, [lineId]: (state[lineId] || 0) + 1 }));
  }

  if (load.status === "loading") return <div style={{ display: "grid", gap: 16 }}><LoadingCard /><LoadingCard lines={4} /></div>;
  if (load.status === "error") return <ErrorState title="Chansons indisponibles" body={load.error && load.error.message} onRetry={() => location.reload()} />;
  if (!selectedSong) return <EmptyState icon="spark" title="Aucun morceau" body="La bibliothèque de chansons est vide." />;

  return (
    <div className="fade-up" style={{ display: "grid", gap: 24 }}>
      <section style={{ display: "flex", justifyContent: "space-between", gap: 20, alignItems: "flex-end", flexWrap: "wrap" }}>
        <div>
          <Eyebrow>Atelier musical</Eyebrow>
          <h1 style={{ fontSize: "clamp(36px, 5vw, 58px)", marginTop: 4 }}>Chansons originales</h1>
          <p className="muted" style={{ maxWidth: 760, margin: "10px 0 0", fontSize: 16 }}>
            Morceaux Suno, paroles cliquables, voix parlée et substitutions guidées par le vocabulaire solide.
          </p>
        </div>
      </section>

      <StatStrip items={[
        { value: songs.length, label: "Morceaux" },
        { value: songLineCount(selectedSong), label: "Lignes", accent: true },
        { value: (selectedSong.substitutionSets || []).length, label: "Patrons" },
        { value: songSubstitutionCount(selectedSong), label: "Substitutions" },
      ]} />

      <div className="atelier-grid" style={{ display: "grid", gridTemplateColumns: "minmax(0, 1fr) 330px", gap: 22, alignItems: "start" }}>
        <div style={{ display: "grid", gap: 18 }}>
          <section className="card" style={{ padding: "var(--pad-card)", display: "grid", gap: 16 }}>
            <div style={{ display: "flex", justifyContent: "space-between", gap: 14, alignItems: "start", flexWrap: "wrap" }}>
              <div style={{ minWidth: 0 }}>
                <Eyebrow>Morceau 1</Eyebrow>
                <h2 className="kr" style={{ fontSize: "clamp(34px, 6vw, 54px)", marginTop: 4 }}>{selectedSong.title}</h2>
                <div className="muted" style={{ fontSize: 15, marginTop: 6 }}>{selectedSong.titleFr}</div>
              </div>
              <div style={{ display: "flex", gap: 7, flexWrap: "wrap", justifyContent: "flex-end" }}>
                {(selectedSong.focus || []).slice(0, 4).map((tag) => <span key={tag} className="chip" style={{ fontSize: 11 }}>{tag}</span>)}
              </div>
            </div>
            <audio ref={audioRef} controls src={selectedSong.audioUrl} preload="metadata" onPlay={() => { if (window.LangAudio) window.LangAudio.stop(); }}
              style={{ width: "100%", accentColor: "var(--accent)" }} />
            {activeLine && (
              <div style={{ border: "1px solid var(--rule)", borderRadius: "var(--radius-sm)", background: "var(--card-2)", padding: 14, display: "grid", gap: 8 }}>
                <div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
                  <span className="chip" style={{ fontSize: 11 }}>Ligne active</span>
                  <Btn kind="ghost" size="sm" icon="speaker" onClick={() => speakSpoken(activeLine.kr)}>Voix parlée</Btn>
                </div>
                <div className="kr" style={{ fontSize: 28, lineHeight: 1.35, color: "var(--accent-deep)" }}>{activeLine.kr}</div>
                <div className="muted" style={{ fontSize: 14.5 }}>{activeLine.fr}</div>
              </div>
            )}
          </section>

          <section style={{ display: "grid", gap: 12 }}>
            {songSections(selectedSong).map((section) => (
              <div key={section.id} style={{ display: "grid", gap: 8 }}>
                <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                  <Eyebrow>{section.label}</Eyebrow>
                  <span style={{ flex: 1, borderTop: "1px solid var(--rule)" }} />
                </div>
                {(section.lines || []).map((line) => {
                  const lineIndex = lines.findIndex((entry) => entry.id === line.id);
                  return (
                    <SongLineRow key={line.id} line={{ ...line, section: section.label }} index={lineIndex} active={activeLine && activeLine.id === line.id}
                      shadowed={shadowed[line.id] || 0} onSelect={setActiveLineId} onSpeak={speakSpoken} />
                  );
                })}
              </div>
            ))}
          </section>

          <SongSubstitutionExercise song={selectedSong} cardStates={cardStates} onSpeak={speakSpoken} />
        </div>

        <aside className="atelier-side" style={{ position: "sticky", top: 22, display: "grid", gap: 16 }}>
          <SongLibrary songs={songs} selectedId={selectedSong.id} onSelect={(id) => { setSongId(id); setActiveLineId(""); }} />
          <SongShadowingPanel lines={lines} activeLineId={activeLine && activeLine.id} setActiveLineId={setActiveLineId}
            shadowed={shadowed} markShadowed={markShadowed} onSpeak={speakSpoken} />
        </aside>
      </div>
    </div>
  );
}


Object.assign(window, { SongsWorkshop });
