/**
 * SecureCam Mobile Simulator Logic
 * Refactored for modern UI/UX and stability.
 */

// --- 1. State Management ---
class Store {
  constructor(initialState) {
    this.state = initialState;
    this.listeners = new Set();
  }

  get() {
    return this.state;
  }

  update(partialState) {
    this.state = { ...this.state, ...partialState };
    this.notify();
  }

  subscribe(listener) {
    this.listeners.add(listener);
    return () => this.listeners.delete(listener);
  }

  notify() {
    this.listeners.forEach((listener) => listener(this.state));
  }
}

const store = new Store({
  screen: 'auth', // auth, onboarding, home, activity, settings
  session: null,
  device: null,
  deviceToken: null,
  socketConnected: false,
  isMotionActive: false,
  cameraStatus: 'idle', // idle, recording, streaming
  linkedCameras: [],
  recordings: [],
  motionNotifications: [],
  activeCameraDeviceId: null,
  activeStreamSessionId: null,
  openLinkedCameraMenuId: null,
  activityFeed: [],
  loading: false, // global loading spinner state if needed
});

const PAGE_PATHS = {
  auth: '/sim/mobile-sim-auth.html',
  onboarding: '/sim/mobile-sim-onboarding.html',
  camera: '/sim/mobile-sim-camera.html',
  client: '/sim/mobile-sim-client.html',
  activity: '/sim/mobile-sim-activity.html',
  settings: '/sim/mobile-sim-settings.html',
};

const currentPageKey = document.body?.dataset?.page || '';
const multiPageMode = Boolean(currentPageKey);

const getHomePageKeyForRole = (role) => (role === 'camera' ? 'camera' : 'client');

const getPathForScreen = (screen, role) => {
  if (screen === 'home') {
    return PAGE_PATHS[getHomePageKeyForRole(role)];
  }
  return PAGE_PATHS[screen] || null;
};

const navigateToScreen = (screen, options = {}) => {
  const { replace = false, role = store.get().device?.role } = options;
  const targetPath = getPathForScreen(screen, role);

  if (multiPageMode && targetPath && window.location.pathname !== targetPath) {
    if (replace) {
      window.location.replace(targetPath);
    } else {
      window.location.assign(targetPath);
    }
    return true;
  }

  store.update({ screen });
  return false;
};

const getScreenForCurrentPage = () => {
  if (currentPageKey === 'activity') return 'activity';
  if (currentPageKey === 'settings') return 'settings';
  if (currentPageKey === 'onboarding') return 'onboarding';
  if (currentPageKey === 'camera' || currentPageKey === 'client') return 'home';
  return 'auth';
};

// --- 2. UI Utilities ---
const $ = (selector) => {
  // If it looks like a simple ID (no spaces, dots, hash), use getElementById
  if (/^[a-zA-Z0-9_\-]+$/.test(selector)) {
    return document.getElementById(selector);
  }
  // Otherwise use querySelector (handles #id, .class, complex selectors)
  return document.querySelector(selector);
};
const $$ = (selector) => document.querySelectorAll(selector);
const escapeHtml = (value = '') =>
  String(value)
    .replaceAll('&', '&amp;')
    .replaceAll('<', '&lt;')
    .replaceAll('>', '&gt;')
    .replaceAll('"', '&quot;')
    .replaceAll("'", '&#39;');

const Toast = {
  show(message, type = 'info') {
    const container = $('toast-container');
    const toast = document.createElement('div');

    let alertClass = 'alert-info';
    let icon = '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-current shrink-0 w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>';

    if (type === 'success') {
      alertClass = 'alert-success';
      icon = '<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>';
    } else if (type === 'error') {
      alertClass = 'alert-error';
      icon = '<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>';
    }

    toast.className = `alert ${alertClass} text-white shadow-lg text-xs py-2 px-3 flex flex-row gap-2 toast-enter`;
    toast.innerHTML = `${icon}<span>${message}</span>`;

    container.appendChild(toast);
    setTimeout(() => {
      toast.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
      toast.style.opacity = '0';
      toast.style.transform = 'translateY(100%)';
      setTimeout(() => toast.remove(), 300);
    }, 3000);
  }
};

// --- 3. API Client ---
const API = {
  async request(path, options = {}) {
    const { deviceToken } = store.get();
    const headers = { 'Content-Type': 'application/json' };

    if (deviceToken) {
      headers['Authorization'] = `Bearer ${deviceToken}`;
    }

    try {
      const res = await fetch(path, { ...options, headers: { ...headers, ...options.headers } });
      const data = await res.json().catch(() => ({}));

      if (!res.ok) {
        throw new Error(data.message || data.error || res.statusText);
      }
      return data;
    } catch (e) {
      Toast.show(e.message, 'error');
      throw e;
    }
  },

  auth: {
    signUp: (data) => API.request('/api/auth/sign-up/email', { method: 'POST', body: JSON.stringify(data) }),
    signIn: (data) => API.request('/api/auth/sign-in/email', { method: 'POST', body: JSON.stringify(data) }),
    getSession: () => API.request('/api/auth/get-session'),
    signOut: () => API.request('/api/auth/sign-out', { method: 'POST', body: JSON.stringify({}) }),
  },

  devices: {
    register: (data) => API.request('/devices/register', { method: 'POST', body: JSON.stringify(data) }),
    list: () => API.request('/devices'),
    update: (deviceId, data) => API.request(`/devices/${deviceId}`, { method: 'PATCH', body: JSON.stringify(data) }),
    listLinks: () => API.request('/device-links'),
    link: (cameraDeviceId, clientDeviceId) => API.request('/device-links', { method: 'POST', body: JSON.stringify({ cameraDeviceId, clientDeviceId }) }),
    unlink: (linkId) => API.request(`/device-links/${linkId}`, { method: 'DELETE' }),
  },

  streams: {
    request: (cameraDeviceId) => API.request('/streams/request', { method: 'POST', body: JSON.stringify({ cameraDeviceId, reason: 'on_demand' }) }),
    accept: (id) => API.request(`/streams/${id}/accept`, { method: 'POST', body: JSON.stringify({}) }),
    end: (id) => API.request(`/streams/${id}/end`, { method: 'POST', body: JSON.stringify({ reason: 'completed' }) }),
    getPublishCreds: (id) => API.request(`/streams/${id}/publish-credentials`),
    getSubscribeCreds: (id) => API.request(`/streams/${id}/subscribe-credentials`),
  },

  events: {
    startMotion: () => API.request('/events/motion/start', { method: 'POST', body: JSON.stringify({ title: 'Simulated Motion', triggeredBy: 'motion' }) }),
    endMotion: (id) => API.request(`/events/${id}/motion/end`, { method: 'POST', body: JSON.stringify({ status: 'completed' }) }),
    finalizeRecording: (id, payload) => API.request(`/recordings/${id}/finalize`, { method: 'POST', body: JSON.stringify(payload) }),
  },

  ops: {
    listRecordings: () => API.request('/recordings/me/list'),
    getRecordingDownloadUrl: (recordingId) => API.request(`/recordings/${recordingId}/download-url`),
    listNotifications: () => API.request('/push-notifications/me'),
  }
};

// --- 4. Logic & Controllers ---

let socket = null;
let pollInterval = null;
let localCameraStream = null;
let activeMediaRecorder = null;
let activeRecordingChunks = [];
let activeRecordingStartedAt = null;
let activeRecordingStreamSessionId = null;
let recordingModalUrl = null;
const RECORDING_VIDEO_BITS_PER_SECOND = 850_000;
const COMPRESSED_UPLOAD_MAX_WIDTH = 640;
const COMPRESSED_UPLOAD_MAX_HEIGHT = 360;
const COMPRESSED_UPLOAD_FRAME_RATE = 12;
const COMPRESSED_UPLOAD_BITS_PER_SECOND = 450_000;

// Multi-stream state (for Client)
const peerConnections = new Map(); // streamSessionId -> RTCPeerConnection
const remoteStreams = new Map(); // streamSessionId -> MediaStream
const pendingCandidatesMap = new Map(); // streamSessionId -> Array<ICE candidates>
const streamTimers = new Map(); // streamSessionId -> frameRelay/wait timers
const connectedPeers = new Set(); // streamSessionId

// Legacy fallback for camera single stream
let peerSessionId = null;
let peerTargetDeviceId = null;
let hasWebrtcEverConnected = false;
let webrtcConnected = false;
let frameRelayTimer = null;
let frameRelayStartTimer = null;
let frameCanvas = null;
let frameContext = null;

const requestedStreams = new Set(); // cameraDeviceIds that have been requested

const rtcConfig = {
  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
};

