/* Webinaire — collaboration temps réel (PeerJS, maillage pair-à-pair)
   Étape 1 : caméra + voix de tous les participants, dans l'app.
   (Ne pas redéclarer useState ici : ce script partage le scope global avec webinaire-app.jsx.) */

function makeRoomId(code) {
  return 'yehior-room-' + String(code || '').toLowerCase().replace(/[^a-z0-9]/g, '');
}
function randomCode() {
  return Math.random().toString(36).slice(2, 7); // 5 caractères
}

// Hook : gère la salle et le maillage des flux caméra/voix
function useMesh() {
  const [role, setRole]                 = React.useState(null);   // null | 'host' | 'guest'
  const [code, setCode]                 = React.useState('');
  const [status, setStatus]             = React.useState('idle');  // idle|connecting|connected|error
  const [error, setError]               = React.useState(null);
  const [participants, setParticipants] = React.useState([]);      // [{id,name,stream}]
  const [localStream, setLocalStream]   = React.useState(null);
  const [micOn, setMicOn]               = React.useState(true);
  const [camOn, setCamOn]               = React.useState(true);

  const peerRef   = React.useRef(null);
  const localRef  = React.useRef(null);
  const dataConns = React.useRef({});   // id -> DataConnection
  const mediaConns = React.useRef({});  // id -> MediaConnection
  const roster    = React.useRef({});   // id -> name
  const myName    = React.useRef('Moi');
  const selfId    = React.useRef('');
  const roleRef           = React.useRef(null);   // 'host' | 'guest' (toujours à jour)
  const syncHandlerRef    = React.useRef(null);   // (msg) => void : applique un état reçu
  const stateProviderRef  = React.useRef(null);   // () => état léger (pour les nouveaux arrivants)
  const peerJoinHandlerRef = React.useRef(null);  // (peerId) => void : flux des slides au nouvel arrivant

  const refreshParticipants = () => {
    const list = Object.keys(mediaConns.current)
      .filter(id => mediaConns.current[id] && mediaConns.current[id]._stream)
      .map(id => ({ id, name: roster.current[id] || 'Invité', stream: mediaConns.current[id]._stream }));
    setParticipants(list);
  };

  const getLocal = async () => {
    if (localRef.current) return localRef.current;
    if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
      throw new Error("Caméra non disponible (ouvre bien le site en https, et via Chrome/Safari à jour).");
    }
    // Essais successifs — améliore la compatibilité mobile/tablette
    const tries = [
      { video: { facingMode: 'user', width: { ideal: 1280 }, height: { ideal: 720 } }, audio: true },
      { video: true, audio: true },
      { video: true, audio: false },
    ];
    let s = null, lastErr = null;
    for (const c of tries) {
      try { s = await navigator.mediaDevices.getUserMedia(c); break; } catch (e) { lastErr = e; }
    }
    if (!s) throw lastErr || new Error('Caméra/micro indisponibles');
    localRef.current = s; setLocalStream(s); return s;
  };

  const answerCall = (mc) => {
    mediaConns.current[mc.peer] = mc;
    if (mc.metadata && mc.metadata.name) roster.current[mc.peer] = roster.current[mc.peer] || mc.metadata.name;
    mc.answer(localRef.current);
    mc.on('stream', (s) => { mc._stream = s; refreshParticipants(); });
    mc.on('close', () => { delete mediaConns.current[mc.peer]; refreshParticipants(); });
    mc.on('error', () => {});
  };

  const callPeer = (id) => {
    if (id === selfId.current || mediaConns.current[id] || !localRef.current) return;
    const mc = peerRef.current.call(id, localRef.current, { metadata: { name: myName.current } });
    mediaConns.current[id] = mc;
    mc.on('stream', (s) => { mc._stream = s; refreshParticipants(); });
    mc.on('close', () => { delete mediaConns.current[id]; refreshParticipants(); });
    mc.on('error', () => {});
  };

  // Règle anti-doublon : seul l'id le plus petit lance l'appel média
  const maybeCall = (id) => { if (id !== selfId.current && selfId.current < id) callPeer(id); };

  const applyRoster = (r) => {
    roster.current = Object.assign({}, roster.current, r);
    Object.keys(roster.current).forEach(maybeCall);
    refreshParticipants();
  };

  const broadcastRoster = () => {
    Object.values(dataConns.current).forEach(dc => { try { dc.send({ type: 'roster', roster: roster.current }); } catch (e) {} });
  };

  // ---- Synchronisation d'état (tableau, slides, stylet, Bible) ----
  // L'app enregistre un « handler » (applique un état reçu) et un « provider »
  // (fournit l'état complet courant, envoyé aux nouveaux arrivants).
  const setSyncHandler     = (fn) => { syncHandlerRef.current = fn; };
  const setStateProvider   = (fn) => { stateProviderRef.current = fn; };
  const setPeerJoinHandler = (fn) => { peerJoinHandlerRef.current = fn; };

  // Envoie un message d'état à ses pairs. Hôte -> tous les invités ; invité -> hôte.
  const sendSync = (msg) => {
    Object.values(dataConns.current).forEach(dc => { try { dc.send({ type: 'sync', msg }); } catch (e) {} });
  };

  // Envoie un message d'état à un seul pair (flux ciblé : slides au nouvel arrivant).
  const sendSyncTo = (peerId, msg) => {
    const dc = dataConns.current[peerId];
    if (dc) { try { dc.send({ type: 'sync', msg }); } catch (e) {} }
  };

  // Réception d'un message d'état : on l'applique localement, et si on est l'hôte,
  // on le relaie à tous les autres invités (topologie en étoile).
  const handleIncomingSync = (fromId, msg) => {
    if (syncHandlerRef.current) { try { syncHandlerRef.current(msg); } catch (e) {} }
    if (roleRef.current === 'host') {
      Object.keys(dataConns.current).forEach(id => {
        if (id !== fromId) { try { dataConns.current[id].send({ type: 'sync', msg }); } catch (e) {} }
      });
    }
  };

  const setupCommon = (peer) => {
    peer.on('call', answerCall);
    peer.on('error', (e) => {
      if (e && e.type === 'peer-unavailable') setError('Salle introuvable — vérifie le code.');
      else if (e && e.type === 'unavailable-id') setError('Ce code de salle est déjà pris — choisis-en un autre.');
      else setError((e && (e.type || e.message)) || 'erreur réseau');
    });
  };

  const createRoom = async (roomCode, displayName) => {
    try {
      setStatus('connecting'); setError(null);
      myName.current = displayName || 'Animateur';
      await getLocal();
      const peer = new window.Peer(makeRoomId(roomCode), { debug: 1 });
      peerRef.current = peer;
      setupCommon(peer);
      peer.on('open', (pid) => {
        selfId.current = pid;
        roster.current = { [pid]: myName.current };
        roleRef.current = 'host';
        setRole('host'); setCode(roomCode); setStatus('connected');
      });
      peer.on('connection', (dc) => {
        dataConns.current[dc.peer] = dc;
        dc.on('data', (msg) => {
          if (!msg) return;
          if (msg.type === 'hello') {
            roster.current[dc.peer] = msg.name || 'Invité';
            broadcastRoster();
            applyRoster(roster.current);
            // Met le nouvel arrivant au diapason : état léger d'abord…
            if (stateProviderRef.current) {
              try { dataConns.current[dc.peer].send({ type: 'sync', msg: { kind: 'snapshot', state: stateProviderRef.current() } }); } catch (e) {}
            }
            // …puis flux des slides (un par un) pour ce pair, sans gêner les autres.
            if (peerJoinHandlerRef.current) { try { peerJoinHandlerRef.current(dc.peer); } catch (e) {} }
          } else if (msg.type === 'sync') {
            handleIncomingSync(dc.peer, msg.msg);
          }
        });
        dc.on('close', () => { delete dataConns.current[dc.peer]; delete roster.current[dc.peer]; broadcastRoster(); refreshParticipants(); });
      });
    } catch (e) { setError('Caméra/micro indisponibles (' + (e.name || e.message || '') + '). Autorise la caméra dans le navigateur et garde l\'onglet au premier plan.'); setStatus('error'); }
  };

  const joinRoom = async (roomCode, displayName) => {
    try {
      setStatus('connecting'); setError(null);
      myName.current = displayName || 'Invité';
      await getLocal();
      const peer = new window.Peer({ debug: 1 });
      peerRef.current = peer;
      setupCommon(peer);
      peer.on('open', (pid) => {
        selfId.current = pid;
        const hostId = makeRoomId(roomCode);
        const dc = peer.connect(hostId, { reliable: true });
        dataConns.current[hostId] = dc;
        dc.on('open', () => { dc.send({ type: 'hello', name: myName.current }); roleRef.current = 'guest'; setRole('guest'); setCode(roomCode); setStatus('connected'); });
        dc.on('data', (msg) => {
          if (!msg) return;
          if (msg.type === 'roster') applyRoster(msg.roster);
          else if (msg.type === 'sync') handleIncomingSync(null, msg.msg);
        });
        dc.on('close', () => { setStatus('idle'); });
      });
    } catch (e) { setError('Caméra/micro indisponibles (' + (e.name || e.message || '') + '). Autorise la caméra dans le navigateur et garde l\'onglet au premier plan.'); setStatus('error'); }
  };

  const leaveRoom = () => {
    try { Object.values(mediaConns.current).forEach(m => m && m.close()); } catch (e) {}
    try { Object.values(dataConns.current).forEach(d => d && d.close()); } catch (e) {}
    try { peerRef.current && peerRef.current.destroy(); } catch (e) {}
    try { localRef.current && localRef.current.getTracks().forEach(t => t.stop()); } catch (e) {}
    mediaConns.current = {}; dataConns.current = {}; roster.current = {}; localRef.current = null;
    roleRef.current = null;
    setParticipants([]); setLocalStream(null); setRole(null); setStatus('idle'); setCode(''); setError(null);
    setMicOn(true); setCamOn(true);
  };

  // Activer / couper son propre micro ou sa propre caméra
  const toggleMic = () => { const s = localRef.current; if (!s) return; const on = !micOn; s.getAudioTracks().forEach(t => t.enabled = on); setMicOn(on); };
  const toggleCam = () => { const s = localRef.current; if (!s) return; const on = !camOn; s.getVideoTracks().forEach(t => t.enabled = on); setCamOn(on); };

  return { role, code, status, error, participants, localStream, micOn, camOn, toggleMic, toggleCam,
    createRoom, joinRoom, leaveRoom,
    sendSync, sendSyncTo, setSyncHandler, setStateProvider, setPeerJoinHandler };
}

