//! Shared, safe destinations for screenshots and screencasts.

use std::fs::{File, OpenOptions};
use std::io::{self, Write};
use std::os::unix::fs::{MetadataExt, PermissionsExt};
use std::path::{Component, Path, PathBuf};

/// Local-time stamp `YYYYMMDD_HHMMSS` for capture filenames.
pub fn timestamp() -> String {
    use std::mem::MaybeUninit;
    unsafe {
        let now = libc::time(std::ptr::null_mut());
        let mut tm = MaybeUninit::<libc::tm>::uninit();
        libc::localtime_r(&now, tm.as_mut_ptr());
        let tm = tm.assume_init();
        format!(
            "{:04}{:02}{:02}_{:02}{:02}{:02}",
            tm.tm_year + 1900,
            tm.tm_mon + 1,
            tm.tm_mday,
            tm.tm_hour,
            tm.tm_min,
            tm.tm_sec,
        )
    }
}

/// `$HOME/Screenshots`, created if missing.
pub fn screenshots_dir() -> Option<PathBuf> {
    let dir = dirs::home_dir()?.join("Screenshots");
    std::fs::create_dir_all(&dir).ok()?;
    Some(dir)
}

/// `$HOME/Videos`, created if missing.
pub fn videos_dir() -> Option<PathBuf> {
    let dir = dirs::home_dir()?.join("Videos");
    std::fs::create_dir_all(&dir).ok()?;
    Some(dir)
}

/// A fresh, non-existing `Screenshot_<ts>.png` path under `$HOME/Screenshots`.
pub fn default_screenshot_path() -> Option<PathBuf> {
    Some(screenshots_dir()?.join(format!("Screenshot_{}.png", timestamp())))
}

/// A fresh `Screencast_<ts>.mp4` path under `$HOME/Videos`.
pub fn default_screencast_path() -> Option<PathBuf> {
    Some(videos_dir()?.join(format!("Screencast_{}.mp4", timestamp())))
}

/// Create a fresh `Screenshot_<ts>.png` under `$HOME/Screenshots` with O_EXCL,
/// disambiguating collisions with a numeric suffix. Returns the open file for
/// callers that write the PNG bytes themselves.
pub fn new_screenshot_file() -> io::Result<(PathBuf, File)> {
    let dir = screenshots_dir()
        .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "no home dir"))?;
    let ts = timestamp();
    for n in 0..1000 {
        let name = if n == 0 {
            format!("Screenshot_{ts}.png")
        } else {
            format!("Screenshot_{ts}-{n}.png")
        };
        let path = dir.join(name);
        match OpenOptions::new().write(true).create_new(true).open(&path) {
            Ok(f) => return Ok((path, f)),
            Err(e) if e.kind() == io::ErrorKind::AlreadyExists => continue,
            Err(e) => return Err(e),
        }
    }
    Err(io::Error::new(io::ErrorKind::AlreadyExists, "screenshot name exhausted"))
}

/// Write `png` bytes to a fresh file under `$HOME/Screenshots` and return its
/// `file://` URI.
pub fn write_screenshot_png(png: &[u8]) -> io::Result<String> {
    let (path, mut file) = new_screenshot_file()?;
    file.write_all(png)?;
    Ok(format!("file://{}", path.display()))
}

/// Validate a caller-supplied destination for GNOME compatibility: absolute,
/// no `..`, parent canonicalises under `$HOME`, destination absent (no symlink
/// following), parent owned by us and not world-writable. Returns the resolved
/// path or `None` if it fails any check.
pub fn validate_target(filename: &str) -> Option<PathBuf> {
    let path = Path::new(filename);
    if !path.is_absolute() || path.components().any(|c| c == Component::ParentDir) {
        return None;
    }
    let file_name = path.file_name()?;
    let parent = path.parent()?.canonicalize().ok()?;
    let home = dirs::home_dir()?.canonicalize().ok()?;
    if !parent.starts_with(&home) {
        return None;
    }

    let dest = parent.join(file_name);
    if dest.symlink_metadata().is_ok() {
        return None;
    }

    let meta = std::fs::metadata(&parent).ok()?;
    if meta.uid() != unsafe { libc::getuid() } || meta.permissions().mode() & 0o002 != 0 {
        return None;
    }
    Some(dest)
}
