# Heat User - Integration Flow với EMS

## 🔄 Luồng tích hợp CHÍNH XÁC

### **Architecture Overview**

```
┌─────────┐      ┌─────────┐      ┌───────────┐
│   FE    │◄────▶│   EMS   │      │ Heat User │
│         │      │   API   │      │    API    │
└────┬────┘      └────┬────┘      └─────┬─────┘
     │                │                  │
     └────────────────┴──────────────────┘
           Orchestrated by Frontend
```

---

## 🎯 Case 1: START - Bắt đầu làm bài

### **Timeline:**

```
┌──────────────────────────────────────────────────────────────────┐
│  USER CLICK "BẮT ĐẦU LÀM BÀI"                                   │
└──────────────────────────────────────────────────────────────────┘

[10:00:00] Step 1: FE gọi EMS để tạo session contest
           ├─> POST fe/ems/v1/lmsnew1/mockcontest/session/start
           ├─> Body: {
           │     id_bai_kiem_tra: 456,
           │     student_id: 123
           │   }
           └─> Response: {
                 success: true,
                 idHistoryContest: 100
               }

[10:00:01] Step 2: FE gọi EMS để start exam
           ├─> POST fe/ems/v1/exam/start
           ├─> Body: {
           │     idHistoryContest: 100,
           │     id_bai_kiem_tra: 456,
           │     student_id: 123
           │   }
           └─> Response: {
                 success: true,
                 idHistory: 200,        ← ID của phần con
                 questions: [...]
               }

[10:00:02] Step 3: FE gọi Heat User để track session
           ├─> POST /api/heat-user/study-session/start
           ├─> Body: {
           │     id_history_contest: 100,  ← Từ Step 1
           │     id_history: 200,          ← Từ Step 2
           │     id_bai_kiem_tra: 456
           │   }
           └─> Response: {
                 success: true,
                 type: "new",
                 session_id: 1,         ← Save to localStorage
                 message: "Bắt đầu session mới"
               }

[10:00:03] FE lưu vào localStorage:
           {
             heatUserSessionId: 1,
             idHistoryContest: 100,
             idHistory: 200,
             startedAt: "2025-12-04T10:00:02Z"
           }

[10:00:04] FE bắt đầu heartbeat timer (mỗi 5 phút)
           setInterval(() => {
             POST /api/heat-user/study-session/heartbeat
             Body: { session_id: 1 }
           }, 5 * 60 * 1000)

[10:00:05] FE hiển thị câu hỏi và bắt đầu làm bài
```

---

## ✅ Case 2: SUBMIT - Nộp bài

### **Timeline:**

```
┌──────────────────────────────────────────────────────────────────┐
│  USER CLICK "NỘP BÀI"                                            │
└──────────────────────────────────────────────────────────────────┘

[10:30:00] Step 1: FE gọi Heat User để end session tracking
           ├─> POST /api/heat-user/study-session/submit
           ├─> Body: {
           │     session_id: 1  ← Từ localStorage
           │   }
           └─> Response: {
                 success: true,
                 duration_minutes: 30,
                 message: "Đã nộp bài thành công"
               }

[10:30:01] Step 2: FE gọi EMS để submit exam
           ├─> POST fe/ems/v1/exam/submit
           ├─> Body: {
           │     idHistory: 200,  ← Từ localStorage
           │     answers: [...],
           │     student_id: 123
           │   }
           └─> Response: {
                 success: true,
                 score: 85,
                 details: [...]
               }

[10:30:02] FE xóa localStorage
           localStorage.removeItem('heatUserSession')

[10:30:03] FE clear heartbeat timer
           clearInterval(heartbeatInterval)

[10:30:04] FE hiển thị kết quả hoặc redirect
```

---

## 🔄 Case 3: CLOSE Browser → CONTINUE

### **Timeline:**