const init = async () => {
  // Load local storage
  const saved = localStorage.getItem('mobileSimDevice');
  if (saved) {
    try {
      const parsed = JSON.parse(saved);
      store.update({ device: parsed.device, deviceToken: parsed.deviceToken });
    } catch (e) { console.error('Failed to load saved device', e); }
  }

  try {
    const session = await API.auth.getSession();
    if (session && session.session) {
      store.update({ session });
      if (store.get().deviceToken) {
        const role = store.get().device?.role;
        if (multiPageMode && (currentPageKey === 'auth' || currentPageKey === 'onboarding')) {
          if (navigateToScreen('home', { replace: true, role })) return;
        }
        if (multiPageMode && (currentPageKey === 'camera' || currentPageKey === 'client')) {
          const expectedHome = getHomePageKeyForRole(role);
          if (expectedHome !== currentPageKey) {
            if (navigateToScreen('home', { replace: true, role })) return;
          }
        }

        if (multiPageMode) {
          store.update({ screen: getScreenForCurrentPage() });
        } else {
          navigateBasedOnRole();
        }
        connectSocket();
        startPolling();
      } else {
        if (multiPageMode) {
          if (currentPageKey !== 'onboarding') {
            if (navigateToScreen('onboarding', { replace: true })) return;
          } else {
            store.update({ screen: 'onboarding' });
          }
        } else {
          store.update({ screen: 'onboarding' });
        }
      }
    } else {
      if (multiPageMode) {
        if (currentPageKey !== 'auth') {
          if (navigateToScreen('auth', { replace: true })) return;
        } else {
          store.update({ screen: 'auth' });
        }
      } else {
        store.update({ screen: 'auth' });
      }
    }
  } catch {
    if (multiPageMode) {
      if (currentPageKey !== 'auth') {
        if (navigateToScreen('auth', { replace: true })) return;
      } else {
        store.update({ screen: 'auth' });
      }
    } else {
      store.update({ screen: 'auth' });
    }
  }
};

const navigateBasedOnRole = () => {
  const { device } = store.get();
  if (!device) {
    navigateToScreen('onboarding');
    return;
  }

  // Default home screen based on role
  navigateToScreen('home', { role: device.role });
};

const startCameraPreview = async () => {
  const videoEl = $('cameraVideo');
  if (!videoEl || !navigator.mediaDevices?.getUserMedia) {
    Toast.show('Camera API is not available in this browser', 'error');
    return false;
  }

  if (localCameraStream) {
    videoEl.srcObject = localCameraStream;
    videoEl.classList.remove('hidden');
    return true;
  }

  try {
    localCameraStream = await navigator.mediaDevices.getUserMedia({
      video: {
        width: { ideal: 640, max: 960 },
        height: { ideal: 360, max: 540 },
        frameRate: { ideal: 15, max: 24 },
      },
      audio: false,
    });
    videoEl.srcObject = localCameraStream;
    videoEl.classList.remove('hidden');
    addActivity('Camera', 'Camera access granted');
    return true;
  } catch (error) {
    Toast.show('Camera permission denied or unavailable', 'error');
    addActivity('Camera', 'Camera access failed');
    return false;
  }
};

const stopCameraPreview = () => {
  const videoEl = $('cameraVideo');
  if (localCameraStream) {
    localCameraStream.getTracks().forEach((track) => track.stop());
    localCameraStream = null;
  }
  if (videoEl) {
    videoEl.srcObject = null;
    videoEl.classList.add('hidden');
  }
};

const setClientStreamPlaceholderText = (text) => {
  const placeholderEl = $('clientStreamPlaceholder');
  if (!placeholderEl) return;
  const label = placeholderEl.querySelector('p');
  if (label) {
    label.textContent = text;
  }
};

const setClientStreamMode = (mode) => {
  const videoEl = $('clientStreamVideo');
  const imageEl = $('clientStreamImage');
  const placeholderEl = $('clientStreamPlaceholder');

  if (videoEl) videoEl.classList.toggle('hidden', mode !== 'video');
  if (imageEl) imageEl.classList.toggle('hidden', mode !== 'image');

  if (!placeholderEl) return;

  if (mode === 'video' || mode === 'image') {
    placeholderEl.classList.add('hidden');
    return;
  }

  if (mode === 'unavailable') {
    setClientStreamPlaceholderText('Stream unavailable');
  } else if (mode === 'connecting') {
    setClientStreamPlaceholderText('Connecting stream...');
  } else {
    setClientStreamPlaceholderText('Waiting for stream');
  }
  placeholderEl.classList.remove('hidden');
};

const clearClientStream = () => {
  const { activeStreamSessionId } = store.get();
  if (activeStreamSessionId && streamTimers.has(activeStreamSessionId)) {
    clearTimeout(streamTimers.get(activeStreamSessionId));
    streamTimers.delete(activeStreamSessionId);
  }
  const videoEl = $('clientStreamVideo');
  const imageEl = $('clientStreamImage');
  if (activeStreamSessionId && remoteStreams.has(activeStreamSessionId)) {
    remoteStreams.get(activeStreamSessionId).getTracks().forEach((track) => track.stop());
    remoteStreams.delete(activeStreamSessionId);
  }
  if (videoEl) {
    videoEl.srcObject = null;
  }
  if (imageEl) {
    imageEl.src = '';
  }
  setClientStreamMode('none');
};

const getLinkedCamera = (cameraDeviceId) =>
  store.get().linkedCameras.find((camera) => camera.cameraDeviceId === cameraDeviceId);

const getCameraLabel = (cameraDeviceId, cameraName) => {
  const explicitName = typeof cameraName === 'string' ? cameraName.trim() : '';
  if (explicitName) return explicitName;

  const linkedName = getLinkedCamera(cameraDeviceId)?.cameraName;
  if (typeof linkedName === 'string' && linkedName.trim()) {
    return linkedName.trim();
  }

  return `Camera ${cameraDeviceId?.substring(0, 6) ?? 'Unknown'}`;
};

const pushMotionNotification = (cameraDeviceId) => {
  if (!cameraDeviceId) return;

  const notification = {
    id: crypto.randomUUID(),
    cameraDeviceId,
    message: `${getCameraLabel(cameraDeviceId)} has detected movement`,
    createdAt: new Date().toISOString(),
    isRead: false,
  };

  store.update({
    motionNotifications: [notification, ...store.get().motionNotifications].slice(0, 50),
  });
};

const markMotionNotificationRead = (notificationId) => {
  store.update({
    motionNotifications: store
      .get()
      .motionNotifications.map((notification) =>
        notification.id === notificationId ? { ...notification, isRead: true } : notification,
      ),
  });
};

const markAllNotificationsRead = () => {
  store.update({
    motionNotifications: store
      .get()
      .motionNotifications.map((notification) => (notification.isRead ? notification : { ...notification, isRead: true })),
  });
};

const openRecordingModal = (downloadUrl, title) => {
  const modal = $('recordingModal');
  const videoEl = $('recordingModalVideo');
  const titleEl = $('recordingModalTitle');

  if (!modal || !videoEl || !titleEl) return;

  recordingModalUrl = downloadUrl;
  titleEl.textContent = title || 'Recording Playback';
  videoEl.src = downloadUrl;
  modal.classList.remove('hidden');
  modal.classList.add('flex');
  void videoEl.play().catch(() => { });
};

const closeRecordingModal = () => {
  const modal = $('recordingModal');
  const videoEl = $('recordingModalVideo');
  if (!modal || !videoEl) return;

  modal.classList.add('hidden');
  modal.classList.remove('flex');
  videoEl.pause();
  videoEl.removeAttribute('src');
  videoEl.load();
  recordingModalUrl = null;
};

const stopFrameRelay = () => {
  if (frameRelayStartTimer) {
    clearTimeout(frameRelayStartTimer);
    frameRelayStartTimer = null;
  }
  if (frameRelayTimer) {
    clearInterval(frameRelayTimer);
    frameRelayTimer = null;
  }
};

const startFrameRelay = async (streamSessionId, toDeviceId) => {
  if (!socket || !streamSessionId || !toDeviceId) return;
  if (hasWebrtcEverConnected) return;

  const ready = await startCameraPreview();
  if (!ready) {
    throw new Error('Camera permission is required before streaming');
  }

  const cameraVideoEl = $('cameraVideo');
  if (!cameraVideoEl) return;

  stopFrameRelay();
  frameRelayTimer = setInterval(() => {
    if (webrtcConnected || hasWebrtcEverConnected) return;
    if (!socket || cameraVideoEl.readyState < 2 || !cameraVideoEl.videoWidth || !cameraVideoEl.videoHeight) return;

    if (!frameCanvas) {
      frameCanvas = document.createElement('canvas');
      frameContext = frameCanvas.getContext('2d');
    }
    if (!frameCanvas || !frameContext) return;

    frameCanvas.width = cameraVideoEl.videoWidth;
    frameCanvas.height = cameraVideoEl.videoHeight;
    frameContext.drawImage(cameraVideoEl, 0, 0, frameCanvas.width, frameCanvas.height);
    const frame = frameCanvas.toDataURL('image/jpeg', 0.6);

    socket.emit('stream:frame', {
      toDeviceId,
      streamSessionId,
      frame,
      capturedAt: new Date().toISOString(),
    });
  }, 600);
};

