//! Windows-safe subprocess spawning.
//!
//! When a GUI process (one linked with no attached console) spawns a child
//! via `CreateProcessW`, Windows allocates a fresh console window for the
//! child unless told otherwise. For a quiet background invocation — `git`,
//! `claude`, setup scripts, MCP servers — this surfaces as a cmd.exe window
//! that pops up, lives for the lifetime of the child, and disappears. For
//! a Tauri app that runs many short subprocesses, the flicker storm is
//! unacceptable.
//!
//! The fix is the `CREATE_NO_WINDOW` creation flag (0x08000000). This
//! trait adds a single chainable method — `.no_console_window()` — that
//! applies the flag on Windows and is a no-op on other platforms, so the
//! call site reads the same everywhere.

/// Extension trait for the two `Command` types used in this codebase
/// (`std::process::Command` and `tokio::process::Command`) that hides the
/// platform-specific incantation for suppressing the transient console
/// window Windows creates for GUI-spawned children.
///
/// Call it anywhere in the builder chain before a terminating
/// `.spawn()`/`.output()`/`.status()`:
///
/// ```ignore
/// use claudette::process::command;
///
/// let out = command("git")
///     .args(["status", "--porcelain"])
///     .output()
///     .await?;
/// ```
pub trait CommandWindowExt {
    /// Suppress the cmd.exe console window Windows would otherwise create
    /// for this child. No-op on Unix-likes.
    fn no_console_window(&mut self) -> &mut Self;

    /// Force-allocate a new console window for this child on Windows
    /// (`CREATE_NEW_CONSOLE`, 0x0000_0010). Use this for terminal apps
    /// the user is opening *intentionally* — `cmd.exe`, `powershell.exe`,
    /// `pwsh.exe` — where suppressing the window would leave them
    /// running invisibly, and inheriting the parent's console (the
    /// dev-launcher's PowerShell, the Tauri release binary's empty
    /// console) would either nest the child inside our terminal or
    /// fail outright. No-op on Unix-likes. `wt.exe` and other GUI
    /// terminal launchers ignore this flag because they create their
    /// own window via app-activation, so passing it is harmless there.
    fn new_console_window(&mut self) -> &mut Self;
}

/// Construct a Tokio subprocess command with the Windows console-flash guard
/// applied by default. Use this for async process work instead of raw
/// `tokio::process::Command::new`.
#[allow(
    clippy::disallowed_methods,
    reason = "central subprocess constructor applies Windows CREATE_NO_WINDOW guard"
)]
pub fn command(program: impl AsRef<std::ffi::OsStr>) -> tokio::process::Command {
    let mut cmd = tokio::process::Command::new(program);
    cmd.no_console_window();
    cmd
}

/// Construct a std subprocess command with the Windows console-flash guard
/// applied by default. Use this for blocking process work instead of raw
/// `std::process::Command::new`.
#[allow(
    clippy::disallowed_methods,
    reason = "central subprocess constructor applies Windows CREATE_NO_WINDOW guard"
)]
pub fn std_command(program: impl AsRef<std::ffi::OsStr>) -> std::process::Command {
    let mut cmd = std::process::Command::new(program);
    cmd.no_console_window();
    cmd
}

/// Remove Claude Code harness environment that is valid for inference but can
/// break nested Claude CLI features such as Remote Control. Real Anthropic API
/// keys are preserved.
pub fn sanitize_claude_subprocess_env(cmd: &mut tokio::process::Command) {
    if let Ok(key) = std::env::var("ANTHROPIC_API_KEY")
        && !key.starts_with("sk-ant-api")
    {
        cmd.env_remove("ANTHROPIC_API_KEY");
    }
    cmd.env_remove("ANTHROPIC_AUTH_TOKEN");
    cmd.env_remove("ANTHROPIC_FOUNDRY_API_KEY");
    cmd.env_remove("ANTHROPIC_UNIX_SOCKET");
    cmd.env_remove("AWS_BEARER_TOKEN_BEDROCK");
    cmd.env_remove("CLAUDE_BRIDGE_OAUTH_TOKEN");
    cmd.env_remove("CLAUDE_SESSION_INGRESS_TOKEN_FILE");
    cmd.env_remove("CLAUDE_TRUSTED_DEVICE_TOKEN");
    cmd.env_remove("CLAUDECODE");
    cmd.env_remove("CLAUDE_CODE_API_KEY_FILE_DESCRIPTOR");
    cmd.env_remove("CLAUDE_CODE_ENTRYPOINT");
    cmd.env_remove("CLAUDE_CODE_OAUTH_REFRESH_TOKEN");
    cmd.env_remove("CLAUDE_CODE_OAUTH_SCOPES");
    cmd.env_remove("CLAUDE_CODE_OAUTH_TOKEN");
    cmd.env_remove("CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR");
    cmd.env_remove("CLAUDE_CODE_SESSION_ACCESS_TOKEN");
    cmd.env_remove("CLAUDE_CODE_WEBSOCKET_AUTH_FILE_DESCRIPTOR");
}

