//! Component domain tools — list, show per workspace environment.
//!
//! Components are workspace-level NCL files in `infra/<env>/components/*.ncl`
//! AND extension-level metadata in `extensions/components/<name>/metadata.ncl`.
//! These tools operate on the workspace view (what is deployed where).

use crate::protocol::{ToolCategory, ToolError};
use crate::sources::{NclCache, get_workspace, list_components, list_infra_envs};
use crate::tool::{Context, Tool};
use async_trait::async_trait;
use serde_json::{Value, json};
use std::sync::Arc;

// ── component_list ────────────────────────────────────────────────────────────

pub struct ComponentList {
    cache: Arc<NclCache>,
}
impl ComponentList {
    pub fn new(cache: Arc<NclCache>) -> Self { Self { cache } }
}

#[async_trait]
impl Tool for ComponentList {
    fn name(&self) -> &'static str { "component_list" }
    fn description(&self) -> &'static str { "List components deployed in a workspace environment" }
    fn schema(&self) -> Value {
        json!({
            "type": "object",
            "required": ["workspace"],
            "properties": {
                "workspace": { "type": "string" },
                "env": { "type": "string", "description": "Filter to a specific environment" }
            }
        })
    }
    fn category(&self) -> ToolCategory { ToolCategory::Read }

    async fn invoke(&self, params: Value, ctx: &Context) -> Result<Value, ToolError> {
        let ws_name = params["workspace"]
            .as_str()
            .ok_or_else(|| ToolError::invalid_param("workspace", "required string"))?;
        let env_filter = params["env"].as_str();

        let ws = get_workspace(&ctx.env.workspaces_root, ws_name).await?;
        let envs = list_infra_envs(ws_name, &ws.path).await?;

        let mut items: Vec<Value> = Vec::new();
        for env in &envs {
            if let Some(f) = env_filter {
                if env.environment != f { continue; }
            }
            let comps = list_components(ws_name, &env.environment, &env.path).await?;
            for comp in comps {
                // Try to load the component NCL for additional metadata
                let details = self.cache.get(&comp.path).await.ok().map(|v| (*v).clone());
                items.push(json!({
                    "name": comp.name,
                    "workspace": ws_name,
                    "env": env.environment,
                    "path": comp.path.to_string_lossy(),
                    "config": details,
                }));
            }
        }

        Ok(json!({ "items": items, "total": items.len() }))
    }
}

// ── component_show ────────────────────────────────────────────────────────────

pub struct ComponentShow {
    cache: Arc<NclCache>,
}
impl ComponentShow {
    pub fn new(cache: Arc<NclCache>) -> Self { Self { cache } }
}

#[async_trait]
impl Tool for ComponentShow {
    fn name(&self) -> &'static str { "component_show" }
    fn description(&self) -> &'static str { "Show a component's NCL config in a specific workspace environment" }
    fn schema(&self) -> Value {
        json!({
            "type": "object",
            "required": ["workspace", "env", "name"],
            "properties": {
                "workspace": { "type": "string" },
                "env":       { "type": "string" },
                "name":      { "type": "string" }
            }
        })
    }
    fn category(&self) -> ToolCategory { ToolCategory::Read }

    async fn invoke(&self, params: Value, ctx: &Context) -> Result<Value, ToolError> {
        let ws_name = params["workspace"]
            .as_str()
            .ok_or_else(|| ToolError::invalid_param("workspace", "required string"))?;
        let env_name = params["env"]
            .as_str()
            .ok_or_else(|| ToolError::invalid_param("env", "required string"))?;
        let comp_name = params["name"]
            .as_str()
            .ok_or_else(|| ToolError::invalid_param("name", "required string"))?;

        let ws = get_workspace(&ctx.env.workspaces_root, ws_name).await?;
        let comp_path = ws.path
            .join("infra")
            .join(env_name)
            .join("components")
            .join(format!("{comp_name}.ncl"));

        if !comp_path.exists() {
            return Err(ToolError::not_found(format!(
                "component '{comp_name}' in {ws_name}/{env_name}"
            )));
        }

        let config = self.cache.get(&comp_path).await?;

        // Cross-reference with extension metadata if available
        let ext_meta_path = ctx.env.extensions_root
            .join("components")
            .join(comp_name)
            .join("metadata.ncl");
        let ext_meta = if ext_meta_path.exists() {
            self.cache.get(&ext_meta_path).await.ok().map(|v| (*v).clone())
        } else {
            None
        };

        Ok(json!({
            "name": comp_name,
            "workspace": ws_name,
            "env": env_name,
            "config": (*config).clone(),
            "extension_metadata": ext_meta,
        }))
    }
}