const getPreferredRecordingMimeType = () => {
  if (typeof MediaRecorder === 'undefined') return '';
  const preferredTypes = [
    'video/webm;codecs=vp9',
    'video/webm;codecs=vp8',
    'video/webm',
  ];
  return preferredTypes.find((type) => MediaRecorder.isTypeSupported(type)) ?? '';
};

const startLocalRecording = async () => {
  if (!localCameraStream || typeof MediaRecorder === 'undefined') {
    addActivity('Recording', 'MediaRecorder unavailable');
    return false;
  }

  if (activeMediaRecorder?.state === 'recording') {
    return true;
  }

  activeRecordingChunks = [];
  activeRecordingStartedAt = Date.now();

  try {
    const mimeType = getPreferredRecordingMimeType();
    const recorderOptions = {
      videoBitsPerSecond: RECORDING_VIDEO_BITS_PER_SECOND,
    };
    if (mimeType) {
      recorderOptions.mimeType = mimeType;
    }
    activeMediaRecorder = new MediaRecorder(localCameraStream, recorderOptions);
  } catch (error) {
    console.error('Failed to create MediaRecorder', error);
    addActivity('Recording', 'Failed to start recorder');
    activeMediaRecorder = null;
    return false;
  }

  activeMediaRecorder.ondataavailable = (event) => {
    if (event.data && event.data.size > 0) {
      activeRecordingChunks.push(event.data);
    }
  };

  activeMediaRecorder.start(1000);
  addActivity('Recording', 'Local recording started');
  return true;
};

const stopLocalRecording = async () => {
  if (!activeMediaRecorder || activeMediaRecorder.state === 'inactive') {
    return null;
  }

  return await new Promise((resolve) => {
    const recorder = activeMediaRecorder;
    const startedAt = activeRecordingStartedAt ?? Date.now();

    recorder.onstop = () => {
      const mimeType = recorder.mimeType || 'video/webm';
      const blob = activeRecordingChunks.length > 0 ? new Blob(activeRecordingChunks, { type: mimeType }) : null;
      const durationSeconds = Math.max(1, Math.round((Date.now() - startedAt) / 1000));

      activeMediaRecorder = null;
      activeRecordingChunks = [];
      activeRecordingStartedAt = null;

      resolve(blob ? { blob, durationSeconds } : null);
    };

    recorder.onerror = () => {
      activeMediaRecorder = null;
      activeRecordingChunks = [];
      activeRecordingStartedAt = null;
      resolve(null);
    };

    recorder.stop();
  });
};

const toEvenDimension = (value) => {
  const rounded = Math.max(2, Math.floor(value));
  return rounded % 2 === 0 ? rounded : rounded - 1;
};

const compressRecordingBlob = async (sourceBlob) => {
  if (!sourceBlob || sourceBlob.size === 0) return sourceBlob;
  if (typeof document === 'undefined' || typeof MediaRecorder === 'undefined') return sourceBlob;

  const mimeType = getPreferredRecordingMimeType();
  if (!mimeType) return sourceBlob;

  const sourceUrl = URL.createObjectURL(sourceBlob);
  const videoEl = document.createElement('video');
  videoEl.muted = true;
  videoEl.playsInline = true;
  videoEl.preload = 'auto';

  let rafId = null;
  let captureStream = null;

  try {
    await new Promise((resolve, reject) => {
      videoEl.onloadedmetadata = resolve;
      videoEl.onerror = () => reject(new Error('Failed loading recorded clip'));
      videoEl.src = sourceUrl;
    });

    const sourceWidth = videoEl.videoWidth || COMPRESSED_UPLOAD_MAX_WIDTH;
    const sourceHeight = videoEl.videoHeight || COMPRESSED_UPLOAD_MAX_HEIGHT;
    const scale = Math.min(1, COMPRESSED_UPLOAD_MAX_WIDTH / sourceWidth, COMPRESSED_UPLOAD_MAX_HEIGHT / sourceHeight);
    const width = toEvenDimension(sourceWidth * scale);
    const height = toEvenDimension(sourceHeight * scale);

    const canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;
    const context = canvas.getContext('2d');
    if (!context || typeof canvas.captureStream !== 'function') {
      return sourceBlob;
    }

    captureStream = canvas.captureStream(COMPRESSED_UPLOAD_FRAME_RATE);
    const compressedChunks = [];
    const recorder = new MediaRecorder(captureStream, {
      mimeType,
      videoBitsPerSecond: COMPRESSED_UPLOAD_BITS_PER_SECOND,
    });

    const recorderStopped = new Promise((resolve, reject) => {
      recorder.ondataavailable = (event) => {
        if (event.data?.size > 0) {
          compressedChunks.push(event.data);
        }
      };
      recorder.onerror = (event) => {
        const message = event?.error?.message || 'Compression recorder failed';
        reject(new Error(message));
      };
      recorder.onstop = () => {
        resolve(new Blob(compressedChunks, { type: recorder.mimeType || mimeType }));
      };
    });

    const drawFrame = () => {
      if (videoEl.paused || videoEl.ended) return;
      context.drawImage(videoEl, 0, 0, width, height);
      rafId = requestAnimationFrame(drawFrame);
    };

    recorder.start(300);
    await videoEl.play();
    drawFrame();

    await new Promise((resolve, reject) => {
      videoEl.onended = resolve;
      videoEl.onerror = () => reject(new Error('Failed during compression playback'));
    });

    if (rafId !== null) {
      cancelAnimationFrame(rafId);
      rafId = null;
    }
    recorder.stop();
    const compressedBlob = await recorderStopped;
    if (!compressedBlob || compressedBlob.size === 0 || compressedBlob.size >= sourceBlob.size) {
      return sourceBlob;
    }

    const reductionPct = Math.round(((sourceBlob.size - compressedBlob.size) / sourceBlob.size) * 100);
    addActivity('Recording', `Compressed clip by ${reductionPct}% before upload`);
    return compressedBlob;
  } catch (error) {
    console.warn('Recording compression failed, uploading original clip', error);
    return sourceBlob;
  } finally {
    if (rafId !== null) {
      cancelAnimationFrame(rafId);
    }
    if (captureStream) {
      captureStream.getTracks().forEach((track) => track.stop());
    }
    videoEl.pause();
    videoEl.removeAttribute('src');
    videoEl.load();
    URL.revokeObjectURL(sourceUrl);
  }
};

const teardownPeerConnection = (streamSessionId) => {
  if (!streamSessionId) {
    // Teardown all
    for (const [sid, conn] of peerConnections.entries()) {
      conn.close();
    }
    peerConnections.clear();
    remoteStreams.clear();
    pendingCandidatesMap.clear();
    connectedPeers.clear();
    webrtcConnected = false;
    hasWebrtcEverConnected = false;
    clearClientStream();
    return;
  }

  if (peerConnections.has(streamSessionId)) {
    const conn = peerConnections.get(streamSessionId);
    conn.close();
    peerConnections.delete(streamSessionId);
  }
  remoteStreams.delete(streamSessionId);
  pendingCandidatesMap.delete(streamSessionId);
  connectedPeers.delete(streamSessionId);

  if (peerSessionId === streamSessionId) {
    peerSessionId = null;
    peerTargetDeviceId = null;
    webrtcConnected = false;
    hasWebrtcEverConnected = false;
  }

  if (store.get().activeStreamSessionId === streamSessionId) {
    clearClientStream();
  }
};

const queueRemoteCandidate = ({ streamSessionId, fromDeviceId, data }) => {
  if (!streamSessionId || !fromDeviceId || !data) return;
  if (!pendingCandidatesMap.has(streamSessionId)) {
    pendingCandidatesMap.set(streamSessionId, []);
  }
  const queue = pendingCandidatesMap.get(streamSessionId);
  queue.push({ streamSessionId, fromDeviceId, data, createdAt: Date.now() });
  const cutoff = Date.now() - 120000;
  pendingCandidatesMap.set(streamSessionId, queue.filter((item) => item.createdAt >= cutoff).slice(-200));
};

