//! RPC method handlers.
//!
//! Each sub-module handles a group of JSON-RPC methods.

pub mod approval;
pub mod auth;
pub mod capabilities;
#[cfg(feature = "comms")]
pub mod comms;
pub mod config;
// Runtime-backed external-event convenience handler.
pub mod event;
pub mod help;
pub mod initialize;
pub mod live;
pub mod mcp;
#[cfg(feature = "mob")]
pub mod mob;
pub mod models;
pub mod runtime;
pub mod runtime_host;
pub mod schedule;
pub mod session;
pub mod skills;
pub mod turn;
pub mod workgraph;

use serde_json::value::RawValue;

use crate::error;
use crate::protocol::{RpcId, RpcResponse};
use crate::session_runtime::SessionRuntime;
use meerkat_core::SessionId;

// ---------------------------------------------------------------------------
// Shared helpers
// ---------------------------------------------------------------------------

/// Parse typed params from a `RawValue`, returning an `RpcResponse` error on failure.
#[allow(clippy::result_large_err)]
pub(crate) fn parse_params<T: serde::de::DeserializeOwned>(
    params: Option<&RawValue>,
) -> Result<T, RpcResponse> {
    let raw =
        params.ok_or_else(|| RpcResponse::error(None, error::INVALID_PARAMS, "Missing params"))?;
    serde_json::from_str(raw.get()).map_err(|e| {
        RpcResponse::error(None, error::INVALID_PARAMS, format!("Invalid params: {e}"))
    })
}

/// Extension trait to set the id on an RpcResponse (used when `parse_params`
/// returns an error before we have the id available in the response).
pub(crate) trait RpcResponseExt {
    fn with_id(self, id: Option<RpcId>) -> Self;
}

impl RpcResponseExt for RpcResponse {
    fn with_id(mut self, id: Option<RpcId>) -> Self {
        self.id = id;
        self
    }
}

/// Re-export `WireUsage` from contracts as the canonical usage type.
pub use meerkat_contracts::WireUsage as UsageResult;

/// Parse `<session_id>` or `<realm_id>:<session_id>` and enforce realm match.
#[allow(clippy::result_large_err)]
pub(crate) fn parse_session_id_for_runtime(
    id: Option<RpcId>,
    raw: &str,
    runtime: &SessionRuntime,
) -> Result<SessionId, RpcResponse> {
    let locator = match meerkat_contracts::SessionLocator::parse(raw) {
        Ok(locator) => locator,
        Err(err) => {
            return Err(RpcResponse::error(
                id,
                error::INVALID_PARAMS,
                format!("Invalid session_id '{raw}': {err}"),
            ));
        }
    };
    if let Some(locator_realm) = locator.realm_id.as_ref() {
        let runtime_realm = runtime.realm_id();
        if runtime_realm.as_ref() != Some(locator_realm) {
            return Err(RpcResponse::error(
                id,
                error::INVALID_PARAMS,
                format!(
                    "Session locator realm '{}' does not match active runtime realm '{}'",
                    locator_realm.as_str(),
                    runtime_realm
                        .as_ref()
                        .map(|r| r.as_str())
                        .unwrap_or("<none>")
                ),
            ));
        }
    }
    Ok(locator.session_id)
}
