use std::{ffi::c_void, mem::ManuallyDrop, path::PathBuf, ptr};

use smol_str::SmolStr;

#[derive(Debug)]
#[repr(C)]
pub struct CVec<T> {
    ptr: *mut T,
    length: usize,
    capacity: usize,
}

impl<T> CVec<T> {
    pub fn into_vec(self) -> Vec<T> {
        let vec = ManuallyDrop::new(self);
        let CVec {
            ptr,
            length,
            capacity,
        } = *vec;
        unsafe { Vec::from_raw_parts(ptr, length, capacity) }
    }

    pub fn from_vec(vec: Vec<T>) -> CVec<T> {
        let mut vec = ManuallyDrop::new(vec);
        let ptr = vec.as_mut_ptr();
        let length = vec.len();
        let capacity = vec.capacity();
        Self {
            ptr,
            length,
            capacity,
        }
    }
}

impl<T> From<Vec<T>> for CVec<T> {
    fn from(vec: Vec<T>) -> Self {
        CVec::from_vec(vec)
    }
}

impl<T> Drop for CVec<T> {
    fn drop(&mut self) {
        let CVec {
            ptr,
            length,
            capacity,
        } = *self;
        let _ = unsafe { Vec::from_raw_parts(ptr, length, capacity) };
    }
}

#[derive(Debug)]
#[repr(C)]
pub struct Utf8String {
    ptr: *mut u8,
    length: usize,
    capacity: usize,
}

impl From<String> for Utf8String {
    fn from(mut value: String) -> Self {
        let ptr = value.as_mut_ptr();
        let length = value.len();
        let capacity = value.capacity();
        Self {
            ptr,
            length,
            capacity,
        }
    }
}

impl From<SmolStr> for Utf8String {
    fn from(value: SmolStr) -> Self {
        let mut value = ManuallyDrop::new(value.to_string());
        let ptr = value.as_mut_ptr();
        let length = value.len();
        let capacity = value.capacity();
        Self {
            ptr,
            length,
            capacity,
        }
    }
}

impl Drop for Utf8String {
    fn drop(&mut self) {
        let Self {
            ptr,
            length,
            capacity,
        } = self;
        let _ = unsafe { String::from_raw_parts(*ptr, *length, *capacity) };
    }
}

#[derive(Debug)]
#[repr(C)]
pub struct CPathBuf {
    ptr: *mut u8,
    length: usize,
    capacity: usize,
}

impl From<PathBuf> for CPathBuf {
    fn from(value: PathBuf) -> Self {
        let mut value = ManuallyDrop::new(value.into_os_string().into_encoded_bytes());
        let ptr = value.as_mut_ptr();
        let length = value.len();
        let capacity = value.capacity();
        Self {
            ptr,
            length,
            capacity,
        }
    }
}

impl Drop for CPathBuf {
    fn drop(&mut self) {
        let Self {
            ptr,
            length,
            capacity,
        } = self;
        let _ = unsafe { Vec::from_raw_parts(*ptr, *length, *capacity) };
    }
}

#[derive(Debug)]
#[repr(C)]
pub struct COption<T>(*const T);

impl<I, O> From<Option<I>> for COption<O>
where
    O: From<I>,
{
    fn from(value: Option<I>) -> Self {
        match value {
            Some(value) => COption(&O::from(value) as *const O),
            None => COption(ptr::null()),
        }
    }
}

impl<T> Drop for COption<T> {
    fn drop(&mut self) {
        let Self(ptr) = self;
        if !ptr.is_null() {
            let _ = unsafe { &**ptr as &T };
        }
    }
}

// #[derive(Debug)]
// #[repr(C)]
// pub enum CResult<T, E> {
//     Ok(T),
//     Err(E),
// }

#[derive(Debug)]
#[repr(C)]
pub struct Pair<A, B>(pub A, pub B);

impl<A, B> From<(A, B)> for Pair<A, B> {
    fn from((a, b): (A, B)) -> Self {
        Self(a, b)
    }
}

/// Take manual ownership of the value by converting it to pointer.
/// One must call `from_ptr` to clean it up.
pub fn into_ptr<T>(value: T) -> *mut T {
    debug_assert_eq!(size_of::<*mut T>(), size_of::<*mut c_void>());
    let boxed = Box::new(value);
    Box::into_raw(boxed)
}

pub unsafe fn from_ptr<T>(ptr: *mut T) -> T {
    debug_assert!(!ptr.is_null());
    debug_assert_eq!(size_of::<*mut T>(), size_of::<*mut c_void>());
    *unsafe { Box::from_raw(ptr) }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn cvec() {
        let vec_in: Vec<usize> = vec![2, 5];
        let c_vec = CVec::from(vec_in.clone());
        let vec_out = c_vec.into_vec();
        assert_eq!(vec_in, vec_out);
    }
}