const takeQueuedCandidates = (streamSessionId, fromDeviceId) => {
  if (!pendingCandidatesMap.has(streamSessionId)) return [];
  const queue = pendingCandidatesMap.get(streamSessionId);
  const queued = queue.filter((item) => item.fromDeviceId === fromDeviceId);
  pendingCandidatesMap.set(streamSessionId, queue.filter((item) => item.fromDeviceId !== fromDeviceId));
  return queued;
};

const applyQueuedCandidates = async (connection, streamSessionId, fromDeviceId) => {
  if (!connection?.remoteDescription) return;
  const queued = takeQueuedCandidates(streamSessionId, fromDeviceId);
  for (const candidate of queued) {
    try {
      await connection.addIceCandidate(new RTCIceCandidate(candidate.data));
    } catch (error) {
      console.warn('Dropping queued ICE candidate', error);
    }
  }
};

const ensurePeerConnection = async ({
  streamSessionId,
  targetDeviceId,
  asCamera,
}) => {
  if (peerConnections.has(streamSessionId)) {
    return peerConnections.get(streamSessionId);
  }

  const connection = new RTCPeerConnection(rtcConfig);
  peerConnections.set(streamSessionId, connection);

  if (asCamera) {
    peerSessionId = streamSessionId;
    peerTargetDeviceId = targetDeviceId;
  }

  connection.onicecandidate = (event) => {
    if (!socket || !event.candidate) return;
    socket.emit('webrtc:signal', {
      toDeviceId: targetDeviceId,
      streamSessionId: streamSessionId,
      signalType: 'candidate',
      data: event.candidate.toJSON(),
    });
  };

  connection.onconnectionstatechange = () => {
    if (connection.connectionState === 'connected') {
      addActivity('WebRTC', `Peer connected for ${streamSessionId}`);
      connectedPeers.add(streamSessionId);
      if (asCamera) {
        webrtcConnected = true;
        hasWebrtcEverConnected = true;
        stopFrameRelay();
      }
    } else if (
      connection.connectionState === 'failed' ||
      connection.connectionState === 'disconnected' ||
      connection.connectionState === 'closed'
    ) {
      addActivity('WebRTC', `Peer ${connection.connectionState} for ${streamSessionId}`);
      connectedPeers.delete(streamSessionId);
      if (asCamera) {
        if (!hasWebrtcEverConnected) webrtcConnected = false;
        if (connection.connectionState === 'failed' || connection.connectionState === 'closed') {
          hasWebrtcEverConnected = false;
        }
      }
      if (store.get().device?.role === 'client' && store.get().activeStreamSessionId === streamSessionId) {
        if (connection.connectionState === 'failed' || connection.connectionState === 'closed') {
          clearClientStream();
        }
      }
    }
  };

  connection.ontrack = (event) => {
    if (streamTimers.has(streamSessionId)) {
      clearTimeout(streamTimers.get(streamSessionId));
      streamTimers.delete(streamSessionId);
    }
    const [stream] = event.streams;
    if (!stream) return;

    connectedPeers.add(streamSessionId);
    remoteStreams.set(streamSessionId, stream);

    if (store.get().activeStreamSessionId === streamSessionId) {
      const videoEl = $('clientStreamVideo');
      if (videoEl) {
        videoEl.srcObject = stream;
        setClientStreamMode('video');
        void videoEl.play().catch(() => { });
        store.notify(); // Re-render to show active feed
      }
    } else {
      // If not active, play it hidden anyway so it connects properly
      const tempVideo = document.createElement('video');
      tempVideo.srcObject = stream;
      tempVideo.muted = true;
      tempVideo.playsInline = true;
      void tempVideo.play().catch(() => { });
      store.notify(); // Re-render to show stream active in list
    }
  };

  if (asCamera) {
    const ready = await startCameraPreview();
    if (!ready || !localCameraStream || localCameraStream.getVideoTracks().length === 0) {
      throw new Error('Camera stream unavailable for WebRTC publish');
    }
    if (localCameraStream) {
      localCameraStream.getTracks().forEach((track) => connection.addTrack(track, localCameraStream));
    }
  }

  return connection;
};

const startOfferToClient = async (streamSessionId, requesterDeviceId) => {
  if (!socket) return;

  const connection = await ensurePeerConnection({
    streamSessionId,
    targetDeviceId: requesterDeviceId,
    asCamera: true,
  });

  const offer = await connection.createOffer();
  await connection.setLocalDescription(offer);
  socket.emit('webrtc:signal', {
    toDeviceId: requesterDeviceId,
    streamSessionId,
    signalType: 'offer',
    data: offer,
  });
};

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const finalizeRecordingForStream = async (streamSessionId, captureResult) => {
  const currentDevice = store.get().device;
  if (!currentDevice?.id) {
    addActivity('Recording', 'No device identity for finalize');
    return false;
  }

  for (let attempt = 0; attempt < 8; attempt += 1) {
    const recs = await API.ops.listRecordings().catch(() => ({ recordings: [] }));
    const recording = (recs.recordings || []).find((rec) => rec.streamSessionId === streamSessionId && rec.status === 'awaiting_upload');

    if (recording?.id) {
      try {
        if (!captureResult?.blob || captureResult.blob.size === 0) {
          throw new Error('No captured video blob to upload');
        }
        const compressedBlob = await compressRecordingBlob(captureResult.blob);

        const uploadMeta = await API.request('/videos/upload-url', {
          method: 'POST',
          body: JSON.stringify({
            fileName: `stream-${streamSessionId}.webm`,
            deviceId: currentDevice.id,
            prefix: 'recordings',
            recordingId: recording.id,
          }),
        });

        const uploadResponse = await fetch(uploadMeta.uploadUrl, {
          method: 'PUT',
          headers: { 'Content-Type': compressedBlob.type || 'video/webm' },
          body: compressedBlob,
        });

        if (!uploadResponse.ok) {
          throw new Error(`Upload failed with status ${uploadResponse.status}`);
        }

        await API.events.finalizeRecording(recording.id, {
          objectKey: uploadMeta.objectKey,
          bucket: uploadMeta.bucket,
          durationSeconds: captureResult.durationSeconds,
          sizeBytes: compressedBlob.size,
        });

        addActivity('Recording', 'Recording uploaded and finalized');
        return true;
      } catch (error) {
        console.error('Recording upload failed, falling back to simulated key', error);
        const fallbackObjectKey = `sim/${streamSessionId}/${Date.now()}.webm`;
        await API.events.finalizeRecording(recording.id, {
          objectKey: fallbackObjectKey,
          durationSeconds: captureResult?.durationSeconds ?? 15,
          sizeBytes: captureResult?.blob?.size ?? 5000000,
        });
        addActivity('Recording', 'Upload failed; finalized with simulator fallback');
        return true;
      }
    }

    await sleep(350);
  }

  addActivity('Recording', 'No recording row found to finalize');
  return false;
};

const uploadStandaloneMotionRecording = async (captureResult) => {
  const currentDevice = store.get().device;
  if (!currentDevice?.id) {
    addActivity('Recording', 'Cannot upload motion clip without device identity');
    return false;
  }

  if (!captureResult?.blob || captureResult.blob.size === 0) {
    addActivity('Recording', 'No motion clip captured for upload');
    return false;
  }

  try {
    const compressedBlob = await compressRecordingBlob(captureResult.blob);
    const uploadMeta = await API.request('/videos/upload-url', {
      method: 'POST',
      body: JSON.stringify({
        fileName: `motion-${Date.now()}.webm`,
        deviceId: currentDevice.id,
        prefix: 'recordings',
        eventId: lastMotionEventId,
      }),
    });

    const uploadResponse = await fetch(uploadMeta.uploadUrl, {
      method: 'PUT',
      headers: { 'Content-Type': compressedBlob.type || 'video/webm' },
      body: compressedBlob,
    });

    if (!uploadResponse.ok) {
      throw new Error(`Upload failed with status ${uploadResponse.status}`);
    }

    await API.events.finalizeRecording(uploadMeta.video.id, {
      objectKey: uploadMeta.objectKey,
      bucket: uploadMeta.bucket,
      durationSeconds: captureResult.durationSeconds,
      sizeBytes: compressedBlob.size,
    });

    addActivity('Recording', `Motion clip uploaded (${uploadMeta.objectKey})`);
    return true;
  } catch (error) {
    console.error('Standalone motion upload failed', error);
    addActivity('Recording', 'Standalone motion upload failed');
    return false;
  }
};

