// did-avatar.jsx — D-ID Streaming API · Real-time talking avatar via WebRTC
//
// Setup (free trial at https://www.d-id.com):
//   1. Sign up → Dashboard → API Keys → copy your key
//   2. Paste it in the Tweaks panel → "D-ID API Key"
//   3. The avatar will connect and start moving in real-time
//
// The source image must be a PUBLIC URL (D-ID servers fetch it).
// Default: Ryan poster from D-ID's own sample library.

// All D-ID calls go through our backend proxy to avoid browser CORS restrictions.
// The backend forwards them to api.d-id.com with proper auth.
const DID_API = 'http://localhost:3000/api/did';

// Ryan's public image (accessible by D-ID servers)
const DID_DEFAULT_IMAGE = 'https://cdn.prod.website-files.com/65e89895c5a4b8d764c0d70e/689f3589df6d7b00d0e16931_Ryan-6.jpg';

// Microsoft Azure Neural voices — natural male voices per language
const DID_VOICES = {
  es: 'es-ES-AlvaroNeural',
  en: 'en-US-GuyNeural',
  fr: 'fr-FR-HenriNeural',
  de: 'de-DE-ConradNeural',
  it: 'it-IT-DiegoNeural',
  pt: 'pt-PT-DuarteNeural',
  zh: 'zh-CN-YunxiNeural',
  ru: 'ru-RU-DmitryNeural',
};

// ── useDIDStream ─────────────────────────────────────────────────────────────
// Manages a D-ID WebRTC stream session lifecycle.
function useDIDStream({ apiKey, sourceUrl }) {
  const [status, setStatus]     = React.useState('idle'); // idle|connecting|connected|error
  const [lastError, setLastError] = React.useState('');
  const videoRef    = React.useRef(null);
  const pcRef       = React.useRef(null);
  const streamIdRef = React.useRef(null);
  const sessionRef  = React.useRef(null);

  // D-ID key format from dashboard: base64(email):raw_secret
  // Auth header needs: Basic base64(email:raw_secret)
  const authHeader = React.useMemo(() => {
    if (!apiKey) return null;
    try {
      const colonIdx = apiKey.indexOf(':');
      if (colonIdx > 0) {
        const emailB64 = apiKey.slice(0, colonIdx);
        const secret   = apiKey.slice(colonIdx + 1);
        const email    = atob(emailB64);           // decode "ZWRl..." → "user@gmail.com"
        return 'Basic ' + btoa(email + ':' + secret); // re-encode standard Basic
      }
    } catch (_) {}
    return `Basic ${apiKey}`;  // fallback: use as-is
  }, [apiKey]);

  // Pass the raw key to our backend via x-did-key; backend builds correct auth
  const headers = React.useCallback(
    () => ({ 'x-did-key': apiKey, 'Content-Type': 'application/json' }),
    [apiKey]
  );

  // ── connect ───────────────────────────────────────────────────────────────
  const connect = React.useCallback(async () => {
    if (!apiKey || !sourceUrl) return;
    setStatus('connecting');
    console.log('[D-ID] Connecting…', { sourceUrl, authHeader: authHeader?.slice(0, 20) + '…' });
    try {
      // 1. Create stream session → get WebRTC offer + ICE servers
      const res = await fetch(`${DID_API}/streams`, {
        method: 'POST',
        headers: headers(),
        body: JSON.stringify({
          source_url: sourceUrl,
          driver_url: 'bank://lively',
          config: { stitch: true, result_format: 'mp4' },
        }),
      });
      const responseText = await res.text();
      let data;
      try { data = JSON.parse(responseText); } catch (_) { data = {}; }
      console.log('[D-ID] /streams response:', res.status, data);
      if (!res.ok) {
        throw new Error(data.description || data.message || data.error || `HTTP ${res.status}`);
      }
      streamIdRef.current = data.id;
      sessionRef.current  = data.session_id;

      // 2. Create WebRTC peer connection
      const pc = new RTCPeerConnection({ iceServers: data.ice_servers });
      pcRef.current = pc;

      // Route incoming video/audio to the <video> element
      pc.ontrack = (evt) => {
        if (videoRef.current && evt.streams[0]) {
          videoRef.current.srcObject = evt.streams[0];
        }
      };

      // Forward ICE candidates to D-ID
      pc.onicecandidate = async ({ candidate }) => {
        if (!candidate || !streamIdRef.current) return;
        await fetch(`${DID_API}/streams/${streamIdRef.current}/ice`, {
          method: 'POST',
          headers: headers(),
          body: JSON.stringify({
            candidate: candidate.candidate,
            sdpMid: candidate.sdpMid,
            sdpMLineIndex: candidate.sdpMLineIndex,
            session_id: sessionRef.current,
          }),
        }).catch(() => {});
      };

      // 3. Answer the WebRTC offer
      await pc.setRemoteDescription(new RTCSessionDescription(data.offer));
      const answer = await pc.createAnswer();
      await pc.setLocalDescription(answer);

      // 4. Submit our answer to D-ID
      const sdpRes = await fetch(`${DID_API}/streams/${data.id}/sdp`, {
        method: 'POST',
        headers: headers(),
        body: JSON.stringify({
          answer: { type: answer.type, sdp: answer.sdp },
          session_id: data.session_id,
        }),
      });
      if (!sdpRes.ok) throw new Error(`SDP submit failed: ${sdpRes.status}`);

      pc.onconnectionstatechange = () => {
        if (pc.connectionState === 'connected') setStatus('connected');
        if (pc.connectionState === 'failed')    setStatus('error');
        if (pc.connectionState === 'closed')    setStatus('idle');
      };

      // Optimistic transition if browser doesn't fire 'connected'
      setTimeout(() => setStatus(s => s === 'connecting' ? 'connected' : s), 4000);

    } catch (e) {
      console.error('[D-ID] connect failed:', e.message);
      setLastError(e.message);
      setStatus('error');
    }
  }, [apiKey, sourceUrl, authHeader, headers]);

  // ── speak ─────────────────────────────────────────────────────────────────
  // Send a text clip to D-ID → avatar speaks with lip sync
  const speak = React.useCallback(async (text, lang) => {
    if (!streamIdRef.current || status !== 'connected') return false;
    try {
      const res = await fetch(`${DID_API}/streams/${streamIdRef.current}/clip`, {
        method: 'POST',
        headers: headers(),
        body: JSON.stringify({
          script: {
            type: 'text',
            input: text,
            provider: {
              type: 'microsoft',
              voice_id: DID_VOICES[lang] || DID_VOICES.en,
            },
          },
          config: { fluent: true, pad_audio: 0.0, driver_expressions: {
            expressions: [{ start_frame: 0, expression: 'neutral', intensity: 0.5 }],
          }},
          session_id: sessionRef.current,
        }),
      });
      return res.ok;
    } catch (e) {
      console.error('[D-ID] speak failed:', e);
      return false;
    }
  }, [status, headers]);

  // ── disconnect ────────────────────────────────────────────────────────────
  const disconnect = React.useCallback(async () => {
    if (streamIdRef.current) {
      await fetch(
        `${DID_API}/streams/${streamIdRef.current}?session_id=${sessionRef.current}`,
        { method: 'DELETE', headers: headers() }
      ).catch(() => {});
      streamIdRef.current = null;
      sessionRef.current  = null;
    }
    if (pcRef.current) { pcRef.current.close(); pcRef.current = null; }
    setStatus('idle');
  }, [headers]);

  // Auto-connect when API key is set; clean up on unmount
  React.useEffect(() => {
    if (apiKey) connect();
    return () => { disconnect(); };
  }, [apiKey, connect, disconnect]);

  return { videoRef, status, speak, connect, lastError };
}

