use std::io;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};

pub fn take_fd(fd: RawFd) -> FdHandle {
    assert!(
        fd >= 0,
        "dope invariant violated: io result cannot be negative"
    );
    unsafe { FdHandle::from_raw(fd) }
}

#[derive(Clone, Copy, Debug)]
pub struct Fd {
    fd: RawFd,
}

impl Fd {
    #[must_use]
    pub const fn new(fd: RawFd) -> Self {
        Self { fd }
    }
}

impl AsRawFd for Fd {
    fn as_raw_fd(&self) -> RawFd {
        self.fd
    }
}

#[derive(Clone, Copy, Debug)]
pub struct FixedFd {
    fd: RawFd,
    fixed: u32,
}

impl FixedFd {
    #[must_use]
    pub const fn new(fd: RawFd, fixed: u32) -> Self {
        Self { fd, fixed }
    }

    pub const fn fixed_index(self) -> u32 {
        self.fixed
    }

    pub const fn as_fd(self) -> Fd {
        Fd::new(self.fd)
    }
}

#[derive(Debug)]
pub struct FdHandle {
    fd: OwnedFd,
}

impl FdHandle {
    /// # Safety
    /// `fd` must be a valid open file descriptor with no other owner.
    #[must_use]
    pub unsafe fn from_raw(fd: RawFd) -> Self {
        Self {
            fd: unsafe { OwnedFd::from_raw_fd(fd) },
        }
    }

    #[must_use]
    pub fn into_owned(self) -> OwnedFd {
        self.fd
    }

    pub fn try_clone(&self) -> io::Result<Self> {
        Ok(Self::from(self.fd.try_clone()?))
    }

    pub fn as_fd(&self) -> Fd {
        Fd::new(self.fd.as_raw_fd())
    }

    pub fn fixed_io(&self, fixed: u32) -> FixedFd {
        FixedFd::new(self.fd.as_raw_fd(), fixed)
    }
}

impl From<OwnedFd> for FdHandle {
    fn from(fd: OwnedFd) -> Self {
        Self { fd }
    }
}

impl AsRawFd for FdHandle {
    fn as_raw_fd(&self) -> RawFd {
        self.fd.as_raw_fd()
    }
}
