/**
 * ═══════════════════════════════════════════════════════════════════════════
 *  ECO COMPUTER — VOICE AGENT WIDGET  (Phase C, v1.5.0)
 * ═══════════════════════════════════════════════════════════════════════════
 *  Floating "🎤 Talk to us" pill — sits to the LEFT of the Ask AI button.
 *
 *  Flow:
 *    1. User taps the button.
 *    2. We POST to /voice.php → get ephemeral token (value: "ek_...").
 *    3. Browser opens WebRTC peer connection directly to OpenAI's
 *       /v1/realtime/calls endpoint using that ephemeral token.
 *    4. Mic audio streams up; AI audio streams back.
 *    5. A data channel ('oai-events') carries control events:
 *       - transcripts (user + assistant)
 *       - function/tool calls
 *    6. When the model calls a tool (lookup_order / amend_order /
 *       list_products), we execute it against the existing PHP endpoints
 *       and send the JSON result back through the data channel.
 *    7. Session auto-ends after VOICE_MAX_SESSION_SEC (from server, default 300s).
 *
 *  No real OpenAI API key is ever exposed to the browser.
 * ═══════════════════════════════════════════════════════════════════════════
 */
(function () {
  const { useState, useRef, useEffect, useCallback } = React;

  // ──────────────────────────────────────────────────────────────────────────
  // 1. THEME — matches existing chatbot pill (green, rounded, soft shadow)
  // ──────────────────────────────────────────────────────────────────────────
  const GREEN_500 = '#3a7d52';
  const GREEN_700 = '#2e6742';
  const GREEN_900 = '#1a3d26';
  const RED       = '#cc1a1a';

  // ──────────────────────────────────────────────────────────────────────────
  // 2. TOOL EXECUTORS — these mirror the chatbot's PHP endpoints.
  //    Called by the model via the data channel.
  // ──────────────────────────────────────────────────────────────────────────
  async function execTool(name, args) {
    try {
      if (name === 'list_products') {
        const r = await fetch('products_list.php');
        return await r.json();
      }
      if (name === 'lookup_order') {
        const r = await fetch('order_lookup.php', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            order_number: args.order_number || '',
            phone:        args.phone        || '',
          }),
        });
        return await r.json();
      }
      if (name === 'amend_order') {
        const r = await fetch('order_amend.php', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(args),
        });
        return await r.json();
      }
      // ── Shopping-cart tools — drive the live on-page cart (window.ecoCart) ──
      const cart = (typeof window !== 'undefined' && window.ecoCart && window.ecoCart.ready) ? window.ecoCart : null;
      if (name === 'view_cart') {
        if (!cart) return { error: 'cart_unavailable' };
        return { items: cart.list(), total: cart.total(), count: cart.count() };
      }
      if (name === 'add_to_cart') {
        if (!cart) return { error: 'cart_unavailable' };
        const r = cart.add(args.sku, args.quantity || 1);
        return { ...r, cart: cart.list(), total: cart.total(), count: cart.count() };
      }
      if (name === 'update_cart_item') {
        if (!cart) return { error: 'cart_unavailable' };
        const r = cart.setQty(args.sku, args.quantity);
        return { ...r, cart: cart.list(), total: cart.total(), count: cart.count() };
      }
      if (name === 'remove_cart_item') {
        if (!cart) return { error: 'cart_unavailable' };
        const r = cart.remove(args.sku);
        return { ...r, cart: cart.list(), total: cart.total(), count: cart.count() };
      }
      if (name === 'clear_cart') {
        if (!cart) return { error: 'cart_unavailable' };
        cart.clear();
        return { ok: true, cart: [], total: 0, count: 0 };
      }
      if (name === 'open_checkout') {
        if (!cart) return { error: 'cart_unavailable' };
        cart.openCheckout();
        return { ok: true };
      }
      return { error: 'Unknown tool: ' + name };
    } catch (e) {
      return { error: 'Tool execution failed: ' + (e?.message || String(e)) };
    }
  }

  // ──────────────────────────────────────────────────────────────────────────
  // 3. ICONS
  // ──────────────────────────────────────────────────────────────────────────
  const MicIcon = () => (
    <svg width="18" height="18" viewBox="0 0 24 24" fill="none">
      <path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" fill="currentColor"/>
      <path d="M19 11a1 1 0 1 0-2 0 5 5 0 0 1-10 0 1 1 0 1 0-2 0 7 7 0 0 0 6 6.92V21h-2a1 1 0 1 0 0 2h6a1 1 0 1 0 0-2h-2v-3.08A7 7 0 0 0 19 11Z" fill="currentColor"/>
    </svg>
  );
  const PhoneOffIcon = () => (
    <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
      <path d="M10.7 13.3a16 16 0 0 1-3-3M3 3l18 18M5 5a2 2 0 0 1 2-2h2l2 4-2 1a11 11 0 0 0 1.8 2.9M14 10.1l1-1 4 2v2a2 2 0 0 1-2 2 16 16 0 0 1-7-2"/>
    </svg>
  );
  const MuteIcon = ({ muted }) => muted ? (
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
      <path d="M3 3l18 18M9 9v3a3 3 0 0 0 5.1 2.1M15 12V5a3 3 0 0 0-6 0v1M19 11a7 7 0 0 1-.6 2.8M12 19v3"/>
    </svg>
  ) : <MicIcon />;

  // ──────────────────────────────────────────────────────────────────────────
  // 4. MAIN COMPONENT
  // ──────────────────────────────────────────────────────────────────────────
  function EcoVoiceAgent() {
    const [open, setOpen]       = useState(false);   // panel visibility
    const [status, setStatus]   = useState('idle');  // idle | connecting | listening | speaking | error | ended
    const [muted, setMuted]     = useState(false);
    const [remaining, setRemaining] = useState(null); // seconds left
    const [transcript, setTranscript] = useState([]); // [{role:'user'|'assistant', text:''}]
    const [errorMsg, setErrorMsg] = useState('');

    // Refs for things that must persist across renders without re-rendering
    const pcRef    = useRef(null);   // RTCPeerConnection
    const dcRef    = useRef(null);   // RTCDataChannel
    const micRef   = useRef(null);   // MediaStream
    const audioRef = useRef(null);   // <audio> element for AI output
    const timerRef = useRef(null);
    const whatsappRef = useRef('https://wa.me/60123286692');

    // Build a streaming transcript: append delta to last assistant entry
    const appendDelta = useCallback((role, delta) => {
      setTranscript(prev => {
        const next = [...prev];
        const last = next[next.length - 1];
        if (last && last.role === role && last.streaming) {
          last.text += delta;
          return next;
        }
        next.push({ role, text: delta, streaming: true });
        return next;
      });
    }, []);

    const finalizeLast = useCallback((role, fullText) => {
      setTranscript(prev => {
        const next = [...prev];
        const last = next[next.length - 1];
        if (last && last.role === role && last.streaming) {
          last.streaming = false;
          if (fullText) last.text = fullText;
        } else if (fullText) {
          next.push({ role, text: fullText, streaming: false });
        }
        return next;
      });
    }, []);

    // ─── Handle data-channel messages from OpenAI ──────────────────────────
    const handleEvent = useCallback(async (evt) => {
      // Useful for debugging — remove for production
      // console.log('[voice]', evt.type, evt);

      switch (evt.type) {
        case 'session.created':
          setStatus('listening');
          break;

        case 'input_audio_buffer.speech_started':
          setStatus('listening');
          break;

        case 'response.created':
          setStatus('speaking');
          break;

        case 'response.done':
          setStatus('listening');
          break;

        // User transcription
        case 'conversation.item.input_audio_transcription.completed':
          if (evt.transcript) finalizeLast('user', evt.transcript);
          break;

        // Assistant transcript deltas
        case 'response.output_audio_transcript.delta':
        case 'response.audio_transcript.delta': // beta fallback
          if (evt.delta) appendDelta('assistant', evt.delta);
          break;

        case 'response.output_audio_transcript.done':
        case 'response.audio_transcript.done':
          finalizeLast('assistant');
          break;

        // Tool call — model finished assembling arguments, now we execute
        case 'response.function_call_arguments.done': {
          let args = {};
          try { args = JSON.parse(evt.arguments || '{}'); } catch (_) {}
          const result = await execTool(evt.name, args);
          // Send result back through data channel
          if (dcRef.current && dcRef.current.readyState === 'open') {
            dcRef.current.send(JSON.stringify({
              type: 'conversation.item.create',
              item: {
                type: 'function_call_output',
                call_id: evt.call_id,
                output: JSON.stringify(result),
              },
            }));
            // Ask the model to respond now that it has the tool output
            dcRef.current.send(JSON.stringify({ type: 'response.create' }));
          }
          break;
        }

        case 'error':
          console.error('[voice] OpenAI error', evt);
          setErrorMsg(evt.error?.message || 'OpenAI returned an error.');
          break;
      }
    }, [appendDelta, finalizeLast]);

    // ─── Start a voice session ────────────────────────────────────────────
    const startCall = useCallback(async () => {
      setErrorMsg('');
      setTranscript([]);
      setStatus('connecting');

      try {
        // 1. Mint ephemeral token from our PHP
        const tokenResp = await fetch('voice.php', { method: 'POST' });
        const tokenJson = await tokenResp.json();
        if (!tokenResp.ok || !tokenJson.value) {
          const msg = tokenJson.error || 'Failed to start voice session.';
          if (tokenJson.whatsapp) whatsappRef.current = tokenJson.whatsapp;
          throw new Error(msg);
        }
        const ephemeralKey = tokenJson.value;
        const maxSec = tokenJson._eco?.max_session_sec || 300;
        setRemaining(maxSec);
        if (tokenJson._eco?.whatsapp) whatsappRef.current = tokenJson._eco.whatsapp;

        // 2. Get the model identifier the server picked (for SDP URL)
        const model = tokenJson.session?.model || 'gpt-realtime';

        // 3. Create peer connection
        const pc = new RTCPeerConnection();
        pcRef.current = pc;

        // 4. Remote audio element
        const audioEl = document.createElement('audio');
        audioEl.autoplay = true;
        audioRef.current = audioEl;
        pc.ontrack = (e) => { audioEl.srcObject = e.streams[0]; };

        // 5. Mic capture
        const mic = await navigator.mediaDevices.getUserMedia({ audio: true });
        micRef.current = mic;
        mic.getTracks().forEach(t => pc.addTrack(t, mic));

        // 6. Data channel for events
        const dc = pc.createDataChannel('oai-events');
        dcRef.current = dc;
        dc.addEventListener('message', (e) => {
          try { handleEvent(JSON.parse(e.data)); } catch (_) {}
        });

        // 7. SDP offer/answer with OpenAI
        const offer = await pc.createOffer();
        await pc.setLocalDescription(offer);

        const sdpResp = await fetch(
          `https://api.openai.com/v1/realtime/calls?model=${encodeURIComponent(model)}`,
          {
            method: 'POST',
            body: offer.sdp,
            headers: {
              Authorization: `Bearer ${ephemeralKey}`,
              'Content-Type': 'application/sdp',
            },
          }
        );
        if (!sdpResp.ok) {
          const errText = await sdpResp.text();
          throw new Error('OpenAI rejected the call: ' + errText.slice(0, 200));
        }
        const answer = { type: 'answer', sdp: await sdpResp.text() };
        await pc.setRemoteDescription(answer);

        // 8. Countdown timer — auto end at limit
        timerRef.current = setInterval(() => {
          setRemaining(r => {
            if (r === null) return null;
            if (r <= 1) { endCall(); return 0; }
            return r - 1;
          });
        }, 1000);

      } catch (e) {
        console.error('[voice] start failed', e);
        setErrorMsg(e.message || 'Could not start voice session.');
        setStatus('error');
        cleanup();
      }
    }, [handleEvent]);

    // ─── Clean shutdown ────────────────────────────────────────────────────
    const cleanup = useCallback(() => {
      if (timerRef.current) { clearInterval(timerRef.current); timerRef.current = null; }
      if (micRef.current)   { micRef.current.getTracks().forEach(t => t.stop()); micRef.current = null; }
      if (dcRef.current)    { try { dcRef.current.close(); } catch(_){} dcRef.current = null; }
      if (pcRef.current)    { try { pcRef.current.close();  } catch(_){} pcRef.current = null; }
      if (audioRef.current) { audioRef.current.srcObject = null; audioRef.current = null; }
      setRemaining(null);
    }, []);

    const endCall = useCallback(() => {
      cleanup();
      setStatus('ended');
    }, [cleanup]);

    const toggleMute = useCallback(() => {
      if (!micRef.current) return;
      const next = !muted;
      micRef.current.getAudioTracks().forEach(t => t.enabled = !next);
      setMuted(next);
    }, [muted]);

    // Clean up on unmount
    useEffect(() => () => cleanup(), [cleanup]);

    // ─── Render ────────────────────────────────────────────────────────────
    const active = ['connecting','listening','speaking'].includes(status);

    return (
      <React.Fragment>
        {/* Pill button — sits to the LEFT of the Ask AI button */}
        <button
          onClick={() => {
            if (!open) setOpen(true);
            if (!active && status !== 'connecting') startCall();
          }}
          title="Talk to our voice assistant"
          style={{
            position: 'fixed', bottom: 28, left: '50%', transform: 'translateX(-50%)', zIndex: 500,
            display: 'flex', alignItems: 'center', gap: 8,
            background: active ? RED : GREEN_700,
            color: 'white', border: 'none', borderRadius: 999,
            padding: '12px 18px', fontWeight: 600, fontSize: 14,
            boxShadow: '0 6px 20px rgba(0,0,0,0.18)',
            cursor: 'pointer', fontFamily: 'inherit',
            transition: 'background 0.2s ease, transform 0.1s ease',
          }}
        >
          <span style={{
            display: 'inline-flex', position: 'relative',
            animation: active ? 'eco-pulse 1.2s ease-in-out infinite' : 'none',
          }}>
            <MicIcon />
          </span>
          <span>{active ? 'Listening…' : 'Talk to us'}</span>
        </button>

        {/* Panel */}
        {open && (
          <div style={{
            position: 'fixed', bottom: 92, left: '50%', transform: 'translateX(-50%)', zIndex: 499,
            width: 340, maxWidth: 'calc(100vw - 32px)',
            background: 'white', borderRadius: 20,
            boxShadow: '0 20px 50px rgba(0,0,0,0.25)',
            border: '1px solid #e7e3dc',
            overflow: 'hidden',
            fontFamily: 'inherit',
            display: 'flex', flexDirection: 'column',
            maxHeight: '70vh',
          }}>
            {/* Header */}
            <div style={{
              padding: '16px 18px',
              background: GREEN_900, color: 'white',
              display: 'flex', alignItems: 'center', justifyContent: 'space-between',
            }}>
              <div>
                <div style={{ fontWeight: 700, fontSize: 15 }}>Voice Assistant</div>
                <div style={{ fontSize: 12, opacity: 0.8, marginTop: 2 }}>
                  {status === 'idle'       && 'Ready when you are'}
                  {status === 'connecting' && 'Connecting…'}
                  {status === 'listening'  && '🎙 Listening — speak naturally'}
                  {status === 'speaking'   && '🔊 Speaking…'}
                  {status === 'ended'      && 'Session ended'}
                  {status === 'error'      && 'Error'}
                </div>
              </div>
              <button
                onClick={() => { endCall(); setOpen(false); }}
                style={{
                  background: 'transparent', border: 'none', color: 'white',
                  fontSize: 22, cursor: 'pointer', padding: 4, lineHeight: 1,
                }}
                aria-label="Close voice assistant"
              >×</button>
            </div>

            {/* Body */}
            <div style={{
              flex: 1, overflowY: 'auto', padding: '14px 18px',
              background: '#faf9f7',
              display: 'flex', flexDirection: 'column', gap: 10,
            }}>
              {errorMsg && (
                <div style={{
                  background: '#fff5f5', color: '#8b1a1a',
                  border: '1px solid #f3c2c2', borderRadius: 10,
                  padding: '10px 12px', fontSize: 13, lineHeight: 1.5,
                }}>
                  {errorMsg}
                  <div style={{ marginTop: 8 }}>
                    <a href={whatsappRef.current} target="_blank" rel="noreferrer"
                       style={{ color: GREEN_700, fontWeight: 600, textDecoration: 'none' }}>
                      → Message us on WhatsApp instead
                    </a>
                  </div>
                </div>
              )}

              {transcript.length === 0 && !errorMsg && (
                <div style={{ fontSize: 13, color: '#666', lineHeight: 1.55, padding: '6px 0' }}>
                  {status === 'idle'
                    ? 'Tap "Talk to us" to start. You can ask about products, track or amend an order. Speak naturally in English or Bahasa Malaysia.'
                    : 'Listening… say something to begin.'}
                </div>
              )}

              {transcript.map((t, i) => (
                <div key={i} style={{
                  alignSelf: t.role === 'user' ? 'flex-end' : 'flex-start',
                  maxWidth: '85%',
                  background: t.role === 'user' ? GREEN_700 : 'white',
                  color: t.role === 'user' ? 'white' : '#1a1a1a',
                  border: t.role === 'user' ? 'none' : '1px solid #ece8e0',
                  borderRadius: 14,
                  padding: '8px 12px', fontSize: 13, lineHeight: 1.5,
                  whiteSpace: 'pre-wrap',
                }}>
                  {t.text}
                  {t.streaming && <span style={{ opacity: 0.5 }}> ▋</span>}
                </div>
              ))}
            </div>

            {/* Controls */}
            <div style={{
              borderTop: '1px solid #ece8e0',
              padding: '12px 14px',
              display: 'flex', alignItems: 'center', gap: 10,
              background: 'white',
            }}>
              {active ? (
                <React.Fragment>
                  <button
                    onClick={toggleMute}
                    title={muted ? 'Unmute' : 'Mute'}
                    style={{
                      ...iconBtnStyle,
                      background: muted ? '#fff0f0' : '#f0ede8',
                      color: muted ? RED : GREEN_900,
                    }}
                  ><MuteIcon muted={muted} /></button>

                  <div style={{
                    flex: 1, fontSize: 12, color: '#666',
                    display: 'flex', alignItems: 'center', gap: 8,
                  }}>
                    {remaining !== null && (
                      <span style={{
                        fontVariantNumeric: 'tabular-nums',
                        color: remaining < 30 ? RED : '#888',
                      }}>
                        {Math.floor(remaining/60)}:{String(remaining%60).padStart(2,'0')}
                      </span>
                    )}
                    <span style={{ opacity: 0.6 }}>left</span>
                  </div>

                  <button
                    onClick={endCall}
                    style={{
                      ...iconBtnStyle,
                      background: RED, color: 'white',
                      width: 'auto', padding: '0 14px', gap: 6,
                    }}
                  ><PhoneOffIcon /><span style={{ fontSize: 13, fontWeight: 600 }}>End</span></button>
                </React.Fragment>
              ) : (
                <React.Fragment>
                  <button
                    onClick={startCall}
                    style={{
                      flex: 1, background: GREEN_700, color: 'white',
                      border: 'none', borderRadius: 10, padding: '10px 14px',
                      fontWeight: 600, fontSize: 14, cursor: 'pointer',
                      display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
                    }}
                  ><MicIcon />{status === 'ended' ? 'Start again' : 'Start talking'}</button>
                </React.Fragment>
              )}
            </div>
          </div>
        )}

        {/* Pulse keyframe — injected once */}
        <style>{`
          @keyframes eco-pulse {
            0%, 100% { transform: scale(1);   opacity: 1; }
            50%      { transform: scale(1.18); opacity: 0.75; }
          }
        `}</style>
      </React.Fragment>
    );
  }

  const iconBtnStyle = {
    width: 38, height: 38, borderRadius: 10,
    border: 'none', cursor: 'pointer',
    display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
    fontFamily: 'inherit',
  };

  // ──────────────────────────────────────────────────────────────────────────
  // 5. MOUNT
  // ──────────────────────────────────────────────────────────────────────────
  let host = document.getElementById('eco-voice-root');
  if (!host) {
    host = document.createElement('div');
    host.id = 'eco-voice-root';
    document.body.appendChild(host);
  }
  ReactDOM.createRoot(host).render(<EcoVoiceAgent />);
})();
