// Adapter: implements `crate::fuse::HvfMapper` (the trait FuseServer's
// DAX session uses) by calling into `crate::hvf::Vm`. This is the only
// HVF-coupled piece of the virtio-fs DAX stack; everything else
// (FuseServer, DaxSession, PosixFs) is mockable + testable without
// spinning up a real VM.
//
// Lifetime: one `HvfDaxMapper` per VM. The mapper holds a `'static`
// indirection because hv_vm_map calls are process-wide singletons —
// there's exactly one HVF VM per process — so passing the VM by
// reference around `Arc<dyn HvfMapper>` callers would conflict with
// the existing `MicroVm` ownership in vmm::vstate. The mapper just
// calls `av::hv_vm_map` directly; the existence of `MicroVm` in this
// process is the safety witness.

#![cfg(all(target_os = "macos", target_arch = "aarch64"))]

use applevisor_sys as av;

use crate::fuse::dax::{HvfMapper, DAX_PROT_READ, DAX_PROT_WRITE};
use crate::fuse::Errno;

/// Apple Silicon page granule. All hv_vm_map sizes / GPAs must be a
/// multiple of this.
const PAGE_SIZE: u64 = 0x4000;

/// Adapter that plumbs DaxSession SETUPMAPPING/REMOVEMAPPING into
/// applevisor-sys's hv_vm_map / hv_vm_unmap. Construct one per VM and
/// hand to `FuseServer::set_dax(DaxSession::new(... , mapper))`.
pub struct HvfDaxMapper;

impl HvfDaxMapper {
    pub fn new() -> Self {
        Self
    }
}

impl Default for HvfDaxMapper {
    fn default() -> Self {
        Self::new()
    }
}

fn hvf_to_errno(rc: i32) -> Errno {
    // HVF's hv_return_t is a 32-bit Mach-style status. We map a few
    // common ones to FUSE errnos; everything else becomes EIO. The
    // distinction matters for guest-visible errno on SETUPMAPPING
    // failure (the guest's virtio-fs driver propagates it to the
    // syscall that asked for the DAX mapping).
    match rc as u32 {
        // HV_SUCCESS = 0 — caller checks before calling this
        0 => 0,
        // HV_ERROR (general) — EIO
        0xfae9_4001 => crate::fuse::backend::EIO,
        // HV_BUSY
        0xfae9_4002 => -16, // EBUSY
        // HV_BAD_ARGUMENT
        0xfae9_4003 => crate::fuse::backend::EINVAL,
        // HV_NO_RESOURCES
        0xfae9_4005 => crate::fuse::backend::ENOSPC,
        // HV_NO_DEVICE
        0xfae9_4006 => -19, // ENODEV
        _ => crate::fuse::backend::EIO,
    }
}

impl HvfMapper for HvfDaxMapper {
    fn map(&self, host_va: *mut u8, gpa: u64, len: u64, prot: u32) -> Result<(), Errno> {
        // Alignment: caller (DaxSession) already enforced 16 KiB, but
        // defense-in-depth — HVF will return BAD_ARGUMENT if either
        // gpa or len isn't aligned to the page granule.
        if (gpa | len) & (PAGE_SIZE - 1) != 0 {
            return Err(crate::fuse::backend::EINVAL);
        }
        let mut flags: u64 = 0;
        if prot & DAX_PROT_READ != 0 {
            flags |= av::HV_MEMORY_READ;
        }
        if prot & DAX_PROT_WRITE != 0 {
            flags |= av::HV_MEMORY_WRITE;
        }
        // SAFETY: caller guarantees host_va is a valid mmap that
        // lives until the matching unmap call. The DaxSession owns
        // that lifetime contract via its Slot table + dax_unmap.
        let rc = unsafe { av::hv_vm_map(host_va as *const _, gpa, len as usize, flags) };
        if rc == 0 {
            Ok(())
        } else {
            Err(hvf_to_errno(rc as i32))
        }
    }

    fn unmap(&self, gpa: u64, len: u64) -> Result<(), Errno> {
        if (gpa | len) & (PAGE_SIZE - 1) != 0 {
            return Err(crate::fuse::backend::EINVAL);
        }
        // SAFETY: caller (DaxSession::remove) ensures no vCPU is
        // accessing the range during this call — guest code only
        // touches DAX memory inside its own vCPU thread, and
        // REMOVEMAPPING is dispatched on the host's worker thread
        // synchronously w.r.t. the guest's request queue.
        let rc = unsafe { av::hv_vm_unmap(gpa, len as usize) };
        if rc == 0 {
            Ok(())
        } else {
            Err(hvf_to_errno(rc as i32))
        }
    }
}
