//! `wai docs search` (`docs.search`) and `wai registry show` (`registry.show`): the command line's
//! door to two operations `wai mcp` serves. Each runs the same tool (`../mcp/tools.rs`) and prints
//! its answer: the envelope's `data` with `--json`, lines for a person otherwise. Nothing leaves
//! the machine.

#![cfg_attr(not(feature = "mcp"), allow(dead_code, unused_imports))]

use super::envelope::{self as agent, EXIT_NOT_FOUND, OpError};
use serde_json::{Value, json};
use std::path::PathBuf;

type R = Result<bool, OpError>;

/// A tool's error, as the command line's: a specification that is not here, or a capability the
/// registry does not hold, is not found (exit 5).
fn failure(op: &str, e: String) -> OpError {
    if e.contains("no specification tree") || e.contains("holds no SPEC.md") {
        let m = e.split(": start the server").next().unwrap_or(&e).to_string();
        return OpError::new(EXIT_NOT_FOUND, "spec-not-found", format!("{op}: {m}"), "give --spec-root <the repository's wai/ directory>, or set WAI_SPEC_ROOT");
    }
    if let Some((c, _)) = e.split_once(" is not in the registry") {
        return OpError::new(EXIT_NOT_FOUND, "capability-not-found", format!("{c} is not in the registry"), "wai capabilities lists them");
    }
    OpError::from(e)
}

/// The tool's answer, as the envelope with `--json` and as `text` of it otherwise.
#[cfg(feature = "mcp")]
fn answer(op: &str, args: &Value, spec_root: Option<PathBuf>, text: impl Fn(&Value) -> String) -> R {
    let v = super::mcp::run_once(op, args, spec_root).map_err(|e| failure(op, e))?;
    if agent::ctx().json {
        agent::print(&agent::envelope_with(op, true, v, None));
    } else {
        println!("{}", text(&v));
    }
    Ok(true)
}

/// `wai docs search <word>... [--limit N] [--spec-root <wai-dir>]`.
#[cfg(feature = "mcp")]
pub fn docs_search(args: &[String]) -> R {
    let usage = super::usage("docs.search");
    let with_value = ["--limit", "--spec-root"];
    let words = agent::check_args(args, &with_value, &with_value, usize::MAX, &usage)?;
    if words.is_empty() {
        return Err(OpError::usage("the words to search for", &usage));
    }
    let mut a = json!({"query": words.join(" ")});
    if let Some(l) = super::opt(args, "--limit") {
        let n = l.parse::<u64>().ok().filter(|n| *n >= 1).ok_or_else(|| OpError::usage(format!("--limit {l}: a whole number, at least 1"), &usage))?;
        a["limit"] = json!(n);
    }
    answer("docs.search", &a, super::opt(args, "--spec-root").map(PathBuf::from), |v| {
        let hits = v["hits"].as_array().cloned().unwrap_or_default();
        let more = if v["truncated"] == true { "; there may be more (--limit)" } else { "" };
        let mut t = format!("{} line(s) in {} file(s) under {}{more}", hits.len(), v["files_searched"], v["root"].as_str().unwrap_or("?"));
        for h in &hits {
            t.push_str(&format!("\n{}:{}  {}\n    {}", h["file"].as_str().unwrap_or("?"), h["line"], h["section"].as_str().unwrap_or(""), h["text"].as_str().unwrap_or("")));
        }
        t
    })
}

/// `wai registry show <capability> [--spec-root <wai-dir>]`.
#[cfg(feature = "mcp")]
pub fn registry_show(args: &[String]) -> R {
    let usage = super::usage("registry.show");
    let with_value = ["--spec-root"];
    let pos = agent::check_args(args, &with_value, &with_value, 1, &usage)?;
    let c = *pos.first().ok_or_else(|| OpError::usage("the capability", &usage))?;
    answer("registry.show", &json!({"capability": c}), super::opt(args, "--spec-root").map(PathBuf::from), |v| {
        let r = &v["registry"];
        let member = |k: &str| r[k].as_str().unwrap_or("none").to_string();
        let yes = |k: &str| if v[k] == true { "yes" } else { "no" };
        let mut t = format!(
            "{c}\n  kind {}, status {}, media {}, role {}, tier {}, licence {}, replicate {}\n  {}\n  decoded natively by this build: {}; dispatched standalone: {}; companion only: {}; a candidate: {}",
            member("kind"),
            member("status"),
            member("media"),
            member("role"),
            member("tier"),
            member("licence"),
            member("replicate"),
            member("spec_section"),
            yes("decoded_natively_here"),
            yes("dispatched_standalone"),
            yes("companion_only"),
            yes("candidate"),
        );
        match v["spec_lines"].as_array() {
            Some(lines) => {
                t.push_str("\n  in the specification:");
                for h in lines {
                    t.push_str(&format!("\n    {}:{}  {}", h["file"].as_str().unwrap_or("?"), h["line"], h["text"].as_str().unwrap_or("")));
                }
            }
            None => t.push_str("\n  the specification's text is not here (--spec-root, or WAI_SPEC_ROOT)"),
        }
        t
    })
}

#[cfg(not(feature = "mcp"))]
pub fn docs_search(_: &[String]) -> R {
    Err(crate::door::not_built(wai::catalogue::op("docs.search").expect("listed")))
}

#[cfg(not(feature = "mcp"))]
pub fn registry_show(_: &[String]) -> R {
    Err(crate::door::not_built(wai::catalogue::op("registry.show").expect("listed")))
}
