impl UpstreamMediaRuntime {
    /// Activate a camera stream as the current owner for the session.
    #[must_use]
    pub fn activate_camera(&self) -> UpstreamStreamLease {
        self.activate(UpstreamMediaKind::Camera, true)
    }

    /// Activate a microphone stream as the current owner for the session.
    #[must_use]
    pub fn activate_microphone(&self) -> UpstreamStreamLease {
        self.activate(UpstreamMediaKind::Microphone, true)
    }

    /// Reserve the single live microphone sink slot for one generation.
    ///
    /// Inputs: the microphone lease generation that wants to own the UAC sink.
    /// Outputs: an owned semaphore permit while that generation still owns the
    /// microphone slot, or `None` if a newer stream superseded it before the
    /// previous sink fully stood down.
    /// Why: ALSA only allows one live owner of the UAC playback device, so a
    /// replacement stream must wait for the old owner to release the sink
    /// before opening a new playback pipeline.
    pub async fn reserve_microphone_sink(&self, generation: u64) -> Option<OwnedSemaphorePermit> {
        let permit = self
            .microphone_sink_gate
            .clone()
            .acquire_owned()
            .await
            .ok()?;
        self.is_microphone_active(generation).then_some(permit)
    }

    fn activate(&self, kind: UpstreamMediaKind, reset_on_replace: bool) -> UpstreamStreamLease {
        let generation = match kind {
            UpstreamMediaKind::Camera => {
                self.next_camera_generation.fetch_add(1, Ordering::SeqCst) + 1
            }
            UpstreamMediaKind::Microphone => {
                self.next_microphone_generation
                    .fetch_add(1, Ordering::SeqCst)
                    + 1
            }
        };

        let mut state = self
            .state
            .lock()
            .expect("upstream media state mutex poisoned");
        let replacing_existing_owner = match kind {
            UpstreamMediaKind::Camera => state.active_camera_generation.is_some(),
            UpstreamMediaKind::Microphone => state.active_microphone_generation.is_some(),
        };
        let starting_new_session =
            state.active_camera_generation.is_none() && state.active_microphone_generation.is_none();
        if starting_new_session {
            state.session_id = self.next_session_id.fetch_add(1, Ordering::SeqCst) + 1;
            reset_timing_anchors(&mut state);
            state.session_started_at = Some(tokio::time::Instant::now());
            state.phase = UpstreamSyncPhase::Acquiring;
            state.last_reason = "new upstream session acquiring media".to_string();
        } else if reset_on_replace && replacing_existing_owner {
            reset_timing_anchors(&mut state);
            state.session_started_at = Some(tokio::time::Instant::now());
            state.phase = UpstreamSyncPhase::Acquiring;
            state.last_reason = "upstream stream replacement reset media anchors".to_string();
        }
        match kind {
            UpstreamMediaKind::Camera => state.active_camera_generation = Some(generation),
            UpstreamMediaKind::Microphone => state.active_microphone_generation = Some(generation),
        }
        UpstreamStreamLease {
            session_id: state.session_id,
            generation,
        }
    }

    /// Return whether the supplied camera lease is still the active owner.
    #[must_use]
    pub fn is_camera_active(&self, generation: u64) -> bool {
        self.is_active(UpstreamMediaKind::Camera, generation)
    }

    /// Return whether the supplied microphone lease is still the active owner.
    #[must_use]
    pub fn is_microphone_active(&self, generation: u64) -> bool {
        self.is_active(UpstreamMediaKind::Microphone, generation)
    }

    fn is_active(&self, kind: UpstreamMediaKind, generation: u64) -> bool {
        let state = self
            .state
            .lock()
            .expect("upstream media state mutex poisoned");
        match kind {
            UpstreamMediaKind::Camera => state.active_camera_generation == Some(generation),
            UpstreamMediaKind::Microphone => state.active_microphone_generation == Some(generation),
        }
    }

    /// Mark a camera stream as closed if it still owns the camera slot.
    pub fn close_camera(&self, generation: u64) {
        self.close(UpstreamMediaKind::Camera, generation);
    }

    /// Mark a microphone stream as closed if it still owns the microphone slot.
    pub fn close_microphone(&self, generation: u64) {
        self.close(UpstreamMediaKind::Microphone, generation);
    }

    /// Supersede any current microphone owner without touching the USB gadget.
    ///
    /// Inputs: none.
    /// Outputs: none.
    /// Why: UAC recovery should force the active gRPC/audio sink to drain and
    /// reconnect, not reset UDC or disturb UVC/HID.
    pub fn soft_recover_microphone(&self) {
        let lease = self.activate(UpstreamMediaKind::Microphone, false);
        self.close_microphone(lease.generation);
    }

    fn close(&self, kind: UpstreamMediaKind, generation: u64) {
        let mut state = self
            .state
            .lock()
            .expect("upstream media state mutex poisoned");
        match kind {
            UpstreamMediaKind::Camera if state.active_camera_generation == Some(generation) => {
                state.active_camera_generation = None;
            }
            UpstreamMediaKind::Microphone
                if state.active_microphone_generation == Some(generation) =>
            {
                state.active_microphone_generation = None;
            }
            _ => return,
        }
        if state.active_camera_generation.is_none() && state.active_microphone_generation.is_none()
        {
            reset_timing_anchors(&mut state);
        }
        self.pairing_state_notify.notify_waiters();
        self.audio_progress_notify.notify_waiters();
    }
}

fn reset_timing_anchors(state: &mut UpstreamClockState) {
    state.session_started_at = None;
    state.first_camera_remote_pts_us = None;
    state.first_microphone_remote_pts_us = None;
    state.latest_camera_remote_pts_us = None;
    state.latest_microphone_remote_pts_us = None;
    state.camera_startup_ready = false;
    state.session_base_remote_pts_us = None;
    state.last_video_local_pts_us = None;
    state.last_audio_local_pts_us = None;
    state.last_video_presented_pts_us = None;
    state.last_audio_presented_pts_us = None;
    state.camera_packet_count = 0;
    state.microphone_packet_count = 0;
    state.startup_anchor_logged = false;
    state.playout_epoch = None;
    state.pairing_anchor_deadline = None;
    state.freshness_reanchors = 0;
    state.stale_audio_drops = 0;
    state.stale_video_drops = 0;
    state.skew_video_drops = 0;
    state.startup_timeouts = 0;
    state.video_freezes = 0;
    state.latest_camera_timing = None;
    state.latest_microphone_timing = None;
    state.recent_camera_timing = Default::default();
    state.recent_microphone_timing = Default::default();
    state.latest_paired_client_capture_skew_ms = None;
    state.latest_paired_client_send_skew_ms = None;
    state.latest_paired_server_receive_skew_ms = None;
    state.latest_camera_presentation = None;
    state.latest_microphone_presentation = None;
    state.client_capture_skew_window_ms = Default::default();
    state.client_send_skew_window_ms = Default::default();
    state.server_receive_skew_window_ms = Default::default();
    state.camera_client_queue_age_window_ms = Default::default();
    state.microphone_client_queue_age_window_ms = Default::default();
    state.sink_handoff_skew_window_ms = Default::default();
    state.camera_sink_late_window_ms = Default::default();
    state.microphone_sink_late_window_ms = Default::default();
    state.phase = UpstreamSyncPhase::Acquiring;
    state.last_reason = "timing anchors reset".to_string();
}