```
┌──────────────────────────────────────────────────────────────────┐
│  NGÀY 1: User làm bài → đóng browser                            │
└──────────────────────────────────────────────────────────────────┘

[10:00:00] User start (Steps 1-3 như Case 1)
           localStorage: { sessionId: 1, idHistory: 200 }

[10:05:00] Heartbeat sent

[10:08:00] User đóng browser (KHÔNG gọi API)
           localStorage vẫn còn!

[10:18:00] Cron job auto-end session 1
           ├─> Status: active → auto_ended
           ├─> Duration: 15 phút (min rule)
           └─> Daily stats: +15 phút

┌──────────────────────────────────────────────────────────────────┐
│  NGÀY 2: User quay lại                                           │
└──────────────────────────────────────────────────────────────────┘

[14:00:00] User load page
           ├─> FE check localStorage
           └─> Có sessionId: 1, idHistory: 200

[14:00:01] FE gọi Heat User để check session
           ├─> GET /api/heat-user/study-session/active?id_history=200
           └─> Response: {
                 success: true,
                 data: {
                   session_id: 1,
                   status: "auto_ended"
                 }
               }

[14:00:02] FE hiển thị [TIẾP TỤC LÀM BÀI]

[14:00:03] User click CONTINUE

[14:00:04] Step 1: FE gọi Heat User để continue
           ├─> POST /api/heat-user/study-session/continue
           ├─> Body: { session_id: 1 }
           └─> Response: {
                 success: true,
                 session_id: 2,           ← Session mới
                 old_session_duration: 15
               }

[14:00:05] Step 2: FE GỌI LẠI EMS để lấy questions
           ├─> POST fe/ems/v1/exam/start
           ├─> Body: {
           │     idHistoryContest: 100,  ← Same
           │     idHistory: 200,         ← Same (không đổi!)
           │     is_continue: true       ← Flag để EMS biết
           │   }
           └─> Response: {
                 success: true,
                 idHistory: 200,          ← GIỐNG CŨ
                 questions: [...],
                 answers_saved: [...]     ← Câu trả lời đã lưu
               }

[14:00:06] FE update localStorage:
           {
             heatUserSessionId: 2,      ← New session
             idHistoryContest: 100,     ← Same
             idHistory: 200,            ← Same
             startedAt: "2025-12-04T14:00:04Z"
           }

[14:00:07] FE restart heartbeat timer

[14:00:08] User tiếp tục làm bài
```

---

## 🛑 Case 4: STOP - Dừng hẳn

### **Timeline:**

```
[10:00:00] User start (như Case 1)

[10:18:00] User click [DỪNG LÀM BÀI]

[10:18:01] FE hiển thị confirm:
           "Bạn có chắc muốn dừng? Bạn sẽ phải bắt đầu lại từ đầu."

[10:18:02] User confirm

[10:18:03] Step 1: FE gọi Heat User để stop
           ├─> POST /api/heat-user/study-session/stop
           ├─> Body: { session_id: 1 }
           └─> Response: {
                 success: true,
                 duration_minutes: 18,
                 message: "Đã dừng session"
               }

[10:18:04] Step 2: FE có thể gọi EMS để cancel (optional)
           ├─> POST fe/ems/v1/exam/cancel (nếu EMS có API này)
           └─> Hoặc không gọi gì cả

[10:18:05] FE xóa localStorage

[10:18:06] FE redirect về danh sách bài

┌──────────────────────────────────────────────────────────────────┐
│  SAU ĐÓ: User muốn làm lại                                       │
└──────────────────────────────────────────────────────────────────┘

[15:00:00] User click "BẮT ĐẦU LÀM BÀI" lại

[15:00:01] FE gọi EMS Step 1
           ├─> POST fe/ems/v1/lmsnew1/mockcontest/session/start
           └─> idHistoryContest: 100 (có thể TRÙNG) hoặc 101 (MỚI)

[15:00:02] FE gọi EMS Step 2
           ├─> POST fe/ems/v1/exam/start
           └─> idHistory: 300 (MỚI - EMS tạo mới)

[15:00:03] FE gọi Heat User
           ├─> POST /api/heat-user/study-session/start
           ├─> Body: {
           │     id_history_contest: 101,
           │     id_history: 300      ← MỚI hoàn toàn
           │   }
           └─> Success (vì idHistory khác)
```