const connectSocket = () => {
  const { deviceToken } = store.get();
  if (!deviceToken) return;

  if (socket) socket.disconnect();

  socket = io({ auth: { token: deviceToken } });

  socket.on('connect', () => {
    store.update({ socketConnected: true });
    addActivity('System', 'Connected to realtime server');
    if (store.get().device?.role === 'camera') {
      startCameraPreview();
    }
  });

  socket.on('disconnect', () => {
    store.update({ socketConnected: false });
    stopFrameRelay();
    void stopLocalRecording();
    teardownPeerConnection();
    store.update({ activeCameraDeviceId: null, activeStreamSessionId: null });
  });

  // Handle commands (as Camera)
  socket.on('command:received', async (payload) => {
    addActivity('Command', `Received ${payload.commandType}`);

    try {
      if (payload.commandType === 'start_stream') {
        const streamId = payload.payload.streamSessionId;
        const ready = await startCameraPreview();
        if (!ready) {
          throw new Error('Camera permission is required before streaming');
        }
        activeRecordingStreamSessionId = streamId;
        await API.streams.accept(streamId);
        await API.streams.getPublishCreds(streamId);
        await startLocalRecording();
        if (payload.sourceDeviceId) {
          await startOfferToClient(streamId, payload.sourceDeviceId);
          frameRelayStartTimer = setTimeout(() => {
            if (!webrtcConnected && !hasWebrtcEverConnected) {
              void startFrameRelay(streamId, payload.sourceDeviceId);
            }
          }, 2500);
          addActivity('Stream', 'Accepted & Published');
        }

        socket.emit('command:ack', { commandId: payload.commandId, status: 'acknowledged' });
      }
    } catch (e) {
      socket.emit('command:ack', { commandId: payload.commandId, status: 'rejected', error: e.message });
    }
  });

  // Handle Events (as Client)
  socket.on('motion:detected', (payload) => {
    const cameraDeviceId = payload.cameraDeviceId || payload.deviceId;
    addActivity('Motion', `${getCameraLabel(cameraDeviceId)} has detected movement`);
    Toast.show('Motion Detected!', 'info');
    pushMotionNotification(cameraDeviceId);

    // Auto display this camera's active stream on motion
    if (cameraDeviceId) {
      store.update({ activeCameraDeviceId: cameraDeviceId });
      const existingSession = store.get().activeStreamSessionId;
      // If we don't know the exact session ID associated, requestStream will fetch a new one or join
      // For simplicity, directly requesting stream again is fine (idempotent setup).
      Actions.requestStream(cameraDeviceId);
    }
  });

  socket.on('stream:started', async (payload) => {
    addActivity('Stream', 'Stream is live, connecting...');

    // Always store latest session ID for the camera
    if (payload.cameraDeviceId === store.get().activeCameraDeviceId) {
      store.update({ activeStreamSessionId: payload.streamSessionId });
    }

    // Track camera to stream session map
    store.update({
      cameraSessions: {
        ...(store.get().cameraSessions || {}),
        [payload.cameraDeviceId]: payload.streamSessionId
      }
    });

    try {
      await API.streams.getSubscribeCreds(payload.streamSessionId);
      console.log(`Connected to Stream ${payload.streamSessionId}`);

      streamTimers.set(payload.streamSessionId, setTimeout(() => {
        if (!remoteStreams.has(payload.streamSessionId)) {
          console.log(`Stream connected but no video received for ${payload.streamSessionId}`);
          addActivity('Stream', `No remote video track received for ${payload.streamSessionId}`);
        }
      }, 6000));
    } catch (e) {
      console.error('Stream connect failed', e);
    }
  });

  socket.on('stream:frame', (payload) => {
    if (connectedPeers.has(payload.streamSessionId)) return;
    if (!payload?.frame) return;

    if (streamTimers.has(payload.streamSessionId)) {
      clearTimeout(streamTimers.get(payload.streamSessionId));
      streamTimers.delete(payload.streamSessionId);
    }

    if (payload.streamSessionId === store.get().activeStreamSessionId) {
      const imageEl = $('clientStreamImage');
      if (!imageEl) return;
      imageEl.src = payload.frame;
      imageEl.classList.remove('hidden');
      const videoEl = $('clientStreamVideo');
      if (videoEl) {
        videoEl.classList.add('hidden');
      }
      setClientStreamMode('image');
    }
  });

  socket.on('stream:ended', async (payload) => {
    if (payload?.streamSessionId) {
      const streamSessionId = payload.streamSessionId;
      teardownPeerConnection(payload.streamSessionId);
      if (streamSessionId === store.get().activeStreamSessionId) {
        store.update({ activeStreamSessionId: null });
      }

      if (store.get().device?.role === 'camera') {
        const shouldFinalize =
          activeRecordingStreamSessionId === streamSessionId || activeMediaRecorder?.state === 'recording';

        if (shouldFinalize) {
          const captureResult = await stopLocalRecording();
          await finalizeRecordingForStream(streamSessionId, captureResult);
        }

        if (activeRecordingStreamSessionId === streamSessionId) {
          activeRecordingStreamSessionId = null;
        }
      }
    }
  });

  socket.on('webrtc:signal', async (payload) => {
    const device = store.get().device;
    if (!device || !payload?.streamSessionId || !payload?.signalType || !payload?.fromDeviceId) return;

    try {
      if (payload.signalType === 'offer') {
        if (device.role !== 'client') return;
        addActivity('WebRTC', 'Offer received');
        const connection = await ensurePeerConnection({
          streamSessionId: payload.streamSessionId,
          targetDeviceId: payload.fromDeviceId,
          asCamera: false,
        });
        await connection.setRemoteDescription(new RTCSessionDescription(payload.data));
        await applyQueuedCandidates(connection, payload.streamSessionId, payload.fromDeviceId);
        const answer = await connection.createAnswer();
        await connection.setLocalDescription(answer);
        socket.emit('webrtc:signal', {
          toDeviceId: payload.fromDeviceId,
          streamSessionId: payload.streamSessionId,
          signalType: 'answer',
          data: answer,
        });
        addActivity('WebRTC', 'Answer sent');
        return;
      }

      if (payload.signalType === 'answer') {
        const conn = peerConnections.get(payload.streamSessionId);
        if (device.role !== 'camera' || !conn) return;

        if (conn.signalingState !== 'have-local-offer') {
          if (conn.signalingState === 'stable' && conn.remoteDescription?.type === 'answer') {
            return;
          }
          return;
        }
        await conn.setRemoteDescription(new RTCSessionDescription(payload.data));
        await applyQueuedCandidates(conn, payload.streamSessionId, payload.fromDeviceId);
        addActivity('WebRTC', 'Answer received and applied');
        return;
      }

      if (payload.signalType === 'candidate') {
        if (!payload.data) return;
        const conn = peerConnections.get(payload.streamSessionId);
        if (!conn) {
          queueRemoteCandidate(payload);
          return;
        }
        if (!conn.remoteDescription) {
          queueRemoteCandidate(payload);
          return;
        }
        await conn.addIceCandidate(new RTCIceCandidate(payload.data));
        return;
      }

      if (payload.signalType === 'hangup') {
        teardownPeerConnection(payload.streamSessionId);
        if (store.get().activeStreamSessionId === payload.streamSessionId) {
          store.update({ activeStreamSessionId: null });
        }
        addActivity('Stream', 'Remote stream ended');
      }
    } catch (error) {
      console.error('Failed handling WebRTC signal', error);
      Toast.show('WebRTC negotiation failed', 'error');
    }
  });

  socket.on('error:webrtc_signal', (payload) => {
    const message = payload?.message || 'WebRTC signaling error';
    addActivity('WebRTC', message);
    Toast.show(message, 'error');
  });
};

const startPolling = () => {
  if (pollInterval) clearInterval(pollInterval);

  const poller = async () => {
    const { device, screen } = store.get();
    if (!device) return;

    if (screen === 'home' && device.role === 'client') {
      const [recs, links, deviceList] = await Promise.all([
        API.ops.listRecordings().catch(() => ({ recordings: [] })),
        API.devices.listLinks().catch(() => ({ links: [] })),
        API.devices.list().catch(() => ({ devices: [] })),
      ]);

      const cameraById = new Map(
        (deviceList.devices || [])
          .filter((entry) => entry.role === 'camera')
          .map((entry) => [entry.id, entry]),
      );

      const linkedCameras = (links.links || []).map((link) => {
        const camera = cameraById.get(link.cameraDeviceId);
        return {
          ...link,
          cameraName: camera?.name ?? null,
          cameraStatus: camera?.status ?? 'offline',
        };
      });

      store.update({
        recordings: recs.recordings || [],
        linkedCameras,
      });

      // Request streams for all linked cameras if not already requested
      for (const link of linkedCameras) {
        if (!requestedStreams.has(link.cameraDeviceId)) {
          requestedStreams.add(link.cameraDeviceId);
          void Actions.requestStream(link.cameraDeviceId);
        }
      }
    }

    if (screen === 'activity') {
      // maybe poll notifications
    }
  };

  poller();
  pollInterval = setInterval(poller, 5000);
};

// --- Actions ---

