impl EyeHub {
    fn spawn<S>(mut stream: S, lease: lesavka_server::capture_power::CapturePowerLease) -> Arc<Self>
    where
        S: Stream<Item = Result<VideoPacket, Status>> + Unpin + Send + 'static,
    {
        let (tx, _) = broadcast::channel(32);
        let running = Arc::new(AtomicBool::new(true));
        let subscribers = Arc::new(AtomicUsize::new(0));
        let tx_for_task = tx.clone();
        let running_for_task = Arc::clone(&running);
        let subscribers_for_task = Arc::clone(&subscribers);
        let task = tokio::spawn(async move {
            let _lease = lease;
            let mut idle_ticks = 0_u32;
            while running_for_task.load(Ordering::Relaxed) {
                match stream.next().await {
                    Some(Ok(pkt)) => {
                        let _ = tx_for_task.send(pkt);
                        if subscribers_for_task.load(Ordering::Relaxed) == 0 {
                            idle_ticks = idle_ticks.saturating_add(1);
                            if idle_ticks >= 60 {
                                break;
                            }
                        } else {
                            idle_ticks = 0;
                        }
                    }
                    Some(Err(err)) => {
                        warn!(?err, "shared eye hub stream error");
                        break;
                    }
                    None => break,
                }
            }
            running_for_task.store(false, Ordering::Relaxed);
        });

        Arc::new(Self {
            tx: tx.clone(),
            running: Arc::clone(&running),
            subscribers: Arc::clone(&subscribers),
            task: Arc::new(Mutex::new(Some(task))),
        })
    }

    fn shutdown(&self) {
        if self.running.swap(false, Ordering::AcqRel)
            && let Ok(mut task) = self.task.try_lock()
            && let Some(task) = task.take()
        {
            task.abort();
        }
    }

    async fn shutdown_and_wait(&self, timeout: Duration) {
        self.running.store(false, Ordering::Release);
        let task = self.task.lock().await.take();
        if let Some(task) = task {
            task.abort();
            let _ = tokio::time::timeout(timeout, task).await;
        }
    }
}

fn eye_hub_reopen_grace() -> Duration {
    std::env::var("LESAVKA_EYE_HUB_REOPEN_GRACE_MS")
        .ok()
        .and_then(|value| value.trim().parse::<u64>().ok())
        .map(Duration::from_millis)
        .unwrap_or_else(|| Duration::from_millis(250))
}

fn take_conflicting_eye_hubs(
    hubs: &mut HashMap<EyeHubKey, Arc<EyeHub>>,
    key: EyeHubKey,
) -> Vec<Arc<EyeHub>> {
    let stale_keys: Vec<_> = hubs
        .iter()
        .filter_map(|(existing_key, hub)| {
            let running = hub.running.load(Ordering::Relaxed);
            let conflicting_source =
                existing_key.source_id == key.source_id && *existing_key != key;
            if !running || conflicting_source {
                Some(*existing_key)
            } else {
                None
            }
        })
        .collect();

    stale_keys
        .into_iter()
        .filter_map(|existing_key| hubs.remove(&existing_key))
        .collect()
}
