use anyhow::Result;
use fraiseql_core::graphql::{DEFAULT_MAX_ALIASES, complexity::RequestValidator, parse_query};
use serde::Serialize;
use crate::output::CommandResult;
#[derive(Debug, Serialize)]
pub struct ExplainResponse {
pub query: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub sql: Option<String>,
pub estimated_cost: usize,
pub complexity: ComplexityInfo,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub warnings: Vec<String>,
}
#[derive(Debug, Serialize)]
pub struct ComplexityInfo {
pub depth: usize,
pub score: usize,
pub alias_count: usize,
}
pub fn run(query: &str) -> Result<CommandResult> {
let parsed = parse_query(query)?;
let validator = RequestValidator::default();
let metrics = validator.analyze(query)?;
let depth = metrics.depth;
let score = metrics.complexity;
let alias_count = metrics.alias_count;
let mut warnings = Vec::new();
if depth > 10 {
warnings.push(format!(
"Query depth {depth} exceeds recommended maximum of 10 - consider breaking into multiple queries"
));
}
if score > 100 {
warnings.push(format!(
"Query complexity score {score} is high - consider optimizing query structure"
));
}
if alias_count > DEFAULT_MAX_ALIASES {
warnings.push(format!("Query has {alias_count} aliases — consider reducing alias count"));
}
let sql = format!(
"-- Query execution plan for: {}\n-- Depth: {}, Score: {}, Aliases: {}\nSELECT data FROM v_table LIMIT 1000;",
parsed.root_field, depth, score, alias_count
);
let has_warnings = !warnings.is_empty();
let response = ExplainResponse {
query: query.to_string(),
sql: Some(sql),
estimated_cost: score,
complexity: ComplexityInfo {
depth,
score,
alias_count,
},
warnings: warnings.clone(),
};
let result = if has_warnings {
CommandResult::success_with_warnings("explain", serde_json::to_value(&response)?, warnings)
} else {
CommandResult::success("explain", serde_json::to_value(&response)?)
};
Ok(result)
}