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 crate::storage::DeveloperProfile;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tracing::info;

pub mod aggregation;
pub mod visualization;
pub mod insights;

pub use aggregation::TeamAggregator;

/// Team configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TeamConfig {
    pub name: String,
    pub description: Option<String>,
    pub members: Vec<TeamMember>,
    pub repositories: Vec<String>,
    pub created_at: chrono::DateTime<chrono::Utc>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TeamMember {
    pub github_username: String,
    pub role: TeamRole,
    pub joined_at: chrono::DateTime<chrono::Utc>,
    pub profile_path: Option<String>, // Path to their i-self profile
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TeamRole {
    Lead,
    Senior,
    Mid,
    Junior,
    Contributor,
}

impl std::fmt::Display for TeamRole {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            TeamRole::Lead => write!(f, "Lead"),
            TeamRole::Senior => write!(f, "Senior"),
            TeamRole::Mid => write!(f, "Mid"),
            TeamRole::Junior => write!(f, "Junior"),
            TeamRole::Contributor => write!(f, "Contributor"),
        }
    }
}

/// Aggregated team profile
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TeamProfile {
    pub config: TeamConfig,
    pub aggregated_at: chrono::DateTime<chrono::Utc>,
    
    // Skill coverage
    pub language_coverage: HashMap<String, TeamLanguageCoverage>,
    pub technology_coverage: HashMap<String, TechnologyCoverage>,
    
    // Member stats
    pub member_stats: Vec<MemberStats>,
    
    // Collaboration insights
    pub collaboration_patterns: CollaborationPatterns,
    
    // Knowledge distribution
    pub knowledge_distribution: KnowledgeDistribution,
    
    // Recommendations
    pub recommendations: Vec<TeamRecommendation>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TeamLanguageCoverage {
    pub language: String,
    pub total_experience_years: f64,
    pub expert_count: usize,
    pub advanced_count: usize,
    pub intermediate_count: usize,
    pub beginner_count: usize,
    pub primary_experts: Vec<String>, // GitHub usernames
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TechnologyCoverage {
    pub name: String,
    pub category: String, // e.g., "framework", "database", "tool"
    pub users: Vec<String>, // GitHub usernames
    pub expertise_level: TeamExpertiseLevel,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TeamExpertiseLevel {
    Deep,      // Multiple experts
    Moderate,  // Some experience
    Limited,   // Few users
    None,      // No one uses it
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemberStats {
    pub github_username: String,
    pub role: TeamRole,
    pub primary_languages: Vec<String>,
    pub contribution_score: f64, // Based on commits, PRs, etc.
    pub code_review_activity: CodeReviewStats,
    pub knowledge_areas: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CodeReviewStats {
    pub reviews_given: i64,
    pub reviews_received: i64,
    pub avg_review_time_hours: Option<f64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CollaborationPatterns {
    pub most_active_pairs: Vec<(String, String, i64)>, // (user1, user2, interaction_count)
    pub code_review_network: Vec<CodeReviewEdge>,
    pub knowledge_sharing_score: f64, // 0-1
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CodeReviewEdge {
    pub reviewer: String,
    pub reviewee: String,
    pub review_count: i64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KnowledgeDistribution {
    pub bus_factor: HashMap<String, usize>, // technology -> min people to lose knowledge
    pub knowledge_silos: Vec<KnowledgeSilo>,
    pub cross_training_opportunities: Vec<CrossTrainingOpportunity>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KnowledgeSilo {
    pub technology: String,
    pub primary_experts: Vec<String>,
    pub risk_level: RiskLevel,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RiskLevel {
    Critical,  // Single point of failure
    High,      // Very few experts
    Medium,    // Some coverage
    Low,       // Well distributed
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CrossTrainingOpportunity {
    pub technology: String,
    pub potential_mentors: Vec<String>,
    pub recommended_trainees: Vec<String>,
    pub priority: Priority,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Priority {
    Critical,
    High,
    Medium,
    Low,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TeamRecommendation {
    pub category: RecommendationCategory,
    pub title: String,
    pub description: String,
    pub action_items: Vec<String>,
    pub priority: Priority,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RecommendationCategory {
    SkillGap,
    KnowledgeSharing,
    Hiring,
    Process,
    Architecture,
}

/// Team manager for loading and managing team profiles
pub struct TeamManager {
    storage_path: std::path::PathBuf,
}

impl TeamManager {
    pub fn new(base_path: &std::path::Path) -> Result<Self> {
        let storage_path = base_path.join("teams");
        std::fs::create_dir_all(&storage_path)?;
        
        Ok(Self { storage_path })
    }

    /// Create a new team
    pub fn create_team(&self, name: &str, description: Option<&str>) -> Result<TeamConfig> {
        let config = TeamConfig {
            name: name.to_string(),
            description: description.map(|s| s.to_string()),
            members: Vec::new(),
            repositories: Vec::new(),
            created_at: chrono::Utc::now(),
        };
        
        self.save_team(&config)?;
        info!("Created team: {}", name);
        
        Ok(config)
    }

    /// Load team configuration
    pub fn load_team(&self, name: &str) -> Result<TeamConfig> {
        let path = self.storage_path.join(format!("{}.json", name));
        let content = std::fs::read_to_string(&path)?;
        let config: TeamConfig = serde_json::from_str(&content)?;
        Ok(config)
    }

    /// Save team configuration
    pub fn save_team(&self, config: &TeamConfig) -> Result<()> {
        let path = self.storage_path.join(format!("{}.json", config.name));
        let content = serde_json::to_string_pretty(config)?;
        std::fs::write(&path, content)?;
        Ok(())
    }

    /// List all teams
    pub fn list_teams(&self) -> Result<Vec<String>> {
        let mut teams = Vec::new();
        
        for entry in std::fs::read_dir(&self.storage_path)? {
            let entry = entry?;
            let path = entry.path();
            
            if path.extension().map(|e| e == "json").unwrap_or(false) {
                if let Some(stem) = path.file_stem() {
                    teams.push(stem.to_string_lossy().to_string());
                }
            }
        }
        
        Ok(teams)
    }

    /// Add member to team
    pub fn add_member(
        &self,
        team_name: &str,
        github_username: &str,
        role: TeamRole,
        profile_path: Option<&str>,
    ) -> Result<()> {
        let mut config = self.load_team(team_name)?;
        
        // Check if already a member
        if config.members.iter().any(|m| m.github_username == github_username) {
            anyhow::bail!("User {} is already a team member", github_username);
        }
        
        config.members.push(TeamMember {
            github_username: github_username.to_string(),
            role,
            joined_at: chrono::Utc::now(),
            profile_path: profile_path.map(|s| s.to_string()),
        });
        
        self.save_team(&config)?;
        info!("Added {} to team {}", github_username, team_name);
        
        Ok(())
    }

    /// Remove member from team
    pub fn remove_member(&self, team_name: &str, github_username: &str) -> Result<()> {
        let mut config = self.load_team(team_name)?;
        config.members.retain(|m| m.github_username != github_username);
        self.save_team(&config)?;
        info!("Removed {} from team {}", github_username, team_name);
        Ok(())
    }

    /// Load all member profiles for a team
    pub async fn load_member_profiles(&self, team_name: &str) -> Result<Vec<DeveloperProfile>> {
        let config = self.load_team(team_name)?;
        let mut profiles = Vec::new();
        
        for member in &config.members {
            if let Some(profile_path) = &member.profile_path {
                // Load from specific path
                let content = tokio::fs::read_to_string(profile_path).await?;
                let profile: DeveloperProfile = serde_json::from_str(&content)?;
                profiles.push(profile);
            } else {
                // Try to load from default location
                let default_path = dirs::home_dir()
                    .ok_or_else(|| anyhow::anyhow!("Could not find home directory"))?
                    .join(".i-self")
                    .join("profile.json");
                
                if default_path.exists() {
                    let content = tokio::fs::read_to_string(&default_path).await?;
                    let profile: DeveloperProfile = serde_json::from_str(&content)?;
                    profiles.push(profile);
                }
            }
        }
        
        Ok(profiles)
    }
}