const Actions = {
  toggleAuthMode: () => {
    const isRegistering = !$('authNameField').classList.contains('hidden');
    if (isRegistering) {
      $('authNameField').classList.add('hidden');
      $('signInBtn').textContent = 'Sign In';
      $('toggleAuthModeBtn').textContent = 'Create an account';
    } else {
      $('authNameField').classList.remove('hidden');
      $('signInBtn').textContent = 'Create Account';
      $('toggleAuthModeBtn').textContent = 'I already have an account';
    }
  },

  submitAuth: async () => {
    const email = $('authEmail').value;
    const password = $('authPassword').value;
    const name = $('authName').value || email.split('@')[0];
    const isRegistering = !$('authNameField').classList.contains('hidden');

    try {
      if (isRegistering) {
        await API.auth.signUp({ email, password, name });
      }
      await API.auth.signIn({ email, password });
      const session = await API.auth.getSession();
      store.update({ session });
      Toast.show(`Welcome, ${session.user.name}`, 'success');

      // Proceed
      if (store.get().deviceToken) {
        const role = store.get().device?.role;
        if (multiPageMode && currentPageKey === 'auth') {
          if (navigateToScreen('home', { replace: true, role })) return;
        } else {
          navigateBasedOnRole();
        }
        connectSocket();
        startPolling();
      } else {
        if (navigateToScreen('onboarding')) return;
      }
    } catch (e) {
      // handled by API wrapper toast
    }
  },

  selectRole: (role) => {
    $('role').value = role;
    const btnCamera = $('btn-role-camera');
    const btnClient = $('btn-role-client');

    btnCamera.setAttribute('data-active', role === 'camera');
    btnClient.setAttribute('data-active', role === 'client');

    // DaisyUI/Tailwind manual toggle logic for visual feedback
    if (role === 'camera') {
      btnCamera.classList.add('bg-blue-600', 'text-white');
      btnCamera.classList.remove('text-gray-400');
      btnClient.classList.remove('bg-blue-600', 'text-white');
      btnClient.classList.add('text-gray-400');
    } else {
      btnClient.classList.add('bg-blue-600', 'text-white');
      btnClient.classList.remove('text-gray-400');
      btnCamera.classList.remove('bg-blue-600', 'text-white');
      btnCamera.classList.add('text-gray-400');
    }
  },

  registerDevice: async () => {
    const name = $('deviceName').value || 'Web Simulator';
    const role = $('role').value;
    const pushToken = $('pushToken').value;

    try {
      const payload = { name, role, platform: 'web', appVersion: 'sim-2.0' };
      if (pushToken && pushToken.trim().length > 0) {
        payload.pushToken = pushToken.trim();
      }

      const res = await API.devices.register(payload);

      store.update({ device: res.device, deviceToken: res.deviceToken });
      localStorage.setItem('mobileSimDevice', JSON.stringify({ device: res.device, deviceToken: res.deviceToken }));

      Toast.show('Device Registered', 'success');
      navigateBasedOnRole();
      connectSocket();
      startPolling();
    } catch (e) {
      // handled
    }
  },

  signOut: async () => {
    await API.auth.signOut();
    store.update({ session: null, screen: 'auth', device: null, deviceToken: null, socketConnected: false });
    if (socket) socket.disconnect();
    stopFrameRelay();
    await stopLocalRecording();
    teardownPeerConnection();
    stopCameraPreview();
    localStorage.removeItem('mobileSimDevice');
    if (navigateToScreen('auth', { replace: true })) return;
    Toast.show('Signed Out', 'info');
  },

  // Camera Actions
  startMotion: async () => {
    try {
      const res = await API.events.startMotion();
      await startCameraPreview();
      await startLocalRecording();
      store.update({ isMotionActive: true, lastMotionEventId: res.event.id });
      Toast.show('Motion Event Started', 'success');
      addActivity('Motion', 'Started event ' + res.event.id);
    } catch (e) { }
  },

  endMotion: async () => {
    const { lastMotionEventId } = store.get();
    if (!lastMotionEventId) return;
    try {
      const streamSessionId = activeRecordingStreamSessionId;
      if (streamSessionId) {
        await API.streams.end(streamSessionId);
        addActivity('Stream', `Ended stream ${streamSessionId}`);
      } else if (activeMediaRecorder?.state === 'recording') {
        const captureResult = await stopLocalRecording();
        await uploadStandaloneMotionRecording(captureResult);
      }
      await API.events.endMotion(lastMotionEventId);
      store.update({ isMotionActive: false });
      Toast.show('Motion Ended', 'success');
      addActivity('Motion', 'Ended event');
    } catch (e) { }
  },

  // Client Actions
  linkCamera: async () => {
    const id = prompt('Enter Camera Device ID:'); // Simple prompt for now, could be better UI
    if (!id) return;
    try {
      await API.devices.link(id, store.get().device.id);
      Toast.show('Camera Linked', 'success');
      startPolling(); // refresh list
    } catch (e) { }
  },

  renameLinkedCamera: async (cameraDeviceId) => {
    const linked = getLinkedCamera(cameraDeviceId);
    if (!linked?.cameraDeviceId) return;

    const currentName = linked.cameraName?.trim() || '';
    const nextName = prompt('Enter a new camera name:', currentName || getCameraLabel(linked.cameraDeviceId));

    if (nextName == null) return;

    const trimmedName = nextName.trim();
    if (!trimmedName) {
      Toast.show('Camera name cannot be empty', 'error');
      return;
    }

    if (trimmedName === currentName) return;

    try {
      await API.devices.update(linked.cameraDeviceId, { name: trimmedName });
      store.update({
        linkedCameras: store.get().linkedCameras.map((entry) =>
          entry.cameraDeviceId === linked.cameraDeviceId ? { ...entry, cameraName: trimmedName } : entry,
        ),
      });
      Toast.show('Camera Renamed', 'success');
    } catch (e) { }
  },

  deleteLinkedCamera: async (linkId) => {
    const link = store.get().linkedCameras.find((entry) => entry.id === linkId);
    if (!link) return;

    const cameraLabel = getCameraLabel(link.cameraDeviceId, link.cameraName);
    const confirmed = window.confirm(`Remove "${cameraLabel}" from linked cameras?`);
    if (!confirmed) return;

    try {
      await API.devices.unlink(linkId);

      const remaining = store.get().linkedCameras.filter((entry) => entry.id !== linkId);
      const isDeletedCameraActive = store.get().activeCameraDeviceId === link.cameraDeviceId;

      if (isDeletedCameraActive) {
        clearClientStream();
      }

      requestedStreams.delete(link.cameraDeviceId);

      store.update({
        linkedCameras: remaining,
        activeCameraDeviceId: isDeletedCameraActive ? null : store.get().activeCameraDeviceId,
        activeStreamSessionId: isDeletedCameraActive ? null : store.get().activeStreamSessionId,
        openLinkedCameraMenuId: null,
      });

      Toast.show('Camera Link Removed', 'success');
    } catch (e) { }
  },

  requestStream: async (camId) => {
    try {
      console.log(`Requesting Stream from ${camId}...`);
      await API.streams.request(camId);
      // Socket will handle the rest ('stream:started')
    } catch (e) { }
  },

  openRecording: async (recordingId) => {
    try {
      const result = await API.ops.getRecordingDownloadUrl(recordingId);
      if (!result?.downloadUrl) {
        Toast.show('Recording URL unavailable', 'error');
        return;
      }
      const recording = store.get().recordings.find((entry) => entry.id === recordingId);
      const title = recording ? `${new Date(recording.createdAt).toLocaleString()} recording` : 'Recording Playback';
      openRecordingModal(result.downloadUrl, title);
    } catch (e) {
      // handled by API wrapper
    }
  },

  closeRecordingModal: () => {
    closeRecordingModal();
  },

  openMotionNotificationTarget: async (notificationId, cameraDeviceId) => {
    markMotionNotificationRead(notificationId);
    if (!cameraDeviceId) return;

    const recs = await API.ops.listRecordings().catch(() => ({ recordings: [] }));
    const readyRecording = (recs.recordings || [])
      .filter((recording) => recording.cameraDeviceId === cameraDeviceId && recording.status === 'ready')
      .sort((left, right) => new Date(right.createdAt).getTime() - new Date(left.createdAt).getTime())[0];

    if (readyRecording?.id) {
      await Actions.openRecording(readyRecording.id);
      return;
    }

    if (navigateToScreen('home')) return;
    await Actions.requestStream(cameraDeviceId);
  },
};

// --- 5. Rendering ---

