i-self 0.4.3

Personal developer-companion CLI: scans your repos, indexes code semantically, watches your activity, and moves AI-agent sessions between tools (Claude Code, Aider, Goose, OpenAI Codex CLI, Continue.dev, OpenCode).
use super::*;
use crate::storage::profile::Proficiency;
use tracing::info;

/// Aggregates individual profiles into team insights
pub struct TeamAggregator;

impl TeamAggregator {
    pub fn new() -> Self {
        Self
    }

    /// Aggregate team profile from individual profiles
    pub fn aggregate(
        &self,
        config: &TeamConfig,
        profiles: &[DeveloperProfile],
    ) -> Result<TeamProfile> {
        info!("Aggregating team profile for: {}", config.name);
        
        let language_coverage = self.aggregate_language_coverage(profiles);
        let technology_coverage = self.aggregate_technology_coverage(profiles);
        let member_stats = self.aggregate_member_stats(config, profiles);
        let collaboration_patterns = self.analyze_collaboration_patterns(profiles);
        let knowledge_distribution = self.analyze_knowledge_distribution(profiles, &language_coverage);
        let recommendations = self.generate_recommendations(
            profiles,
            &language_coverage,
            &knowledge_distribution,
        );

        Ok(TeamProfile {
            config: config.clone(),
            aggregated_at: chrono::Utc::now(),
            language_coverage,
            technology_coverage,
            member_stats,
            collaboration_patterns,
            knowledge_distribution,
            recommendations,
        })
    }

    fn aggregate_language_coverage(
        &self,
        profiles: &[DeveloperProfile],
    ) -> HashMap<String, TeamLanguageCoverage> {
        let mut coverage: HashMap<String, TeamLanguageCoverage> = HashMap::new();

        for profile in profiles {
            let username = &profile.identity.github_username;
            
            for (lang, _) in &profile.github.primary_languages {
                let entry = coverage.entry(lang.clone()).or_insert_with(|| TeamLanguageCoverage {
                    language: lang.clone(),
                    total_experience_years: 0.0,
                    expert_count: 0,
                    advanced_count: 0,
                    intermediate_count: 0,
                    beginner_count: 0,
                    primary_experts: Vec::new(),
                });

                // Find detailed skill info if available
                if let Some(skill) = profile.skills.languages.iter().find(|s| &s.name == lang) {
                    entry.total_experience_years += skill.years_experience;
                    
                    match skill.proficiency {
                        Proficiency::Expert => {
                            entry.expert_count += 1;
                            if let Some(username) = username {
                                entry.primary_experts.push(username.clone());
                            }
                        }
                        Proficiency::Advanced => entry.advanced_count += 1,
                        Proficiency::Intermediate => entry.intermediate_count += 1,
                        Proficiency::Beginner => entry.beginner_count += 1,
                    }
                } else {
                    // Estimate from GitHub data
                    entry.intermediate_count += 1;
                }
            }
        }

        coverage
    }

    fn aggregate_technology_coverage(
        &self,
        profiles: &[DeveloperProfile],
    ) -> HashMap<String, TechnologyCoverage> {
        let mut coverage: HashMap<String, TechnologyCoverage> = HashMap::new();

        for profile in profiles {
            let username = profile.identity.github_username.clone().unwrap_or_default();
            
            // Frameworks
            for framework in &profile.skills.frameworks {
                let entry = coverage.entry(framework.name.clone()).or_insert_with(|| TechnologyCoverage {
                    name: framework.name.clone(),
                    category: "framework".to_string(),
                    users: Vec::new(),
                    expertise_level: TeamExpertiseLevel::None,
                });
                
                entry.users.push(username.clone());
            }

            // Tools
            for tool in &profile.skills.tools {
                let entry = coverage.entry(tool.name.clone()).or_insert_with(|| TechnologyCoverage {
                    name: tool.name.clone(),
                    category: "tool".to_string(),
                    users: Vec::new(),
                    expertise_level: TeamExpertiseLevel::None,
                });
                
                if !entry.users.contains(&username) {
                    entry.users.push(username.clone());
                }
            }

            // Libraries from coding patterns
            for (lang, libs) in &profile.coding_patterns.common_libraries {
                for lib in libs {
                    let key = format!("{}:{}", lang, lib);
                    let entry = coverage.entry(key.clone()).or_insert_with(|| TechnologyCoverage {
                        name: lib.clone(),
                        category: "library".to_string(),
                        users: Vec::new(),
                        expertise_level: TeamExpertiseLevel::None,
                    });
                    
                    if !entry.users.contains(&username) {
                        entry.users.push(username.clone());
                    }
                }
            }
        }

        // Calculate expertise levels
        let total_members = profiles.len();
        for entry in coverage.values_mut() {
            let user_count = entry.users.len();
            let ratio = user_count as f64 / total_members as f64;
            
            entry.expertise_level = if ratio >= 0.5 {
                TeamExpertiseLevel::Deep
            } else if ratio >= 0.25 {
                TeamExpertiseLevel::Moderate
            } else if user_count > 0 {
                TeamExpertiseLevel::Limited
            } else {
                TeamExpertiseLevel::None
            };
        }

        coverage
    }

