use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::{debug, info};
use crate::brp_messages::DebugCommand;
use crate::error::Result;
use crate::pattern_learning::{AnonymizedCommand, DebugPattern, PatternLearningSystem};
const MAX_SUGGESTIONS: usize = 5;
const MIN_SUGGESTION_CONFIDENCE: f64 = 0.6;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DebugSuggestion {
pub command: String,
pub confidence: f64,
pub reasoning: String,
pub expected_outcome: String,
pub pattern_id: Option<String>,
pub priority: i32,
}
#[derive(Debug, Clone)]
pub struct SuggestionContext {
pub session_id: String,
pub recent_commands: Vec<DebugCommand>,
pub system_state: SystemState,
pub user_goal: Option<String>,
}
#[derive(Debug, Clone)]
pub struct SystemState {
pub entity_count: usize,
pub fps: f32,
pub memory_mb: f32,
pub active_systems: usize,
pub has_errors: bool,
}
pub struct SuggestionEngine {
pattern_system: Arc<PatternLearningSystem>,
suggestion_history: Arc<RwLock<HashMap<String, SuggestionMetrics>>>,
templates: Arc<RwLock<Vec<SuggestionTemplate>>>,
}
#[derive(Debug, Clone)]
struct SuggestionMetrics {
suggested_count: usize,
accepted_count: usize,
success_rate: f64,
}
#[derive(Debug, Clone)]
struct SuggestionTemplate {
condition: SuggestionCondition,
template: String,
command_template: String,
priority: i32,
}
#[derive(Debug, Clone)]
enum SuggestionCondition {
HighEntityCount(usize),
LowFPS(f32),
HighMemory(f32),
HasErrors,
AfterCommand(String),
SequenceMatch(Vec<String>),
}
impl SuggestionEngine {
pub fn new(pattern_system: Arc<PatternLearningSystem>) -> Self {
let engine = Self {
pattern_system,
suggestion_history: Arc::new(RwLock::new(HashMap::new())),
templates: Arc::new(RwLock::new(Vec::new())),
};
let templates_clone = engine.templates.clone();
tokio::spawn(async move {
let mut templates = templates_clone.write().await;
templates.extend(Self::create_default_templates());
});
engine
}
pub async fn generate_suggestions(&self, context: &SuggestionContext) -> Vec<DebugSuggestion> {
let mut suggestions = Vec::new();
let pattern_suggestions = self.generate_pattern_suggestions(context).await;
suggestions.extend(pattern_suggestions);
let template_suggestions = self.generate_template_suggestions(context).await;
suggestions.extend(template_suggestions);
let context_suggestions = self.generate_context_suggestions(context).await;
suggestions.extend(context_suggestions);
suggestions.sort_by(|a, b| {
let priority_cmp = b.priority.cmp(&a.priority);
if priority_cmp == std::cmp::Ordering::Equal {
b.confidence.partial_cmp(&a.confidence).unwrap()
} else {
priority_cmp
}
});
suggestions
.into_iter()
.filter(|s| s.confidence >= MIN_SUGGESTION_CONFIDENCE)
.take(MAX_SUGGESTIONS)
.collect()
}
async fn generate_pattern_suggestions(&self, context: &SuggestionContext) -> Vec<DebugSuggestion> {
let mut suggestions = Vec::new();
let anonymized: Vec<AnonymizedCommand> = context
.recent_commands
.iter()
.map(|cmd| self.anonymize_command(cmd.clone()))
.collect();
let patterns = self.pattern_system.find_matching_patterns(&anonymized).await;
for pattern in patterns {
if let Some(suggestion) = self.pattern_to_suggestion(&pattern, context).await {
suggestions.push(suggestion);
}
}
suggestions
}
async fn generate_template_suggestions(&self, context: &SuggestionContext) -> Vec<DebugSuggestion> {
let mut suggestions = Vec::new();
let templates = self.templates.read().await;
for template in templates.iter() {
if self.matches_condition(&template.condition, context) {
suggestions.push(DebugSuggestion {
command: template.command_template.clone(),
confidence: 0.8,
reasoning: template.template.clone(),
expected_outcome: "Based on system state analysis".to_string(),
pattern_id: None,
priority: template.priority,
});
}
}
suggestions
}
async fn generate_context_suggestions(&self, context: &SuggestionContext) -> Vec<DebugSuggestion> {
let mut suggestions = Vec::new();
if context.system_state.fps < 30.0 {
suggestions.push(DebugSuggestion {
command: "profile_system".to_string(),
confidence: 0.9,
reasoning: format!("FPS is low ({:.1}), profiling can identify bottlenecks", context.system_state.fps),
expected_outcome: "Identify systems causing frame drops".to_string(),
pattern_id: None,
priority: 10,
});
}
if context.system_state.memory_mb > 500.0 {
suggestions.push(DebugSuggestion {
command: "profile_memory".to_string(),
confidence: 0.85,
reasoning: format!("High memory usage ({:.1}MB)", context.system_state.memory_mb),
expected_outcome: "Identify memory leaks or excessive allocations".to_string(),
pattern_id: None,
priority: 9,
});
}
if context.system_state.entity_count > 10000 {
suggestions.push(DebugSuggestion {
command: "observe entities --limit 100".to_string(),
confidence: 0.75,
reasoning: format!("High entity count ({})", context.system_state.entity_count),
expected_outcome: "Inspect entity distribution and types".to_string(),
pattern_id: None,
priority: 8,
});
}
if context.system_state.has_errors {
suggestions.push(DebugSuggestion {
command: "detect_issues".to_string(),
confidence: 0.95,
reasoning: "System has errors that need investigation".to_string(),
expected_outcome: "Identify and diagnose system errors".to_string(),
pattern_id: None,
priority: 15,
});
}
suggestions
}
async fn pattern_to_suggestion(
&self,
pattern: &DebugPattern,
context: &SuggestionContext,
) -> Option<DebugSuggestion> {
let current_len = context.recent_commands.len();
if current_len >= pattern.sequence.len() {
return None;
}
let next_cmd = &pattern.sequence[current_len];
Some(DebugSuggestion {
command: self.anonymized_to_command_string(next_cmd),
confidence: pattern.confidence,
reasoning: format!(
"Based on pattern with {:.0}% success rate (seen {} times)",
pattern.success_rate * 100.0,
pattern.frequency
),
expected_outcome: format!(
"Continue debugging workflow (pattern confidence: {:.2})",
pattern.confidence
),
pattern_id: Some(pattern.id.clone()),
priority: (pattern.confidence * 10.0) as i32,
})
}
fn matches_condition(&self, condition: &SuggestionCondition, context: &SuggestionContext) -> bool {
match condition {
SuggestionCondition::HighEntityCount(threshold) => {
context.system_state.entity_count > *threshold
}
SuggestionCondition::LowFPS(threshold) => {
context.system_state.fps < *threshold
}
SuggestionCondition::HighMemory(threshold) => {
context.system_state.memory_mb > *threshold
}
SuggestionCondition::HasErrors => {
context.system_state.has_errors
}
SuggestionCondition::AfterCommand(cmd_type) => {
context.recent_commands.last()
.map(|cmd| self.get_command_type(cmd) == *cmd_type)
.unwrap_or(false)
}
SuggestionCondition::SequenceMatch(sequence) => {
let recent_types: Vec<String> = context.recent_commands
.iter()
.map(|cmd| self.get_command_type(cmd))
.collect();
recent_types.ends_with(sequence)
}
}
}
pub async fn track_suggestion_acceptance(
&self,
suggestion_id: &str,
accepted: bool,
success: bool,
) {
let mut history = self.suggestion_history.write().await;
history
.entry(suggestion_id.to_string())
.and_modify(|metrics| {
metrics.suggested_count += 1;
if accepted {
metrics.accepted_count += 1;
metrics.success_rate = (metrics.success_rate * (metrics.accepted_count - 1) as f64
+ if success { 1.0 } else { 0.0 })
/ metrics.accepted_count as f64;
}
})
.or_insert(SuggestionMetrics {
suggested_count: 1,
accepted_count: if accepted { 1 } else { 0 },
success_rate: if accepted && success { 1.0 } else { 0.0 },
});
debug!("Tracked suggestion acceptance: {} (accepted: {}, success: {})",
suggestion_id, accepted, success);
}
pub async fn get_suggestion_metrics(&self) -> HashMap<String, (f64, f64)> {
let history = self.suggestion_history.read().await;
history
.iter()
.map(|(id, metrics)| {
let acceptance_rate = metrics.accepted_count as f64 / metrics.suggested_count as f64;
(id.clone(), (acceptance_rate, metrics.success_rate))
})
.collect()
}
fn create_default_templates() -> Vec<SuggestionTemplate> {
vec![
SuggestionTemplate {
condition: SuggestionCondition::LowFPS(30.0),
template: "Performance is below target, profile systems".to_string(),
command_template: "profile_system --duration 5".to_string(),
priority: 10,
},
SuggestionTemplate {
condition: SuggestionCondition::HighMemory(500.0),
template: "Memory usage is high, check for leaks".to_string(),
command_template: "profile_memory --detect-leaks".to_string(),
priority: 9,
},
SuggestionTemplate {
condition: SuggestionCondition::AfterCommand("observe".to_string()),
template: "After observing, inspect specific entities".to_string(),
command_template: "inspect_entity --detailed".to_string(),
priority: 5,
},
]
}
fn anonymize_command(&self, command: DebugCommand) -> AnonymizedCommand {
AnonymizedCommand {
command_type: self.get_command_type(&command),
param_shape: HashMap::new(),
time_bucket: crate::pattern_learning::TimeBucket::Medium,
}
}
fn get_command_type(&self, command: &DebugCommand) -> String {
match command {
DebugCommand::InspectEntity { .. } => "inspect_entity",
DebugCommand::GetHierarchy { .. } => "get_hierarchy",
DebugCommand::GetSystemInfo { .. } => "get_system_info",
DebugCommand::ProfileSystem { .. } => "profile_system",
DebugCommand::SetVisualDebug { .. } => "set_visual_debug",
_ => "other",
}.to_string()
}
fn anonymized_to_command_string(&self, cmd: &AnonymizedCommand) -> String {
cmd.command_type.clone()
}
}