<?php

namespace App\Controllers\Admin;

use App\Controllers\BaseController;
use App\Models\ExamEventModel;
use App\Models\ExamRoomModel;
use App\Models\ExamRoomRuleModel;
use App\Models\ExamParticipantModel;
use App\Models\ExamScheduleModel;
use App\Models\AcademicPeriodModel;
use App\Models\ClassroomModel;
use App\Models\StudentModel;
use App\Models\GradeModel;
use App\Models\SchoolSettingModel;
use App\Models\StudentCardSettingModel;
use App\Models\StudentAcademicHistoryModel;
use App\Models\TeacherModel;
use App\Models\ExamEventTeacherModel;
use App\Models\ExamScheduleProctorModel;

class ExamEvent extends BaseController
{
    protected $eventModel;
    protected $roomModel;
    protected $ruleModel;
    protected $participantModel;
    protected $examScheduleModel;
    protected $periodModel;
    protected $classroomModel;
    protected $studentModel;
    protected $gradeModel;
    protected $schoolSettingModel;
    protected $cardSettingModel;
    protected $historyModel;
    protected $teacherModel;
    protected $eventTeacherModel;
    protected $scheduleProctorModel;

    public function __construct()
    {
        $this->eventModel        = new ExamEventModel();
        $this->roomModel         = new ExamRoomModel();
        $this->ruleModel         = new ExamRoomRuleModel();
        $this->participantModel  = new ExamParticipantModel();
        $this->examScheduleModel = new ExamScheduleModel();
        $this->periodModel       = new AcademicPeriodModel();
        $this->classroomModel    = new ClassroomModel();
        $this->studentModel      = new StudentModel();
        $this->gradeModel        = new GradeModel();
        $this->schoolSettingModel= new SchoolSettingModel();
        $this->cardSettingModel  = new StudentCardSettingModel();
        $this->historyModel      = new StudentAcademicHistoryModel();
        $this->teacherModel      = new TeacherModel();
        $this->eventTeacherModel  = new ExamEventTeacherModel();
        $this->scheduleProctorModel = new ExamScheduleProctorModel();
    }

    private function checkLogin()
    {
        if (!session()->get('logged_in')) {
            return redirect()->to('/login');
        }
        $role = session()->get('user_role');
        if (!in_array($role, ['superadmin', 'admin'])) {
            return redirect()->to('/dashboard')->with('error', 'Akses ditolak!');
        }
        return null;
    }

    // ==========================================
    // 1. CRUD EVENT
    // ==========================================
    public function index()
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $activePeriod = $this->periodModel->getActive();
        $events = $this->eventModel->getAllEvents();

        $data = [
            'title'        => 'Manajemen Administrasi Ujian',
            'events'       => $events,
            'activePeriod' => $activePeriod,
        ];

