impl UsbGadget {
    /// Hard-reset the gadget → identical to a physical cable re-plug
    #[cfg(coverage)]
    pub fn cycle(&self) -> Result<()> {
        self.cycle_internal(env::var("LESAVKA_GADGET_FORCE_CYCLE").is_ok())
    }

    #[cfg(coverage)]
    pub fn cycle_forced(&self) -> Result<()> {
        self.cycle_internal(true)
    }

    pub fn recover_enumeration(&self) -> Result<()> {
        self.recover_enumeration_internal()
    }

    #[cfg(coverage)]
    fn cycle_internal(&self, force_cycle: bool) -> Result<()> {
        let ctrl = Self::find_controller().or_else(|_| {
            Self::probe_platform_udc()?.ok_or_else(|| anyhow::anyhow!("no UDC present"))
        })?;

        if !force_cycle {
            match Self::state(&ctrl) {
                Ok(state)
                    if matches!(
                        state.as_str(),
                        "configured" | "addressed" | "default" | "suspended" | "unknown"
                    ) =>
                {
                    return Ok(());
                }
                Err(_) => return Ok(()),
                _ => {}
            }
        }

        let _ = Self::write_attr(self.udc_file, "");
        let _ = Self::wait_state_any(&ctrl, 3_000);
        let _ = Self::rebind_driver(&ctrl);
        let _ = Self::wait_udc_present(&ctrl, 3_000);
        Self::write_attr(self.udc_file, &ctrl)?;
        let _ = Self::wait_state_any(&ctrl, 6_000);
        Ok(())
    }

    #[cfg(not(coverage))]
    pub fn cycle(&self) -> Result<()> {
        self.cycle_internal(env::var("LESAVKA_GADGET_FORCE_CYCLE").is_ok())
    }

    #[cfg(not(coverage))]
    pub fn cycle_forced(&self) -> Result<()> {
        self.cycle_internal(true)
    }

    #[cfg(not(coverage))]
    fn cycle_internal(&self, force_cycle: bool) -> Result<()> {
        /* 0 - ensure we *know* the controller even after a previous crash */
        let ctrl = Self::find_controller().or_else(|_| {
            Self::probe_platform_udc()?.ok_or_else(|| anyhow::anyhow!("no UDC present"))
        })?;
        match Self::state(&ctrl) {
            Ok(state)
                if !force_cycle
                    && matches!(
                        state.as_str(),
                        "configured" | "addressed" | "default" | "suspended"
                    ) =>
            {
                warn!(
                    "🔒 refusing gadget cycle while host attached (state={state}); set LESAVKA_GADGET_FORCE_CYCLE=1 to override"
                );
                return Ok(());
            }
            Ok(state) if !force_cycle && state == "unknown" => {
                warn!(
                    "🔒 refusing gadget cycle with unknown UDC state; set LESAVKA_GADGET_FORCE_CYCLE=1 to override"
                );
                return Ok(());
            }
            Err(_) if !force_cycle => {
                warn!(
                    "🔒 refusing gadget cycle without UDC state; set LESAVKA_GADGET_FORCE_CYCLE=1 to override"
                );
                return Ok(());
            }
            _ => {}
        }

        /* 1 - detach gadget */
        info!("🔌 detaching gadget from {ctrl}");
        // a) drop pull-ups (if the controller offers the switch)
        let sc = Self::soft_connect_path(&ctrl);
        if let Some(sc) = sc.as_deref() {
            let _ = Self::write_attr(sc, "0"); // ignore errors - not all HW has it
        }

        // b) clear the UDC attribute; the kernel may transiently answer EBUSY
        for attempt in 1..=10 {
            match Self::write_attr(self.udc_file, "") {
                Ok(_) => break,
                Err(err)
                    if {
                        // only swallow EBUSY
                        err.downcast_ref::<std::io::Error>()
                            .and_then(|io| io.raw_os_error())
                            == Some(libc::EBUSY)
                            && attempt < 10
                    } =>
                {
                    trace!("⏳ UDC busy (attempt {attempt}/10) - retrying…");
                    thread::sleep(Duration::from_millis(100));
                }
                Err(err) => return Err(err),
            }
        }
        Self::wait_state(&ctrl, "not attached", 3_000)?;

        /* 2 - reset driver */
        Self::rebind_driver(&ctrl)?;

        /* 3 - wait UDC node to re-appear */
        Self::wait_udc_present(&ctrl, 3_000)?;

        /* 4 - re-attach + pull-up */
        info!("🔌 re-attaching gadget to {ctrl}");
        Self::write_attr(self.udc_file, &ctrl)?;
        if let Some(sc) = sc.as_deref() {
            // try to set the pull-up; ignore if the kernel rejects it
            match Self::write_attr(sc, "1") {
                Err(err) => {
                    // only swallow specific errno values
                    if let Some(io) = err.downcast_ref::<std::io::Error>() {
                        match io.raw_os_error() {
                            // EINVAL | EPERM | ENOENT
                            Some(libc::EINVAL) | Some(libc::EPERM) | Some(libc::ENOENT) => {
                                warn!("⚠️ soft_connect unsupported ({io}); continuing");
                            }
                            _ => return Err(err), // propagate all other errors
                        }
                    } else {
                        return Err(err); // non-IO errors: propagate
                    }
                }
                Ok(_) => { /* success */ }
            }
        }

        /* 5 - wait for host (but tolerate sleep) */
        Self::wait_state(&ctrl, "configured", 6_000).or_else(|e| {
            // If the host is physically absent (sleep / KVM paused)
            // we allow 'not attached' and continue - we can still
            // accept keyboard/mouse data and the host will enumerate
            // later without another reset.
            let last = fs::read_to_string(format!("{}/class/udc/{ctrl}/state", Self::sysfs_root()))
                .unwrap_or_default();
            if last.trim() == "not attached" {
                warn!("⚠️ host did not enumerate within 6 s - continuing (state = {last:?})");
                Ok(())
            } else {
                Err(e)
            }
        })?;

        info!("✅ USB-gadget cycle complete");
        Ok(())
    }

}
