//! Shared utility functions for the simple API

use crate::agent::utils::render_prompt_for_display;
use crate::model::{AgentMessage, MessagePart};
use crate::tools::CapabilityRequirement;
use crate::tools::builtins::all_builtin_tools;
use anyhow::{Result, anyhow};
use once_cell::sync::Lazy;
use querymt::LLMParams;
use querymt::chat::ChatRole;
#[cfg(feature = "plugin-loaders")]
use querymt::dynamic::PluginRegistryDynamicExt;
use querymt::plugin::host::PluginRegistry;
use std::collections::HashSet;
use std::path::PathBuf;

/// Converts a path to an absolute path, canonicalizing if it exists
pub(super) fn to_absolute_path(path: PathBuf) -> Result<PathBuf> {
    if path.is_absolute() {
        path.canonicalize()
            .map_err(|e| anyhow!("Failed to canonicalize path {:?}: {}", path, e))
    } else {
        std::env::current_dir()
            .map_err(|e| anyhow!("Failed to get current directory: {}", e))?
            .join(&path)
            .canonicalize()
            .map_err(|e| anyhow!("Failed to canonicalize path {:?}: {}", path, e))
    }
}

pub(super) async fn default_registry() -> Result<PluginRegistry> {
    // Best-effort bootstrap of provider metadata used by plugin initialization paths.
    if let Err(err) = querymt::providers::update_providers_if_stale().await {
        log::warn!("Failed to update providers metadata cache: {}", err);
    }

    let cfg_path = querymt_utils::providers::get_providers_config(None).await?;
    #[cfg(feature = "plugin-loaders")]
    {
        PluginRegistry::from_path(&cfg_path)
            .map(|registry| registry.with_dynamic_loaders())
            .map_err(|e| anyhow!("Failed to load plugin registry: {}", e))
    }

    #[cfg(not(feature = "plugin-loaders"))]
    {
        PluginRegistry::from_path(&cfg_path)
            .map_err(|e| anyhow!("Failed to load plugin registry: {}", e))
    }
}

pub(super) fn latest_assistant_message(messages: &[AgentMessage]) -> Option<String> {
    messages
        .iter()
        .rev()
        .find(|msg| msg.role == ChatRole::Assistant)
        .map(|msg| {
            msg.parts
                .iter()
                .filter_map(|part| match part {
                    MessagePart::Text { content } => Some(content.clone()),
                    MessagePart::Prompt { blocks } => Some(render_prompt_for_display(blocks)),
                    _ => None,
                })
                .collect::<Vec<_>>()
                .join("")
        })
}

pub(super) fn infer_required_capabilities(tools: &[String]) -> HashSet<CapabilityRequirement> {
    let mut caps = HashSet::new();
    for tool in tools {
        if tool_requires_filesystem(tool) {
            caps.insert(CapabilityRequirement::Filesystem);
        }
    }
    caps
}

/// Set of builtin tool names that require filesystem access.
/// Lazily computed from `all_builtin_tools()` to stay in sync with tool definitions.
static FILESYSTEM_TOOLS: Lazy<HashSet<String>> = Lazy::new(|| {
    all_builtin_tools()
        .into_iter()
        .filter(|tool| {
            tool.required_capabilities()
                .contains(&CapabilityRequirement::Filesystem)
        })
        .map(|tool| tool.name().to_string())
        .collect()
});

pub(super) fn tool_requires_filesystem(tool_name: &str) -> bool {
    FILESYSTEM_TOOLS.contains(tool_name)
}

use super::config::AgentConfig;

pub(super) fn build_llm_config(config: &AgentConfig) -> Result<LLMParams> {
    config
        .llm_config
        .clone()
        .ok_or_else(|| anyhow!("LLM configuration required (call .provider() first)"))
}
