//! Declarative tunnel binding via `tunnel_bind!` macro.
//!
//! Declares a tunnel identity with its network configuration.
//! Protocol details (WS path, join header, env var, timeout) are internalized
//! in the declaration — calling code uses typed accessors only.

/// Declare a tunnel connection identity.
///
/// # Full declaration (internalizes all protocol details)
///
/// ```ignore
/// tunnel_bind!(AliceTunnel,
///     ws_path: "/api/hub/ws",
///     join_header: "x-join-token",
///     env_host: "ALICE_HOST",
///     rpc_timeout_secs: 30,
/// );
/// ```
///
/// # Minimal declaration (ws_path only, backward compatible)
///
/// ```ignore
/// tunnel_bind!(Name, ws_path: "/api/hub/ws");
/// ```
#[macro_export]
macro_rules! tunnel_bind {
    // ── Full declaration: ws_path + join_header + env_host + rpc_timeout ──
    ($name:ident,
        ws_path: $ws_path:literal,
        join_header: $jh:literal,
        env_host: $eh:literal,
        rpc_timeout_secs: $timeout:expr $(,)?
    ) => {
        pub struct $name {
            client: std::sync::Arc<$crate::TunnelClient>,
        }

        impl $name {
            /// The WebSocket path declared for this tunnel.
            pub fn ws_path() -> &'static str {
                $ws_path
            }

            /// The header name for join token authentication.
            pub fn join_header() -> &'static str {
                $jh
            }

            /// RPC call timeout for this tunnel.
            pub fn rpc_timeout() -> std::time::Duration {
                std::time::Duration::from_secs($timeout)
            }

            /// Resolve this engine's HTTP endpoint.
            ///
            /// Reads the declared env var to determine externally-reachable address.
            /// Falls back to `http://localhost:{port}` if not set.
            pub fn resolve_endpoint(local_port: u16) -> String {
                std::env::var($eh)
                    .ok()
                    .filter(|s| !s.is_empty())
                    .map(|h| {
                        if h.starts_with("http") {
                            h
                        } else {
                            format!("http://{}", h)
                        }
                    })
                    .unwrap_or_else(|| format!("http://localhost:{}", local_port))
            }

            /// Connect to a host at the given HTTP URL.
            ///
            /// Constructs the join header internally — caller only provides
            /// the token value, not the header name.
            pub async fn connect_to(
                http_url: &str,
                join_token: String,
                dispatchers: Vec<Box<dyn $crate::Dispatch>>,
                connected_tx: $crate::__tokio::sync::mpsc::UnboundedSender<()>,
            ) -> Result<std::sync::Arc<Self>, String> {
                let headers = vec![
                    ($jh.to_owned(), join_token),
                ];
                let client = $crate::TunnelClient::connect_to_http(
                    http_url, $ws_path, dispatchers, headers, connected_tx,
                ).await?;
                Ok(std::sync::Arc::new(Self { client }))
            }

            /// Disconnect from the tunnel server.
            pub fn disconnect(&self) {
                self.client.disconnect();
            }

            /// Access the underlying `TunnelClient`.
            pub fn client(&self) -> &std::sync::Arc<$crate::TunnelClient> {
                &self.client
            }
        }

        #[$crate::async_trait]
        impl $crate::TunnelCaller for $name {
            async fn call(
                &self,
                method: &str,
                params: $crate::serde_json::Value,
            ) -> Result<$crate::serde_json::Value, $crate::tunnel::TunnelError> {
                self.client.call(method, params).await
            }
        }
    };

    // ── Minimal: ws_path only (backward compatible) ─────────────
    ($name:ident, ws_path: $ws_path:literal) => {
        pub struct $name {
            client: std::sync::Arc<$crate::TunnelClient>,
        }

        impl $name {
            pub fn ws_path() -> &'static str {
                $ws_path
            }

            pub async fn connect_to(
                http_url: &str,
                dispatchers: Vec<Box<dyn $crate::Dispatch>>,
                headers: Vec<(String, String)>,
                connected_tx: $crate::__tokio::sync::mpsc::UnboundedSender<()>,
            ) -> Result<std::sync::Arc<Self>, String> {
                let client = $crate::TunnelClient::connect_to_http(
                    http_url, $ws_path, dispatchers, headers, connected_tx,
                ).await?;
                Ok(std::sync::Arc::new(Self { client }))
            }

            pub fn disconnect(&self) {
                self.client.disconnect();
            }

            pub fn client(&self) -> &std::sync::Arc<$crate::TunnelClient> {
                &self.client
            }
        }

        #[$crate::async_trait]
        impl $crate::TunnelCaller for $name {
            async fn call(
                &self,
                method: &str,
                params: $crate::serde_json::Value,
            ) -> Result<$crate::serde_json::Value, $crate::tunnel::TunnelError> {
                self.client.call(method, params).await
            }
        }
    };
}