use std::{
    io::Write,
    path::PathBuf,
    process::{Command, Stdio},
};

use anyhow::{anyhow, Result};
use which::which;

fn exec_copy_with_args(command: &str, args: &[&str], text: &str, pipe_stderr: bool) -> Result<()> {
    let binary = which(command)
        .ok()
        .unwrap_or_else(|| PathBuf::from(command));

    let mut process = Command::new(binary)
        .args(args)
        .stdin(Stdio::piped())
        .stdout(Stdio::null())
        .stderr(if pipe_stderr {
            Stdio::piped()
        } else {
            Stdio::null()
        })
        .spawn()
        .map_err(|e| anyhow!("`{:?}`: {}", command, e))?;

    process
        .stdin
        .as_mut()
        .ok_or_else(|| anyhow!("`{:?}`", command))?
        .write_all(text.as_bytes())
        .map_err(|e| anyhow!("`{:?}`: {}", command, e))?;

    let out = process
        .wait_with_output()
        .map_err(|e| anyhow!("`{:?}`: {}", command, e))?;

    if out.status.success() {
        Ok(())
    } else {
        let msg = if out.stderr.is_empty() {
            format!("{}", out.status).into()
        } else {
            String::from_utf8_lossy(&out.stderr)
        };
        Err(anyhow!("`{command:?}`: {msg}"))
    }
}

#[cfg(all(target_family = "unix", not(target_os = "macos")))]
pub fn copy_string(text: &str) -> Result<()> {
    if std::env::var("WAYLAND_DISPLAY").is_ok() {
        return exec_copy_with_args("wl-copy", &[], text, false);
    }

    if exec_copy_with_args("xclip", &["-selection", "clipboard"], text, false).is_err() {
        return exec_copy_with_args("xsel", &["--clipboard"], text, true);
    }

    Ok(())
}

#[cfg(any(target_os = "macos", windows))]
fn exec_copy(command: &str, text: &str) -> Result<()> {
    exec_copy_with_args(command, &[], text, true)
}

#[cfg(target_os = "macos")]
pub fn copy_string(text: &str) -> Result<()> {
    exec_copy("pbcopy", text)
}

#[cfg(windows)]
pub fn copy_string(text: &str) -> Result<()> {
    exec_copy("clip", text)
}
