//! Service binding implementations.
//!
//! Each binding protocol implements the `Binding` trait.
//! The proxy uses whatever binding is configured in `el.toml`.

pub mod direct;
pub mod grpc;
pub mod rest;
pub mod websocket;

use crate::ServiceResult;
use std::collections::HashMap;

/// Which protocol this service binding uses.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BindingKind {
    /// HTTP REST — maps service methods to HTTP endpoints.
    Rest,
    /// WebSocket — persistent connection, message-based RPC.
    WebSocket,
    /// gRPC — protobuf over HTTP/2 (stub; codegen is a future TODO).
    Grpc,
    /// Direct — server-side only; calls Rust functions directly, zero network.
    Direct,
}

impl BindingKind {
    pub fn from_str(s: &str) -> Option<Self> {
        match s.to_lowercase().as_str() {
            "rest" => Some(Self::Rest),
            "websocket" | "ws" => Some(Self::WebSocket),
            "grpc" => Some(Self::Grpc),
            "direct" => Some(Self::Direct),
            _ => None,
        }
    }

    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Rest => "rest",
            Self::WebSocket => "websocket",
            Self::Grpc => "grpc",
            Self::Direct => "direct",
        }
    }
}

/// A service call request — method name and parameters.
#[derive(Debug, Clone)]
pub struct ServiceRequest {
    pub service: String,
    pub method: String,
    /// Parameters as key-value pairs. Complex types are JSON-serialized strings.
    pub params: HashMap<String, String>,
}

impl ServiceRequest {
    pub fn new(service: impl Into<String>, method: impl Into<String>) -> Self {
        Self {
            service: service.into(),
            method: method.into(),
            params: HashMap::new(),
        }
    }

    pub fn with_param(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.params.insert(key.into(), value.into());
        self
    }
}

/// A service call response — raw body string (JSON, protobuf bytes as hex, etc.)
#[derive(Debug, Clone)]
pub struct ServiceResponse {
    pub status: u16,
    pub body: String,
    pub headers: HashMap<String, String>,
}

impl ServiceResponse {
    pub fn ok(body: impl Into<String>) -> Self {
        Self {
            status: 200,
            body: body.into(),
            headers: HashMap::new(),
        }
    }

    pub fn error(status: u16, body: impl Into<String>) -> Self {
        Self {
            status,
            body: body.into(),
            headers: HashMap::new(),
        }
    }

    pub fn is_success(&self) -> bool {
        (200..300).contains(&self.status)
    }
}

/// The core binding trait — implemented by each protocol.
pub trait Binding: Send + Sync {
    /// The kind of binding this is.
    fn kind(&self) -> BindingKind;

    /// Execute a service method call and return the response.
    fn call(&self, request: ServiceRequest) -> ServiceResult<ServiceResponse>;

    /// Whether this binding supports streaming responses.
    fn supports_streaming(&self) -> bool {
        false
    }
}