// Affiche un flux MediaStream (caméra d'un participant)
function StreamTile({ stream, name, muted, mirror }) {
  const ref = React.useRef(null);
  React.useEffect(() => { if (ref.current && stream) ref.current.srcObject = stream; }, [stream]);
  return (
    <div style={{ position: 'absolute', inset: 0, background: '#001e1e' }}>
      <video ref={ref} autoPlay playsInline muted={!!muted}
        style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover',
          transform: mirror ? 'scaleX(-1)' : 'none' }} />
    </div>
  );
}

// Section « Réunion » du panneau : créer / rejoindre une salle
function RoomSection({ room }) {
  const [joinCode, setJoinCode] = React.useState('');
  const inRoom = !!room.role;
  const shareUrl = (typeof location !== 'undefined') ? (location.origin + '/') : '';

  const copy = (txt) => { if (navigator.clipboard) navigator.clipboard.writeText(txt); };

  return (
    <div className="sp-section">
      <div className="sp-sec-title">Réunion (profs à distance)</div>

      {!inRoom && (
        <>
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 10 }}>
            <button className="sp-btn" disabled={room.status === 'connecting'}
              onClick={() => room.createRoom(randomCode())}>+ Créer une salle</button>
          </div>
          <div className="sp-row col">
            <span className="sp-lbl">…ou rejoindre avec un code</span>
            <div style={{ display: 'flex', gap: 8 }}>
              <input className="sp-input" value={joinCode} onChange={e => setJoinCode(e.target.value)} placeholder="code de salle" />
              <button className="sp-btn ghost" disabled={!joinCode || room.status === 'connecting'}
                onClick={() => room.joinRoom(joinCode.trim())}>Rejoindre</button>
            </div>
          </div>
          <div className="sp-help">L'<strong>animateur</strong> crée la salle et partage le <strong>lien + le code</strong>. Les profs ouvrent le lien, mettent le code, et tout le monde se voit/s'entend dans l'app.</div>
        </>
      )}

      {inRoom && (
        <>
          <div className="sp-help" style={{ background: 'rgba(251,176,59,0.12)' }}>
            {room.status === 'connected'
              ? <span><strong style={{ color: '#003333' }}>● En salle</strong> ({room.role === 'host' ? 'animateur' : 'participant'}) — {room.participants.length + 1} en ligne</span>
              : <span>Connexion…</span>}
          </div>
          {room.role === 'host' && (
            <div className="sp-row col">
              <span className="sp-lbl">Code à envoyer aux profs</span>
              <div style={{ display: 'flex', gap: 8 }}>
                <input className="sp-input" readOnly value={room.code} onFocus={e => e.target.select()} style={{ fontWeight: 700, letterSpacing: '0.15em' }} />
                <button className="sp-btn" onClick={() => copy(room.code)}>Copier</button>
              </div>
              <div style={{ display: 'flex', gap: 8, marginTop: 6 }}>
                <input className="sp-input" readOnly value={shareUrl} onFocus={e => e.target.select()} style={{ fontSize: 11 }} />
                <button className="sp-btn ghost" onClick={() => copy(shareUrl + '  (code : ' + room.code + ')')}>Copier le lien</button>
              </div>
            </div>
          )}
          <button className="sp-btn ghost" style={{ marginTop: 8 }} onClick={room.leaveRoom}>Quitter la salle</button>
        </>
      )}

      {room.error && <div className="sp-help" style={{ background: 'rgba(192,57,43,0.1)', color: '#c0392b' }}>{room.error}</div>}
    </div>
  );
}

Object.assign(window, { useMesh, StreamTile, RoomSection, makeRoomId, randomCode });
