//! macOS automation bridges for the Common Agent Runtime.
//!
//! Three modules cover the layered automation stack on macOS:
//!
//! - [`applescript`] — drive any scriptable app via `osascript`.
//!   Reaches the long tail of legacy and pro apps (Mail, Finder, Office,
//!   Adobe, Music) where a structured action API doesn't exist.
//! - [`shortcuts`] — enumerate and invoke user-authored Shortcuts via
//!   the `shortcuts(1)` CLI. Effectively a universal automation bus
//!   that reaches every app donating `AppShortcuts`.
//!
//! Both backends are subprocess wrappers around Apple-shipped tools
//! by deliberate choice — see the crate README for rationale.
//!
//! Everything here is macOS-only. On other platforms each public
//! function returns [`AutomationError::PlatformUnsupported`] without
//! a feature flag — the cfg gating is internal.

use serde::{Deserialize, Serialize};
use thiserror::Error;

pub mod applescript;
pub mod shortcuts;

/// Outcome envelope for a subprocess-backed automation call.
///
/// Carries `stdout` and `stderr` separately so callers can show useful
/// diagnostics when a script half-succeeds (writes to stderr but exits
/// 0, or vice versa).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AutomationOutput {
    /// Captured standard output, trimmed of the trailing newline that
    /// `osascript` and `shortcuts` always append.
    pub stdout: String,
    /// Captured standard error, trimmed.
    pub stderr: String,
    /// Process exit code, when the child exited normally.
    pub exit_code: Option<i32>,
}

#[derive(Debug, Error)]
pub enum AutomationError {
    /// The current platform doesn't support this backend. Returned
    /// from every entry point on non-macOS targets so callers can
    /// branch on the error without `cfg`.
    #[error("automation backend not supported on this platform: {reason}")]
    PlatformUnsupported { reason: &'static str },

    /// The subprocess returned a non-zero exit status. Output is
    /// preserved so the caller can surface the script's own error
    /// message rather than just the exit code.
    #[error("subprocess failed (exit {exit_code:?}): {stderr}")]
    SubprocessFailed {
        exit_code: Option<i32>,
        stdout: String,
        stderr: String,
    },

    /// The subprocess could not be spawned at all (binary missing,
    /// permission denied, etc.). Distinct from `SubprocessFailed`
    /// because the script never ran.
    #[error("could not spawn subprocess: {0}")]
    Spawn(String),

    /// Inputs failed validation before the subprocess was launched.
    #[error("invalid input: {0}")]
    InvalidInput(String),

    /// I/O while reading the child's output streams.
    #[error("i/o error: {0}")]
    Io(String),
}

impl From<std::io::Error> for AutomationError {
    fn from(err: std::io::Error) -> Self {
        AutomationError::Io(err.to_string())
    }
}

pub type Result<T> = std::result::Result<T, AutomationError>;