        return view('exam_event/index', $data);
    }

    public function create()
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $activePeriod = $this->periodModel->getActive();
        if (!$activePeriod) {
            return redirect()->to('/exam-event')->with('error', 'Belum ada periode akademik aktif!');
        }

        $schoolSettings = $this->schoolSettingModel->first();

        $data = [
            'title'          => 'Buat Event Ujian Baru',
            'activePeriod'   => $activePeriod,
            'periods'        => $this->periodModel->getAllWithYear(),
            'grades'         => $this->gradeModel->orderBy('order', 'ASC')->findAll(),
            'schoolSettings' => $schoolSettings,
        ];

        return view('exam_event/create', $data);
    }

    public function store()
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $rules = [
            'name'               => 'required|max_length[100]',
            'event_code'         => 'required|max_length[20]',
            'academic_period_id' => 'required|integer',
            'start_date'         => 'required|valid_date',
            'end_date'           => 'required|valid_date',
            'grade_levels'       => 'required',
            'chairman_name'      => 'permit_empty|max_length[150]',
            'chairman_nip'       => 'permit_empty|max_length[50]',
            'proctor_rules'      => 'permit_empty',
            'student_rules'      => 'permit_empty',
        ];

        if (!$this->validate($rules)) {
            return redirect()->back()->with('errors', $this->validator->getErrors())->withInput();
        }

        $gradeLevels = $this->request->getPost('grade_levels'); // array of grade names/IDs, we save as comma separated string
        $gradeLevelsStr = is_array($gradeLevels) ? implode(',', $gradeLevels) : $gradeLevels;

        $data = [
            'academic_period_id' => $this->request->getPost('academic_period_id'),
            'name'               => $this->request->getPost('name'),
            'event_code'         => strtoupper($this->request->getPost('event_code')),
            'start_date'         => $this->request->getPost('start_date'),
            'end_date'           => $this->request->getPost('end_date'),
            'grade_levels'       => $gradeLevelsStr,
            'is_locked'          => 0,
            'created_by'         => session()->get('user_id'),
            'chairman_name'      => $this->request->getPost('chairman_name'),
            'chairman_nip'       => $this->request->getPost('chairman_nip'),
            'proctor_rules'      => $this->request->getPost('proctor_rules'),
            'student_rules'      => $this->request->getPost('student_rules'),
        ];

        $this->eventModel->insert($data);

        return redirect()->to('/exam-event')->with('success', 'Event ujian berhasil dibuat!');
    }

    public function edit($id)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($id);
        if (!$event) {
            return redirect()->to('/exam-event')->with('error', 'Event tidak ditemukan!');
        }

        if ($event['is_locked']) {
            return redirect()->to('/exam-event')->with('error', 'Event ini sudah dikunci dan tidak bisa diedit!');
        }

        $schoolSettings = $this->schoolSettingModel->first();

        $data = [
            'title'          => 'Edit Event Ujian',
            'event'          => $event,
            'periods'        => $this->periodModel->getAllWithYear(),
            'grades'         => $this->gradeModel->orderBy('order', 'ASC')->findAll(),
            'schoolSettings' => $schoolSettings,
        ];

        return view('exam_event/edit', $data);
    }

    public function update($id)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($id);
        if (!$event) {
            return redirect()->to('/exam-event')->with('error', 'Event tidak ditemukan!');
        }

        if ($event['is_locked']) {
            return redirect()->to('/exam-event')->with('error', 'Event ini sudah dikunci dan tidak bisa diedit!');
        }

        $rules = [
            'name'               => 'required|max_length[100]',
            'event_code'         => 'required|max_length[20]',
            'academic_period_id' => 'required|integer',
            'start_date'         => 'required|valid_date',
            'end_date'           => 'required|valid_date',
            'grade_levels'       => 'required',
            'chairman_name'      => 'permit_empty|max_length[150]',
            'chairman_nip'       => 'permit_empty|max_length[50]',
            'proctor_rules'      => 'permit_empty',
            'student_rules'      => 'permit_empty',
        ];

        if (!$this->validate($rules)) {
            return redirect()->back()->with('errors', $this->validator->getErrors())->withInput();
        }

        $gradeLevels = $this->request->getPost('grade_levels');
        $gradeLevelsStr = is_array($gradeLevels) ? implode(',', $gradeLevels) : $gradeLevels;

        $this->eventModel->update($id, [
            'academic_period_id' => $this->request->getPost('academic_period_id'),
            'name'               => $this->request->getPost('name'),
            'event_code'         => strtoupper($this->request->getPost('event_code')),
            'start_date'         => $this->request->getPost('start_date'),
            'end_date'           => $this->request->getPost('end_date'),
            'grade_levels'       => $gradeLevelsStr,
            'chairman_name'      => $this->request->getPost('chairman_name'),
            'chairman_nip'       => $this->request->getPost('chairman_nip'),
            'proctor_rules'      => $this->request->getPost('proctor_rules'),
            'student_rules'      => $this->request->getPost('student_rules'),
        ]);

        return redirect()->to('/exam-event')->with('success', 'Event ujian berhasil diperbarui!');
    }

    public function delete($id)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($id);
        if (!$event) {
            return redirect()->to('/exam-event')->with('error', 'Event tidak ditemukan!');
        }

        if ($event['is_locked']) {
            return redirect()->to('/exam-event')->with('error', 'Hapus alokasi & buka kunci terlebih dahulu!');
        }

        // Delete related rules & rooms
        $rooms = $this->roomModel->where('exam_event_id', $id)->findAll();
        foreach ($rooms as $room) {
            $this->ruleModel->where('exam_room_id', $room['id'])->delete();
            $this->roomModel->delete($room['id']);
        }
        $this->examScheduleModel->where('exam_event_id', $id)->delete();

        $this->eventModel->delete($id);

        return redirect()->to('/exam-event')->with('success', 'Event ujian berhasil dihapus!');
    }

    // ==========================================
    // 2. CRUD RUANG UJIAN PER EVENT
    // ==========================================
    public function rooms($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event) {
            return redirect()->to('/exam-event')->with('error', 'Event tidak ditemukan!');
        }

        $rooms = $this->roomModel->getRoomsByEvent($eventId);
        $classrooms = $this->classroomModel->getAllWithRelations();
        
        $db = \Config\Database::connect();
        $activePeriod = $this->periodModel->getActive();
        $studentCounts = [];
        if ($activePeriod) {
            $counts = $db->table('student_academic_histories as sah')
                ->select('sah.classroom_id, COUNT(sah.student_id) as total')
                ->join('students s', 's.id = sah.student_id')
                ->where('sah.academic_period_id', $activePeriod['id'])
                ->where('s.deleted_at', null)
                ->where('s.status', 'Aktif')
                ->groupBy('sah.classroom_id')
                ->get()
                ->getResultArray();

            foreach ($counts as $c) {
                $studentCounts[$c['classroom_id']] = (int)$c['total'];
            }
        }

        $otherEvents = $this->eventModel->where('id !=', $eventId)->findAll();

        $data = [
            'title'         => 'Manajemen Ruang - ' . $event['name'],
            'event'         => $event,
            'rooms'         => $rooms,
            'classrooms'    => $classrooms,
            'studentCounts' => $studentCounts,
            'otherEvents'   => $otherEvents,
        ];

        return view('exam_event/rooms', $data);
    }

    public function generateFromClasses($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event || $event['is_locked']) {
            return redirect()->to("/exam-event/{$eventId}/rooms")->with('error', 'Event dikunci atau tidak ditemukan!');
        }

        $db = \Config\Database::connect();
        $activePeriod = $this->periodModel->getActive();
        if (!$activePeriod) {
            return redirect()->back()->with('error', 'Belum ada periode akademik aktif!');
        }

        $classrooms = $this->classroomModel->getAllWithRelations();
        $counts = $db->table('student_academic_histories as sah')
            ->select('sah.classroom_id, COUNT(sah.student_id) as total')
            ->join('students s', 's.id = sah.student_id')
            ->where('sah.academic_period_id', $activePeriod['id'])
            ->where('s.deleted_at', null)
            ->where('s.status', 'Aktif')
            ->groupBy('sah.classroom_id')
            ->get()
            ->getResultArray();

        $studentCounts = [];
        foreach ($counts as $c) {
            $studentCounts[$c['classroom_id']] = (int)$c['total'];
        }

        $insertedCount = 0;
        foreach ($classrooms as $c) {
            $capacity = $studentCounts[$c['id']] ?? 0;
            if ($capacity <= 0) continue; // Skip empty classrooms

            $roomName = "Ruang " . $c['name'];

            // Check if exists
            $exists = $this->roomModel->where('exam_event_id', $eventId)
                ->where('name', $roomName)
                ->first();

            if (!$exists) {
                $this->roomModel->insert([
                    'exam_event_id' => $eventId,
                    'name'          => $roomName,
                    'capacity'      => $capacity
                ]);
                $insertedCount++;
            }
        }

        return redirect()->to("/exam-event/{$eventId}/rooms")->with('success', "Berhasil menambahkan {$insertedCount} ruang ujian otomatis dari data kelas aktif!");
    }

    public function copyFromEvent($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event || $event['is_locked']) {
            return redirect()->to("/exam-event/{$eventId}/rooms")->with('error', 'Event dikunci atau tidak ditemukan!');
        }

        $fromEventId = $this->request->getPost('from_event_id');
        if (empty($fromEventId)) {
            return redirect()->back()->with('error', 'Silakan pilih event asal!');
        }

        $sourceRooms = $this->roomModel->where('exam_event_id', $fromEventId)->findAll();
        if (empty($sourceRooms)) {
            return redirect()->back()->with('error', 'Event asal tidak memiliki ruang ujian!');
        }

        $copiedCount = 0;
        foreach ($sourceRooms as $sr) {
            // Check if exists
            $exists = $this->roomModel->where('exam_event_id', $eventId)
                ->where('name', $sr['name'])
                ->first();

            if (!$exists) {
                $this->roomModel->insert([
                    'exam_event_id' => $eventId,
                    'name'          => $sr['name'],
                    'capacity'      => $sr['capacity']
                ]);
                $copiedCount++;
            }
        }

        return redirect()->to("/exam-event/{$eventId}/rooms")->with('success', "Berhasil menyalin {$copiedCount} ruang ujian dari event asal!");
    }

    public function storeRoom($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event || $event['is_locked']) {
            return redirect()->to('/exam-event')->with('error', 'Aksi ditolak!');
        }

        $rules = [
            'name'     => 'required|max_length[50]',
            'capacity' => 'required|integer|greater_than_equal_to[1]',
        ];

        if (!$this->validate($rules)) {
            return redirect()->back()->with('errors', $this->validator->getErrors());
        }

        $this->roomModel->insert([
            'exam_event_id' => $eventId,
            'name'          => $this->request->getPost('name'),
            'capacity'      => $this->request->getPost('capacity'),
        ]);

        return redirect()->to("/exam-event/{$eventId}/rooms")->with('success', 'Ruang ujian berhasil ditambahkan!');
    }

    public function updateRoom($roomId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $room = $this->roomModel->find($roomId);
        if (!$room) {
            return redirect()->back()->with('error', 'Ruang tidak ditemukan!');
        }

        $event = $this->eventModel->find($room['exam_event_id']);
        if ($event['is_locked']) {
            return redirect()->back()->with('error', 'Event dikunci!');
        }

        $rules = [
            'name'     => 'required|max_length[50]',
            'capacity' => 'required|integer|greater_than_equal_to[1]',
        ];

        if (!$this->validate($rules)) {
            return redirect()->back()->with('errors', $this->validator->getErrors());
        }

        $this->roomModel->update($roomId, [
            'name'     => $this->request->getPost('name'),
            'capacity' => $this->request->getPost('capacity'),
        ]);

        return redirect()->to("/exam-event/{$room['exam_event_id']}/rooms")->with('success', 'Ruang ujian berhasil diperbarui!');
    }

    public function deleteRoom($roomId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $room = $this->roomModel->find($roomId);
        if (!$room) {
            return redirect()->back()->with('error', 'Ruang tidak ditemukan!');
        }

        $event = $this->eventModel->find($room['exam_event_id']);
        if ($event['is_locked']) {
            return redirect()->back()->with('error', 'Event dikunci!');
        }

        // Delete rules inside this room
        $this->ruleModel->where('exam_room_id', $roomId)->delete();
        $this->roomModel->delete($roomId);

        return redirect()->to("/exam-event/{$room['exam_event_id']}/rooms")->with('success', 'Ruang ujian berhasil dihapus!');
    }

    // ==========================================
    // 3. JADWAL UJIAN PER EVENT (UNTUK KARTU)
    // ==========================================
    public function schedule($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event) return redirect()->to('/exam-event')->with('error', 'Event tidak ditemukan!');

        $schedules = $this->examScheduleModel->getSchedulesByEvent($eventId);
        $otherEvents = $this->eventModel->where('id !=', $eventId)->findAll();

        $data = [
            'title'       => 'Jadwal Asesmen - ' . $event['name'],
            'event'       => $event,
            'schedules'   => $schedules,
            'otherEvents' => $otherEvents,
        ];

        return view('exam_event/schedule', $data);
    }

    public function storeSchedule($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event || $event['is_locked']) return redirect()->to('/exam-event')->with('error', 'Aksi ditolak!');

        $rules = [
            'exam_date'    => 'required|valid_date',
            'exam_day'     => 'required|max_length[20]',
        ];

        if (!$this->validate($rules)) {
            return redirect()->back()->with('errors', $this->validator->getErrors())->withInput();
        }

        $subjectsInput = $this->request->getPost('subjects');
        $subjects = [];
        if (is_array($subjectsInput)) {
            foreach ($subjectsInput as $sub) {
                $name = trim($sub['name'] ?? '');
                $time = trim($sub['time'] ?? '');
                if ($name !== '') {
                    $subjects[] = [
                        'name' => $name,
                        'time' => $time,
                    ];
                }
            }
        }

        if (empty($subjects)) {
            return redirect()->back()->with('error', 'Harap masukkan minimal satu mata pelajaran!')->withInput();
        }

        $this->examScheduleModel->insert([
            'exam_event_id' => $eventId,
            'exam_date'     => $this->request->getPost('exam_date'),
            'exam_day'      => $this->request->getPost('exam_day'),
            'subject_list'  => json_encode($subjects),
        ]);

        return redirect()->to("/exam-event/{$eventId}/schedule")->with('success', 'Jadwal ujian berhasil ditambahkan!');
    }

    public function updateSchedule($scheduleId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $schedule = $this->examScheduleModel->find($scheduleId);
        if (!$schedule) return redirect()->back()->with('error', 'Jadwal tidak ditemukan!');

        $event = $this->eventModel->find($schedule['exam_event_id']);
        if ($event['is_locked']) return redirect()->back()->with('error', 'Event dikunci!');

        $rules = [
            'exam_date' => 'required|valid_date',
            'exam_day'  => 'required|max_length[20]',
        ];

        if (!$this->validate($rules)) {
            return redirect()->back()->with('errors', $this->validator->getErrors());
        }

        $subjectsInput = $this->request->getPost('subjects');
        $subjects = [];
        if (is_array($subjectsInput)) {
            foreach ($subjectsInput as $sub) {
                $name = trim($sub['name'] ?? '');
                $time = trim($sub['time'] ?? '');
                if ($name !== '') {
                    $subjects[] = [
                        'name' => $name,
                        'time' => $time,
                    ];
                }
            }
        }

        if (empty($subjects)) {
            return redirect()->back()->with('error', 'Harap masukkan minimal satu mata pelajaran!');
        }

        $this->examScheduleModel->update($scheduleId, [
            'exam_date'    => $this->request->getPost('exam_date'),
            'exam_day'     => $this->request->getPost('exam_day'),
            'subject_list' => json_encode($subjects),
        ]);

        return redirect()->to("/exam-event/{$schedule['exam_event_id']}/schedule")->with('success', 'Jadwal ujian berhasil diperbarui!');
    }

    public function deleteSchedule($scheduleId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $schedule = $this->examScheduleModel->find($scheduleId);
        if (!$schedule) return redirect()->back()->with('error', 'Jadwal tidak ditemukan!');

        $event = $this->eventModel->find($schedule['exam_event_id']);
        if ($event['is_locked']) return redirect()->back()->with('error', 'Event dikunci!');

        $this->examScheduleModel->delete($scheduleId);

        return redirect()->to("/exam-event/{$schedule['exam_event_id']}/schedule")->with('success', 'Jadwal ujian berhasil dihapus!');
    }

    public function copyScheduleFromEvent($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event || $event['is_locked']) {
            return redirect()->to("/exam-event/{$eventId}/schedule")->with('error', 'Event dikunci atau tidak ditemukan!');
        }

        $fromEventId = $this->request->getPost('from_event_id');
        if (empty($fromEventId)) {
            return redirect()->back()->with('error', 'Silakan pilih event asal!');
        }

        $sourceSchedules = $this->examScheduleModel->where('exam_event_id', $fromEventId)->findAll();
        if (empty($sourceSchedules)) {
            return redirect()->back()->with('error', 'Event asal tidak memiliki jadwal!');
        }

        $copiedCount = 0;
        foreach ($sourceSchedules as $ss) {
            // Check if schedule for that date already exists in target event
            $exists = $this->examScheduleModel->where('exam_event_id', $eventId)
                ->where('exam_date', $ss['exam_date'])
                ->first();

            if (!$exists) {
                $this->examScheduleModel->insert([
                    'exam_event_id' => $eventId,
                    'exam_date'     => $ss['exam_date'],
                    'exam_day'      => $ss['exam_day'],
                    'subject_list'  => $ss['subject_list']
                ]);
                $copiedCount++;
            }
        }

        return redirect()->to("/exam-event/{$eventId}/schedule")->with('success', "Berhasil menyalin {$copiedCount} jadwal dari event asal!");
    }

    // ==========================================
    // 4. MAPPING ATURAN PENEMPATAN & VALIDASI
    // ==========================================
    public function allocation($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event) return redirect()->to('/exam-event')->with('error', 'Event tidak ditemukan!');

        $rooms = $this->roomModel->getRoomsByEvent($eventId);
        $rules = $this->ruleModel->getRulesByEvent($eventId);

        // Map rules by room ID and classroom ID
        $rulesByRoom = [];
        $rulesByClassroom = [];
        foreach ($rules as $rule) {
            $rulesByRoom[$rule['exam_room_id']][] = $rule;
            $rulesByClassroom[$rule['classroom_id']][] = $rule;
        }

        // Get active classrooms and count of students in them
        $classrooms = $this->classroomModel->getAllWithRelations();
        $db = \Config\Database::connect();

        // Calculate student counts per class in active period
        $studentCounts = [];
        $activePeriod = $this->periodModel->getActive();
        if ($activePeriod) {
            $counts = $db->table('student_academic_histories as sah')
                ->select('sah.classroom_id, COUNT(sah.student_id) as total')
                ->join('students s', 's.id = sah.student_id')
                ->where('sah.academic_period_id', $activePeriod['id'])
                ->where('s.deleted_at', null)
                ->where('s.status', 'Aktif')
                ->groupBy('sah.classroom_id')
                ->get()
                ->getResultArray();

            foreach ($counts as $c) {
                $studentCounts[$c['classroom_id']] = (int)$c['total'];
            }
        }

        // Decorate rooms with current allocated count
        foreach ($rooms as &$room) {
            $room['rules'] = $rulesByRoom[$room['id']] ?? [];
            $totalAllocated = 0;
            foreach ($room['rules'] as $r) {
                $totalAllocated += ($r['end_absen'] - $r['start_absen'] + 1);
            }
            $room['allocated_count'] = $totalAllocated;
        }

        // Run pre-validation metrics
        $validationResult = $this->runPreValidation($event, $rooms, $rules);

        // Load visual pairing plan
        $pairingPlan = json_decode($event['pairing_plan'] ?? '', true) ?: [];

        // -------------------------------------------------------------
        // SMART CROSS-PAIRING RECOMMENDATIONS ALGORITHM
        // -------------------------------------------------------------
        $classesByLevel = [];
        $participatingGrades = explode(',', $event['grade_levels']);
        
        foreach ($classrooms as $c) {
            $totalSiswa = $studentCounts[$c['id']] ?? 0;
            if ($totalSiswa <= 0) continue;

            // Check if the classroom's grade name is in the participating grades (exact match)
            $matchGrade = null;
            foreach ($participatingGrades as $pg) {
                if (strcasecmp(trim($c['grade_name']), trim($pg)) === 0) {
                    $matchGrade = trim($pg);
                    break;
                }
            }

            if ($matchGrade) {
                // Calculate dynamic unallocated student ranges (contiguous unallocated block)
                $classroomRules = $rulesByClassroom[$c['id']] ?? [];
                $allocatedMap = array_fill(1, $totalSiswa, false);
                foreach ($classroomRules as $rule) {
                    $st = (int)$rule['start_absen'];
                    $en = (int)$rule['end_absen'];
                    for ($i = $st; $i <= $en; $i++) {
                        if (isset($allocatedMap[$i])) {
                            $allocatedMap[$i] = true;
                        }
                    }
                }
                
                $nextStart = null;
                $nextEnd = null;
                for ($i = 1; $i <= $totalSiswa; $i++) {
                    if (!$allocatedMap[$i]) {
                        if ($nextStart === null) {
                            $nextStart = $i;
                        }
                        $nextEnd = $i;
                    } else {
                        if ($nextStart !== null) {
                            break; // first contiguous block found
                        }
                    }
                }
                
                $sisaCount = count(array_filter($allocatedMap, function($val) { return !$val; }));

                $classesByLevel[$matchGrade][] = [
                    'id'         => $c['id'],
                    'name'       => $c['name'],
                    'count'      => $totalSiswa,
                    'sisa'       => $sisaCount,
                    'next_start' => $nextStart,
                    'next_end'   => $nextEnd,
                    'is_full'    => $sisaCount === 0
                ];
            }
        }

        // Apply visual plan sorting to classes in each level column
        foreach ($classesByLevel as $lvl => &$classes) {
            $plannedIds = $pairingPlan[$lvl] ?? [];
            if (!empty($plannedIds)) {
                usort($classes, function($a, $b) use ($plannedIds) {
                    $posA = array_search($a['id'], $plannedIds);
                    $posB = array_search($b['id'], $plannedIds);
                    
                    if ($posA === false && $posB === false) return 0;
                    if ($posA === false) return 1;
                    if ($posB === false) return -1;
                    return $posA <=> $posB;
                });
            }
        }
        unset($classes);

        $triplets = [];
        $pairs = [];
        $usedClassIds = [];

        // 1. Generate Triplets (if we have at least 3 levels with classes)
        $activeLevels = array_keys($classesByLevel);
        if (count($activeLevels) >= 3) {
            // We'll generate all possible triplets from the first 3 active levels
            $lvl1 = $activeLevels[0];
            $lvl2 = $activeLevels[1];
            $lvl3 = $activeLevels[2];

            $allTriplets = [];
            foreach ($classesByLevel[$lvl1] as $c1) {
                foreach ($classesByLevel[$lvl2] as $c2) {
                    foreach ($classesByLevel[$lvl3] as $c3) {
                        $counts = [$c1['count'], $c2['count'], $c3['count']];
                        $diff = max($counts) - min($counts);
                        $allTriplets[] = [
                            'c1'   => $c1,
                            'c2'   => $c2,
                            'c3'   => $c3,
                            'diff' => $diff
                        ];
                    }
                }
            }

            // Sort triplets by difference ascending
            usort($allTriplets, function($a, $b) {
                return $a['diff'] <=> $b['diff'];
            });

            // Greedily select triplets
            foreach ($allTriplets as $t) {
                $id1 = $t['c1']['id'];
                $id2 = $t['c2']['id'];
                $id3 = $t['c3']['id'];

                if (!in_array($id1, $usedClassIds) && !in_array($id2, $usedClassIds) && !in_array($id3, $usedClassIds)) {
                    $usedClassIds[] = $id1;
                    $usedClassIds[] = $id2;
                    $usedClassIds[] = $id3;

                    // Quality label
                    $quality = 'Sangat Serasi';
                    $badgeClass = 'bg-success';
                    if ($t['diff'] > 1 && $t['diff'] <= 3) {
                        $quality = 'Serasi';
                        $badgeClass = 'bg-info';
                    } elseif ($t['diff'] > 3 && $t['diff'] <= 5) {
                        $quality = 'Cukup Serasi';
                        $badgeClass = 'bg-warning';
                    } elseif ($t['diff'] > 5) {
                        $quality = 'Kurang Serasi';
                        $badgeClass = 'bg-secondary';
                    }

                    $triplets[] = [
                        'classes'    => [$t['c1'], $t['c2'], $t['c3']],
                        'diff'       => $t['diff'],
                        'quality'    => $quality,
                        'badgeClass' => $badgeClass
                    ];
                }
            }
        }

        // 2. Generate Pairs for remaining classes (or if count of levels is 2)
        $remainingClasses = [];
        foreach ($classesByLevel as $lvl => $list) {
            foreach ($list as $c) {
                if (!in_array($c['id'], $usedClassIds)) {
                    $remainingClasses[] = array_merge($c, ['level' => $lvl]);
                }
            }
        }

        // Generate all possible pairs between different levels
        $allPairs = [];
        for ($i = 0; $i < count($remainingClasses); $i++) {
            for ($j = $i + 1; $j < count($remainingClasses); $j++) {
                $c1 = $remainingClasses[$i];
                $c2 = $remainingClasses[$j];
                // Only pair if they belong to different levels
                if ($c1['level'] !== $c2['level']) {
                    $diff = abs($c1['count'] - $c2['count']);
                    $allPairs[] = [
                        'c1'   => $c1,
                        'c2'   => $c2,
                        'diff' => $diff
                    ];
                }
            }
        }

        // Sort pairs by difference ascending
        usort($allPairs, function($a, $b) {
            return $a['diff'] <=> $b['diff'];
        });

        // Greedily select pairs
        foreach ($allPairs as $p) {
            $id1 = $p['c1']['id'];
            $id2 = $p['c2']['id'];

            if (!in_array($id1, $usedClassIds) && !in_array($id2, $usedClassIds)) {
                $usedClassIds[] = $id1;
                $usedClassIds[] = $id2;

                // Quality label
                $quality = 'Sangat Serasi';
                $badgeClass = 'bg-success';
                if ($p['diff'] > 1 && $p['diff'] <= 3) {
                    $quality = 'Serasi';
                    $badgeClass = 'bg-info';
                } elseif ($p['diff'] > 3 && $p['diff'] <= 5) {
                    $quality = 'Cukup Serasi';
                    $badgeClass = 'bg-warning';
                } elseif ($p['diff'] > 5) {
                    $quality = 'Kurang Serasi';
                    $badgeClass = 'bg-secondary';
                }

                $pairs[] = [
                    'classes'    => [$p['c1'], $p['c2']],
                    'diff'       => $p['diff'],
                    'quality'    => $quality,
                    'badgeClass' => $badgeClass
                ];
            }
        }

        $data = [
            'title'           => 'Alokasi Peserta - ' . $event['name'],
            'event'           => $event,
            'rooms'           => $rooms,
            'classrooms'      => $classrooms,
            'studentCounts'   => $studentCounts,
            'validation'      => $validationResult,
            'levelsData'      => $classesByLevel,
            'recommendations' => [
                'triplets' => $triplets,
                'pairs'    => $pairs
            ]
        ];

        return view('exam_event/allocation', $data);
    }

    public function savePairingPlan($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event) {
            return $this->response->setJSON([
                'success' => false,
                'message' => 'Event tidak ditemukan!'
            ]);
        }

        if ($event['is_locked']) {
            return $this->response->setJSON([
                'success' => false,
                'message' => 'Event sudah dikunci!'
            ]);
        }

        $pairingPlanJson = $this->request->getPost('pairing_plan');
        
        $this->eventModel->update($eventId, [
            'pairing_plan' => $pairingPlanJson
        ]);

        return $this->response->setJSON([
            'success' => true,
            'message' => 'Rencana silang berhasil disimpan.'
        ]);
    }

    public function saveRule($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event || $event['is_locked']) {
            return redirect()->to('/exam-event')->with('error', 'Event terkunci!');
        }

        $rules = [
            'exam_room_id' => 'required|integer',
            'classroom_id' => 'required|integer',
            'start_absen'  => 'required|integer|greater_than_equal_to[1]',
            'end_absen'    => 'required|integer|greater_than_equal_to[1]',
        ];

        if (!$this->validate($rules)) {
            return redirect()->back()->with('errors', $this->validator->getErrors());
        }

        $roomId      = $this->request->getPost('exam_room_id');
        $classroomId = $this->request->getPost('classroom_id');
        $startAbsen  = (int)$this->request->getPost('start_absen');
        $endAbsen    = (int)$this->request->getPost('end_absen');

        if ($startAbsen > $endAbsen) {
            return redirect()->back()->with('error', 'Nomor absen awal tidak boleh melebihi absen akhir!');
        }

        $room = $this->roomModel->find($roomId);
        if (!$room) return redirect()->back()->with('error', 'Ruang tidak ditemukan!');

        // Verify class capacity
        $activePeriod = $this->periodModel->getActive();
        $db = \Config\Database::connect();
        $studentCount = $db->table('student_academic_histories as sah')
            ->join('students s', 's.id = sah.student_id')
            ->where('sah.classroom_id', $classroomId)
            ->where('sah.academic_period_id', $activePeriod['id'])
            ->where('s.deleted_at', null)
            ->where('s.status', 'Aktif')
            ->countAllResults();

        if ($endAbsen > $studentCount) {
            return redirect()->back()->with('error', "Jumlah siswa di kelas tersebut hanya {$studentCount}. Absen akhir tidak valid!");
        }

        // Verify no duplicate/overlapping roll numbers for this classroom in this event
        $allEventRooms = $this->roomModel->where('exam_event_id', $eventId)->findAll();
        $roomIds = array_column($allEventRooms, 'id');
        
        if (!empty($roomIds)) {
            $existingRules = $this->ruleModel->whereIn('exam_room_id', $roomIds)->findAll();
            foreach ($existingRules as $er) {
                if ((int)$er['classroom_id'] === $classroomId) {
                    $erStart = (int)$er['start_absen'];
                    $erEnd = (int)$er['end_absen'];
                    
                    // Check overlap between [$startAbsen, $endAbsen] and [$erStart, $erEnd]
                    if (max($startAbsen, $erStart) <= min($endAbsen, $erEnd)) {
                        $erRoom = $this->roomModel->find($er['exam_room_id']);
                        $roomName = $erRoom['name'] ?? 'ruang lain';
                        return redirect()->back()->with('error', "Gagal menyimpan! Siswa absen {$erStart} s.d {$erEnd} pada kelas ini sudah dialokasikan di {$roomName}.");
                    }
                }
            }
        }

        // Verify room capacity limit
        $existingRules = $this->ruleModel->where('exam_room_id', $roomId)->findAll();
        $allocatedSoFar = 0;
        foreach ($existingRules as $er) {
            $allocatedSoFar += ($er['end_absen'] - $er['start_absen'] + 1);
        }
        $newAllocation = ($endAbsen - $startAbsen + 1);

        if (($allocatedSoFar + $newAllocation) > $room['capacity']) {
            return redirect()->back()->with('error', "Alokasi baru ({$newAllocation} siswa) menyebabkan isi ruang melebihi kapasitas ({$room['capacity']})!");
        }

        $this->ruleModel->insert([
            'exam_room_id' => $roomId,
            'classroom_id' => $classroomId,
            'start_absen'  => $startAbsen,
            'end_absen'    => $endAbsen,
        ]);

        return redirect()->to("/exam-event/{$eventId}/allocation")->with('success', 'Aturan penempatan berhasil disimpan!');
    }

    public function deleteRule($ruleId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $rule = $this->ruleModel->find($ruleId);
        if (!$rule) return redirect()->back()->with('error', 'Aturan tidak ditemukan!');

        $room = $this->roomModel->find($rule['exam_room_id']);
        $event = $this->eventModel->find($room['exam_event_id']);

        if ($event['is_locked']) {
            return redirect()->back()->with('error', 'Event terkunci!');
        }

        $this->ruleModel->delete($ruleId);

        return redirect()->to("/exam-event/{$event['id']}/allocation")->with('success', 'Aturan penempatan berhasil dihapus!');
    }

    private function runPreValidation($event, $rooms, $rules)
    {
        $db = \Config\Database::connect();
        $activePeriod = $this->periodModel->getActive();
        if (!$activePeriod) return ['success' => false, 'message' => 'Belum ada periode aktif'];

        // 1. Get all students eligible for this event based on grade levels
        $gradeNames = explode(',', $event['grade_levels']); // e.g. ["7", "8", "9"] or list of names
        
        $builder = $db->table('student_academic_histories as sah')
            ->select('students.id, students.full_name, classrooms.name as classroom_name, grades.name as grade_name')
            ->join('students', 'students.id = sah.student_id')
            ->join('classrooms', 'classrooms.id = sah.classroom_id')
            ->join('grades', 'grades.id = classrooms.grade_id')
            ->where('sah.academic_period_id', $activePeriod['id'])
            ->where('students.deleted_at', null)
            ->where('students.status', 'Aktif');

        // Filter by grade names using exact match to prevent VIII matching VII
        $builder->groupStart();
        foreach ($gradeNames as $gn) {
            $builder->orWhere('grades.name', trim($gn));
        }
        $builder->groupEnd();

        $eligibleStudents = $builder->orderBy('classroom_name', 'ASC')
            ->orderBy('students.full_name', 'ASC')
            ->get()
            ->getResultArray();

        // Group eligible students by classroom to easily lookup their relative alphabetical index (absen)
        $studentsByClass = [];
        foreach ($eligibleStudents as $student) {
            $studentsByClass[$student['classroom_name']][] = $student;
        }

        // 2. Map rules to student IDs
        $allocatedStudentIds = [];
        $duplicatePlacements = [];
        $missingStudents = [];

        foreach ($rules as $rule) {
            $roomName = $rule['room_name'] ?? '';
            // find classroom name
            $classroom = $this->classroomModel->find($rule['classroom_id']);
            $className = $classroom['name'] ?? '';

            if (isset($studentsByClass[$className])) {
                $classStudents = $studentsByClass[$className];
                $start = $rule['start_absen'] - 1;
                $end = $rule['end_absen'] - 1;

                for ($idx = $start; $idx <= $end; $idx++) {
                    if (isset($classStudents[$idx])) {
                        $sId = $classStudents[$idx]['id'];
                        if (isset($allocatedStudentIds[$sId])) {
                            $duplicatePlacements[$sId] = [
                                'student' => $classStudents[$idx],
                                'rooms'   => [$allocatedStudentIds[$sId]['room'], $roomName]
                            ];
                        } else {
                            $allocatedStudentIds[$sId] = [
                                'student' => $classStudents[$idx],
                                'room'    => $roomName
                            ];
                        }
                    }
                }
            }
        }

        // 3. Find missing students (eligible but not in allocatedStudentIds)
        foreach ($eligibleStudents as $student) {
            if (!isset($allocatedStudentIds[$student['id']])) {
                $missingStudents[] = $student;
            }
        }

        // 4. Overloaded rooms
        $allocatedByRoom = [];
        foreach ($rules as $rule) {
            $rId = $rule['exam_room_id'];
            $allocatedByRoom[$rId] = ($allocatedByRoom[$rId] ?? 0) + ($rule['end_absen'] - $rule['start_absen'] + 1);
        }

        $overloadedRooms = [];
        foreach ($rooms as $room) {
            $allocatedCount = $allocatedByRoom[$room['id']] ?? 0;
            if ($allocatedCount > $room['capacity']) {
                $room['allocated_count'] = $allocatedCount;
                $overloadedRooms[] = $room;
            }
        }

        return [
            'total_eligible'  => count($eligibleStudents),
            'total_allocated' => count($allocatedStudentIds),
            'missing'         => $missingStudents,
            'duplicates'      => array_values($duplicatePlacements),
            'overloaded'      => $overloadedRooms,
        ];
    }

    // ==========================================
    // 5. FINALISASI (KUNCI & GENERATE PESERTA)
    // ==========================================
    public function finalize($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event || $event['is_locked']) {
            return redirect()->back()->with('error', 'Event sudah dikunci!');
        }

        $rooms = $this->roomModel->getRoomsByEvent($eventId);
        $rules = $this->ruleModel->getRulesByEvent($eventId);

        // Run validation check
        $validation = $this->runPreValidation($event, $rooms, $rules);

        if (!empty($validation['duplicates'])) {
            return redirect()->back()->with('error', 'Tidak bisa mengunci event! Terdapat peserta yang terduplikasi.');
        }

        if (!empty($validation['overloaded'])) {
            return redirect()->back()->with('error', 'Tidak bisa mengunci event! Terdapat ruang yang melebihi kapasitas.');
        }

        // Allow finalizing even if some students are not allocated (warning-only/optional)
        // if (!empty($validation['missing'])) {
        //     return redirect()->back()->with('error', 'Tidak bisa mengunci event! Terdapat siswa yang belum mendapatkan ruang.');
        // }

        if (empty($rooms)) {
            return redirect()->back()->with('error', 'Belum ada ruang ujian!');
        }

        $db = \Config\Database::connect();
        $activePeriod = $this->periodModel->getActive();
        $db->transStart();

        // 1. Ambil data siswa per kelas secara alfabetis untuk lookup absen
        $classroomIds = array_unique(array_column($rules, 'classroom_id'));
        $studentsByClass = [];
        
        foreach ($classroomIds as $classId) {
            $classroom = $this->classroomModel->find($classId);
            $className = $classroom['name'] ?? '';

            $classStudents = $db->table('student_academic_histories as sah')
                ->select('students.id, students.full_name')
                ->join('students', 'students.id = sah.student_id')
                ->where('sah.classroom_id', $classId)
                ->where('sah.academic_period_id', $activePeriod['id'])
                ->where('students.deleted_at', null)
                ->where('students.status', 'Aktif')
                ->orderBy('students.full_name', 'ASC')
                ->get()
                ->getResultArray();

            $studentsByClass[$classId] = $classStudents;
        }

        // Tahun pelajaran singkat, misal 2025/2026 -> 2026
        $academicYear = $db->table('academic_years')->where('id', $activePeriod['academic_year_id'])->get()->getRowArray();
        $yearName = $academicYear['name'] ?? '';
        $yearSingkat = substr($yearName, -4); // ambil 2026 dari "2025/2026"
        if (empty($yearSingkat)) $yearSingkat = date('Y');

        $evtCode = !empty($event['event_code']) ? $event['event_code'] : $event['name'];

        // 2. Loop per ruang untuk menempatkan siswa dengan silang tingkat pola mengular (Snaking, 5 Baris)
        $rulesByRoom = [];
        foreach ($rules as $rule) {
            $rulesByRoom[$rule['exam_room_id']][] = $rule;
        }

        // Counter nomor peserta per kelas (untuk counter unik: e.g. 2026-ASTS-7A-001)
        $classCounters = [];

        foreach ($rooms as $room) {
            $roomId = $room['id'];
            $roomRules = $rulesByRoom[$roomId] ?? [];

            if (empty($roomRules)) continue;

            // Kelompokkan siswa dalam ruang ini berdasarkan Level/Tingkat Kelas (misal 7 vs 8) untuk silang tingkat
            // Kita kumpulkan semua siswa di masing-masing rule
            $streamList = []; // Array berisi list siswa dari masing-masing aturan
            
            // Urutkan rule berdasarkan grade order agar Stream A dan Stream B konsisten
            usort($roomRules, function($a, $b) {
                $classA = $this->classroomModel->find($a['classroom_id']);
                $classB = $this->classroomModel->find($b['classroom_id']);
                $gradeA = $this->gradeModel->find($classA['grade_id']);
                $gradeB = $this->gradeModel->find($classB['grade_id']);
                return ($gradeA['order'] ?? 0) <=> ($gradeB['order'] ?? 0);
            });

            // Stream A (Kiri/Ganjil) dan Stream B (Kanan/Genap)
            $streamA = [];
            $streamB = [];

            if (isset($roomRules[0])) {
                $rule = $roomRules[0];
                $classStudents = $studentsByClass[$rule['classroom_id']] ?? [];
                $start = $rule['start_absen'] - 1;
                $end = $rule['end_absen'] - 1;
                for ($idx = $start; $idx <= $end; $idx++) {
                    if (isset($classStudents[$idx])) {
                        $classStudents[$idx]['classroom_id'] = $rule['classroom_id'];
                        $streamA[] = $classStudents[$idx];
                    }
                }
            }

            if (isset($roomRules[1])) {
                $rule = $roomRules[1];
                $classStudents = $studentsByClass[$rule['classroom_id']] ?? [];
                $start = $rule['start_absen'] - 1;
                $end = $rule['end_absen'] - 1;
                for ($idx = $start; $idx <= $end; $idx++) {
                    if (isset($classStudents[$idx])) {
                        $classStudents[$idx]['classroom_id'] = $rule['classroom_id'];
                        $streamB[] = $classStudents[$idx];
                    }
                }
            }

            // Jika ada rule ke-3, dst (jarang, tapi dihandle sebagai tambahan streamA/B)
            for ($k = 2; $k < count($roomRules); $k++) {
                $rule = $roomRules[$k];
                $classStudents = $studentsByClass[$rule['classroom_id']] ?? [];
                $start = $rule['start_absen'] - 1;
                $end = $rule['end_absen'] - 1;
                for ($idx = $start; $idx <= $end; $idx++) {
                    if (isset($classStudents[$idx])) {
                        $classStudents[$idx]['classroom_id'] = $rule['classroom_id'];
                        // masukkan bergantian
                        if (count($streamA) <= count($streamB)) {
                            $streamA[] = $classStudents[$idx];
                        } else {
                            $streamB[] = $classStudents[$idx];
                        }
                    }
                }
            }

            // Kapasitas Ruang
            $capacity = $room['capacity'];
            // 5 Baris Meja (default R = 5). Kita hitung jumlah kolom C = ceil(capacity / 10).
            $R = 5; 
            $C = ceil($capacity / 10);
            $D = $R * $C; // Total meja

            // Sekarang kita lakukan alokasi kursi mengikuti pola mengular 5 baris
            // Untuk setiap Meja i (1 s.d D):
            // Kursi Ganjil (2i - 1) di sebelah kiri meja: Stream A
            // Kursi Genap (2i) di sebelah kanan meja: Stream B
            for ($i = 1; $i <= $D; $i++) {
                // Sisi Kiri (Ganjil):
                $seatLeft = 2 * $i - 1;
                $studentLeft = array_shift($streamA);

                if ($studentLeft && $seatLeft <= $capacity) {
                    $classId = $studentLeft['classroom_id'];
                    $classroom = $this->classroomModel->find($classId);
                    $className = $classroom['name'] ?? '';

                    if (!isset($classCounters[$classId])) $classCounters[$classId] = 0;
                    $classCounters[$classId]++;
                    $noUrutStr = str_pad($classCounters[$classId], 3, '0', STR_PAD_LEFT);

                    // Format: 2026-ASTS-7A-001
                    $examNumber = "{$yearSingkat}-{$evtCode}-{$className}-{$noUrutStr}";

                    $this->participantModel->insert([
                        'exam_event_id' => $eventId,
                        'exam_room_id'  => $roomId,
                        'student_id'    => $studentLeft['id'],
                        'exam_number'   => $examNumber,
                        'seat_number'   => $seatLeft,
                    ]);
                }

                // Sisi Kanan (Genap):
                $seatRight = 2 * $i;
                $studentRight = array_shift($streamB);

                if ($studentRight && $seatRight <= $capacity) {
                    $classId = $studentRight['classroom_id'];
                    $classroom = $this->classroomModel->find($classId);
                    $className = $classroom['name'] ?? '';

                    if (!isset($classCounters[$classId])) $classCounters[$classId] = 0;
                    $classCounters[$classId]++;
                    $noUrutStr = str_pad($classCounters[$classId], 3, '0', STR_PAD_LEFT);

                    // Format: 2026-ASTS-7A-001
                    $examNumber = "{$yearSingkat}-{$evtCode}-{$className}-{$noUrutStr}";

                    $this->participantModel->insert([
                        'exam_event_id' => $eventId,
                        'exam_room_id'  => $roomId,
                        'student_id'    => $studentRight['id'],
                        'exam_number'   => $examNumber,
                        'seat_number'   => $seatRight,
                    ]);
                }
            }

            // Jika masih ada sisa di streamA atau streamB karena kapasitas meja sisa, diisi sisa kursi kosong
            $sisaSiswa = array_merge($streamA, $streamB);
            if (!empty($sisaSiswa)) {
                // Cari kursi kosong
                $assignedSeats = $this->participantModel->where('exam_room_id', $roomId)->findColumn('seat_number') ?: [];
                for ($seat = 1; $seat <= $capacity; $seat++) {
                    if (!in_array($seat, $assignedSeats)) {
                        $student = array_shift($sisaSiswa);
                        if ($student) {
                            $classId = $student['classroom_id'];
                            $classroom = $this->classroomModel->find($classId);
                            $className = $classroom['name'] ?? '';

                            if (!isset($classCounters[$classId])) $classCounters[$classId] = 0;
                            $classCounters[$classId]++;
                            $noUrutStr = str_pad($classCounters[$classId], 3, '0', STR_PAD_LEFT);

                            $examNumber = "{$yearSingkat}-{$evtCode}-{$className}-{$noUrutStr}";

                            $this->participantModel->insert([
                                'exam_event_id' => $eventId,
                                'exam_room_id'  => $roomId,
                                'student_id'    => $student['id'],
                                'exam_number'   => $examNumber,
                                'seat_number'   => $seat,
                            ]);
                        }
                    }
                }
            }
        }

        // Kunci Event
        $this->eventModel->update($eventId, ['is_locked' => 1]);

        $db->transComplete();

        if ($db->transStatus() === false) {
            return redirect()->back()->with('error', 'Gagal memproses penguncian dan generate nomor!');
        }

        return redirect()->to("/exam-event/{$eventId}/allocation")->with('success', 'Event ujian berhasil dikunci dan nomor peserta berhasil digenerate!');
    }

    public function unfinalize($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event || !$event['is_locked']) {
            return redirect()->back()->with('error', 'Event tidak dalam posisi terkunci!');
        }

        $db = \Config\Database::connect();
        $db->transStart();

        // Hapus semua peserta final
        $this->participantModel->where('exam_event_id', $eventId)->delete();

        // Buka kunci event
        $this->eventModel->update($eventId, ['is_locked' => 0]);

        $db->transComplete();

        if ($db->transStatus() === false) {
            return redirect()->back()->with('error', 'Gagal membatalkan finalisasi!');
        }

        return redirect()->to("/exam-event/{$eventId}/allocation")->with('success', 'Kunci alokasi berhasil dibuka. Data peserta dikembalikan ke draft.');
    }

    // ==========================================
    // 6. CETAK DOKUMEN (PRINT VIEWS)
    // ==========================================
    
    // a. Kartu Peserta (Kertas FOLIO/F4 - 4 Kartu per Halaman Grid 2x2)
    public function printCards($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event || !$event['is_locked']) return redirect()->to('/exam-event')->with('error', 'Event belum dikunci!');

        $schoolSettings = $this->schoolSettingModel->first();
        $event['chairman_name'] = !empty($event['chairman_name']) ? $event['chairman_name'] : ($schoolSettings['principal_name'] ?? '');
        $event['chairman_nip']  = !empty($event['chairman_nip']) ? $event['chairman_nip'] : ($schoolSettings['principal_nip'] ?? '');

        // Sort participants by classroom name and student name (absen order) directly via database join query
        $participants = $this->participantModel->select('exam_participants.*, students.nis, students.nisn, students.full_name, r.name as room_name, c.name as classroom_name')
            ->join('students', 'students.id = exam_participants.student_id')
            ->join('exam_rooms r', 'r.id = exam_participants.exam_room_id')
            ->join('student_academic_histories sah', 'sah.student_id = exam_participants.student_id AND sah.academic_period_id = ' . $event['academic_period_id'])
            ->join('classrooms c', 'c.id = sah.classroom_id')
            ->where('exam_participants.exam_event_id', $eventId)
            ->where('students.deleted_at', null)
            ->orderBy('c.name', 'ASC')
            ->orderBy('students.full_name', 'ASC')
            ->findAll();

        $schedules = $this->examScheduleModel->getSchedulesByEvent($eventId);

        // Ambil data sekolah & settings
        $schoolData = $this->schoolSettingModel->getSettings();
        $cardSetting = $this->cardSettingModel->getSettings();

        // Decode subject lists in schedules
        foreach ($schedules as &$sch) {
            $sch['subjects'] = json_decode($sch['subject_list'], true) ?: [];
        }

        // Fetch active period details with year name & semester
        $activePeriod = $this->periodModel->select('academic_periods.*, ay.name as academic_year_name')
            ->join('academic_years ay', 'ay.id = academic_periods.academic_year_id')
            ->where('academic_periods.id', $event['academic_period_id'])
            ->first();

        $data = [
            'title'        => 'Cetak Kartu Peserta',
            'event'        => $event,
            'participants' => $participants,
            'schedules'    => $schedules,
            'school'       => $schoolData,
            'cardSetting'  => $cardSetting,
            'activePeriod' => $activePeriod,
            'printDate'    => date('d F Y')
        ];

        return view('exam_event/print/cards', $data);
    }

    // b. Kartu Meja (Kertas Label/Kertas Biasa - sorted by room, class, then student name)
    public function printDeskLabels($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event || !$event['is_locked']) return redirect()->to('/exam-event')->with('error', 'Event belum dikunci!');

        $schoolSettings = $this->schoolSettingModel->first();
        $event['chairman_name'] = !empty($event['chairman_name']) ? $event['chairman_name'] : ($schoolSettings['principal_name'] ?? '');
        $event['chairman_nip']  = !empty($event['chairman_nip']) ? $event['chairman_nip'] : ($schoolSettings['principal_nip'] ?? '');

        // Sort participants by room name, classroom name, then student name
        $participants = $this->participantModel->select('exam_participants.*, students.nis, students.nisn, students.full_name, r.name as room_name, c.name as classroom_name')
            ->join('students', 'students.id = exam_participants.student_id')
            ->join('exam_rooms r', 'r.id = exam_participants.exam_room_id')
            ->join('student_academic_histories sah', 'sah.student_id = exam_participants.student_id AND sah.academic_period_id = ' . $event['academic_period_id'])
            ->join('classrooms c', 'c.id = sah.classroom_id')
            ->where('exam_participants.exam_event_id', $eventId)
            ->where('students.deleted_at', null)
            ->orderBy('r.name', 'ASC')
            ->orderBy('c.name', 'ASC')
            ->orderBy('students.full_name', 'ASC')
            ->findAll();

        $schoolData = $this->schoolSettingModel->getSettings();
        
        $activePeriod = $this->periodModel->select('academic_periods.*, ay.name as academic_year_name')
            ->join('academic_years ay', 'ay.id = academic_periods.academic_year_id')
            ->where('academic_periods.id', $event['academic_period_id'])
            ->first();

        $data = [
            'title'        => 'Cetak Kartu Meja',
            'event'        => $event,
            'participants' => $participants,
            'school'       => $schoolData,
            'activePeriod' => $activePeriod,
        ];

        return view('exam_event/print/desk_labels', $data);
    }

    // c. Daftar Hadir (Kertas A4 per Ruang)
    public function printAttendance($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event || !$event['is_locked']) return redirect()->to('/exam-event')->with('error', 'Event belum dikunci!');

        $schoolSettings = $this->schoolSettingModel->first();
        $event['chairman_name'] = !empty($event['chairman_name']) ? $event['chairman_name'] : ($schoolSettings['principal_name'] ?? '');
        $event['chairman_nip']  = !empty($event['chairman_nip']) ? $event['chairman_nip'] : ($schoolSettings['principal_nip'] ?? '');

        $rooms = $this->roomModel->getRoomsByEvent($eventId);
        
        // Get participants sorted by classroom and student name
        $participants = $this->participantModel->select('exam_participants.*, students.nis, students.nisn, students.full_name, students.gender, r.name as room_name, c.name as classroom_name')
            ->join('students', 'students.id = exam_participants.student_id')
            ->join('exam_rooms r', 'r.id = exam_participants.exam_room_id')
            ->join('student_academic_histories sah', 'sah.student_id = exam_participants.student_id AND sah.academic_period_id = ' . $event['academic_period_id'])
            ->join('classrooms c', 'c.id = sah.classroom_id')
            ->where('exam_participants.exam_event_id', $eventId)
            ->where('students.deleted_at', null)
            ->orderBy('r.name', 'ASC')
            ->orderBy('c.name', 'ASC')
            ->orderBy('students.full_name', 'ASC')
            ->findAll();

        // Group participants by room ID
        $participantsByRoom = [];
        foreach ($participants as $p) {
            $participantsByRoom[$p['exam_room_id']][] = $p;
        }

        $data = [
            'title'              => 'Cetak Daftar Hadir',
            'event'              => $event,
            'rooms'              => $rooms,
            'participantsByRoom' => $participantsByRoom,
            'schoolSettings'     => $this->schoolSettingModel->first(),
            'letterSettings'     => $this->getLetterSettings(),
            'printDate'          => date('d F Y'),
        ];

        return view('exam_event/print/attendance', $data);
    }

    // d. Denah Tempat Duduk (Kertas A4 Landscape per Ruang)
    public function printSeatingChart($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event || !$event['is_locked']) return redirect()->to('/exam-event')->with('error', 'Event belum dikunci!');

        $rooms = $this->roomModel->getRoomsByEvent($eventId);
        $participants = $this->participantModel->getParticipantsByEvent($eventId);
        $schoolData = $this->schoolSettingModel->getSettings();

        // Group participants by room and seat number
        $seatsByRoom = [];
        foreach ($participants as $p) {
            $history = $this->historyModel->where('student_id', $p['student_id'])->where('academic_period_id', $event['academic_period_id'])->first();
            $classroom = $this->classroomModel->find($history['classroom_id'] ?? 0);
            $p['classroom_name'] = $classroom['name'] ?? '-';
            $seatsByRoom[$p['exam_room_id']][$p['seat_number']] = $p;
        }

        $data = [
            'title'       => 'Cetak Denah Tempat Duduk',
            'event'       => $event,
            'rooms'       => $rooms,
            'seatsByRoom' => $seatsByRoom,
            'school'      => $schoolData,
            'printDate'   => date('d F Y'),
        ];

        return view('exam_event/print/seating_chart', $data);
    }

    // e. Berita Acara Pelaksanaan (Kertas A4 Portrait - 1 Lembar Kosong)
    public function printOfficialReport($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event) return redirect()->to('/exam-event')->with('error', 'Event tidak ditemukan!');

        $schoolSettings = $this->schoolSettingModel->first();
        $event['chairman_name'] = !empty($event['chairman_name']) ? $event['chairman_name'] : ($schoolSettings['principal_name'] ?? '');
        $event['chairman_nip']  = !empty($event['chairman_nip']) ? $event['chairman_nip'] : ($schoolSettings['principal_nip'] ?? '');

        $activePeriod = $this->periodModel->select('academic_periods.*, ay.name as academic_year_name')
            ->join('academic_years ay', 'ay.id = academic_periods.academic_year_id')
            ->where('academic_periods.id', $event['academic_period_id'])
            ->first();

        $data = [
            'title'          => 'Cetak Berita Acara',
            'event'          => $event,
            'schoolSettings' => $this->schoolSettingModel->first(),
            'letterSettings' => $this->getLetterSettings(),
            'activePeriod'   => $activePeriod,
            'printDate'      => date('d F Y'),
        ];

        return view('exam_event/print/official_report', $data);
    }

    // f. Rekap Peserta per Ruang (Kertas A4 Portrait)
    public function printRoomSummary($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event || !$event['is_locked']) return redirect()->to('/exam-event')->with('error', 'Event belum dikunci!');

        $schoolSettings = $this->schoolSettingModel->first();
        $event['chairman_name'] = !empty($event['chairman_name']) ? $event['chairman_name'] : ($schoolSettings['principal_name'] ?? '');
        $event['chairman_nip']  = !empty($event['chairman_nip']) ? $event['chairman_nip'] : ($schoolSettings['principal_nip'] ?? '');

        $rooms = $this->roomModel->getRoomsByEvent($eventId);
        $participants = $this->participantModel->getParticipantsByEvent($eventId);
        
        $rules = $this->ruleModel->getRulesByEvent($eventId);
        $rulesByRoom = [];
        foreach ($rules as $r) {
            $rulesByRoom[$r['exam_room_id']][] = $r;
        }

        $roomDetails = [];
        $classNames = [];

        foreach ($participants as $p) {
            $history = $this->historyModel->where('student_id', $p['student_id'])->where('academic_period_id', $event['academic_period_id'])->first();
            $classId = $history['classroom_id'] ?? 0;
            if ($classId) {
                if (!isset($classNames[$classId])) {
                    $classroom = $this->classroomModel->find($classId);
                    $classNames[$classId] = $classroom['name'] ?? '-';
                }
                $roomId = $p['exam_room_id'];
                if (!isset($roomDetails[$roomId][$classId])) {
                    $roomDetails[$roomId][$classId] = 0;
                }
                $roomDetails[$roomId][$classId]++;
            }
        }

        $data = [
            'title'          => 'Rekapitulasi Peserta per Ruang',
            'event'          => $event,
            'rooms'          => $rooms,
            'roomDetails'    => $roomDetails,
            'classNames'     => $classNames,
            'rulesByRoom'    => $rulesByRoom,
            'schoolSettings' => $this->schoolSettingModel->first(),
            'letterSettings' => $this->getLetterSettings(),
            'printDate'      => date('d F Y'),
        ];

        return view('exam_event/print/room_summary', $data);
    }

    // g. Daftar Peserta Ruang (Cetak Tempelan Pintu)
    public function printRoomParticipants($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event || !$event['is_locked']) return redirect()->to('/exam-event')->with('error', 'Event belum dikunci!');

        $schoolSettings = $this->schoolSettingModel->first();
        $event['chairman_name'] = !empty($event['chairman_name']) ? $event['chairman_name'] : ($schoolSettings['principal_name'] ?? '');
        $event['chairman_nip']  = !empty($event['chairman_nip']) ? $event['chairman_nip'] : ($schoolSettings['principal_nip'] ?? '');

        $rooms = $this->roomModel->getRoomsByEvent($eventId);

        $participants = $this->participantModel->select('exam_participants.*, students.nis, students.nisn, students.full_name, r.name as room_name, c.name as classroom_name')
            ->join('students', 'students.id = exam_participants.student_id')
            ->join('exam_rooms r', 'r.id = exam_participants.exam_room_id')
            ->join('student_academic_histories sah', 'sah.student_id = exam_participants.student_id AND sah.academic_period_id = ' . $event['academic_period_id'])
            ->join('classrooms c', 'c.id = sah.classroom_id')
            ->where('exam_participants.exam_event_id', $eventId)
            ->where('students.deleted_at', null)
            ->orderBy('r.name', 'ASC')
            ->orderBy('c.name', 'ASC')
            ->orderBy('students.full_name', 'ASC')
            ->findAll();

        $participantsByRoom = [];
        foreach ($participants as $p) {
            $participantsByRoom[$p['exam_room_id']][] = $p;
        }

        $cardSetting = $this->cardSettingModel->getSettings();

        $data = [
            'title'              => 'Daftar Peserta Ruang',
            'event'              => $event,
            'rooms'              => $rooms,
            'participantsByRoom' => $participantsByRoom,
            'schoolSettings'     => $this->schoolSettingModel->first(),
            'letterSettings'     => $this->getLetterSettings(),
            'cardSetting'        => $cardSetting,
        ];

        return view('exam_event/print/room_participants', $data);
    }

    // h. Label Pintu Ruang Dan Rekap Peserta Ruang
    public function printDoorLabel($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event || !$event['is_locked']) return redirect()->to('/exam-event')->with('error', 'Event belum dikunci!');

        $schoolSettings = $this->schoolSettingModel->first();
        $event['chairman_name'] = !empty($event['chairman_name']) ? $event['chairman_name'] : ($schoolSettings['principal_name'] ?? '');
        $event['chairman_nip']  = !empty($event['chairman_nip']) ? $event['chairman_nip'] : ($schoolSettings['principal_nip'] ?? '');

        $rooms = $this->roomModel->getRoomsByEvent($eventId);
        $rules = $this->ruleModel->getRulesByEvent($eventId);
        $rulesByRoom = [];
        foreach ($rules as $r) {
            $rulesByRoom[$r['exam_room_id']][] = $r;
        }

        $participants = $this->participantModel->getParticipantsByEvent($eventId);
        $roomCounts = [];
        foreach ($participants as $p) {
            $roomCounts[$p['exam_room_id']] = ($roomCounts[$p['exam_room_id']] ?? 0) + 1;
        }

        $activePeriod = $this->periodModel->select('academic_periods.*, ay.name as academic_year_name')
            ->join('academic_years ay', 'ay.id = academic_periods.academic_year_id')
            ->where('academic_periods.id', $event['academic_period_id'])
            ->first();

        $data = [
            'title'          => 'Label Pintu Ruang',
            'event'          => $event,
            'rooms'          => $rooms,
            'rulesByRoom'    => $rulesByRoom,
            'roomCounts'     => $roomCounts,
            'activePeriod'   => $activePeriod,
            'schoolSettings' => $this->schoolSettingModel->first(),
            'letterSettings' => $this->getLetterSettings(),
        ];

        return view('exam_event/print/door_label', $data);
    }

    // i. Tanda Bel Asesmen
    public function printBellSchedule($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event) return redirect()->to('/exam-event')->with('error', 'Event tidak ditemukan!');

        $schoolSettings = $this->schoolSettingModel->first();
        $event['chairman_name'] = !empty($event['chairman_name']) ? $event['chairman_name'] : ($schoolSettings['principal_name'] ?? '');
        $event['chairman_nip']  = !empty($event['chairman_nip']) ? $event['chairman_nip'] : ($schoolSettings['principal_nip'] ?? '');

        $data = [
            'title'          => 'Tanda Bel Asesmen',
            'event'          => $event,
            'schoolSettings' => $this->schoolSettingModel->first(),
            'letterSettings' => $this->getLetterSettings(),
        ];

        return view('exam_event/print/bell_schedule', $data);
    }

    // j. Tata Tertib Pengawas Ruang
    public function printProctorRules($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event) return redirect()->to('/exam-event')->with('error', 'Event tidak ditemukan!');

        $schoolSettings = $this->schoolSettingModel->first();
        $event['chairman_name'] = !empty($event['chairman_name']) ? $event['chairman_name'] : ($schoolSettings['principal_name'] ?? '');
        $event['chairman_nip']  = !empty($event['chairman_nip']) ? $event['chairman_nip'] : ($schoolSettings['principal_nip'] ?? '');
        $event['proctor_rules'] = !empty($event['proctor_rules']) ? $event['proctor_rules'] : ($schoolSettings['proctor_rules'] ?? '');

        $data = [
            'title'          => 'Tata Tertib Pengawas Ruang',
            'event'          => $event,
            'schoolSettings' => $this->schoolSettingModel->first(),
            'letterSettings' => $this->getLetterSettings(),
        ];

        return view('exam_event/print/proctor_rules', $data);
    }

    // k. Tata Tertib Peserta Asesmen
    public function printStudentRules($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event) return redirect()->to('/exam-event')->with('error', 'Event tidak ditemukan!');

        $schoolSettings = $this->schoolSettingModel->first();
        $event['chairman_name'] = !empty($event['chairman_name']) ? $event['chairman_name'] : ($schoolSettings['principal_name'] ?? '');
        $event['chairman_nip']  = !empty($event['chairman_nip']) ? $event['chairman_nip'] : ($schoolSettings['principal_nip'] ?? '');
        $event['student_rules'] = !empty($event['student_rules']) ? $event['student_rules'] : ($schoolSettings['student_rules'] ?? '');

        $data = [
            'title'          => 'Tata Tertib Peserta Asesmen',
            'event'          => $event,
            'schoolSettings' => $this->schoolSettingModel->first(),
            'letterSettings' => $this->getLetterSettings(),
        ];

        return view('exam_event/print/student_rules', $data);
    }

    // l. Cetak Blanko Absensi Tidak Hadir (Daftar Peserta Tidak Hadir)
    public function printAbsentStudents($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;
        $event = $this->eventModel->find($eventId);
        if (!$event) return redirect()->to('/exam-event')->with('error', 'Event tidak ditemukan!');

        $schoolSettings = $this->schoolSettingModel->first();
        $event['chairman_name'] = !empty($event['chairman_name']) ? $event['chairman_name'] : ($schoolSettings['principal_name'] ?? '');
        $event['chairman_nip']  = !empty($event['chairman_nip']) ? $event['chairman_nip'] : ($schoolSettings['principal_nip'] ?? '');

        $data = [
            'title'          => 'Cetak Blanko Absensi Tidak Hadir',
            'event'          => $event,
            'schoolSettings' => $schoolSettings,
            'letterSettings' => $this->getLetterSettings(),
            'printDate'      => date('d F Y'),
        ];
        return view('exam_event/print/absent_students', $data);
    }

    // m. Cetak Kartu Kosong (Blank Cards)
    public function printBlankCards($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;
        $event = $this->eventModel->find($eventId);
        if (!$event) return redirect()->to('/exam-event')->with('error', 'Event tidak ditemukan!');

        $schoolData = $this->schoolSettingModel->getSettings();
        $cardSetting = $this->cardSettingModel->getSettings();

        $schedules = $this->examScheduleModel->getSchedulesByEvent($eventId);
        foreach ($schedules as &$sch) {
            $sch['subjects'] = json_decode($sch['subject_list'], true) ?: [];
        }

        $activePeriod = $this->periodModel->select('academic_periods.*, ay.name as academic_year_name')
            ->join('academic_years ay', 'ay.id = academic_periods.academic_year_id')
            ->where('academic_periods.id', $event['academic_period_id'])
            ->first();

        $data = [
            'title'        => 'Cetak Kartu Kosong',
            'event'        => $event,
            'school'       => $schoolData,
            'cardSetting'  => $cardSetting,
            'activePeriod' => $activePeriod,
            'schedules'    => $schedules,
        ];
        return view('exam_event/print/blank_cards', $data);
    }

    // ==========================================
    // 5. MANAJEMEN PENGAWAS (PROCTORS)
    // ==========================================
    public function proctor($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event) return redirect()->to('/exam-event')->with('error', 'Event tidak ditemukan!');

        // Get all active teachers
        $teachers = $this->teacherModel->getAllWithUser();

        // Get configured event teachers roles
        $eventRoles = $this->eventTeacherModel->getEventRoles($eventId);
        $eventTeachersData = $this->eventTeacherModel->getEventTeachersData($eventId);

        // Get rooms
        $rooms = $this->roomModel->getRoomsByEvent($eventId);

        // Get schedules
        $schedules = $this->examScheduleModel->getSchedulesByEvent($eventId);
        foreach ($schedules as &$sch) {
            $sch['subjects'] = json_decode($sch['subject_list'], true) ?: [];
        }

        // Get current proctor assignments
        $assignments = $this->scheduleProctorModel->getEventAssignmentsGrouped($eventId);

        // Lookup maps for teachers (id => teacher info)
        $teacherLookup = [];
        foreach ($teachers as $t) {
            $teacherLookup[$t['id']] = $t;
        }

        $data = [
            'title'             => 'Manajemen Pengawas - ' . $event['name'],
            'event'             => $event,
            'teachers'          => $teachers,
            'eventRoles'        => $eventRoles,
            'eventTeachersData' => $eventTeachersData,
            'rooms'             => $rooms,
            'schedules'         => $schedules,
            'assignments'       => $assignments,
            'teacherLookup'     => $teacherLookup,
        ];

        return view('exam_event/proctor', $data);
    }

    public function saveProctorSettings($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event) return redirect()->to('/exam-event')->with('error', 'Event tidak ditemukan!');
        if ($event['is_locked']) return redirect()->back()->with('error', 'Event dikunci!');

        // Save proctors per room setting
        $proctorsPerRoom = (int)$this->request->getPost('proctors_per_room');
        if (!in_array($proctorsPerRoom, [1, 2])) {
            $proctorsPerRoom = 1;
        }
        $this->eventModel->update($eventId, ['proctors_per_room' => $proctorsPerRoom]);

        // Save teacher roles
        $rolesInput = $this->request->getPost('roles') ?: [];
        $maxDaysInput = $this->request->getPost('max_days') ?: [];
        
        $this->eventTeacherModel->where('exam_event_id', $eventId)->delete();
        
        $insertData = [];
        foreach ($rolesInput as $teacherId => $role) {
            if (in_array($role, ['panitia', 'pengawas'])) {
                $maxDays = isset($maxDaysInput[$teacherId]) ? (int)$maxDaysInput[$teacherId] : 0;
                if ($maxDays < 0) {
                    $maxDays = 0;
                }
                $insertData[] = [
                    'exam_event_id' => $eventId,
                    'teacher_id'    => $teacherId,
                    'role'          => $role,
                    'max_days'      => $maxDays,
                ];
            }
        }
        if (!empty($insertData)) {
            $this->eventTeacherModel->insertBatch($insertData);
        }

        return redirect()->to("/exam-event/{$eventId}/proctor")->with('success', 'Pengaturan peran guru berhasil disimpan!');
    }

    public function generateProctors($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event) return redirect()->to('/exam-event')->with('error', 'Event tidak ditemukan!');
        if ($event['is_locked']) return redirect()->back()->with('error', 'Event dikunci!');

        $proctorsPerRoom = $event['proctors_per_room'];

        // Get rooms
        $rooms = $this->roomModel->getRoomsByEvent($eventId);
        if (empty($rooms)) {
            return redirect()->back()->with('error', 'Belum ada ruang ujian yang diatur! Silakan atur ruang terlebih dahulu.');
        }

        // Get schedules
        $schedules = $this->examScheduleModel->getSchedulesByEvent($eventId);
        if (empty($schedules)) {
            return redirect()->back()->with('error', 'Belum ada jadwal asesmen! Silakan atur jadwal terlebih dahulu.');
        }

        // Get teachers marked as 'pengawas' for this event
        $assignedProctors = $this->eventTeacherModel
            ->select('teachers.id, teachers.short_code, teachers.name')
            ->join('teachers', 'teachers.id = exam_event_teachers.teacher_id')
            ->where('exam_event_teachers.exam_event_id', $eventId)
            ->where('exam_event_teachers.role', 'pengawas')
            ->where('teachers.deleted_at', null)
            ->findAll();

        if (empty($assignedProctors)) {
            return redirect()->back()->with('error', 'Belum ada guru yang dipilih sebagai Pengawas! Silakan atur peran guru terlebih dahulu.');
        }

        // Initialize total assignment count for balancing
        $proctorDayCounts = [];
        $proctorSessionCounts = [];
        foreach ($assignedProctors as $p) {
            $proctorDayCounts[$p['id']] = 0;
            $proctorSessionCounts[$p['id']] = 0;
        }

        // Get event teachers data for max_days limit lookup
        $eventTeachersData = $this->eventTeacherModel->getEventTeachersData($eventId);
        $maxDaysMap = [];
        foreach ($eventTeachersData as $tId => $tData) {
            $maxDaysMap[$tId] = $tData['max_days'] ?? 0;
        }

    // Delete existing assignments for this event
        $this->scheduleProctorModel->where('exam_event_id', $eventId)->delete();

        $assignmentsToInsert = [];

        // Randomization & assignment logic (assigning proctors per session/subject with rotation)
        foreach ($schedules as $sch) {
            $subjects = json_decode($sch['subject_list'], true) ?: [];
            
            // Extract valid subject names
            $validSubjects = [];
            foreach ($subjects as $sub) {
                $subjectName = is_array($sub) ? ($sub['name'] ?? '') : $sub;
                if ($subjectName !== '') {
                    $validSubjects[] = $subjectName;
                }
            }
            if (empty($validSubjects)) continue;

            $sCount = count($validSubjects);
            $rCount = count($rooms);

            // 1. Pilih Guru untuk Pengawas 1 hari ini (sejumlah Ruang)
            $chosenProctors1 = [];
            for ($r = 0; $r < $rCount; $r++) {
                $candidates1 = [];
                foreach ($assignedProctors as $ap) {
                    // Jangan double booking dalam grup pengawas 1 hari ini
                    $alreadyChosen = false;
                    foreach ($chosenProctors1 as $cp) {
                        if ($cp && $cp['id'] === $ap['id']) {
                            $alreadyChosen = true;
                            break;
                        }
                    }
                    if (!$alreadyChosen) {
                        $maxDays = $maxDaysMap[$ap['id']] ?? 0;
                        $currentDays = $proctorDayCounts[$ap['id']];
                        // Karena dia ditugaskan hari ini, maka day count bertambah 1
                        if ($maxDays > 0 && ($currentDays + 1) > $maxDays) {
                            continue;
                        }
                        $candidates1[] = $ap;
                    }
                }

                $chosen1 = null;
                if (!empty($candidates1)) {
                    shuffle($candidates1);
                    usort($candidates1, function($a, $b) use ($proctorDayCounts, $proctorSessionCounts) {
                        $dayA = $proctorDayCounts[$a['id']];
                        $dayB = $proctorDayCounts[$b['id']];
                        if ($dayA !== $dayB) {
                            return $dayA - $dayB;
                        }
                        $sessA = $proctorSessionCounts[$a['id']];
                        $sessB = $proctorSessionCounts[$b['id']];
                        return $sessA - $sessB;
                    });
                    $chosen1 = $candidates1[0];
                    $proctorDayCounts[$chosen1['id']]++;
                    $proctorSessionCounts[$chosen1['id']] += $sCount;
                }
                $chosenProctors1[] = $chosen1;
            }

            // 2. Pilih Guru untuk Pengawas 2 hari ini (jika setting 2 pengawas aktif)
            $chosenProctors2 = [];
            if ($proctorsPerRoom == 2) {
                for ($r = 0; $r < $rCount; $r++) {
                    $candidates2 = [];
                    foreach ($assignedProctors as $ap) {
                        // Jangan double booking dengan pengawas 1 atau pengawas 2 yang sudah terpilih hari ini
                        $alreadyChosen = false;
                        foreach ($chosenProctors1 as $cp) {
                            if ($cp && $cp['id'] === $ap['id']) {
                                $alreadyChosen = true;
                                break;
                            }
                        }
                        foreach ($chosenProctors2 as $cp) {
                            if ($cp && $cp['id'] === $ap['id']) {
                                $alreadyChosen = true;
                                break;
                            }
                        }
                        if (!$alreadyChosen) {
                            $maxDays = $maxDaysMap[$ap['id']] ?? 0;
                            $currentDays = $proctorDayCounts[$ap['id']];
                            if ($maxDays > 0 && ($currentDays + 1) > $maxDays) {
                                continue;
                            }
                            $candidates2[] = $ap;
                        }
                    }

                    $chosen2 = null;
                    if (!empty($candidates2)) {
                        shuffle($candidates2);
                        usort($candidates2, function($a, $b) use ($proctorDayCounts, $proctorSessionCounts) {
                            $dayA = $proctorDayCounts[$a['id']];
                            $dayB = $proctorDayCounts[$b['id']];
                            if ($dayA !== $dayB) {
                                return $dayA - $dayB;
                            }
                            $sessA = $proctorSessionCounts[$a['id']];
                            $sessB = $proctorSessionCounts[$b['id']];
                            return $sessA - $sessB;
                        });
                        $chosen2 = $candidates2[0];
                        $proctorDayCounts[$chosen2['id']]++;
                        $proctorSessionCounts[$chosen2['id']] += $sCount;
                    }
                    $chosenProctors2[] = $chosen2;
                }
            }

            // 3. Distribusikan ke dalam tiap sesi dengan rotasi geser ruangan (Circular Shift)
            for ($sIdx = 0; $sIdx < $sCount; $sIdx++) {
                $subjectName = $validSubjects[$sIdx];

                for ($rIdx = 0; $rIdx < $rCount; $rIdx++) {
                    $room = $rooms[$rIdx];

                    // Tentukan indeks guru dengan rotasi sIdx
                    $p1Idx = ($rIdx + $sIdx) % $rCount;
                    $teacher1 = isset($chosenProctors1[$p1Idx]) && $chosenProctors1[$p1Idx] ? $chosenProctors1[$p1Idx]['id'] : null;

                    $teacher2 = null;
                    if ($proctorsPerRoom == 2) {
                        $p2Idx = ($rIdx + $sIdx) % $rCount;
                        $teacher2 = isset($chosenProctors2[$p2Idx]) && $chosenProctors2[$p2Idx] ? $chosenProctors2[$p2Idx]['id'] : null;
                    }

                    $assignmentsToInsert[] = [
                        'exam_event_id'    => $eventId,
                        'exam_schedule_id' => $sch['id'],
                        'subject_name'     => $subjectName,
                        'exam_room_id'     => $room['id'],
                        'teacher_id_1'     => $teacher1,
                        'teacher_id_2'     => $teacher2,
                    ];
                }
            }
        }

        if (!empty($assignmentsToInsert)) {
            $this->scheduleProctorModel->insertBatch($assignmentsToInsert);
        }

        return redirect()->to("/exam-event/{$eventId}/proctor/matrix")->with('success', 'Jadwal pengawas berhasil diacak secara otomatis!');
    }

    public function proctorMatrix($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event) return redirect()->to('/exam-event')->with('error', 'Event tidak ditemukan!');

        // Get all active teachers
        $teachers = $this->teacherModel->getAllWithUser();

        // Get configured event teachers roles & data
        $eventRoles = $this->eventTeacherModel->getEventRoles($eventId);
        $eventTeachersData = $this->eventTeacherModel->getEventTeachersData($eventId);

        // Get rooms
        $rooms = $this->roomModel->getRoomsByEvent($eventId);

        // Get schedules
        $schedules = $this->examScheduleModel->getSchedulesByEvent($eventId);
        foreach ($schedules as &$sch) {
            $sch['subjects'] = json_decode($sch['subject_list'], true) ?: [];
        }

        // Get current proctor assignments
        $assignments = $this->scheduleProctorModel->getEventAssignmentsGrouped($eventId);

        // Lookup maps for teachers (id => teacher info)
        $teacherLookup = [];
        foreach ($teachers as $t) {
            $teacherLookup[$t['id']] = $t;
        }

        // Calculate workload
        $workload = [];
        foreach ($teachers as $t) {
            $role = $eventRoles[$t['id']] ?? 'tidak_bertugas';
            if ($role === 'tidak_bertugas') continue; // only show active proctors/committee
            $workload[$t['id']] = [
                'teacher' => $t,
                'role' => $role,
                'max_days' => $eventTeachersData[$t['id']]['max_days'] ?? 0,
                'days_count' => 0,
                'sessions_count' => 0,
                'days' => []
            ];
        }

        // Get all raw assignments to build workload
        $rawAssignments = $this->scheduleProctorModel->where('exam_event_id', $eventId)->findAll();
        foreach ($rawAssignments as $assign) {
            $schId = $assign['exam_schedule_id'];
            if ($assign['teacher_id_1'] && isset($workload[$assign['teacher_id_1']])) {
                $workload[$assign['teacher_id_1']]['sessions_count']++;
                $workload[$assign['teacher_id_1']]['days'][$schId] = true;
            }
            if ($assign['teacher_id_2'] && isset($workload[$assign['teacher_id_2']])) {
                $workload[$assign['teacher_id_2']]['sessions_count']++;
                $workload[$assign['teacher_id_2']]['days'][$schId] = true;
            }
        }

        foreach ($workload as $tid => &$wl) {
            $wl['days_count'] = count($wl['days']);
        }
        unset($wl);

        // Sort workload: roles first ('pengawas' first, then 'panitia'), then days_count desc, then sessions_count desc
        uasort($workload, function($a, $b) {
            if ($a['role'] !== $b['role']) {
                return $a['role'] === 'pengawas' ? -1 : 1;
            }
            if ($a['days_count'] !== $b['days_count']) {
                return $b['days_count'] - $a['days_count'];
            }
            return $b['sessions_count'] - $a['sessions_count'];
        });

        $data = [
            'title'          => 'Matrix & Beban Kerja Pengawas - ' . $event['name'],
            'event'          => $event,
            'teachers'       => $teachers,
            'eventRoles'     => $eventRoles,
            'rooms'          => $rooms,
            'schedules'      => $schedules,
            'assignments'    => $assignments,
            'teacherLookup'  => $teacherLookup,
            'workload'       => $workload,
        ];

        return view('exam_event/proctor_matrix', $data);
    }

    public function clearProctors($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event) return redirect()->to('/exam-event')->with('error', 'Event tidak ditemukan!');
        if ($event['is_locked']) return redirect()->back()->with('error', 'Event dikunci!');

        $this->scheduleProctorModel->where('exam_event_id', $eventId)->delete();

        return redirect()->to("/exam-event/{$eventId}/proctor")->with('success', 'Jadwal pengawas berhasil dibersihkan!');
    }

    public function printProctorSchedule($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event) return redirect()->to('/exam-event')->with('error', 'Event tidak ditemukan!');

        // Fetch school settings & active academic period
        $schoolSettings = $this->schoolSettingModel->first();
        $event['chairman_name'] = !empty($event['chairman_name']) ? $event['chairman_name'] : ($schoolSettings['principal_name'] ?? '');
        $event['chairman_nip']  = !empty($event['chairman_nip']) ? $event['chairman_nip'] : ($schoolSettings['principal_nip'] ?? '');

        $cardSetting = $this->cardSettingModel->getSettings();

        $activePeriod = $this->periodModel->select('academic_periods.*, ay.name as academic_year_name')
            ->join('academic_years ay', 'ay.id = academic_periods.academic_year_id')
            ->where('academic_periods.id', $event['academic_period_id'])
            ->first();

        // Get rooms
        $rooms = $this->roomModel->getRoomsByEvent($eventId);

        // Get schedules
        $schedules = $this->examScheduleModel->getSchedulesByEvent($eventId);
        foreach ($schedules as &$sch) {
            $sch['subjects'] = json_decode($sch['subject_list'], true) ?: [];
        }

        // Get current proctor assignments
        $assignments = $this->scheduleProctorModel->getEventAssignmentsGrouped($eventId);

        // Get all active teachers for legend lookup
        $teachers = $this->teacherModel->getAllWithUser();
        $teacherLookup = [];
        foreach ($teachers as $t) {
            $teacherLookup[$t['id']] = $t;
        }

        // Compile only assigned teachers to display in the legend
        $assignedTeacherIds = [];
        foreach ($assignments as $schId => $subMap) {
            foreach ($subMap as $subName => $roomMap) {
                foreach ($roomMap as $roomId => $proctors) {
                    if (!empty($proctors['teacher_id_1'])) {
                        $assignedTeacherIds[] = $proctors['teacher_id_1'];
                    }
                    if (!empty($proctors['teacher_id_2'])) {
                        $assignedTeacherIds[] = $proctors['teacher_id_2'];
                    }
                }
            }
        }
        $assignedTeacherIds = array_unique($assignedTeacherIds);
        
        $assignedTeachers = [];
        foreach ($assignedTeacherIds as $tid) {
            if (isset($teacherLookup[$tid])) {
                $assignedTeachers[] = $teacherLookup[$tid];
            }
        }
        
        // Sort legend by short_code case-insensitively
        usort($assignedTeachers, function($a, $b) {
            $codeA = strtoupper($a['short_code'] ?? '');
            $codeB = strtoupper($b['short_code'] ?? '');
            if ($codeA === '') return 1;
            if ($codeB === '') return -1;
            return strnatcmp($codeA, $codeB);
        });

        $data = [
            'title'            => 'Cetak Jadwal Pengawas Ujian - ' . $event['name'],
            'event'            => $event,
            'schoolSettings'   => $schoolSettings,
            'letterSettings'   => $this->getLetterSettings(),
            'cardSetting'      => $cardSetting,
            'activePeriod'     => $activePeriod,
            'rooms'            => $rooms,
            'schedules'        => $schedules,
            'assignments'      => $assignments,
            'teacherLookup'    => $teacherLookup,
            'assignedTeachers' => $assignedTeachers,
        ];

        return view('exam_event/print/proctor_schedule', $data);
    }

    public function printProctorAttendance($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event) return redirect()->to('/exam-event')->with('error', 'Event tidak ditemukan!');

        // Fetch school settings & active academic period
        $schoolSettings = $this->schoolSettingModel->first();
        $event['chairman_name'] = !empty($event['chairman_name']) ? $event['chairman_name'] : ($schoolSettings['principal_name'] ?? '');
        $event['chairman_nip']  = !empty($event['chairman_nip']) ? $event['chairman_nip'] : ($schoolSettings['principal_nip'] ?? '');

        $cardSetting = $this->cardSettingModel->getSettings();

        $activePeriod = $this->periodModel->select('academic_periods.*, ay.name as academic_year_name')
            ->join('academic_years ay', 'ay.id = academic_periods.academic_year_id')
            ->where('academic_periods.id', $event['academic_period_id'])
            ->first();

        // Get rooms
        $rooms = $this->roomModel->getRoomsByEvent($eventId);

        // Get schedules
        $schedules = $this->examScheduleModel->getSchedulesByEvent($eventId);
        foreach ($schedules as &$sch) {
            $sch['subjects'] = json_decode($sch['subject_list'], true) ?: [];
        }

        // Get current proctor assignments
        $assignments = $this->scheduleProctorModel->getEventAssignmentsGrouped($eventId);

        // Get all active teachers
        $teachers = $this->teacherModel->getAllWithUser();
        $teacherLookup = [];
        foreach ($teachers as $t) {
            $teacherLookup[$t['id']] = $t;
        }

        // Compile only assigned teachers and rooms into lists per session/day
        $sessions = [];
        foreach ($schedules as $sch) {
            foreach ($sch['subjects'] as $sub) {
                $subjectName = is_array($sub) ? ($sub['name'] ?? '') : $sub;
                $subjectTime = is_array($sub) ? ($sub['time'] ?? '') : '';
                if (empty($subjectName)) continue;

                // Strip leading numbering prefix like "1. ", "2.", etc.
                $cleanSubjectName = preg_replace('/^\d+[\.\s\-]+\s*/', '', $subjectName);
                
                $sessionProctors = [];
                foreach ($rooms as $room) {
                    $assign = $assignments[$sch['id']][$subjectName][$room['id']] ?? null;
                    $p1 = null;
                    $p2 = null;
                    
                    if ($assign) {
                        if (!empty($assign['teacher_id_1']) && isset($teacherLookup[$assign['teacher_id_1']])) {
                            $p1 = $teacherLookup[$assign['teacher_id_1']];
                        }
                        if (!empty($assign['teacher_id_2']) && isset($teacherLookup[$assign['teacher_id_2']])) {
                            $p2 = $teacherLookup[$assign['teacher_id_2']];
                        }
                    }
                    
                    $sessionProctors[] = [
                        'teacher_name' => $p1 ? $p1['name'] : '-',
                        'room_name'    => $room['name'],
                    ];
                    
                    if ($event['proctors_per_room'] == 2) {
                        $sessionProctors[] = [
                            'teacher_name' => $p2 ? $p2['name'] : '-',
                            'room_name'    => $room['name'],
                        ];
                    }
                }
                
                // Chunk proctors into pages so first page accommodates Kop, and subsequent pages don't repeat Kop.
                $pages = [];
                $temp = $sessionProctors;
                $isFirst = true;
                while (!empty($temp)) {
                    $chunkSize = $isFirst ? 24 : 30;
                    $pages[] = array_splice($temp, 0, $chunkSize);
                    $isFirst = false;
                }
                if (empty($pages)) {
                    $pages[] = [];
                }
                
                $sessions[] = [
                    'schedule_id'  => $sch['id'],
                    'exam_day'     => $sch['exam_day'],
                    'exam_date'    => $sch['exam_date'],
                    'subject_name' => $cleanSubjectName,
                    'subject_time' => $subjectTime,
                    'pages'        => $pages,
                ];
            }
        }

        $data = [
            'title'            => 'Cetak Daftar Hadir Pengawas - ' . $event['name'],
            'event'            => $event,
            'schoolSettings'   => $schoolSettings,
            'letterSettings'   => $this->getLetterSettings(),
            'cardSetting'      => $cardSetting,
            'activePeriod'     => $activePeriod,
            'rooms'            => $rooms,
            'sessions'         => $sessions,
        ];

        return view('exam_event/print/proctor_attendance', $data);
    }

    public function printExamSchedule($eventId)
    {
        if ($redirect = $this->checkLogin()) return $redirect;

        $event = $this->eventModel->find($eventId);
        if (!$event) return redirect()->to('/exam-event')->with('error', 'Event tidak ditemukan!');

        // Fetch school settings & active academic period
        $schoolSettings = $this->schoolSettingModel->first();
        $event['chairman_name'] = !empty($event['chairman_name']) ? $event['chairman_name'] : ($schoolSettings['principal_name'] ?? '');
        $event['chairman_nip']  = !empty($event['chairman_nip']) ? $event['chairman_nip'] : ($schoolSettings['principal_nip'] ?? '');

        $cardSetting = $this->cardSettingModel->getSettings();

        $activePeriod = $this->periodModel->select('academic_periods.*, ay.name as academic_year_name')
            ->join('academic_years ay', 'ay.id = academic_periods.academic_year_id')
            ->where('academic_periods.id', $event['academic_period_id'])
            ->first();

        // Get schedules
        $schedules = $this->examScheduleModel->getSchedulesByEvent($eventId);
        foreach ($schedules as &$sch) {
            $sch['subjects'] = json_decode($sch['subject_list'], true) ?: [];
        }

        $data = [
            'title'          => 'Cetak Jadwal Asesmen - ' . $event['name'],
            'event'          => $event,
            'schoolSettings' => $schoolSettings,
            'letterSettings' => $this->getLetterSettings(),
            'cardSetting'    => $cardSetting,
            'activePeriod'   => $activePeriod,
            'schedules'      => $schedules,
        ];

        return view('exam_event/print/exam_schedule', $data);
    }

    /**
     * Get letter settings from settings table
     */
    private function getLetterSettings()
    {
        $settings = $this->schoolSettingModel->first();
        
        return [
            'kemenag_office'     => $settings['letter_kemenag_office'] ?? 'Kantor Kementerian Agama Kab. Wonogiri',
            'letter_school_name' => $settings['letter_school_name']
                ?? strtoupper($settings['school_name'] ?? 'MADRASAH TSANAWIYAH NEGERI'),
            'school_code'        => $settings['letter_school_code'] ?? 'MTs.11.22',
            'city'               => $settings['letter_city'] ?? $settings['city'] ?? 'Wonogiri',
            'logo_kemenag'       => $settings['logo_kemenag'] ?? 'logo_kemenag.png',
        ];
    }
}
