//! Client bridge for Send/!Send boundary crossing.
//!
//! This module provides types that allow a `Send + Sync` agent (like `AgentHandle`)
//! to communicate with a `!Send` client connection (like `AgentSideConnection` from the SDK).
//!
//! ## Architecture
//!
//! ```text
//! ┌─────────────────────────────────────────────────────────────┐
//! │                        LocalSet                              │
//! │  ┌──────────────────┐      ┌──────────────────────────────┐ │
//! │  │AgentSideConnection│◄─────│    Bridge Task               │ │
//! │  │  (!Send)         │      │  - Receives from mpsc        │ │
//! │  │                  │      │  - Calls connection methods  │ │
//! │  └──────────────────┘      └──────────────▲───────────────┘ │
//! │                                            │                 │
//! │                                            │                 │
//! └────────────────────────────────────────────┼─────────────────┘
//!                                              │
//!                                              │ ClientBridgeMessage
//!                                              │ (Send types only)
//!                                              │
//!                          ┌───────────────────┴────────────────┐
//!                          │  QueryMTAgent (Send + Sync)        │
//!                          │  - Holds ClientBridgeSender        │
//!                          │  - Sends messages via mpsc channel │
//!                          └────────────────────────────────────┘
//! ```
//!
//! ## Usage
//!
//! The bridge is set up automatically by the ACP stdio server (`serve_stdio`).
//! The agent uses `ClientBridgeSender` to communicate with the client:
//!
//! ```rust,ignore
//! // In QueryMTAgent methods
//! if let Some(bridge) = self.bridge() {
//!     // Send a notification (fire-and-forget)
//!     bridge.notify(notification).await?;
//!     
//!     // Request permission (wait for response)
//!     let response = bridge.request_permission(request).await?;
//! }
//! ```

use agent_client_protocol::schema::{
    Error, RequestPermissionRequest, RequestPermissionResponse, SessionNotification,
};
use tokio::sync::{mpsc, oneshot};

/// Messages sent from agent (Send context) to bridge task (LocalSet context).
///
/// These messages cross the `Send`/`!Send` boundary via an mpsc channel.
/// The bridge task running in a `LocalSet` receives these messages and
/// forwards them to the `!Send` client connection.
pub enum ClientBridgeMessage {
    /// Fire-and-forget session notification.
    ///
    /// The bridge task will call `connection.session_notification(...)`.
    /// No response is expected.
    Notification(SessionNotification),

    /// Request-response permission request.
    ///
    /// The bridge task will call `connection.request_permission(...)` and
    /// send the response back through the oneshot channel.
    RequestPermission {
        request: RequestPermissionRequest,
        response_tx: oneshot::Sender<Result<RequestPermissionResponse, Error>>,
    },

    /// Request-response elicitation request.
    ///
    /// The bridge task will handle elicitation and return the response.
    Elicit {
        elicitation_id: String,
        message: String,
        requested_schema: serde_json::Value,
        source: String,
        response_tx: oneshot::Sender<crate::elicitation::ElicitationResponse>,
    },

    /// Workspace query request (agent → client → VS Code LSP).
    ///
    /// The bridge task sends this as an ACP SDK `AgentRequest::ExtMethodRequest`
    /// using the wire-form method `"_workspace/query"`.
    /// The client (VS Code extension) handles it by calling VS Code language APIs
    /// and returns the result.
    WorkspaceQuery {
        query: crate::workspace_query::WorkspaceQueryRequest,
        response_tx: oneshot::Sender<Result<crate::workspace_query::WorkspaceQueryResponse, Error>>,
    },
}

/// Send-side handle for the client bridge.
///
/// This type is `Send + Sync` and can be cloned and used from multi-threaded contexts.
/// It allows a `Send + Sync` agent to communicate with a `!Send` client connection
/// by sending messages through an mpsc channel to a bridge task running in `LocalSet`.
///
/// ## Examples
///
/// ```rust,ignore
/// // Create bridge channel
/// let (tx, rx) = mpsc::channel::<ClientBridgeMessage>(100);
/// let sender = ClientBridgeSender::new(tx);
///
/// // Set on agent
/// agent.set_bridge(sender);
///
/// // In agent methods:
/// agent.bridge().unwrap().notify(notification).await?;
/// ```
#[derive(Clone)]
pub struct ClientBridgeSender {
    tx: mpsc::Sender<ClientBridgeMessage>,
}