---

## 📋 Case 5: START → SUBMIT cùng ngày (không close)

### **Timeline:**

```
[10:00:00] User start (Steps 1-3 như Case 1)
           → sessionId: 1

[10:05:00] Heartbeat
[10:10:00] Heartbeat
[10:15:00] Heartbeat

[10:18:00] User click SUBMIT (18 phút sau)
           ├─> Heat User: /submit → duration: 18 phút
           └─> EMS: /exam/submit → score: 85

📊 Kết quả: Ngày hôm nay: 18 phút
```

---

## 🌙 Case 6: START 23:59 → SUBMIT qua ngày

### **Timeline:**

```
[01/01 23:59] User start
             → sessionId: 1

[01/02 00:04] Heartbeat
[01/02 00:09] Heartbeat

[01/02 01:00] User submit (61 phút total)

📊 Kết quả:
- Ngày 01/01: 1 phút (23:59 → 24:00)
- Ngày 01/02: 60 phút (00:00 → 01:00)

⚙️ Backend tự động phân bổ thời gian theo tỷ lệ
```

---

## ⏱️ Case 7: START → FE auto-submit (timeout)

### **Timeline:**

```
[10:00:00] User start

[10:05:00] Heartbeat
[10:10:00] Heartbeat

[10:10:01] User idle (không tương tác)

[10:15:01] FE detect idle > 5 phút
           ├─> FE TỰ ĐỘNG gọi Heat User: /submit
           └─> FE TỰ ĐỘNG gọi EMS: /exam/submit

📊 Kết quả: Ngày hôm nay: 15 phút

⚠️ FE cần implement idle detection!
```

---

## 🔁 Case 8: Multiple Submissions (contest_type ≠ 29)

### **⭐ QUAN TRỌNG: Contest Type Logic**

Khi gọi `/lmsnew1/mockcontest/session/start`, response trả về:
```json
{
  "idHistoryContest": 100,
  "contest_type": 29    // hoặc khác 29
}
```

### **A. Contest Type = 29 (Single Submission)**
- Chỉ submit 1 lần cho toàn bộ idHistoryContest
- Flow như Case 1-7

### **B. Contest Type ≠ 29 (Multiple Submissions)**
- Một idHistoryContest có nhiều phần (parts)
- Mỗi phần là một idHistory riêng
- Submit từng phần một

### **Timeline cho Contest Type ≠ 29:**