    fn aggregate_member_stats(
        &self,
        config: &TeamConfig,
        profiles: &[DeveloperProfile],
    ) -> Vec<MemberStats> {
        profiles.iter()
            .map(|profile| {
                let username = profile.identity.github_username.clone().unwrap_or_default();
                
                // Find role from team config
                let role = config.members.iter()
                    .find(|m| m.github_username == username)
                    .map(|m| m.role.clone())
                    .unwrap_or(TeamRole::Contributor);

                MemberStats {
                    github_username: username.clone(),
                    role,
                    primary_languages: profile.github.primary_languages.iter()
                        .take(3)
                        .map(|(l, _)| l.clone())
                        .collect(),
                    contribution_score: self.calculate_contribution_score(profile),
                    code_review_activity: CodeReviewStats {
                        reviews_given: 0, // Would need GitHub API data
                        reviews_received: 0,
                        avg_review_time_hours: None,
                    },
                    knowledge_areas: profile.skills.languages.iter()
                        .filter(|s| matches!(s.proficiency, Proficiency::Advanced | Proficiency::Expert))
                        .map(|s| s.name.clone())
                        .collect(),
                }
            })
            .collect()
    }

    fn calculate_contribution_score(&self, profile: &DeveloperProfile) -> f64 {
        let mut score = 0.0;
        
        // GitHub activity
        score += profile.github.total_contributions_30d as f64 * 0.1;
        score += profile.github.total_repositories as f64 * 0.5;
        
        // Language diversity
        score += profile.github.primary_languages.len() as f64 * 0.3;
        
        // Skill depth
        let expert_skills = profile.skills.languages.iter()
            .filter(|s| matches!(s.proficiency, Proficiency::Expert))
            .count();
        score += expert_skills as f64 * 1.0;
        
        score
    }

    fn analyze_collaboration_patterns(
        &self,
        _profiles: &[DeveloperProfile],
    ) -> CollaborationPatterns {
        // This would require GitHub API data about PR reviews, mentions, etc.
        // For now, return empty patterns
        
        CollaborationPatterns {
            most_active_pairs: Vec::new(),
            code_review_network: Vec::new(),
            knowledge_sharing_score: 0.0,
        }
    }