impl ClientBridgeSender {
    /// Create a new bridge sender wrapping the channel.
    ///
    /// This is typically called by the ACP server when setting up the bridge.
    pub fn new(tx: mpsc::Sender<ClientBridgeMessage>) -> Self {
        Self { tx }
    }

    /// Send a session notification (fire-and-forget).
    ///
    /// The notification is queued and sent asynchronously to the client.
    /// This method does not wait for the client to receive or process the notification.
    ///
    /// # Errors
    ///
    /// Returns an error if the bridge channel is closed (client disconnected).
    pub async fn notify(&self, notification: SessionNotification) -> Result<(), Error> {
        self.tx
            .send(ClientBridgeMessage::Notification(notification))
            .await
            .map_err(|_| Error::from(crate::error::AgentError::ClientBridgeClosed))
    }

    /// Request permission from the client and wait for response.
    ///
    /// This method blocks until the client responds to the permission request.
    /// The response flows back through a oneshot channel embedded in the message.
    ///
    /// # Errors
    ///
    /// Returns an error if:
    /// - The bridge channel is closed
    /// - The client disconnects before responding
    /// - The client rejects the permission request
    pub async fn request_permission(
        &self,
        request: RequestPermissionRequest,
    ) -> Result<RequestPermissionResponse, Error> {
        let (response_tx, response_rx) = oneshot::channel();
        self.tx
            .send(ClientBridgeMessage::RequestPermission {
                request,
                response_tx,
            })
            .await
            .map_err(|_| Error::from(crate::error::AgentError::ClientBridgeClosed))?;

        response_rx
            .await
            .map_err(|_| Error::from(crate::error::AgentError::PermissionChannelDropped))?
    }

    /// Send a workspace query to the client (VS Code) and wait for response.
    ///
    /// This enables the agent to access language intelligence (LSP data) from
    /// the editor: diagnostics, references, definitions, symbols, hover info.
    ///
    /// Only available when connected to a client that supports workspace queries
    /// (currently the VS Code extension). In CLI mode, this is not available.
    ///
    /// # Errors
    ///
    /// Returns an error if:
    /// - The bridge channel is closed
    /// - The client disconnects before responding
    /// - The client does not support workspace queries
    pub async fn workspace_query(
        &self,
        query: crate::workspace_query::WorkspaceQueryRequest,
    ) -> Result<crate::workspace_query::WorkspaceQueryResponse, Error> {
        let (response_tx, response_rx) = oneshot::channel();
        self.tx
            .send(ClientBridgeMessage::WorkspaceQuery { query, response_tx })
            .await
            .map_err(|_| Error::from(crate::error::AgentError::ClientBridgeClosed))?;

        response_rx
            .await
            .map_err(|_| Error::from(crate::error::AgentError::WorkspaceQueryChannelDropped))?
    }

    /// Request elicitation from the user and wait for response.
    ///
    /// This method blocks until the user responds to the elicitation request.
    /// The response flows back through a oneshot channel embedded in the message.
    ///
    /// # Errors
    ///
    /// Returns an error if:
    /// - The bridge channel is closed
    /// - The client disconnects before responding
    pub async fn elicit(
        &self,
        elicitation_id: String,
        message: String,
        requested_schema: serde_json::Value,
        source: String,
    ) -> Result<crate::elicitation::ElicitationResponse, String> {
        let (response_tx, response_rx) = oneshot::channel();
        self.tx
            .send(ClientBridgeMessage::Elicit {
                elicitation_id,
                message,
                requested_schema,
                source,
                response_tx,
            })
            .await
            .map_err(|_| "Client bridge closed".to_string())?;

        response_rx
            .await
            .map_err(|_| "Elicitation response channel dropped".to_string())
    }
}