```
┌──────────────────────────────────────────────────────────────────┐
│  VÍ DỤ: idHistoryContest: 100 có 4 phần                         │
└──────────────────────────────────────────────────────────────────┘

=== PHẦN 1 (Reading) ===

[09:00:00] Step 1: FE gọi EMS contest start
           ├─> POST /lmsnew1/mockcontest/session/start
           └─> Response: {
                 idHistoryContest: 100,
                 contest_type: 30      ← ≠ 29
               }

[09:00:01] Step 2: FE gọi EMS exam start (Phần 1)
           ├─> POST /exam/start
           ├─> Body: {
           │     idHistoryContest: 100,
           │     part: 1              ← Phần 1
           │   }
           └─> Response: {
                 idHistory: 201,       ← ID Phần 1
                 part_name: "Reading",
                 questions: [...]
               }

[09:00:02] Step 3: FE gọi Heat User start (Phần 1)
           ├─> POST /api/heat-user/study-session/start
           ├─> Body: {
           │     id_history_contest: 100,
           │     id_history: 201,     ← Phần 1
           │     id_bai_kiem_tra: 456
           │   }
           └─> sessionId: 1

[09:05:00] Heartbeat
[09:10:00] Heartbeat

[09:20:00] User hoàn thành Phần 1, click SUBMIT

[09:20:01] Submit Phần 1
           ├─> Heat User: /submit (session: 1) → 20 phút
           └─> EMS: /exam/submit (idHistory: 201) → score part 1

=== PHẦN 2 (Listening) ===

[09:21:00] User click "Làm phần tiếp theo"

[09:21:01] Step 1: FE gọi EMS exam start (Phần 2)
           ├─> POST /exam/start
           ├─> Body: {
           │     idHistoryContest: 100,  ← SAME
           │     part: 2                 ← Phần 2
           │   }
           └─> Response: {
                 idHistory: 202,          ← ID Phần 2 (MỚI)
                 part_name: "Listening",
                 questions: [...]
               }

[09:21:02] Step 2: FE gọi Heat User start (Phần 2)
           ├─> POST /api/heat-user/study-session/start
           ├─> Body: {
           │     id_history_contest: 100,  ← SAME
           │     id_history: 202,          ← MỚI
           │     id_bai_kiem_tra: 456
           │   }
           └─> sessionId: 2                 ← Session MỚI

[09:26:00] Heartbeat
[09:31:00] Heartbeat

[09:35:00] User hoàn thành Phần 2, click SUBMIT

[09:35:01] Submit Phần 2
           ├─> Heat User: /submit (session: 2) → 14 phút
           └─> EMS: /exam/submit (idHistory: 202) → score part 2

=== PHẦN 3 (Writing) ===

[09:36:00] Tương tự...
           → idHistory: 203
           → sessionId: 3

=== PHẦN 4 (Speaking) ===

[10:00:00] Tương tự...
           → idHistory: 204
           → sessionId: 4

┌──────────────────────────────────────────────────────────────────┐
│  KẾT QUẢ CUỐI CÙNG                                               │
└──────────────────────────────────────────────────────────────────┘

📊 Study Sessions:
- Session 1 (idHistory: 201): 20 phút
- Session 2 (idHistory: 202): 14 phút  
- Session 3 (idHistory: 203): 18 phút
- Session 4 (idHistory: 204): 22 phút
- TOTAL: 74 phút

📝 Database:
student_study_sessions:
  - 4 records (một record cho mỗi phần)
  - Cùng id_history_contest: 100
  - Khác id_history: 201, 202, 203, 204

student_daily_study_time:
  - Ngày hôm nay: 74 phút total
  - session_count: 4
```

---

## 🔄 Case 9: Multiple Parts + Close Browser + Continue

### **Timeline:**

```
=== PHẦN 1 ===

[09:00:00] Start Phần 1 (idHistory: 201, session: 1)
[09:10:00] User đóng browser

--- Cron auto-end session 1 ---

[14:00:00] User quay lại
           ├─> Check localStorage: có idHistory: 201
           └─> Hiển thị [CONTINUE PHẦN 1]

[14:00:01] User click Continue
           ├─> Heat User: /continue (session: 1) → session: 2
           └─> EMS: /exam/start (idHistory: 201, is_continue: true)

[14:20:00] Submit Phần 1
           ├─> Heat User: /submit (session: 2)
           └─> EMS: /exam/submit (idHistory: 201)

=== PHẦN 2 ===

[14:21:00] Start Phần 2 (idHistory: 202, session: 3)
[14:35:00] Submit Phần 2

=== PHẦN 3 ===

[14:36:00] Start Phần 3 (idHistory: 203, session: 4)
[14:40:00] User đóng browser

[16:00:00] User quay lại
           ├─> Continue Phần 3 (session: 5)
           └─> Submit Phần 3

📊 Kết quả:
- 5 sessions total
- 3 parts completed
- Multiple days possible
```

---

## 🎯 Case 10: Mix Stop và Continue

### **Timeline:**

```
[09:00:00] Start Phần 1 (idHistory: 201)
[09:15:00] User click STOP
           ├─> Heat User: /stop (session: 1)
           ├─> Status: stopped
           └─> localStorage cleared

--- SAU ĐÓ ---

[10:00:00] User muốn làm lại
           ├─> FE phải gọi EMS start LẠI
           ├─> EMS tạo idHistory MỚI: 205
           └─> Heat User start với idHistory: 205

⚠️ QUAN TRỌNG:
- STOP → idHistory MỚI (EMS tạo mới)
- CLOSE → idHistory CŨ (continue)
```

---

## 💻 Frontend Implementation (Complete)

### **JavaScript Class - Full Implementation**

