// Copyright 2025 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0

use std::cell::Cell;
use std::os::raw::c_int;
use std::time::{Duration, Instant};

const M_MMAP_THRESHOLD: c_int = -3;

/// Minimum interval between two actual `malloc_trim` calls.
///
/// `trim` is called at the end of every `update()` and `view()`, which can
/// reach 60-200 Hz during typical scrolling, resize, or animation. Each
/// `malloc_trim` walks the glibc heap (10 µs to several ms depending on
/// fragmentation), so calling it at render frequency can consume a
/// substantial fraction of the frame budget. Throttling to 1 Hz keeps RSS
/// bounded while removing the per-frame syscall from the hot path.
const TRIM_MIN_INTERVAL: Duration = Duration::from_millis(1000);

thread_local! {
    static LAST_TRIM: Cell<Option<Instant>> = const { Cell::new(None) };
}

unsafe extern "C" {
    fn malloc_trim(pad: usize);

    fn mallopt(param: c_int, value: c_int) -> c_int;
}

/// Throttled wrapper over `malloc_trim`. Safe to call at render frequency:
/// consecutive calls within `TRIM_MIN_INTERVAL` (per-thread) skip the syscall.
#[inline]
pub fn trim(pad: usize) {
    LAST_TRIM.with(|last| {
        let now = Instant::now();
        let should_trim = match last.get() {
            None => true,
            Some(prev) => now.duration_since(prev) >= TRIM_MIN_INTERVAL,
        };
        if should_trim {
            last.set(Some(now));
            unsafe {
                malloc_trim(pad);
            }
        }
    });
}

/// Prevents glibc from hoarding memory via memory fragmentation.
#[inline]
pub fn limit_mmap_threshold(threshold: i32) {
    unsafe {
        mallopt(M_MMAP_THRESHOLD, threshold as c_int);
    }
}
