//! Unix wrapper-file emission for `bougie tool`.
//!
//! The wrapper is a tiny PHP file with a bougie-binary shebang. The
//! kernel sees the shebang and execs `bougie tool-exec <wrapper-path>`;
//! `tool-exec` then sets `PHP_INI_SCAN_DIR` and `execve`s the pinned
//! PHP interpreter with the wrapper script as argv[1]. PHP runs the
//! wrapper, which fixes up `$argv[0]` for clean usage messages and
//! `require`s the package's real entry point from its `vendor/` tree.
//!
//! Two shape constraints:
//!
//! - Shebang points at the **stable** `$BOUGIE_LOCAL/bin/bougie`
//!   symlink, not the user's `current_exe()` path. That symlink is
//!   refreshed by self-update so the wrapper survives bougie upgrades.
//! - The `require` line uses `__DIR__` and is **relative** to the
//!   wrapper. The whole tool dir is then relocatable — moving
//!   `$BOUGIE_LOCAL` doesn't invalidate wrappers.

use eyre::Result;
#[cfg(unix)]
use eyre::WrapErr;
use std::path::Path;

/// Generate the textual contents of a Unix tool wrapper.
///
/// `bougie_stable_bin` is the path the wrapper's shebang will invoke —
/// callers pass the stable symlink path, not `current_exe()`.
/// `bin_name` ends up in `$argv[0]` (so the tool prints
/// `Usage: phpstan …`, not `Usage: /tool/dir/bin/phpstan …`).
/// `vendor_relative_bin` is the package's `composer.json` `bin` entry,
/// already vendor-prefixed (`phpstan/phpstan/bin/phpstan`).
pub fn render_unix(
    bougie_stable_bin: &Path,
    bin_name: &str,
    vendor_relative_bin: &str,
) -> String {
    // Shebang lines have a single-arg limit on Linux; `tool-exec` is one
    // arg, the wrapper path is appended by the kernel. We need the
    // shebang path absolute or PATH-resolvable; render absolute.
    format!(
        "#!{bougie} tool-exec\n\
         <?php\n\
         // Generated by bougie tool. Regenerate with\n\
         // `bougie tool upgrade --reinstall {bin_name}`.\n\
         \n\
         $argv[0] = $_SERVER['argv'][0] = {name_lit};\n\
         require __DIR__ . '/../vendor/{vendor_bin}';\n",
        bougie = bougie_stable_bin.display(),
        bin_name = bin_name,
        name_lit = php_single_quote(bin_name),
        vendor_bin = vendor_relative_bin,
    )
}

/// Write `content` to `path` and chmod 0755 on Unix.
#[cfg(unix)]
pub fn write_executable(path: &Path, content: &str) -> Result<()> {
    use std::os::unix::fs::PermissionsExt;
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent)
            .wrap_err_with(|| format!("creating {}", parent.display()))?;
    }
    std::fs::write(path, content)
        .wrap_err_with(|| format!("writing wrapper {}", path.display()))?;
    std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o755))
        .wrap_err_with(|| format!("chmod 0755 {}", path.display()))?;
    Ok(())
}

#[cfg(not(unix))]
pub fn write_executable(_path: &Path, _content: &str) -> Result<()> {
    eyre::bail!("bougie tool wrapper emission is Unix-only in Phase 1")
}

/// Escape a string for a PHP single-quoted literal. Composer bin names
/// are practically always safe (lowercase ASCII + `-_`), but rendering
/// `'name'` without escapes risks injection if Packagist ever ships
/// something exotic.
fn php_single_quote(s: &str) -> String {
    let escaped = s.replace('\\', r"\\").replace('\'', r"\'");
    format!("'{escaped}'")
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::path::PathBuf;

    #[test]
    fn render_matches_fixture() {
        let bougie = PathBuf::from("/home/u/.local/share/bougie/bin/bougie");
        let out = render_unix(&bougie, "phpstan", "phpstan/phpstan/bin/phpstan");
        let expected = "\
#!/home/u/.local/share/bougie/bin/bougie tool-exec
<?php
// Generated by bougie tool. Regenerate with
// `bougie tool upgrade --reinstall phpstan`.

$argv[0] = $_SERVER['argv'][0] = 'phpstan';
require __DIR__ . '/../vendor/phpstan/phpstan/bin/phpstan';
";
        assert_eq!(out, expected);
    }

    #[test]
    fn php_single_quote_escapes_backslash_and_quote() {
        assert_eq!(php_single_quote("a'b"), "'a\\'b'");
        assert_eq!(php_single_quote(r"a\b"), r"'a\\b'");
        assert_eq!(php_single_quote("phpstan"), "'phpstan'");
    }

    #[cfg(unix)]
    #[test]
    fn write_executable_sets_0755() {
        use std::os::unix::fs::PermissionsExt;
        let td = tempfile::TempDir::new().unwrap();
        let p = td.path().join("bin").join("phpstan");
        write_executable(&p, "#!/bin/sh\necho ok\n").unwrap();
        let mode = std::fs::metadata(&p).unwrap().permissions().mode() & 0o777;
        assert_eq!(mode, 0o755);
    }
}