```javascript
class StudySessionManager {
  constructor() {
    this.emsBaseUrl = '/fe/ems/v1';
    this.heatUserBaseUrl = '/api/heat-user/study-session';
    this.heartbeatInterval = null;
    this.localStorage_key = 'study_session';
    this.currentPart = 1; // For multi-part exams
  }

  // ==========================================
  // START FLOW (3 steps)
  // ==========================================
  async start(idBaiKiemTra, part = 1) {
    try {
      // Get stored session info (for multi-part exams)
      const stored = this.getStoredSession();
      let idHistoryContest;
      let contestType;

      // Step 1: EMS - Create contest session (only first time)
      if (!stored || !stored.idHistoryContest) {
        console.log('Step 1: Creating contest session...');
        const contestResponse = await fetch(
          `${this.emsBaseUrl}/lmsnew1/mockcontest/session/start`,
          {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              id_bai_kiem_tra: idBaiKiemTra,
              student_id: this.getStudentId()
            })
          }
        );
        const contestData = await contestResponse.json();
        
        if (!contestData.success) {
          throw new Error('Failed to create contest session');
        }
        
        idHistoryContest = contestData.idHistoryContest;
        contestType = contestData.contest_type;
        console.log('✓ idHistoryContest:', idHistoryContest);
        console.log('✓ contest_type:', contestType);
      } else {
        // Use existing idHistoryContest for multi-part
        idHistoryContest = stored.idHistoryContest;
        contestType = stored.contestType;
        console.log('Using existing idHistoryContest:', idHistoryContest);
      }

      // Step 2: EMS - Start exam (with part for multi-part exams)
      console.log(`Step 2: Starting exam part ${part}...`);
      const examResponse = await fetch(`${this.emsBaseUrl}/exam/start`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          idHistoryContest: idHistoryContest,
          id_bai_kiem_tra: idBaiKiemTra,
          part: part,  // ⭐ Part number for multi-part exams
          student_id: this.getStudentId()
        })
      });
      const examData = await examResponse.json();
      
      if (!examData.success) {
        throw new Error('Failed to start exam');
      }
      
      const idHistory = examData.idHistory;
      const partName = examData.part_name || `Part ${part}`;
      console.log('✓ idHistory:', idHistory);
      console.log('✓ Part:', partName);

      // Step 3: Heat User - Track session
      console.log('Step 3: Starting tracking...');
      const trackResponse = await fetch(`${this.heatUserBaseUrl}/start`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          id_history_contest: idHistoryContest,
          id_history: idHistory,
          id_bai_kiem_tra: idBaiKiemTra
        })
      });
      const trackData = await trackResponse.json();

      if (trackData.type === 'need_continue') {
        // Có session chưa hoàn thành
        return {
          type: 'need_continue',
          sessionId: trackData.session_id,
          idHistory: idHistory
        };
      }

      if (trackData.success) {
        // Success - save to localStorage
        this.saveSession({
          heatUserSessionId: trackData.session_id,
          idHistoryContest: idHistoryContest,
          idHistory: idHistory,
          idBaiKiemTra: idBaiKiemTra,
          contestType: contestType,          // ⭐ Save contest type
          currentPart: part,                 // ⭐ Save current part
          partName: partName,                // ⭐ Save part name
          startedAt: new Date().toISOString()
        });

        // Start heartbeat
        this.startHeartbeat(trackData.session_id);

        return {
          type: 'success',
          sessionId: trackData.session_id,
          contestType: contestType,
          part: part,
          partName: partName,
          questions: examData.questions
        };
      }

    } catch (error) {
      console.error('Start failed:', error);
      throw error;
    }
  }

  // ==========================================
  // START NEXT PART (for multi-part exams, contest_type ≠ 29)
  // ==========================================
  async startNextPart() {
    const stored = this.getStoredSession();
    if (!stored) {
      throw new Error('No active contest session');
    }

    // Check if this is multi-part exam
    if (stored.contestType === 29) {
      throw new Error('This is single-part exam. Cannot start next part.');
    }

    const nextPart = stored.currentPart + 1;
    console.log(`Starting next part: ${nextPart}`);

    // Use start() method with next part number
    return await this.start(stored.idBaiKiemTra, nextPart);
  }

  // ==========================================
  // CONTINUE FLOW
  // ==========================================
  async continue(oldSessionId) {
    try {
      const stored = this.getStoredSession();
      if (!stored) {
        throw new Error('No stored session found');
      }

      // Step 1: Heat User - Continue tracking
      console.log('Step 1: Continue tracking...');
      const trackResponse = await fetch(`${this.heatUserBaseUrl}/continue`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ session_id: oldSessionId })
      });
      const trackData = await trackResponse.json();

      if (!trackData.success) {
        throw new Error('Failed to continue tracking');
      }

      const newSessionId = trackData.session_id;
      console.log('✓ New session ID:', newSessionId);

      // Step 2: EMS - Get exam data (with saved answers)
      console.log('Step 2: Getting exam data...');
      const examResponse = await fetch(`${this.emsBaseUrl}/exam/start`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          idHistoryContest: stored.idHistoryContest,
          idHistory: stored.idHistory,          // SAME idHistory
          id_bai_kiem_tra: stored.idBaiKiemTra,
          is_continue: true                      // Flag for EMS
        })
      });
      const examData = await examResponse.json();

      // Update localStorage
      stored.heatUserSessionId = newSessionId;
      stored.startedAt = new Date().toISOString();
      this.saveSession(stored);

      // Restart heartbeat
      this.startHeartbeat(newSessionId);

      return {
        type: 'success',
        sessionId: newSessionId,
        questions: examData.questions,
        savedAnswers: examData.answers_saved
      };

    } catch (error) {
      console.error('Continue failed:', error);
      throw error;
    }
  }

  // ==========================================
  // SUBMIT FLOW (2 steps)
  // ==========================================
  async submit(answers) {
    try {
      const stored = this.getStoredSession();
      if (!stored) {
        throw new Error('No active session');
      }

      // Step 1: Heat User - End tracking
      console.log('Step 1: Ending tracking...');
      const trackResponse = await fetch(`${this.heatUserBaseUrl}/submit`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ 
          session_id: stored.heatUserSessionId 
        })
      });
      const trackData = await trackResponse.json();
      
      console.log('✓ Study time:', trackData.duration_minutes, 'minutes');

      // Step 2: EMS - Submit exam
      console.log('Step 2: Submitting to EMS...');
      const submitResponse = await fetch(`${this.emsBaseUrl}/exam/submit`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          idHistory: stored.idHistory,
          answers: answers,
          student_id: this.getStudentId()
        })
      });
      const submitData = await submitResponse.json();

      // Cleanup
      this.cleanup();

      return {
        success: true,
        score: submitData.score,
        studyTime: trackData.duration_minutes,
        details: submitData.details
      };

    } catch (error) {
      console.error('Submit failed:', error);
      throw error;
    }
  }

  // ==========================================
  // STOP FLOW
  // ==========================================
  async stop() {
    const confirmed = confirm(
      'Bạn có chắc muốn dừng? Bạn sẽ phải bắt đầu lại từ đầu.'
    );
    if (!confirmed) return;

    try {
      const stored = this.getStoredSession();
      if (!stored) return;

      // Heat User - Stop tracking
      await fetch(`${this.heatUserBaseUrl}/stop`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ 
          session_id: stored.heatUserSessionId 
        })
      });

      // Cleanup
      this.cleanup();

      // Redirect
      window.location.href = '/exams';

    } catch (error) {
      console.error('Stop failed:', error);
    }
  }

  // ==========================================
  // HEARTBEAT
  // ==========================================
  startHeartbeat(sessionId) {
    if (this.heartbeatInterval) {
      clearInterval(this.heartbeatInterval);
    }

    this.heartbeatInterval = setInterval(async () => {
      try {
        await fetch(`${this.heatUserBaseUrl}/heartbeat`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ session_id: sessionId })
        });
        console.log('💓 Heartbeat sent');
      } catch (error) {
        console.error('Heartbeat failed:', error);
      }
    }, 5 * 60 * 1000); // 5 minutes
  }

  // ==========================================
  // HELPERS
  // ==========================================
  saveSession(data) {
    localStorage.setItem(this.localStorage_key, JSON.stringify(data));
  }

  getStoredSession() {
    const stored = localStorage.getItem(this.localStorage_key);
    return stored ? JSON.parse(stored) : null;
  }

  cleanup() {
    clearInterval(this.heartbeatInterval);
    this.heartbeatInterval = null;
    localStorage.removeItem(this.localStorage_key);
  }

  getStudentId() {
    // Get from auth or context
    return window.currentUser?.id || 1;
  }

  // Check on page load
  async checkAndRestore() {
    const stored = this.getStoredSession();
    if (!stored) return null;

    // Check if session still active on server
    try {
      const response = await fetch(
        `${this.heatUserBaseUrl}/active?id_history=${stored.idHistory}`
      );
      const data = await response.json();

      if (data.success && data.data) {
        return {
          canContinue: true,
          sessionId: data.data.session_id,
          status: data.data.status
        };
      }
    } catch (error) {
      console.error('Check session failed:', error);
    }

    return null;
  }
}

// ==========================================
// USAGE EXAMPLE - Single Part Exam (contest_type = 29)
// ==========================================

const sessionManager = new StudySessionManager();

// On page load - check existing session
window.addEventListener('DOMContentLoaded', async () => {
  const existing = await sessionManager.checkAndRestore();
  
  if (existing && existing.canContinue) {
    // Show continue button
    document.getElementById('continueBtn').style.display = 'block';
    document.getElementById('continueBtn').onclick = async () => {
      const result = await sessionManager.continue(existing.sessionId);
      // Load questions and saved answers
      loadExam(result.questions, result.savedAnswers);
    };
  }
});

// Start button
document.getElementById('startBtn').onclick = async () => {
  try {
    const result = await sessionManager.start(456); // idBaiKiemTra
    
    if (result.type === 'need_continue') {
      // Show continue modal
      alert('Bạn có bài chưa hoàn thành. Vui lòng tiếp tục.');
    } else {
      // Load questions
      loadExam(result.questions);
      
      // Check if multi-part exam
      if (result.contestType !== 29) {
        showPartInfo(result.part, result.partName);
      }
    }
  } catch (error) {
    alert('Không thể bắt đầu: ' + error.message);
  }
};

// Submit button
document.getElementById('submitBtn').onclick = async () => {
  const answers = collectAnswers(); // Get user's answers
  
  try {
    const result = await sessionManager.submit(answers);
    
    const stored = sessionManager.getStoredSession();
    
    // Check if multi-part exam
    if (stored && stored.contestType !== 29) {
      // Multi-part exam
      alert(`Hoàn thành ${stored.partName}! Điểm: ${result.score}. Thời gian: ${result.studyTime} phút`);
      
      // Show next part button
      const nextPart = confirm('Bạn có muốn làm phần tiếp theo?');
      if (nextPart) {
        const nextResult = await sessionManager.startNextPart();
        loadExam(nextResult.questions);
        showPartInfo(nextResult.part, nextResult.partName);
      } else {
        // Done all parts
        window.location.href = '/results';
      }
    } else {
      // Single part exam
      alert(`Điểm: ${result.score}. Thời gian học: ${result.studyTime} phút`);
      window.location.href = '/results';
    }
  } catch (error) {
    alert('Nộp bài thất bại: ' + error.message);
  }
};

// Stop button
document.getElementById('stopBtn').onclick = () => {
  sessionManager.stop();
};

// ==========================================
// USAGE EXAMPLE - Multi Part Exam (contest_type ≠ 29)
// ==========================================

// Vue.js / React example
class ExamComponent {
  constructor() {
    this.sessionManager = new StudySessionManager();
    this.currentQuestions = [];
    this.contestType = null;
    this.currentPart = 1;
    this.totalParts = 4; // Reading, Listening, Writing, Speaking
  }

  async startExam(idBaiKiemTra) {
    try {
      const result = await this.sessionManager.start(idBaiKiemTra, 1);
      
      this.contestType = result.contestType;
      this.currentPart = result.part;
      this.currentQuestions = result.questions;
      
      this.render();
    } catch (error) {
      this.showError(error.message);
    }
  }

  async submitCurrentPart() {
    try {
      const answers = this.collectAnswers();
      const result = await this.sessionManager.submit(answers);
      
      this.showPartResult(result);
      
      // If multi-part and not last part
      if (this.contestType !== 29 && this.currentPart < this.totalParts) {
        const next = confirm(`Hoàn thành Part ${this.currentPart}!\nBạn có muốn làm Part ${this.currentPart + 1}?`);
        
        if (next) {
          await this.startNextPart();
        } else {
          this.showFinalResults();
        }
      } else {
        // Done - show final results
        this.showFinalResults();
      }
    } catch (error) {
      this.showError(error.message);
    }
  }

  async startNextPart() {
    try {
      const result = await this.sessionManager.startNextPart();
      
      this.currentPart = result.part;
      this.currentQuestions = result.questions;
      
      this.render();
    } catch (error) {
      this.showError(error.message);
    }
  }

  render() {
    // Update UI
    document.getElementById('partIndicator').textContent = 
      this.contestType === 29 
        ? 'Bài thi' 
        : `Part ${this.currentPart}/${this.totalParts}`;
    
    // Render questions
    this.renderQuestions(this.currentQuestions);
    
    // Show/hide next part button
    const isLastPart = this.currentPart >= this.totalParts;
    document.getElementById('nextPartBtn').style.display = 
      (this.contestType !== 29 && !isLastPart) ? 'block' : 'none';
  }

  collectAnswers() {
    // Collect user answers
    return [];
  }

  renderQuestions(questions) {
    // Render questions to DOM
  }

  showPartResult(result) {
    alert(`Điểm: ${result.score}\nThời gian: ${result.studyTime} phút`);
  }

  showFinalResults() {
    // Show all parts results
    window.location.href = '/results';
  }

  showError(message) {
    alert('Lỗi: ' + message);
  }
}

// Initialize
const examComponent = new ExamComponent();

document.getElementById('startBtn').onclick = () => {
  examComponent.startExam(456);
};

document.getElementById('submitBtn').onclick = () => {
  examComponent.submitCurrentPart();
};

document.getElementById('nextPartBtn').onclick = () => {
  examComponent.submitCurrentPart(); // Submit then ask for next
};
```

