//! Service proxy — the generated client for a service.
//!
//! A `ServiceProxy` wraps a binding and provides typed method calls.
//! The codegen (a future agent) will generate typed proxy structs from service
//! definitions. This is the runtime base that generated code builds on.

use crate::{
    binding::{Binding, ServiceRequest, ServiceResponse},
    config::ServiceConfig,
    ServiceResult,
};
use std::collections::HashMap;
use std::sync::Arc;

/// A service method descriptor — used by the proxy and codegen.
#[derive(Debug, Clone)]
pub struct ServiceMethod {
    pub name: String,
    /// Parameter names in order (for positional call support).
    pub param_names: Vec<String>,
    pub return_type: String,
}

impl ServiceMethod {
    pub fn new(name: impl Into<String>, return_type: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            param_names: Vec::new(),
            return_type: return_type.into(),
        }
    }

    pub fn with_params(mut self, params: Vec<impl Into<String>>) -> Self {
        self.param_names = params.into_iter().map(|p| p.into()).collect();
        self
    }
}

/// A runtime service proxy.
///
/// The codegen produces a typed wrapper around this.
/// Directly usable for dynamic invocation (e.g., from the AOP layer).
pub struct ServiceProxy {
    pub service_name: String,
    pub config: ServiceConfig,
    pub binding: Arc<dyn Binding>,
    pub methods: Vec<ServiceMethod>,
}

impl ServiceProxy {
    pub fn new(
        service_name: impl Into<String>,
        config: ServiceConfig,
        binding: Arc<dyn Binding>,
    ) -> Self {
        Self {
            service_name: service_name.into(),
            config,
            binding,
            methods: Vec::new(),
        }
    }

    pub fn with_method(mut self, method: ServiceMethod) -> Self {
        self.methods.push(method);
        self
    }

    /// Call a service method by name with named parameters.
    pub fn call(
        &self,
        method_name: &str,
        params: HashMap<String, String>,
    ) -> ServiceResult<ServiceResponse> {
        let request = ServiceRequest {
            service: self.service_name.clone(),
            method: method_name.to_string(),
            params,
        };
        self.binding.call(request)
    }

    /// Call a service method by name with positional parameters.
    /// Parameters are matched to the method's `param_names` in order.
    pub fn call_positional(
        &self,
        method_name: &str,
        args: Vec<String>,
    ) -> ServiceResult<ServiceResponse> {
        let method = self
            .methods
            .iter()
            .find(|m| m.name == method_name)
            .ok_or_else(|| crate::ServiceError::MethodNotFound {
                service: self.service_name.clone(),
                method: method_name.to_string(),
            })?;

        let params: HashMap<String, String> = method
            .param_names
            .iter()
            .zip(args.iter())
            .map(|(name, val)| (name.clone(), val.clone()))
            .collect();

        self.call(method_name, params)
    }

    /// List all registered method names on this proxy.
    pub fn method_names(&self) -> Vec<&str> {
        self.methods.iter().map(|m| m.name.as_str()).collect()
    }
}
