//! Minimal NCCL FFI bindings (hand-written, like the CUDA bindings in
//! xtrain-cuda). Only the collectives data-parallel training needs:
//! unique-id creation, communicator init/destroy, and AllReduce. Mirrors
//! xserv-distributed's FFI.

use std::ffi::c_void;
use std::os::raw::c_char;
use xtrain_cuda::ffi::CudaStream;

/// Opaque NCCL communicator handle (`ncclComm_t`).
pub type NcclComm = *mut c_void;

/// `ncclUniqueId` is a 128-byte opaque blob shared from rank 0 to every rank.
#[repr(C)]
#[derive(Clone, Copy)]
pub struct NcclUniqueId {
    pub internal: [c_char; 128],
}

impl Default for NcclUniqueId {
    fn default() -> Self {
        Self { internal: [0; 128] }
    }
}

// ncclDataType_t (subset) — DDP all-reduces fp32 gradients.
pub const NCCL_FLOAT32: i32 = 7;

// ncclRedOp_t
pub const NCCL_SUM: i32 = 0;

// ncclResult_t
pub const NCCL_SUCCESS: i32 = 0;

unsafe extern "C" {
    pub fn ncclGetUniqueId(uid: *mut NcclUniqueId) -> i32;
    // ncclUniqueId is passed BY VALUE (a 128-byte struct) per the NCCL ABI.
    pub fn ncclCommInitRank(
        comm: *mut NcclComm,
        nranks: i32,
        commid: NcclUniqueId,
        rank: i32,
    ) -> i32;
    pub fn ncclCommDestroy(comm: NcclComm) -> i32;
    pub fn ncclAllReduce(
        sendbuff: *const c_void,
        recvbuff: *mut c_void,
        count: usize,
        datatype: i32,
        op: i32,
        comm: NcclComm,
        stream: CudaStream,
    ) -> i32;
    pub fn ncclGroupStart() -> i32;
    pub fn ncclGroupEnd() -> i32;
    pub fn ncclGetErrorString(result: i32) -> *const c_char;
}

pub fn err_string(result: i32) -> String {
    unsafe {
        let p = ncclGetErrorString(result);
        if p.is_null() {
            return format!("nccl error {result}");
        }
        std::ffi::CStr::from_ptr(p).to_string_lossy().into_owned()
    }
}

pub fn check(result: i32, what: &str) {
    assert_eq!(
        result,
        NCCL_SUCCESS,
        "{what} failed: {}",
        err_string(result)
    );
}
