use std::fs::OpenOptions;
use std::path::{Path, PathBuf};

use anyhow::Context;

pub(crate) const LOOP_LOCK_FILE: &str = ".ralph/loop.lock";

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum LockState {
    Active,
    Inactive,
    Stale,
}

pub(crate) fn lock_path(workspace_root: &Path) -> PathBuf {
    workspace_root.join(LOOP_LOCK_FILE)
}

pub(crate) fn lock_state(workspace_root: &Path) -> anyhow::Result<LockState> {
    let path = lock_path(workspace_root);

    if !path.exists() {
        return Ok(LockState::Inactive);
    }

    #[cfg(unix)]
    {
        use nix::errno::Errno;
        use nix::fcntl::{Flock, FlockArg};

        let file = OpenOptions::new()
            .read(true)
            .write(true)
            .open(&path)
            .with_context(|| format!("open lock file at {}", path.display()))?;

        match Flock::lock(file, FlockArg::LockExclusiveNonblock) {
            Ok(_flock) => Ok(LockState::Stale),
            Err((_, errno)) if errno == Errno::EWOULDBLOCK || errno == Errno::EAGAIN => {
                Ok(LockState::Active)
            }
            Err((_, errno)) => Err(anyhow::anyhow!(
                "flock failed for {}: {}",
                path.display(),
                errno
            )),
        }
    }

    #[cfg(not(unix))]
    {
        let _ = path;
        Ok(LockState::Active)
    }
}