// ── DIDAvatar component ───────────────────────────────────────────────────────
function DIDAvatar({ apiKey, sourceUrl, mode, size, name, role }) {
  const { videoRef, status, speak, lastError } = useDIDStream({ apiKey, sourceUrl });
  const isSm = size === 'sm';
  const wrapCls = [
    'portrait',
    isSm ? 'portrait-sm' : 'portrait-hero',
    mode === 'speaking'  ? 'is-speaking'  : '',
    mode === 'listening' ? 'is-listening' : '',
  ].filter(Boolean).join(' ');

  // Expose speak globally so app.jsx speakLine can use it
  React.useEffect(() => {
    window.__didSpeak  = speak;
    window.__didStatus = status;
    return () => { window.__didSpeak = null; window.__didStatus = 'idle'; };
  }, [speak, status]);

  const badgeLabel =
    status === 'connecting' ? 'Conectando…' :
    status === 'connected'  ? 'LIVE · HOLA' :
    status === 'error'      ? `⚠ ${lastError || 'Sin conexión'}` : 'HOLA';

  return (
    <div className={wrapCls} aria-label="Avatar Hola">
      <div className="portrait-stage">

        {/* D-ID WebRTC video stream */}
        <video
          ref={videoRef}
          className="portrait-photo"
          autoPlay playsInline
          style={{ objectFit: 'cover', objectPosition: '50% 18%', display: status === 'connected' ? 'block' : 'none' }}
        />

        {/* Static photo shown while connecting / on error */}
        <img
          className="portrait-photo"
          src="assets/leo-portrait.png"
          alt={name}
          draggable="false"
          style={{ display: status === 'connected' ? 'none' : 'block', opacity: status === 'connecting' ? 0.6 : 1 }}
        />

        {/* Connecting spinner overlay */}
        {status === 'connecting' && (
          <div className="did-connecting" aria-label="Conectando avatar…">
            <div className="did-spinner" />
            <span>Iniciando avatar…</span>
          </div>
        )}

        <div className="portrait-vignette" />
        <div className="portrait-rings"><i/><i/><i/></div>

        <div className="portrait-badge">
          <span className={`live-dot ${status !== 'connected' ? 'live-dot--pending' : ''}`} />
          <span>{badgeLabel}</span>
        </div>

        {!isSm && (
          <div className="portrait-identity">
            <div className="ident-name">{name}</div>
            <div className="ident-role">{role}</div>
          </div>
        )}

        <div className="portrait-wave">
          {Array.from({ length: isSm ? 14 : 28 }).map((_, i) => (
            <i key={i} style={{ animationDelay: `${(i * 0.06) % 1.2}s` }} />
          ))}
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { DIDAvatar, DID_VOICES, DID_DEFAULT_IMAGE });
