//! The 5 outbound tools the plugin registers via manifest.
//!
//! Each tool's argument schema lives in `nexo-plugin.toml`'s
//! `[[plugin.tools.outbound]]` blocks; this module owns the
//! handlers + dispatcher routing JSON-RPC `voice.<tool>` calls.

use std::sync::Arc;

use serde_json::Value;

use crate::breaker::BreakerRegistry;
use crate::cache::AudioCache;
use crate::error::Result;
use crate::fallback::FallbackConfig;
use crate::pipeline::synthesize_voice_note;
use crate::policy::ModelOverridesPolicy;
use crate::providers::TtsProvider;
use crate::store::VoiceModeStore;

pub mod inbound_transform;
pub mod language_style_inbound_transform;
pub mod list_voices;
pub mod reply_transform;
pub mod set;
pub mod unset;

/// Shared handler context. Cloneable; clones reference the same
/// underlying Arcs.
#[derive(Clone)]
pub struct VoiceToolContext {
    pub store: VoiceModeStore,
    pub providers: Vec<Arc<dyn TtsProvider>>,
    pub fallback: FallbackConfig,
    pub breakers: BreakerRegistry,
    pub policy: ModelOverridesPolicy,
    pub cache: AudioCache,
    pub max_text_length: usize,
}

/// Dispatcher: maps the broker's tool name → handler. Returns
/// JSON ready to ship back over the wire.
pub async fn dispatch(ctx: &VoiceToolContext, tool: &str, args: Value) -> Result<Value> {
    match tool {
        "voice_inbound_transform" => inbound_transform::handle(ctx, args).await,
        "voice_language_style_inbound_transform" => {
            language_style_inbound_transform::handle(ctx, args).await
        }
        "voice_reply_transform" => reply_transform::handle(ctx, args).await,
        "voice_list_voices" => list_voices::handle(ctx, args).await,
        "voice_set" => set::handle(ctx, args).await,
        "voice_unset" => unset::handle(ctx, args).await,
        other => Err(crate::error::VoiceError::NotSupported(Box::leak(
            format!("unknown tool `{other}`").into_boxed_str(),
        ))),
    }
}

#[doc(hidden)]
pub fn _expose_synthesize() -> &'static str {
    // Suppress unused-import warning for `synthesize_voice_note`;
    // it's used by the reply_transform handler.
    "ok"
}

#[allow(dead_code)]
pub(crate) async fn invoke_pipeline(
    ctx: &VoiceToolContext,
    text: &str,
    voice_id: &str,
    provider_hint: Option<&str>,
    speed: Option<f32>,
    pitch_pct: Option<i32>,
    style: Option<&str>,
    model_id: Option<&str>,
) -> Result<crate::pipeline::SynthesisOutcome> {
    use crate::pipeline::PipelineRequest;
    synthesize_voice_note(
        &ctx.providers,
        &ctx.fallback,
        &ctx.breakers,
        &ctx.cache,
        PipelineRequest {
            text,
            voice_id,
            provider_hint,
            speed,
            pitch_pct,
            style,
            model_id,
            max_text_length: ctx.max_text_length,
        },
    )
    .await
}