const render = (state) => {
  // 1. Screen Visibility
  $$('section[id^="screen-"]').forEach(el => el.classList.add('hidden'));

  const showSectionById = (id) => {
    const element = $(id);
    if (!element) return false;
    element.classList.remove('hidden');
    return true;
  };

  if (state.screen === 'home') {
    const preferredHomeId = state.device?.role === 'camera' ? 'screen-home-camera' : 'screen-home-client';
    if (!showSectionById(preferredHomeId)) {
      const fallbackHomeId = preferredHomeId === 'screen-home-camera' ? 'screen-home-client' : 'screen-home-camera';
      showSectionById(fallbackHomeId);
    }
  } else {
    showSectionById(`screen-${state.screen}`);
  }

  // 2. Top Bar Status
  const statusDot = $('#connectionStatus .status-dot');
  const statusText = $('#connectionStatus span:last-child');
  if (statusDot && statusText) {
    if (state.socketConnected) {
      statusDot.className = 'status-dot status-online transition-colors duration-300';
      statusText.textContent = 'ONLINE';
    } else {
      statusDot.className = 'status-dot status-offline transition-colors duration-300';
      statusText.textContent = 'OFFLINE';
    }
  }

  const authBadge = $('authStatusBadge');
  if (authBadge) {
    if (state.session?.user) {
      authBadge.textContent = state.session.user.email;
      authBadge.classList.add('text-blue-400');
    } else {
      authBadge.textContent = 'Signed Out';
      authBadge.classList.remove('text-blue-400');
    }
  }

  // 3. Bottom Nav Visibility & State
  const nav = $('bottomNav');
  const unreadNotifications = state.motionNotifications.filter((notification) => !notification.isRead).length;
  updateNotificationDot(unreadNotifications > 0);

  if (nav) {
    if (state.session && state.device) {
      nav.classList.remove('hidden');
      $$('.nav-btn').forEach(btn => {
        const target = btn.dataset.target;
        const isActive = target === state.screen || (target === 'home' && (state.screen === 'home-camera' || state.screen === 'home-client'));
        btn.setAttribute('data-active', isActive);
      });
    } else {
      nav.classList.add('hidden');
    }
  }

  // 4. Camera Mode specifics
  if (state.device?.role === 'camera' && state.screen === 'home') {
    const preview = $('cameraPreview');
    const offlineOverlay = $('cameraOfflineOverlay');
    const startMotionBtn = $('startMotionBtn');
    const endMotionBtn = $('endMotionBtn');

    if (!preview || !offlineOverlay || !startMotionBtn || !endMotionBtn) return;

    if (state.socketConnected) {
      offlineOverlay.classList.add('hidden');
      if (state.isMotionActive) {
        preview.classList.remove('bg-black/50');
        preview.classList.add('bg-red-900/20');
        startMotionBtn.classList.add('hidden');
        endMotionBtn.classList.remove('hidden');
        endMotionBtn.disabled = false;
      } else {
        preview.classList.add('bg-black/50');
        preview.classList.remove('bg-red-900/20');
        startMotionBtn.classList.remove('hidden');
        endMotionBtn.classList.add('hidden');
      }
    } else {
      offlineOverlay.classList.remove('hidden');
    }
  }

  // 5. Client Mode Lists
  if (state.device?.role === 'client' && state.screen === 'home') {
    const list = $('linkedCamerasList');
    if (list) {
      if (state.linkedCameras.length === 0) {
        list.innerHTML = `<div class="min-w-full text-center py-8 bg-gray-900/30 rounded-xl border border-dashed border-gray-800"><p class="text-gray-600 text-xs">No cameras linked yet</p></div>`;
      } else {
        list.innerHTML = state.linkedCameras.map(link => {
        const cameraName = getCameraLabel(link.cameraDeviceId, link.cameraName);
        const escapedCameraName = escapeHtml(cameraName);
        const cameraStatus = (link.cameraStatus || '').toLowerCase() === 'online' ? 'Online' : 'Offline';
        const statusDotClass = cameraStatus === 'Online' ? 'bg-green-500' : 'bg-gray-600';

        return `
                <div class="min-w-[240px] max-w-[240px] bg-gray-900/60 rounded-xl border border-white/5 overflow-hidden flex-shrink-0 cursor-pointer hover:border-blue-500/50 transition-colors camera-card" data-camera-id="${link.cameraDeviceId}">
                    <div class="relative overflow-hidden bg-black/40 border-b border-white/5 aspect-video">
                      ${state.activeCameraDeviceId === link.cameraDeviceId
          ? `
                            <div class="absolute inset-0 flex flex-col items-center justify-center gap-2 text-blue-400 bg-blue-500/10">
                              <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
                              </svg>
                              <p class="text-[10px] font-medium uppercase tracking-wider">Viewing</p>
                            </div>
                          `
          : `
                            <div class="absolute inset-0 flex flex-col items-center justify-center gap-2 text-gray-500 hover:text-gray-300 transition-colors">
                              <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
                              </svg>
                              <p class="text-[10px]">${Array.from(connectedPeers).includes(link.cameraDeviceId) ? 'Live Stream Active' : (requestedStreams.has(link.cameraDeviceId) ? 'Connecting...' : 'Click to view')}</p>
                            </div>
                          `
        }
                    </div>
                    <div class="px-3 py-3">
                      <div class="flex items-start justify-between gap-2">
                        <div class="flex items-center gap-2 min-w-0">
                          <div class="w-2 h-2 rounded-full shrink-0 ${statusDotClass}"></div>
                          <div class="min-w-0">
                            <p class="text-xs font-bold text-gray-200 truncate" title="${escapedCameraName}">${escapedCameraName}</p>
                            <p class="text-[10px] text-gray-400 capitalize">${cameraStatus}</p>
                          </div>
                        </div>
                        <div class="relative linked-camera-menu">
                          <button
                            type="button"
                            class="btn btn-ghost btn-xs btn-circle text-gray-400 hover:text-white linked-camera-menu-toggle"
                            data-link-id="${link.id}"
                            aria-haspopup="menu"
                            aria-expanded="${state.openLinkedCameraMenuId === link.id ? 'true' : 'false'}"
                            aria-label="Camera options"
                            title="Camera options">
                            <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
                              <circle cx="10" cy="4" r="1.6" />
                              <circle cx="10" cy="10" r="1.6" />
                              <circle cx="10" cy="16" r="1.6" />
                            </svg>
                          </button>
                          <div
                            class="${state.openLinkedCameraMenuId === link.id ? '' : 'hidden '}absolute right-0 bottom-8 z-20 w-28 overflow-hidden rounded-lg border border-white/10 bg-gray-900/95 shadow-xl backdrop-blur linked-camera-menu-dropdown"
                            role="menu"
                            aria-label="Linked camera actions">
                            <button
                              type="button"
                              class="w-full px-3 py-2 text-left text-xs text-gray-200 hover:bg-white/10 linked-camera-rename-btn"
                              data-camera-id="${link.cameraDeviceId}"
                              role="menuitem">
                              Rename
                            </button>
                            <button
                              type="button"
                              class="w-full px-3 py-2 text-left text-xs text-red-300 hover:bg-red-500/10 linked-camera-delete-btn"
                              data-link-id="${link.id}"
                              role="menuitem">
                              Delete
                            </button>
                          </div>
                        </div>
                      </div>
                    </div>
                </div>
            `;
        }).join('');

        // Show/hide main wrapper
        const viewerWrapper = $('clientStreamViewerWrapper');
        if (viewerWrapper) {
          if (state.activeCameraDeviceId) {
            viewerWrapper.classList.remove('hidden');
            const title = $('clientStreamViewerTitle');
            if (title) title.textContent = `Live Feed: ${getCameraLabel(state.activeCameraDeviceId)}`;
          } else {
            viewerWrapper.classList.add('hidden');
          }
        }

        if (state.activeCameraDeviceId) {
          // Find session ID for active camera if known
          let foundSessionId = state.activeStreamSessionId;
          const sessions = state.cameraSessions || {};
          if (!foundSessionId && sessions[state.activeCameraDeviceId]) {
            foundSessionId = sessions[state.activeCameraDeviceId];
          }

          const currentStream = foundSessionId ? remoteStreams.get(foundSessionId) : null;
          if (currentStream) {
            const videoEl = $('clientStreamVideo');
            if (videoEl && videoEl.srcObject !== currentStream) {
              videoEl.srcObject = currentStream;
              setClientStreamMode('video');
              $('clientLiveDot')?.classList.remove('hidden');
              // Only play if it's not already playing to prevent interruptions
              if (videoEl.paused) {
                void videoEl.play().catch(() => { });
              }
            }
          } else {
            $('clientLiveDot')?.classList.add('hidden');
          }
        } else {
          $('clientLiveDot')?.classList.add('hidden');
        }

        const imageEl = $('clientStreamImage');
        if (imageEl && !imageEl.dataset.errorBound) {
          imageEl.dataset.errorBound = '1';
          imageEl.addEventListener('error', () => {
            const videoEl = $('clientStreamVideo');
            if (videoEl) {
              videoEl.classList.add('hidden');
            }
            setClientStreamMode('unavailable');
          });
        }
      }
    }

    const recList = $('recordingsList');
    if (recList) {
      if (state.recordings.length === 0) {
        recList.innerHTML = `<div class="text-center py-4 bg-gray-900/30 rounded-xl"><p class="text-gray-600 text-xs text-center">No recordings found</p></div>`;
      } else {
        recList.innerHTML = state.recordings.slice(0, 5).map(rec => `
        <div class="flex items-center justify-between p-3 bg-gray-900/40 rounded-lg border border-white/5 hover:bg-gray-800 transition-colors">
            <div class="flex flex-col">
                <span class="text-xs font-medium text-gray-300">${new Date(rec.createdAt).toLocaleString()}</span>
                <span class="text-[10px] text-gray-500">${rec.durationSeconds != null ? `${rec.durationSeconds}s duration` : 'Duration pending'} · ${rec.status ?? 'unknown'}</span>
            </div>
            <button class="btn btn-xs btn-outline border-white/10 text-gray-400 download-recording-btn" data-recording-id="${rec.id}" ${rec.status === 'ready' ? '' : 'disabled'}>
              View
            </button>
        </div>
      `).join('');
      }
    }
  }

  if (state.screen === 'activity') {
    const activityFeed = $('activityFeedList');
    if (activityFeed) {
      if (state.motionNotifications.length === 0) {
        activityFeed.innerHTML = `
        <div class="text-center py-10 opacity-50">
          <svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10 mx-auto mb-2 text-gray-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
          </svg>
          <p class="text-sm text-gray-500">No notifications yet</p>
        </div>
      `;
      } else {
        activityFeed.innerHTML = state.motionNotifications.map((notification) => `
        <button class="w-full text-left p-3 rounded-lg border border-white/5 ${notification.isRead ? 'bg-gray-900/30' : 'bg-blue-900/20'} motion-notification-btn" data-notification-id="${notification.id}" data-camera-device-id="${notification.cameraDeviceId}">
          <p class="text-xs font-medium text-gray-200">${notification.message}</p>
          <p class="text-[10px] text-gray-500 mt-1">${new Date(notification.createdAt).toLocaleString()}</p>
        </button>
      `).join('');
      }
    }
  }

  // 6. Settings Screen
  if (state.session?.user && state.screen === 'settings') {
    const profileName = $('profileName');
    const profileEmail = $('profileEmail');
    const profileInitials = $('profileInitials');
    if (profileName) profileName.textContent = state.session.user.name;
    if (profileEmail) profileEmail.textContent = state.session.user.email;
    if (profileInitials) profileInitials.textContent = state.session.user.name.charAt(0).toUpperCase();
  }
};

