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 serde::{Deserialize, Serialize};
use std::collections::HashMap;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LearningResource {
    pub id: String,
    pub title: String,
    pub description: String,
    pub resource_type: ResourceType,
    pub url: String,
    pub provider: String,
    pub difficulty: String,
    pub duration_hours: Option<f32>,
    pub cost: f32,
    pub rating: Option<f32>,
    pub skills: Vec<String>,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum ResourceType {
    Course,
    Book,
    Tutorial,
    Documentation,
    Video,
    Project,
    Practice,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LearningPath {
    pub id: String,
    pub target_skill: String,
    pub target_level: String,
    pub resources: Vec<LearningResource>,
    pub estimated_total_hours: f32,
    pub prerequisites: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LearningRecommendation {
    pub skill_name: String,
    pub current_level: String,
    pub target_level: String,
    pub priority: u8,
    pub reason: String,
    pub path: Option<LearningPath>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LearningPlan {
    pub recommendations: Vec<LearningRecommendation>,
    pub total_estimated_hours: f32,
    pub focus_areas: Vec<String>,
}

pub struct LearningPathGenerator;

impl LearningPathGenerator {
    pub fn generate_plan(
        skill_gaps: &[super::skills::SkillGap],
        _current_skills: &HashMap<String, super::skills::Skill>,
    ) -> LearningPlan {
        let mut recommendations = Vec::new();
        let mut total_hours = 0.0;
        let mut focus_areas = Vec::new();

        for gap in skill_gaps {
            let priority = if gap.is_critical { 1 } else { 3 };
            
            let reason = if gap.gap_score > 30 {
                format!("Significant gap in {} - recommended for career growth", gap.skill_name)
            } else {
                format!("Strengthen {} skills to meet requirements", gap.skill_name)
            };

            let path = Self::generate_path(&gap.skill_name, gap.current_level, gap.required_level);
            
            if let Some(ref p) = path {
                total_hours += p.estimated_total_hours;
                focus_areas.push(gap.skill_name.clone());
            }

            recommendations.push(LearningRecommendation {
                skill_name: gap.skill_name.clone(),
                current_level: format!("{:?}", gap.current_level),
                target_level: format!("{:?}", gap.required_level),
                priority,
                reason,
                path,
            });
        }

        recommendations.sort_by_key(|r| r.priority);

        LearningPlan {
            recommendations,
            total_estimated_hours: total_hours,
            focus_areas,
        }
    }

    fn generate_path(
        skill: &str,
        current: super::skills::SkillLevel,
        target: super::skills::SkillLevel,
    ) -> Option<LearningPath> {
        let resources = Self::get_resources_for_skill(skill, current, target)?;
        
        let total_hours: f32 = resources.iter()
            .filter_map(|r| r.duration_hours)
            .sum();

        Some(LearningPath {
            id: uuid::Uuid::new_v4().to_string(),
            target_skill: skill.to_string(),
            target_level: format!("{:?}", target),
            resources,
            estimated_total_hours: total_hours,
            prerequisites: Self::get_prerequisites(skill),
        })
    }

    fn get_resources_for_skill(
        skill: &str,
        current: super::skills::SkillLevel,
        target: super::skills::SkillLevel,
    ) -> Option<Vec<LearningResource>> {
        let skill_lower = skill.to_lowercase();
        
        let resources = match skill_lower.as_str() {
            "rust" => Self::rust_resources(current, target),
            "python" => Self::python_resources(current, target),
            "javascript" | "js" => Self::javascript_resources(current, target),
            "typescript" | "ts" => Self::typescript_resources(current, target),
            "react" => Self::react_resources(current, target),
            "docker" => Self::docker_resources(current, target),
            "kubernetes" | "k8s" => Self::kubernetes_resources(current, target),
            "aws" => Self::aws_resources(current, target),
            "sql" => Self::sql_resources(current, target),
            "go" | "golang" => Self::go_resources(current, target),
            _ => return None,
        };

        if resources.is_empty() {
            None
        } else {
            Some(resources)
        }
    }

    fn rust_resources(current: super::skills::SkillLevel, target: super::skills::SkillLevel) -> Vec<LearningResource> {
        let mut resources = Vec::new();
        
        if current < super::skills::SkillLevel::Intermediate {
            resources.push(LearningResource {
                id: "rust-book".to_string(),
                title: "The Rust Programming Language".to_string(),
                description: "Official book covering Rust fundamentals".to_string(),
                resource_type: ResourceType::Book,
                url: "https://doc.rust-lang.org/book/".to_string(),
                provider: "Rust Team".to_string(),
                difficulty: "Beginner".to_string(),
                duration_hours: Some(30.0),
                cost: 0.0,
                rating: Some(4.9),
                skills: vec!["Rust".to_string()],
            });
        }
        
        if target >= super::skills::SkillLevel::Advanced {
            resources.push(LearningResource {
                id: "rust-rxo".to_string(),
                title: "Rust for Rustaceans".to_string(),
                description: "Advanced Rust programming patterns".to_string(),
                resource_type: ResourceType::Book,
                url: "https://nostarch.com/rust-rustaceans".to_string(),
                provider: "No Starch Press".to_string(),
                difficulty: "Advanced".to_string(),
                duration_hours: Some(20.0),
                cost: 44.99,
                rating: Some(4.8),
                skills: vec!["Rust".to_string()],
            });
            
            resources.push(LearningResource {
                id: "rust-practice".to_string(),
                title: "Rustlings".to_string(),
                description: "Small exercises to learn Rust".to_string(),
                resource_type: ResourceType::Practice,
                url: "https://github.com/rust-lang/rustlings".to_string(),
                provider: "Rust Team".to_string(),
                difficulty: "Intermediate".to_string(),
                duration_hours: Some(15.0),
                cost: 0.0,
                rating: Some(4.7),
                skills: vec!["Rust".to_string()],
            });
        }
        
        resources
    }

    fn python_resources(current: super::skills::SkillLevel, target: super::skills::SkillLevel) -> Vec<LearningResource> {
        let mut resources = Vec::new();
        
        if current < super::skills::SkillLevel::Intermediate {
            resources.push(LearningResource {
                id: "py-official".to_string(),
                title: "Python Official Tutorial".to_string(),
                description: "Official Python documentation tutorial".to_string(),
                resource_type: ResourceType::Tutorial,
                url: "https://docs.python.org/3/tutorial/".to_string(),
                provider: "Python.org".to_string(),
                difficulty: "Beginner".to_string(),
                duration_hours: Some(20.0),
                cost: 0.0,
                rating: Some(4.8),
                skills: vec!["Python".to_string()],
            });
        }
        
        if target >= super::skills::SkillLevel::Advanced {
            resources.push(LearningResource {
                id: "py-fluent".to_string(),
                title: "Fluent Python".to_string(),
                description: "Deep dive into Python idioms".to_string(),
                resource_type: ResourceType::Book,
                url: "https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/".to_string(),
                provider: "O'Reilly".to_string(),
                difficulty: "Advanced".to_string(),
                duration_hours: Some(25.0),
                cost: 49.99,
                rating: Some(4.9),
                skills: vec!["Python".to_string()],
            });
        }
        
        resources
    }

    fn javascript_resources(_current: super::skills::SkillLevel, _target: super::skills::SkillLevel) -> Vec<LearningResource> {
        vec![LearningResource {
            id: "js-mdn".to_string(),
            title: "MDN JavaScript Guide".to_string(),
            description: "Comprehensive JavaScript documentation".to_string(),
            resource_type: ResourceType::Documentation,
            url: "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide".to_string(),
            provider: "Mozilla".to_string(),
            difficulty: "Beginner".to_string(),
            duration_hours: Some(25.0),
            cost: 0.0,
            rating: Some(4.9),
            skills: vec!["JavaScript".to_string()],
        }]
    }

    fn typescript_resources(_current: super::skills::SkillLevel, _target: super::skills::SkillLevel) -> Vec<LearningResource> {
        vec![LearningResource {
            id: "ts-handbook".to_string(),
            title: "TypeScript Handbook".to_string(),
            description: "Official TypeScript documentation".to_string(),
            resource_type: ResourceType::Documentation,
            url: "https://www.typescriptlang.org/docs/".to_string(),
            provider: "Microsoft".to_string(),
            difficulty: "Intermediate".to_string(),
            duration_hours: Some(15.0),
            cost: 0.0,
            rating: Some(4.8),
            skills: vec!["TypeScript".to_string()],
        }]
    }

    fn react_resources(_current: super::skills::SkillLevel, _target: super::skills::SkillLevel) -> Vec<LearningResource> {
        vec![LearningResource {
            id: "react-docs".to_string(),
            title: "React Documentation".to_string(),
            description: "Official React tutorial and docs".to_string(),
            resource_type: ResourceType::Documentation,
            url: "https://react.dev/learn".to_string(),
            provider: "Meta".to_string(),
            difficulty: "Intermediate".to_string(),
            duration_hours: Some(20.0),
            cost: 0.0,
            rating: Some(4.9),
            skills: vec!["React".to_string()],
        }]
    }

    fn docker_resources(_current: super::skills::SkillLevel, _target: super::skills::SkillLevel) -> Vec<LearningResource> {
        vec![LearningResource {
            id: "docker-get-started".to_string(),
            title: "Docker Get Started".to_string(),
            description: "Official Docker tutorial".to_string(),
            resource_type: ResourceType::Tutorial,
            url: "https://docs.docker.com/get-started/".to_string(),
            provider: "Docker".to_string(),
            difficulty: "Beginner".to_string(),
            duration_hours: Some(10.0),
            cost: 0.0,
            rating: Some(4.7),
            skills: vec!["Docker".to_string()],
        }]
    }

    fn kubernetes_resources(_current: super::skills::SkillLevel, _target: super::skills::SkillLevel) -> Vec<LearningResource> {
        vec![LearningResource {
            id: "k8s-learn".to_string(),
            title: "Kubernetes Learning".to_string(),
            description: "Official Kubernetes tutorials".to_string(),
            resource_type: ResourceType::Tutorial,
            url: "https://kubernetes.io/docs/tutorials/".to_string(),
            provider: "CNCF".to_string(),
            difficulty: "Intermediate".to_string(),
            duration_hours: Some(20.0),
            cost: 0.0,
            rating: Some(4.8),
            skills: vec!["Kubernetes".to_string()],
        }]
    }

    fn aws_resources(_current: super::skills::SkillLevel, _target: super::skills::SkillLevel) -> Vec<LearningResource> {
        vec![LearningResource {
            id: "aws-cloud-practitioner".to_string(),
            title: "AWS Cloud Practitioner".to_string(),
            description: "AWS fundamentals certification".to_string(),
            resource_type: ResourceType::Course,
            url: "https://aws.amazon.com/training/path-cloud-practitioner/".to_string(),
            provider: "AWS".to_string(),
            difficulty: "Beginner".to_string(),
            duration_hours: Some(15.0),
            cost: 0.0,
            rating: Some(4.7),
            skills: vec!["AWS".to_string()],
        }]
    }

    fn sql_resources(_current: super::skills::SkillLevel, _target: super::skills::SkillLevel) -> Vec<LearningResource> {
        vec![LearningResource {
            id: "sql-z".to_string(),
            title: "SQLZoo".to_string(),
            description: "Interactive SQL tutorials".to_string(),
            resource_type: ResourceType::Tutorial,
            url: "https://sqlzoo.net/".to_string(),
            provider: "SQLZoo".to_string(),
            difficulty: "Beginner".to_string(),
            duration_hours: Some(10.0),
            cost: 0.0,
            rating: Some(4.6),
            skills: vec!["SQL".to_string()],
        }]
    }

    fn go_resources(_current: super::skills::SkillLevel, _target: super::skills::SkillLevel) -> Vec<LearningResource> {
        vec![LearningResource {
            id: "go-tour".to_string(),
            title: "A Tour of Go".to_string(),
            description: "Interactive Go tutorial".to_string(),
            resource_type: ResourceType::Tutorial,
            url: "https://go.dev/tour/welcome/1".to_string(),
            provider: "Google".to_string(),
            difficulty: "Beginner".to_string(),
            duration_hours: Some(5.0),
            cost: 0.0,
            rating: Some(4.9),
            skills: vec!["Go".to_string()],
        }]
    }

    fn get_prerequisites(skill: &str) -> Vec<String> {
        match skill.to_lowercase().as_str() {
            "react" => vec!["JavaScript".to_string(), "TypeScript".to_string()],
            "kubernetes" => vec!["Docker".to_string(), "Linux".to_string()],
            "typescript" => vec!["JavaScript".to_string()],
            _ => Vec::new(),
        }
    }
}