// Molara — Real video visit. WebRTC peer-to-peer with server WebSocket signaling.
// Both browsers join /ws?consultId=... and exchange offer/answer/ICE.

function visitRouteParams(route) {
  const q = (route || window.location.hash.replace(/^#/, '') || '').split('?')[1] || '';
  return new URLSearchParams(q);
}

const ICE_SERVERS = [
  { urls: ['stun:stun.l.google.com:19302', 'stun:global.stun.twilio.com:3478'] },
];

function VideoVisit({ tone = 'warm', route = '' }) {
  const { user } = useAuth();
  const consults = useConsults();
  const params = visitRouteParams(route);
  const consultId = params.get('consult') || consults[0]?.id || 'demo';
  const visitRole = params.get('role') || (user?.role === 'doctor' ? 'doctor' : 'patient');
  const consult = consults.find((c) => c.id === consultId) || consults[0] || null;
  const doc = consult ? DOC_BY_ID(consult.docId) : DOC_BY_ID(1);
  const isAudioOnly = consult?.mode === 'audio';
  const messages = useVisitMessages(consultId);

  // Payment gate (patients only). If this consult isn't paid AND the patient
  // isn't in a 7-day follow-up window, bounce to /pay before they can start.
  React.useEffect(() => {
    if (visitRole !== 'patient') return;
    if (!consult) return;
    const j = window.consultIsJoinable && window.consultIsJoinable(consult);
    if (j && !j.ok) {
      window.navigate && window.navigate('/pay?consult=' + encodeURIComponent(consult.id));
    }
  }, [consult?.id, consult?.paymentStatus, visitRole]);

  const [mic, setMic] = React.useState(true);
  const [cam, setCam] = React.useState(true);
  const [chat, setChat] = React.useState(true);
  const [tab, setTab] = React.useState('chat');
  const [draft, setDraft] = React.useState('');
  const [elapsed, setElapsed] = React.useState(0);
  const [callState, setCallState] = React.useState('idle'); // idle | starting | connecting | live | error
  const [statusMsg, setStatusMsg] = React.useState('Click Start call to share camera and mic.');
  const [peerJoined, setPeerJoined] = React.useState(false);
  const [endedByDoctor, setEndedByDoctor] = React.useState(false);

  const localVideoRef = React.useRef(null);
  const remoteVideoRef = React.useRef(null);
  const localStreamRef = React.useRef(null);
  const pcRef = React.useRef(null);
  const wsRef = React.useRef(null);
  const pendingIceRef = React.useRef([]);

  const wsApi = /^https?:$/.test(window.location.protocol);

  React.useEffect(() => {
    if (callState !== 'live') return;
    const t = setInterval(() => setElapsed((s) => s + 1), 1000);
    return () => clearInterval(t);
  }, [callState]);

  const mm = String(Math.floor(elapsed / 60)).padStart(2, '0');
  const ss = String(elapsed % 60).padStart(2, '0');

  const sendChat = () => {
    if (!draft.trim()) return;
    sendVisitMessage(consultId, visitRole, draft);
    setDraft('');
  };

  // ---------- Cleanup ----------
  const teardown = React.useCallback(() => {
    try { pcRef.current?.close(); } catch (_) {}
    pcRef.current = null;
    try { wsRef.current?.close(); } catch (_) {}
    wsRef.current = null;
    if (localStreamRef.current) {
      localStreamRef.current.getTracks().forEach((t) => t.stop());
      localStreamRef.current = null;
    }
    if (remoteVideoRef.current) remoteVideoRef.current.srcObject = null;
    if (localVideoRef.current) localVideoRef.current.srcObject = null;
    pendingIceRef.current = [];
  }, []);

  React.useEffect(() => () => teardown(), [teardown]);

  // ---------- Start the call ----------
  const startCall = async () => {
    if (!wsApi) {
      setCallState('error');
      setStatusMsg('Real video calls require the server. Run locally with `npm start` or open the deployed URL.');
      return;
    }
    setCallState('starting');
    setStatusMsg('Requesting camera and microphone…');

    let stream;
    try {
      stream = await navigator.mediaDevices.getUserMedia({ video: isAudioOnly ? false : { width: 1280, height: 720 }, audio: true });
    } catch (err) {
      console.error(err);
      setCallState('error');
      setStatusMsg('Camera or mic blocked. Allow access in your browser and try again.');
      return;
    }
    localStreamRef.current = stream;
    if (localVideoRef.current) {
      localVideoRef.current.srcObject = stream;
      localVideoRef.current.muted = true;
      localVideoRef.current.playsInline = true;
      localVideoRef.current.play().catch(() => {});
    }

    // Build peer connection
    const pc = new RTCPeerConnection({ iceServers: ICE_SERVERS });
    pcRef.current = pc;
    stream.getTracks().forEach((t) => pc.addTrack(t, stream));

    pc.ontrack = (ev) => {
      const [remote] = ev.streams;
      if (remoteVideoRef.current && remote) {
        remoteVideoRef.current.srcObject = remote;
        remoteVideoRef.current.playsInline = true;
        remoteVideoRef.current.play().catch(() => {});
      }
    };

    pc.onicecandidate = (ev) => {
      if (ev.candidate && wsRef.current?.readyState === 1) {
        wsRef.current.send(JSON.stringify({ type: 'webrtc-signal', kind: 'ice', candidate: ev.candidate }));
      }
    };

    pc.onconnectionstatechange = () => {
      const s = pc.connectionState;
      if (s === 'connected') { setCallState('live'); setStatusMsg('Connected.'); }
      else if (s === 'failed' || s === 'disconnected') { setStatusMsg('Connection lost. Try again.'); }
    };

    // Open signaling channel
    const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
    let sid = '';
    try { sid = localStorage.getItem('molara.sid.v1') || ''; } catch (_) {}
    const sidQ = sid ? `&sid=${encodeURIComponent(sid)}` : '';
    const ws = new WebSocket(`${proto}//${location.host}/ws?consultId=${encodeURIComponent(consultId)}${sidQ}`);
    wsRef.current = ws;

    ws.addEventListener('open', () => {
      setCallState('connecting');
      setStatusMsg('Waiting for the other party to join…');
    });

    ws.addEventListener('message', async (event) => {
      let msg; try { msg = JSON.parse(event.data); } catch (_) { return; }
      if (msg.type === 'peer-joined' && msg.role !== visitRole) {
        // We are the offerer — make and send an offer
        setPeerJoined(true);
        setStatusMsg('Other party joined. Negotiating…');
        try {
          const offer = await pc.createOffer();
          await pc.setLocalDescription(offer);
          ws.send(JSON.stringify({ type: 'webrtc-signal', kind: 'offer', sdp: offer }));
        } catch (err) { console.error(err); }
        return;
      }

      if (msg.type === 'call-ended') {
        setEndedByDoctor(true);
        setStatusMsg('The doctor ended the consultation.');
        teardown();
        window.navigate && window.navigate('/visit/summary?consult=' + encodeURIComponent(consultId));
        return;
      }

      if (msg.type === 'peer-left' && msg.role === 'doctor' && visitRole === 'patient') {
        setEndedByDoctor(true);
        setStatusMsg('The doctor left the consultation.');
        teardown();
        window.navigate && window.navigate('/visit/summary?consult=' + encodeURIComponent(consultId));
        return;
      }

      if (msg.type !== 'webrtc-signal') return;

      if (msg.kind === 'offer') {
        setPeerJoined(true);
        setStatusMsg('Incoming call. Connecting…');
        try {
          await pc.setRemoteDescription(new RTCSessionDescription(msg.sdp));
          // Drain any ICE we received early
          for (const c of pendingIceRef.current) { try { await pc.addIceCandidate(new RTCIceCandidate(c)); } catch (_) {} }
          pendingIceRef.current = [];
          const answer = await pc.createAnswer();
          await pc.setLocalDescription(answer);
          ws.send(JSON.stringify({ type: 'webrtc-signal', kind: 'answer', sdp: answer }));
        } catch (err) { console.error(err); }
      } else if (msg.kind === 'answer') {
        try {
          await pc.setRemoteDescription(new RTCSessionDescription(msg.sdp));
          for (const c of pendingIceRef.current) { try { await pc.addIceCandidate(new RTCIceCandidate(c)); } catch (_) {} }
          pendingIceRef.current = [];
        } catch (err) { console.error(err); }
      } else if (msg.kind === 'ice') {
        if (!pc.remoteDescription || !pc.remoteDescription.type) {
          pendingIceRef.current.push(msg.candidate);
        } else {
          try { await pc.addIceCandidate(new RTCIceCandidate(msg.candidate)); } catch (err) { console.warn(err); }
        }
      }
    });

    ws.addEventListener('close', () => {
      if (callState !== 'live') { setStatusMsg('Signaling closed before connection.'); }
    });
    ws.addEventListener('error', () => {
      setCallState('error');
      setStatusMsg('Could not reach the signaling server.');
    });
  };

  const toggleMic = () => {
    const next = !mic;
    setMic(next);
    localStreamRef.current?.getAudioTracks().forEach((t) => (t.enabled = next));
  };
  const toggleCam = () => {
    const next = !cam;
    setCam(next);
    localStreamRef.current?.getVideoTracks().forEach((t) => (t.enabled = next));
  };

  const leave = async () => {
    const now = new Date().toISOString();
    if (visitRole === 'doctor') {
      try {
        wsRef.current?.send(JSON.stringify({ type: 'call-ended', consultId, endedAt: now }));
      } catch (_) {}
      if (consult?.id && window.updateConsult) {
        await window.updateConsult(consult.id, { endedAt: now, status: 'awaiting-prescription', inProgress: false });
      }
      teardown();
      window.navigate && window.navigate('/visit/summary?consult=' + encodeURIComponent(consultId) + '&role=doctor&write=1');
      return;
    }
    teardown();
    window.navigate && window.navigate(visitRole === 'doctor' ? '/doctor-dashboard' : '/visit/summary');
  };

  return (
    <div className="ml-root" style={{ height: '100%', background: '#0b0f14', color: '#fff', display: 'grid', gridTemplateColumns: chat ? '1fr 360px' : '1fr', overflow: 'hidden' }}>
      <div style={{ position: 'relative', display: 'flex', flexDirection: 'column' }}>
        {/* Top bar */}
        <div style={{ display: 'flex', alignItems: 'center', padding: '16px 24px', background: 'linear-gradient(180deg, rgba(0,0,0,.5), transparent)', position: 'absolute', top: 0, left: 0, right: 0, zIndex: 3 }}>
          <div style={{ display: 'inline-flex', alignItems: 'center', gap: 10 }}>
            <Logo size={17}/>
            <span style={{ opacity: .4 }}>·</span>
            <span className="ml-pill" style={{ background: 'rgba(255,255,255,.1)', color: '#fff', border: '1px solid rgba(255,255,255,.15)' }}>
              {callState === 'live' ? <><span className="ml-live"/> Live · P2P encrypted</> :
               callState === 'connecting' ? 'Connecting…' :
               callState === 'starting' ? 'Starting…' :
               callState === 'error' ? 'Error' : 'Not started'}
            </span>
            {callState === 'live' && <span style={{ fontFamily: 'var(--mono)', fontSize: 12, opacity: .7, marginLeft: 6 }}>{mm}:{ss}</span>}
          </div>
          <div style={{ marginLeft: 'auto', fontSize: 12, color: 'rgba(255,255,255,.6)' }}>
            {visitRole === 'doctor' ? `Patient: ${consult?.patientName || '—'}` : `Dentist: ${doc.n}`} · Visit #{consult?.id || 'MLR-DEMO'}
          </div>
        </div>

        {/* Main remote video */}
        <div style={{ flex: 1, position: 'relative', background: '#000', display: 'grid', placeItems: 'center' }}>
          <video ref={remoteVideoRef} autoPlay playsInline
            style={{ width: '100%', height: '100%', objectFit: 'cover',
              display: callState === 'live' ? 'block' : 'none' }}/>

            {callState !== 'live' && (
            <div style={{ textAlign: 'center', maxWidth: 460, padding: 32 }}>
              <div className="ml-avatar" style={{ width: 88, height: 88, borderRadius: 22, fontSize: 26, margin: '0 auto 18px' }}>
                {visitRole === 'doctor' ? (consult?.patientInitials || 'PT') : doc.avatar}
              </div>
              <h2 style={{ fontSize: 26, marginBottom: 8, color: '#fff' }}>
                {visitRole === 'doctor' ? (consult?.patientName || 'Patient') : doc.n}
              </h2>
              {isAudioOnly && <div className="ml-pill" style={{ background: 'rgba(255,255,255,.1)', color: '#fff', marginBottom: 12 }}>Audio consult</div>}
              <p style={{ fontSize: 14, color: 'rgba(255,255,255,.6)', marginBottom: 24, lineHeight: 1.55 }}>
                {endedByDoctor ? 'The doctor has ended the consultation. Your summary will appear here after the prescription is completed.' : statusMsg}
              </p>
              {callState === 'idle' || callState === 'error' ? (
                <button onClick={startCall} className="ml-btn ml-btn--primary" style={{ padding: '14px 28px' }}>
                  <I.video size={15}/> Start call
                </button>
              ) : (
                <div style={{ display: 'inline-flex', alignItems: 'center', gap: 10, color: 'rgba(255,255,255,.7)', fontSize: 13 }}>
                  <span style={{ width: 10, height: 10, borderRadius: 5, background: 'oklch(72% 0.13 165)', animation: 'ml-pulse 1.4s infinite' }}/>
                  {peerJoined ? 'Negotiating media…' : 'Open this same link on the other device.'}
                </div>
              )}
            </div>
          )}

          {/* Self PiP */}
          {(callState === 'starting' || callState === 'connecting' || callState === 'live') && (
            <div style={{ position: 'absolute', right: 24, bottom: 110, width: 220, height: 156, borderRadius: 12, overflow: 'hidden', border: '2px solid rgba(255,255,255,.25)', background: '#222' }}>
              <video ref={localVideoRef} autoPlay playsInline muted
                style={{ width: '100%', height: '100%', objectFit: 'cover', transform: 'scaleX(-1)' }}/>
              <div style={{ position: 'absolute', left: 8, top: 8, fontSize: 10, fontWeight: 500, padding: '3px 8px', background: 'rgba(0,0,0,.5)', borderRadius: 999, letterSpacing: 1 }}>YOU</div>
              {!mic && <div style={{ position: 'absolute', right: 8, top: 8, background: 'var(--danger)', width: 24, height: 24, borderRadius: 12, display: 'grid', placeItems: 'center' }}><I.micOff size={12}/></div>}
            </div>
          )}
        </div>

        {/* Control dock */}
        <div style={{ position: 'absolute', left: '50%', transform: 'translateX(-50%)', bottom: 28, display: 'flex', gap: 8, background: 'rgba(20,24,30,.85)', backdropFilter: 'blur(14px)', padding: 8, borderRadius: 999, border: '1px solid rgba(255,255,255,.08)' }}>
          <Ctrl active={mic} onClick={toggleMic} Ic={mic ? I.mic : I.micOff} label="Mic"/>
          {!isAudioOnly && <Ctrl active={cam} onClick={toggleCam} Ic={I.video} label="Camera"/>}
          <Ctrl active={chat} onClick={() => setChat(!chat)} Ic={I.chat} label="Chat"/>
          <button onClick={leave} style={{ background: 'var(--danger)', color: '#fff', border: 'none', borderRadius: 999, padding: '0 18px', height: 48, fontSize: 13, fontWeight: 500, cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 8 }}>
            <I.phoneDown size={15}/> {visitRole === 'doctor' ? 'End & write Rx' : 'Leave'}
          </button>
        </div>
      </div>

      {chat && (
        <aside style={{ background: '#11161c', borderLeft: '1px solid rgba(255,255,255,.06)', display: 'flex', flexDirection: 'column', minHeight: 0 }}>
          <div style={{ padding: '16px 18px', borderBottom: '1px solid rgba(255,255,255,.06)' }}>
            <div style={{ display: 'flex', alignItems: 'center', marginBottom: 10 }}>
              <div className="ml-label" style={{ color: 'rgba(255,255,255,.45)' }}>VISIT ROOM · #{consultId}</div>
              <button onClick={() => setChat(false)} style={{ marginLeft: 'auto', background: 'transparent', border: 'none', color: 'rgba(255,255,255,.5)', cursor: 'pointer' }}><I.close size={14}/></button>
            </div>
            <div style={{ display: 'flex', gap: 4 }}>
              {['chat','notes'].map((t) => (
                <button key={t} onClick={() => setTab(t)}
                  style={{ padding: '6px 12px', borderRadius: 6, border: 'none', background: tab === t ? 'rgba(255,255,255,.08)' : 'transparent', color: tab === t ? '#fff' : 'rgba(255,255,255,.55)', fontSize: 12, cursor: 'pointer', textTransform: 'capitalize', fontFamily: 'var(--sans)' }}>
                  {t}
                </button>
              ))}
            </div>
          </div>

          {tab === 'notes' && (
            <div style={{ padding: 18, overflowY: 'auto', flex: 1 }}>
              <div className="ml-label" style={{ color: 'rgba(255,255,255,.45)', marginBottom: 10 }}>REASON</div>
              <div style={{ fontSize: 14, marginBottom: 18 }}>
                {consult ? `${consult.reason} · pain ${consult.severity}/10 · ${consult.careType}` : 'No consult linked to this room.'}
              </div>
              <div className="ml-label" style={{ color: 'rgba(255,255,255,.45)', marginBottom: 10 }}>PATIENT NOTE</div>
              <div style={{ fontSize: 13, lineHeight: 1.55, color: 'rgba(255,255,255,.85)', background: 'rgba(255,255,255,.04)', padding: 14, borderRadius: 8 }}>
                {consult?.note || 'No note added.'}
              </div>
            </div>
          )}

          {tab === 'chat' && (
            <>
              <div style={{ padding: 18, overflowY: 'auto', flex: 1, display: 'flex', flexDirection: 'column', gap: 12 }}>
                {messages.length === 0 ? (
                  <div style={{ fontSize: 12, color: 'rgba(255,255,255,.4)', textAlign: 'center', padding: 30 }}>
                    No messages yet. Say hello.
                  </div>
                ) : messages.map((m) => (
                  <Bubble key={m.id} from={m.from === visitRole ? 'me' : 'them'} text={m.text}/>
                ))}
              </div>
              <div style={{ padding: 12, borderTop: '1px solid rgba(255,255,255,.06)', display: 'flex', gap: 8 }}>
                <input className="ml-input" placeholder="Type a message…" value={draft}
                  onChange={(e) => setDraft(e.target.value)}
                  onKeyDown={(e) => { if (e.key === 'Enter') sendChat(); }}
                  style={{ background: 'rgba(255,255,255,.06)', borderColor: 'rgba(255,255,255,.1)', color: '#fff' }}/>
                <button onClick={sendChat} className="ml-btn ml-btn--primary"><I.send size={13}/></button>
              </div>
            </>
          )}
        </aside>
      )}

      <style>{`@keyframes ml-pulse { 0%,100% { opacity:1 } 50% { opacity:.4 } }`}</style>
    </div>
  );
}

function Ctrl({ active = true, onClick, Ic, label }) {
  return (
    <button onClick={onClick} title={label}
      style={{ width: 48, height: 48, borderRadius: 999, border: 'none', cursor: 'pointer',
        background: active ? 'rgba(255,255,255,.08)' : 'var(--danger)',
        color: '#fff', display: 'grid', placeItems: 'center', transition: 'background .12s' }}>
      <Ic size={17}/>
    </button>
  );
}

function Bubble({ from, text }) {
  const me = from === 'me';
  return (
    <div style={{ alignSelf: me ? 'flex-end' : 'flex-start', maxWidth: '84%', background: me ? 'var(--blue)' : 'rgba(255,255,255,.08)', padding: '9px 13px', borderRadius: 12, fontSize: 13, color: '#fff', wordBreak: 'break-word' }}>
      {text}
    </div>
  );
}

Object.assign(window, { VideoVisit });