const addActivity = (type, msg) => {
  const list = $('activityFeedList');
  if (list) {
    const item = document.createElement('div');
    item.className = 'p-3 rounded-lg bg-gray-900/40 border border-white/5 flex flex-col gap-1';
    item.innerHTML = `
        <div class="flex justify-between items-start">
            <span class="text-[10px] font-bold text-gray-400 uppercase tracking-wider">${type}</span>
            <span class="text-[10px] text-gray-600">${new Date().toLocaleTimeString()}</span>
        </div>
        <p class="text-xs text-gray-300">${msg}</p>
    `;
    list.prepend(item);
  }

  // Also update camera logs if applicable
  if ($('cameraLogs')) {
    const logLine = document.createElement('div');
    logLine.textContent = `[${new Date().toLocaleTimeString()}] ${type}: ${msg}`;
    $('cameraLogs').prepend(logLine);
  }
};

const updateNotificationDot = (show) => {
  const dot = $('notificationDot');
  if (!dot) return;
  if (show) dot.classList.remove('hidden');
  else dot.classList.add('hidden');
};

// --- 6. Event Listeners ---

const bind = (id, eventName, handler) => {
  const element = $(id);
  if (!element) return;
  element.addEventListener(eventName, handler);
};

bind('toggleAuthModeBtn', 'click', Actions.toggleAuthMode);
bind('signInBtn', 'click', Actions.submitAuth);
bind('registerBtn', 'click', Actions.registerDevice);
bind('loadSavedBtn', 'click', () => { /* Handle legacy loading if needed */ });
$$('#screen-onboarding [data-role]').forEach((btn) => {
  btn.addEventListener('click', () => Actions.selectRole(btn.dataset.role));
});
bind('recordingsList', 'click', (event) => {
  const target = event.target.closest('.download-recording-btn');
  if (!target || target.disabled) return;
  const recordingId = target.dataset.recordingId;
  if (!recordingId) return;
  Actions.openRecording(recordingId);
});
bind('activityFeedList', 'click', (event) => {
  const target = event.target.closest('.motion-notification-btn');
  if (!target) return;
  const notificationId = target.dataset.notificationId;
  const cameraDeviceId = target.dataset.cameraDeviceId;
  if (!notificationId || !cameraDeviceId) return;
  Actions.openMotionNotificationTarget(notificationId, cameraDeviceId);
});

$('linkedCamerasList')?.addEventListener('click', (event) => {
  const menuToggleBtn = event.target.closest('.linked-camera-menu-toggle');
  if (menuToggleBtn) {
    event.stopPropagation();
    const linkId = menuToggleBtn.dataset.linkId;
    if (!linkId) return;

    const { openLinkedCameraMenuId } = store.get();
    store.update({
      openLinkedCameraMenuId: openLinkedCameraMenuId === linkId ? null : linkId,
    });
    return;
  }

  const renameBtn = event.target.closest('.linked-camera-rename-btn');
  if (renameBtn) {
    event.stopPropagation();
    const cameraId = renameBtn.dataset.cameraId;
    if (cameraId) {
      store.update({ openLinkedCameraMenuId: null });
      void Actions.renameLinkedCamera(cameraId);
    }
    return;
  }

  const deleteBtn = event.target.closest('.linked-camera-delete-btn');
  if (deleteBtn) {
    event.stopPropagation();
    const linkId = deleteBtn.dataset.linkId;
    if (linkId) {
      store.update({ openLinkedCameraMenuId: null });
      void Actions.deleteLinkedCamera(linkId);
    }
    return;
  }

  const card = event.target.closest('.camera-card');
  if (!card) return;
  const camId = card.dataset.cameraId;
  if (camId) {
    const sessions = store.get().cameraSessions || {};
    store.update({
      activeCameraDeviceId: camId,
      activeStreamSessionId: sessions[camId] || null,
      openLinkedCameraMenuId: null,
    });
    // If not currently streamed or requested, kick off request
    if (!requestedStreams.has(camId)) {
      requestedStreams.add(camId);
      Actions.requestStream(camId);
    } else {
      // Just re-render
      store.notify();
    }
  }
});

document.addEventListener('click', (event) => {
  if (!event.target.closest('.linked-camera-menu')) {
    const { openLinkedCameraMenuId } = store.get();
    if (openLinkedCameraMenuId) {
      store.update({ openLinkedCameraMenuId: null });
    }
  }
});

// Navbar
$$('.nav-btn').forEach(btn => {
  btn.addEventListener('click', () => {
    if (btn.dataset.target === 'activity') {
      markAllNotificationsRead();
    }
    if (navigateToScreen(btn.dataset.target)) return;
  });
});

// Camera Controls
bind('cameraGoOnlineBtn', 'click', async () => {
  if (store.get().device?.role === 'camera') {
    await startCameraPreview();
  }
  connectSocket();
});
bind('startMotionBtn', 'click', Actions.startMotion);
bind('endMotionBtn', 'click', Actions.endMotion);

// Client Controls
bind('linkCameraBtn', 'click', Actions.linkCamera);
bind('refreshClientBtn', 'click', startPolling);

// Settings
bind('signOutBtn', 'click', Actions.signOut);
bind('clearActivityBtn', 'click', () => {
  store.update({ motionNotifications: [] });
});
bind('recordingModalCloseBtn', 'click', Actions.closeRecordingModal);
bind('recordingModal', 'click', (event) => {
  if (event.target === $('recordingModal')) {
    Actions.closeRecordingModal();
  }
});
$('closeStreamViewerBtn')?.addEventListener('click', () => {
  store.update({ activeCameraDeviceId: null, activeStreamSessionId: null });
});

// Init
store.subscribe(render);
init();

window.addEventListener('beforeunload', () => {
  stopFrameRelay();
  void stopLocalRecording();
  teardownPeerConnection();
  stopCameraPreview();
});

window.Actions = Actions;
