use agileplus_domain::intent_graph::{IntentGraph, NodeType};
use anyhow::{Context, Result};
use clap::Args;
use serde_json;
use std::io::Write;
#[derive(Debug, Args)]
#[command(name = "query")]
pub struct QueryCommand {
#[arg(short, long)]
input: String,
#[arg(short, long)]
node_type: Option<String>,
#[arg(short, long)]
output: Option<String>,
}
#[derive(Debug, serde::Serialize)]
struct QueryResult {
nodes: Vec<agileplus_domain::intent_graph::Node>,
total: usize,
filtered: usize,
}
impl QueryCommand {
pub fn run(&self) -> Result<()> {
let json = std::fs::read_to_string(&self.input)
.with_context(|| format!("Failed to read input: {}", self.input))?;
let graph: IntentGraph = serde_json::from_str(&json)
.with_context(|| format!("Failed to parse JSON from: {}", self.input))?;
let filtered_nodes: Vec<_> = match &self.node_type {
Some(nt) => {
let target_type = nt.parse::<NodeType>().map_err(|e| {
anyhow::anyhow!("Invalid node type '{}': {}", nt, e)
})?;
graph.nodes.into_iter().filter(|n| n.node_type == target_type).collect()
}
None => graph.nodes,
};
let result = QueryResult {
total: graph.metadata.node_count.unwrap_or(0),
filtered: filtered_nodes.len(),
nodes: filtered_nodes,
};
let out = serde_json::to_string_pretty(&result)?;
match &self.output {
Some(path) => {
std::fs::write(path, &out)
.with_context(|| format!("Failed to write output: {}", path))?;
eprintln!("Query result written to {}", path);
}
None => {
std::io::stdout().write_all(out.as_bytes())?;
std::io::stdout().write_all(b"\n")?;
}
}
Ok(())
}
}