    fn analyze_knowledge_distribution(
        &self,
        profiles: &[DeveloperProfile],
        language_coverage: &HashMap<String, TeamLanguageCoverage>,
    ) -> KnowledgeDistribution {
        let mut bus_factor: HashMap<String, usize> = HashMap::new();
        let mut knowledge_silos: Vec<KnowledgeSilo> = Vec::new();
        let mut cross_training_opportunities: Vec<CrossTrainingOpportunity> = Vec::new();

        for (lang, coverage) in language_coverage {
            // Calculate bus factor
            let experts = coverage.expert_count + coverage.advanced_count;
            bus_factor.insert(lang.clone(), experts.max(1));

            // Identify knowledge silos
            if coverage.expert_count <= 1 && coverage.advanced_count <= 2 {
                let risk_level = if coverage.expert_count == 0 {
                    RiskLevel::High
                } else {
                    RiskLevel::Medium
                };

                knowledge_silos.push(KnowledgeSilo {
                    technology: lang.clone(),
                    primary_experts: coverage.primary_experts.clone(),
                    risk_level,
                });

                // Suggest cross-training
                if coverage.expert_count > 0 {
                    // Find potential trainees (people who know it at intermediate level)
                    let trainees: Vec<String> = profiles.iter()
                        .filter(|p| {
                            p.github.primary_languages.iter().any(|(l, _)| l == lang) &&
                            !coverage.primary_experts.iter().any(|e| {
                                p.identity.github_username.as_ref() == Some(e)
                            })
                        })
                        .filter_map(|p| p.identity.github_username.clone())
                        .collect();

                    if !trainees.is_empty() {
                        cross_training_opportunities.push(CrossTrainingOpportunity {
                            technology: lang.clone(),
                            potential_mentors: coverage.primary_experts.clone(),
                            recommended_trainees: trainees,
                            priority: if coverage.expert_count == 1 {
                                Priority::High
                            } else {
                                Priority::Medium
                            },
                        });
                    }
                }
            }
        }

        KnowledgeDistribution {
            bus_factor,
            knowledge_silos,
            cross_training_opportunities,
        }
    }

    fn generate_recommendations(
        &self,
        profiles: &[DeveloperProfile],
        language_coverage: &HashMap<String, TeamLanguageCoverage>,
        knowledge_distribution: &KnowledgeDistribution,
    ) -> Vec<TeamRecommendation> {
        let mut recommendations = Vec::new();

        // Check for critical knowledge silos
        for silo in &knowledge_distribution.knowledge_silos {
            if matches!(silo.risk_level, RiskLevel::Critical | RiskLevel::High) {
                recommendations.push(TeamRecommendation {
                    category: RecommendationCategory::KnowledgeSharing,
                    title: format!("Address Knowledge Silo: {}", silo.technology),
                    description: format!(
                        "Only {} expert(s) in {}. Risk of knowledge loss if they leave.",
                        silo.primary_experts.len(),
                        silo.technology
                    ),
                    action_items: vec![
                        format!("Pair program with {} on {}", silo.primary_experts.join(", "), silo.technology),
                        format!("Document {} best practices", silo.technology),
                        "Schedule knowledge sharing session".to_string(),
                    ],
                    priority: if silo.primary_experts.len() == 1 {
                        Priority::Critical
                    } else {
                        Priority::High
                    },
                });
            }
        }

        // Check for skill gaps
        let has_frontend = language_coverage.contains_key("JavaScript") 
            || language_coverage.contains_key("TypeScript");
        let has_backend = language_coverage.contains_key("Rust")
            || language_coverage.contains_key("Go")
            || language_coverage.contains_key("Python")
            || language_coverage.contains_key("Java");

        if has_backend && !has_frontend {
            recommendations.push(TeamRecommendation {
                category: RecommendationCategory::SkillGap,
                title: "Consider Frontend Expertise".to_string(),
                description: "Team has strong backend skills but limited frontend coverage.".to_string(),
                action_items: vec![
                    "Evaluate need for frontend development".to_string(),
                    "Consider hiring frontend specialist".to_string(),
                    "Cross-train backend developers in basic frontend".to_string(),
                ],
                priority: Priority::Medium,
            });
        }

        // Check team size vs coverage
        if profiles.len() < 3 && language_coverage.len() > 5 {
            recommendations.push(TeamRecommendation {
                category: RecommendationCategory::Hiring,
                title: "Team May Be Spread Too Thin".to_string(),
                description: format!(
                    "Small team ({}) maintaining expertise in {} different technologies.",
                    profiles.len(),
                    language_coverage.len()
                ),
                action_items: vec![
                    "Prioritize core technologies".to_string(),
                    "Consider consolidating tech stack".to_string(),
                    "Plan strategic hiring".to_string(),
                ],
                priority: Priority::High,
            });
        }

        recommendations
    }
}

impl Default for TeamAggregator {
    fn default() -> Self {
        Self::new()
    }
}