---

## 📝 Checklist Integration

### **Backend:**
- [x] Heat User APIs đã tạo
- [x] Cron job đã setup
- [x] Migration đã chạy

### **Frontend cần làm:**
- [ ] Implement StudySessionManager class
- [ ] Call EMS APIs (Step 1 & 2) trước khi gọi Heat User
- [ ] Lưu localStorage khi start
- [ ] Setup heartbeat timer (5 phút)
- [ ] Check existing session on page load
- [ ] Handle continue flow
- [ ] Submit flow: Heat User → EMS
- [ ] Stop flow with confirmation

---

## ⚠️ Lưu ý quan trọng

1. **idHistory không đổi khi continue:**
   - Close browser → Continue: `idHistory` GIỐNG
   - Stop → Start lại: `idHistory` MỚI (EMS tạo mới)

2. **Thứ tự API calls:**
   - Start: EMS (2 calls) → Heat User (1 call)
   - Submit: Heat User (1 call) → EMS (1 call)
   - Continue: Heat User (1 call) → EMS (1 call)

3. **localStorage rất quan trọng:**
   - Phải lưu: `heatUserSessionId`, `idHistory`, `idHistoryContest`
   - Không xóa khi close browser
   - Chỉ xóa khi submit hoặc stop

4. **Heartbeat:**
   - Gọi mỗi 5 phút
   - Chỉ gọi Heat User API (không cần gọi EMS)

---

Bạn xem flow này đã đúng với luồng của bạn chưa? Có điểm nào cần điều chỉnh không? 😊

