use super::*;

#[derive(Debug)]
pub(crate) struct VmaCloseNotification {
    pub(crate) file: Arc<File>,
    pub(crate) region: VirtRegion,
    pub(crate) vm_flags: VmFlags,
}

#[derive(Default)]
pub(crate) struct VmaCloseNotifications {
    pub(crate) vma: Vec<VmaCloseNotification>,
    pub(crate) sysv: Vec<Arc<SysVShmAttach>>,
}

impl VmaCloseNotifications {
    pub(super) fn is_empty(&self) -> bool {
        self.vma.is_empty() && self.sysv.is_empty()
    }

    pub(super) fn extend(&mut self, mut other: VmaCloseNotifications) {
        self.vma.append(&mut other.vma);
        self.sysv.append(&mut other.sysv);
    }
}

pub(super) struct MremapOutcome {
    pub(super) addr: VirtAddr,
    pub(super) notifications: VmaCloseNotifications,
    /// Explicit executable-publication plan for ranges whose uprobe sites must
    /// be installed before their original execute permission is restored.
    pub(super) exec_publication: super::mremap::MremapExecPublication,
    /// Locked pages in this committed range must be populated only after the
    /// caller has released `AddressSpace::write()`.  A file fault can return
    /// `VM_FAULT_RETRY` and wait on PageCache invalidation.
    pub(super) post_commit_population: Option<(VirtRegion, Weak<LockedVMA>)>,
}

/// Result of a committed program-break operation.
///
/// `set_brk()` runs under `AddressSpace::write()` to change the VMA set, but
/// a new heap VMA can inherit `MCL_FUTURE`.  Its population must therefore be
/// deferred to the outer `AddressSpace` owner: a file-backed fault reached by
/// the generic population machinery may return `VM_FAULT_RETRY` and must not
/// wait while that write guard is held.
pub(super) struct BrkOutcome {
    pub(super) old_brk: VirtAddr,
    pub(super) post_commit_population: Option<(VirtRegion, Weak<LockedVMA>)>,
}

/// A committed locked VMA that must be populated after its owner drops
/// `AddressSpace::write()`.
///
/// This is intentionally an owned request rather than an `InnerAddressSpace`
/// callback: retrying a file-backed fault can block on PageCache invalidation,
/// while the VMA identity must still be revalidated after that wait.
pub(crate) struct LockedPopulationRequest {
    pub(super) region: VirtRegion,
    pub(super) expected_vma: Weak<LockedVMA>,
}

pub(super) struct MremapFailure {
    pub(super) err: SystemError,
    pub(super) notifications: VmaCloseNotifications,
    /// A source barrier can survive phase-A rollback. The outer owner must
    /// reapply source sites and publish this plan before dropping mm.write().
    pub(super) exec_publication: super::mremap::MremapExecPublication,
}

impl From<SystemError> for MremapFailure {
    fn from(err: SystemError) -> Self {
        Self {
            err,
            notifications: VmaCloseNotifications::default(),
            exec_publication: super::mremap::MremapExecPublication::default(),
        }
    }
}

pub(super) struct MmapFailure {
    pub(super) err: SystemError,
    pub(super) notifications: VmaCloseNotifications,
}

impl From<SystemError> for MmapFailure {
    fn from(err: SystemError) -> Self {
        Self {
            err,
            notifications: VmaCloseNotifications::default(),
        }
    }
}

pub(crate) struct VmaOpFailure {
    pub(crate) err: SystemError,
    pub(crate) notifications: VmaCloseNotifications,
}

impl From<SystemError> for VmaOpFailure {
    fn from(err: SystemError) -> Self {
        Self {
            err,
            notifications: VmaCloseNotifications::default(),
        }
    }
}

pub(super) struct VmaSplitFailure {
    pub(super) err: SystemError,
    pub(super) lifecycle: VmaSplitLifecycle,
}

impl VmaSplitFailure {
    pub(super) fn rollback_into(self, notifications: &mut VmaCloseNotifications) -> SystemError {
        self.lifecycle.rollback_into(notifications);
        self.err
    }
}

pub(super) struct MunmapVmaPlan {
    pub(super) original_region: VirtRegion,
    pub(super) intersection: VirtRegion,
    pub(super) locked_vm_after_unmap: Option<usize>,
    pub(super) split_lifecycle: VmaSplitLifecycle,
}

pub(super) struct MprotectVmaPlan {
    pub(super) original_region: VirtRegion,
    pub(super) intersection: VirtRegion,
    #[cfg(target_arch = "x86_64")]
    pub(super) old_vm_flags: VmFlags,
    #[cfg(target_arch = "x86_64")]
    pub(super) defer_uprobe_execute: bool,
    pub(super) new_vm_flags: VmFlags,
    pub(super) split_lifecycle: VmaSplitLifecycle,
}

pub(super) struct MadviseVmaPlan {
    pub(super) original_region: VirtRegion,
    pub(super) intersection: VirtRegion,
    pub(super) split_lifecycle: VmaSplitLifecycle,
}

#[derive(Debug)]
pub(super) struct VmaSplitLifecycle {
    pub(super) sysv_shm: Option<Arc<SysVShmAttach>>,
    pub(super) open_count: usize,
    pub(super) file_opens: Vec<VmaCloseNotification>,
    pub(super) committed: bool,
}

impl VmaSplitLifecycle {
    pub(super) fn none() -> Self {
        Self {
            sysv_shm: None,
            open_count: 0,
            file_opens: Vec::new(),
            committed: false,
        }
    }

    pub(super) fn commit(mut self) {
        self.committed = true;
    }

    pub(super) fn rollback_into(mut self, notifications: &mut VmaCloseNotifications) {
        if self.committed {
            return;
        }
        if let Some(sysv_shm) = self.sysv_shm.take() {
            for _ in 0..self.open_count {
                notifications.sysv.push(sysv_shm.clone());
            }
        }
        notifications.vma.append(&mut self.file_opens);
        self.open_count = 0;
        self.committed = true;
    }

    pub(super) fn failure(self, err: SystemError) -> VmaSplitFailure {
        VmaSplitFailure {
            err,
            lifecycle: self,
        }
    }
}

impl Drop for VmaSplitLifecycle {
    fn drop(&mut self) {
        if self.committed {
            return;
        }
        error!(
            "VmaSplitLifecycle dropped without explicit commit/rollback; VMA closes must be routed through VmaCloseNotifications"
        );
    }
}