#[cfg(windows)]
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
#[cfg(windows)]
const CREATE_NEW_CONSOLE: u32 = 0x0000_0010;

impl CommandWindowExt for std::process::Command {
    fn no_console_window(&mut self) -> &mut Self {
        #[cfg(windows)]
        {
            use std::os::windows::process::CommandExt;
            self.creation_flags(CREATE_NO_WINDOW);
        }
        self
    }

    fn new_console_window(&mut self) -> &mut Self {
        #[cfg(windows)]
        {
            use std::os::windows::process::CommandExt;
            self.creation_flags(CREATE_NEW_CONSOLE);
        }
        self
    }
}

impl CommandWindowExt for tokio::process::Command {
    fn no_console_window(&mut self) -> &mut Self {
        // tokio's `Command` exposes `creation_flags` as an inherent method
        // on Windows — no `CommandExt` trait import needed here, unlike
        // the std impl above.
        #[cfg(windows)]
        self.creation_flags(CREATE_NO_WINDOW);
        self
    }

    fn new_console_window(&mut self) -> &mut Self {
        #[cfg(windows)]
        self.creation_flags(CREATE_NEW_CONSOLE);
        self
    }
}

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

    /// The trait must be callable on a newly-constructed `std::process::Command`
    /// and return `&mut Self` so it fits mid-chain with `.arg()` / `.output()`.
    /// We don't actually spawn — that would require a known-present Unix/Windows
    /// binary and makes tests flaky. The compile-and-mutate check is enough to
    /// catch breakage of the trait's shape (return type, method name, impls).
    #[test]
    fn std_command_chain_typechecks() {
        let mut cmd = crate::process::std_command("true");
        // If this compiles, the `&mut Self` return lets us keep chaining.
        let _ref: &mut std::process::Command = cmd.no_console_window().arg("-x");
    }

    #[test]
    fn tokio_command_chain_typechecks() {
        let mut cmd = crate::process::command("true");
        let _ref: &mut tokio::process::Command = cmd.no_console_window().arg("-x");
    }

    /// The trait must be idempotent: calling it twice on the same command
    /// mustn't panic, change the program name, or otherwise corrupt state.
    /// (`CommandExt::creation_flags` *sets* the value rather than OR-ing,
    /// so the second call just writes the same flag back — no bit
    /// accumulation, but the post-state is identical either way.)
    #[test]
    fn repeat_application_is_safe() {
        let mut cmd = crate::process::std_command("true");
        cmd.no_console_window();
        cmd.no_console_window();
        assert_eq!(cmd.get_program(), "true");
    }

    /// On Windows, `CREATE_NO_WINDOW` is the exact flag we want — nothing
    /// else. This guards against an accidental change to the constant (e.g.
    /// mixing up `DETACHED_PROCESS = 0x0000_0008` — close in hex but wrong
    /// semantics) without having to spawn a subprocess.
    #[cfg(windows)]
    #[test]
    fn windows_flag_is_create_no_window() {
        assert_eq!(CREATE_NO_WINDOW, 0x0800_0000);
    }

    /// `CREATE_NEW_CONSOLE` (0x0000_0010) is the inverse-purpose flag:
    /// where `CREATE_NO_WINDOW` *suppresses* console allocation,
    /// `CREATE_NEW_CONSOLE` *forces* a fresh one. Mixing these up
    /// silently breaks "Open in Terminal" on Windows (cmd/pwsh
    /// launches invisibly, or gets nested into Claudette's own
    /// console).
    #[cfg(windows)]
    #[test]
    fn windows_flag_is_create_new_console() {
        assert_eq!(CREATE_NEW_CONSOLE, 0x0000_0010);
    }

    #[test]
    fn std_command_new_console_chain_typechecks() {
        let mut cmd = crate::process::std_command("true");
        let _ref: &mut std::process::Command = cmd.new_console_window().arg("-x");
    }

    #[test]
    fn tokio_command_new_console_chain_typechecks() {
        let mut cmd = crate::process::command("true");
        let _ref: &mut tokio::process::Command = cmd.new_console_window().arg("-x");
    }

    /// On Windows, spawning a child with the flag set should succeed exactly
    /// like spawning without it — the flag only controls the console-window
    /// allocation, not the process itself. This also exercises the cfg-gated
    /// `CommandExt::creation_flags` code path end-to-end.
    #[cfg(windows)]
    #[test]
    fn windows_std_command_default_flag_spawns() {
        // `cmd /C exit 0` is always available on Windows. Do not call
        // `.no_console_window()` here: this test exercises the std helper's
        // default constructor path.
        let status = crate::process::std_command("cmd")
            .args(["/C", "exit", "0"])
            .status()
            .expect("cmd.exe should be spawnable");
        assert!(status.success());
    }
}
