//! Convenience type combining process and client.

use std::ops::Deref;
use std::path::Path;

use crate::client::TapClient;
use crate::config::TapConfig;
use crate::process::TapProcess;
use crate::Result;

/// A convenience type that owns both a tap process and its client.
///
/// `TapHandle` manages the lifecycle of a tap subprocess and provides
/// access to a [`TapClient`] for interacting with it. When dropped,
/// the process is gracefully shut down.
///
/// # Example
///
/// ```no_run
/// use tapped::{TapHandle, TapConfig};
///
/// #[tokio::main]
/// async fn main() -> tapped::Result<()> {
///     let config = TapConfig::builder()
///         .database_url("sqlite://tap.db")
///         .build();
///
///     // Spawn tap and get a handle
///     let handle = TapHandle::spawn_default(config).await?;
///
///     // Use the client methods directly on the handle
///     handle.health().await?;
///     
///     let mut channel = handle.channel().await?;
///     while let Ok(event) = channel.recv().await {
///         println!("Event: {:?}", event.event);
///     }
///
///     Ok(())
/// }
/// ```
pub struct TapHandle {
    process: TapProcess,
    client: TapClient,
}

impl TapHandle {
    /// Spawn a tap process at the given path and create a client for it.
    ///
    /// This combines [`TapProcess::spawn`] and [`TapClient`] creation into
    /// a single convenient call.
    ///
    /// # Arguments
    ///
    /// * `path` - Path to the tap binary
    /// * `config` - Configuration for the tap instance
    ///
    /// # Errors
    ///
    /// Returns an error if the process fails to start or become healthy.
    pub async fn spawn(path: impl AsRef<Path>, config: TapConfig) -> Result<Self> {
        let process = TapProcess::spawn(path, config).await?;
        let client = process.client()?;
        Ok(Self { process, client })
    }

    /// Spawn a tap process using the default binary location.
    ///
    /// This looks for `tap` in the current directory first, then on the PATH.
    /// Combines [`TapProcess::spawn_default`] and [`TapClient`] creation.
    ///
    /// # Arguments
    ///
    /// * `config` - Configuration for the tap instance
    ///
    /// # Errors
    ///
    /// Returns an error if no tap binary is found or it fails to start.
    pub async fn spawn_default(config: TapConfig) -> Result<Self> {
        let process = TapProcess::spawn_default(config).await?;
        let client = process.client()?;
        Ok(Self { process, client })
    }

    /// Get a reference to the underlying process.
    pub fn process(&self) -> &TapProcess {
        &self.process
    }

    /// Get a mutable reference to the underlying process.
    pub fn process_mut(&mut self) -> &mut TapProcess {
        &mut self.process
    }

    /// Get a reference to the client.
    pub fn client(&self) -> &TapClient {
        &self.client
    }

    /// Check if the tap process is still running.
    pub fn is_running(&mut self) -> bool {
        self.process.is_running()
    }

    /// Gracefully shut down the tap process.
    ///
    /// This sends SIGTERM and waits for the process to exit, then
    /// sends SIGKILL if it doesn't exit within the shutdown timeout.
    pub async fn shutdown(&mut self) -> Result<()> {
        self.process.shutdown().await
    }
}

impl Deref for TapHandle {
    type Target = TapClient;

    fn deref(&self) -> &Self::Target {
        &self.client
    }
}
