scenarios:
  - id: null-reference
    title: "Fix a Null/Undefined Crash"
    difficulty: beginner
    xp: 25
    setup:
      - "mkdir -p workshop && cd workshop"
      - "npm init -y"
    files:
      - name: app.js
        content: |
          const user = { id: 1, profile: null };
          function greet(user) {
            return `Hello, ${user.profile.name}!`;
          }
          try {
            console.log(greet(user));
          } catch (e) {
            console.error('Error:', e.message);
          }
        language: javascript
        readonly: false
    task: "Run node app.js. The program crashes because user.profile is null and we access user.profile.name. Find and fix the null reference so the program runs without error."
    validations:
      - label: "Program runs and prints a greeting"
        check: output-contains
        pattern: "Hello"
    hints:
      - "What value is user.profile when the crash happens? How could you guard before accessing .name?"
      - "Options: check if profile exists before using it, use optional chaining (user.profile?.name), or provide a default."
      - "Consider: user.profile ? user.profile.name : 'Guest' or user.profile?.name ?? 'Guest'."
    agent_prompts:
      on_start: "What happens when you try to read a property of null? How would you discover that without seeing the code?"

  - id: async-race
    title: "Fix an Async Race Condition"
    difficulty: intermediate
    xp: 35
    setup:
      - "mkdir -p workshop && cd workshop"
      - "npm init -y"
    files:
      - name: fetchData.js
        content: |
          async function fetchUser(id) {
            return new Promise(resolve => setTimeout(() => resolve({ id, name: 'User ' + id }), 100));
          }
          async function fetchOrders(userId) {
            return new Promise(resolve => setTimeout(() => resolve([{ orderId: 1 }, { orderId: 2 }]), 150));
          }
          async function run() {
            const user = fetchUser(1);
            const orders = fetchOrders(1);
            console.log('User:', user);
            console.log('Orders:', orders);
            console.log('Done');
          }
          run();
        language: javascript
        readonly: false
    task: "Run node fetchData.js. The output shows Promise objects instead of data because we use the results before the promises resolve. Fix the timing so both fetches complete before we log, and the output shows the actual user and orders."
    validations:
      - label: "fetchData.js was modified"
        check: file-changed
        pattern: "fetchData.js"
      - label: "Output shows user and orders data"
        check: output-contains
        pattern: "User:"
    hints:
      - "fetchUser and fetchOrders return Promises. We need to wait for them before using the results."
      - "Use await on each call, or await Promise.all([user, orders]) to wait for both."
      - "Ensure run() awaits the fetches before console.log."
    agent_prompts:
      on_start: "What's the difference between calling fetchUser(1) and awaiting it? When does the data become available?"

  - id: off-by-one
    title: "Fix an Off-by-One Error in Pagination"
    difficulty: intermediate
    xp: 30
    setup:
      - "mkdir -p workshop && cd workshop"
      - "npm init -y"
    files:
      - name: pagination.js
        content: |
          function getPageRange(totalItems, itemsPerPage, page) {
            const totalPages = Math.ceil(totalItems / itemsPerPage);
            if (page < 1 || page > totalPages) return null;
            const start = (page - 1) * itemsPerPage;
            const end = start + itemsPerPage;
            return { start, end };
          }
          module.exports = { getPageRange };
        language: javascript
        readonly: false
      - name: pagination.test.js
        content: |
          const { getPageRange } = require('./pagination');
          function runTests() {
            let passed = 0;
            let failed = 0;
            const tests = [
              () => { const r = getPageRange(25, 10, 1); if (r && r.start === 0 && r.end === 10) passed++; else failed++; },
              () => { const r = getPageRange(25, 10, 2); if (r && r.start === 10 && r.end === 20) passed++; else failed++; },
              () => { const r = getPageRange(25, 10, 3); if (r && r.start === 20 && r.end === 25) passed++; else failed++; },
              () => { const r = getPageRange(30, 10, 3); if (r && r.start === 20 && r.end === 30) passed++; else failed++; },
            ];
            tests.forEach(t => t());
            return { passed, failed };
          }
          const { passed, failed } = runTests();
          console.log(`Passed: ${passed}, Failed: ${failed}`);
          if (failed === 0) console.log('All tests passed');
          process.exit(failed > 0 ? 1 : 0);
        language: javascript
        readonly: true
    task: "Run node pagination.test.js. One or more tests fail because the last page's end index is wrong. Fix getPageRange so all tests pass. The end index should be the exclusive upper bound (e.g. for items 20-24, start=20, end=25)."
    validations:
      - label: "All tests passed"
        check: output-contains
        pattern: "All tests passed"
    hints:
      - "For the last page, we might have fewer items than itemsPerPage. What should end be?"
      - "end should be Math.min(start + itemsPerPage, totalItems) so we never exceed totalItems."
      - "Page 3 of 25 items with 10 per page: start=20, end should be 25, not 30."
    agent_prompts:
      on_start: "When you have 25 items and 10 per page, how many pages are there? What's the start and end index for the last page?"
