// Agent renderer: connects to the signaling server, handles consent,
// captures the screen, and streams it over WebRTC to the technician's viewer.
// Remote input events arrive on the data channel and are handed to the main
// process (via the preload bridge) for OS-level injection.

const statusEl = document.getElementById('status');
const consentBox = document.getElementById('consentBox');
const indicator = document.getElementById('indicator');
const logEl = document.getElementById('log');

let cfg = null;
let ws = null;
let pc = null;
let localStream = null;
let currentSessionId = null;

const log = (t) => { const d = document.createElement('div'); d.textContent = t; logEl.appendChild(d); logEl.scrollTop = logEl.scrollHeight; };
const setStatus = (t, cls = '') => { statusEl.textContent = t; statusEl.className = 'status ' + cls; };

window.agent.onConfig((c) => {
  cfg = c;
  if (!cfg.enrollToken) {
    setStatus('No enroll token. Set AGENT_ENROLL_TOKEN and restart.', 'warn');
    return;
  }
  connect();
});

function wsUrl() {
  const u = new URL(cfg.serverUrl);
  const proto = u.protocol === 'https:' ? 'wss:' : 'ws:';
  return `${proto}//${u.host}/ws`;
}

function connect() {
  setStatus('Connecting to server…');
  ws = new WebSocket(wsUrl());
  ws.onopen = () => {
    ws.send(JSON.stringify({ type: 'agent-hello', enrollToken: cfg.enrollToken }));
  };
  ws.onmessage = (e) => handle(JSON.parse(e.data));
  ws.onclose = () => { setStatus('Disconnected. Reconnecting in 3s…', 'warn'); setTimeout(connect, 3000); };
}

async function handle(m) {
  switch (m.type) {
    case 'agent-registered':
      setStatus(`Online as "${m.name}". Waiting for sessions.`, 'on');
      log(`registered: ${m.name}`);
      break;

    case 'session-request':
      if (m.unattended) { log(`unattended session ${m.sessionId} — auto-granting`); grant(m.sessionId); }
      else showConsent(m);
      break;

    case 'start-stream':
      currentSessionId = m.sessionId;
      await startStreaming();
      break;

    case 'answer':
      if (pc) await pc.setRemoteDescription(new RTCSessionDescription(m.sdp));
      break;
    case 'ice-candidate':
      if (m.candidate && pc) await pc.addIceCandidate(new RTCIceCandidate(m.candidate));
      break;

    case 'session-ended':
      teardown();
      break;
    case 'error':
      setStatus('Server: ' + m.message, 'warn');
      break;
  }
}

function showConsent(m) {
  consentBox.innerHTML = `
    <div class="consent">
      <h2>Allow remote support?</h2>
      <p class="muted"><b>${escapeHtml(m.technician)}</b> is requesting to view and control this PC.</p>
      <button class="grant" id="grantBtn">Allow</button>
      <button class="deny" id="denyBtn">Deny</button>
    </div>`;
  document.getElementById('grantBtn').onclick = () => { consentBox.innerHTML = ''; grant(m.sessionId); };
  document.getElementById('denyBtn').onclick = () => { consentBox.innerHTML = ''; deny(m.sessionId); };
}

const grant = (sessionId) => ws.send(JSON.stringify({ type: 'consent', sessionId, granted: true }));
const deny = (sessionId) => ws.send(JSON.stringify({ type: 'consent', sessionId, granted: false }));

async function startStreaming() {
  setStatus('Sharing screen with technician…', 'on');
  indicator.classList.add('show');
  try {
    localStream = await navigator.mediaDevices.getDisplayMedia({ video: { frameRate: { ideal: 30 } }, audio: false });
  } catch (err) {
    log('getDisplayMedia failed: ' + err.message);
    setStatus('Screen capture failed.', 'warn');
    return;
  }

  pc = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] });
  localStream.getTracks().forEach((t) => pc.addTrack(t, localStream));

  // The agent is the OFFERER, so it must create the input data channel — otherwise the
  // SCTP m-line is absent from the offer and the channel never negotiates (viewer's stays
  // closed, so no input arrives). The viewer receives this channel via ondatachannel.
  const inputCh = pc.createDataChannel('input', { ordered: true });
  inputCh.onmessage = (msg) => {
    let evt; try { evt = JSON.parse(msg.data); } catch { return; }
    window.agent.injectInput(evt); // -> main process -> OS injection
  };

  pc.onicecandidate = (ev) => {
    if (ev.candidate) ws.send(JSON.stringify({ type: 'ice-candidate', sessionId: currentSessionId, candidate: ev.candidate }));
  };

  const offer = await pc.createOffer();
  await pc.setLocalDescription(offer);
  ws.send(JSON.stringify({ type: 'offer', sessionId: currentSessionId, sdp: pc.localDescription }));
  log('sent offer for session ' + currentSessionId);
}

function teardown() {
  indicator.classList.remove('show');
  window.agent.sessionEnded();
  if (localStream) { localStream.getTracks().forEach((t) => t.stop()); localStream = null; }
  if (pc) { pc.close(); pc = null; }
  currentSessionId = null;
  setStatus('Session ended. Waiting for sessions.', 'on');
}

function escapeHtml(s) { return String(s).replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c])); }
