<?php

namespace App\Controllers\Admin;

use App\Controllers\BaseController;
use App\Models\ScheduleModel;
use App\Models\ScheduleEntryModel;
use App\Models\AcademicYearModel;
use App\Models\AcademicPeriodModel;
use App\Services\ScheduleGeneratorService;


class ScheduleGenerator extends BaseController
{
    protected ScheduleModel       $scheduleModel;
    protected ScheduleEntryModel  $entryModel;
    protected AcademicYearModel   $yearModel;
    protected AcademicPeriodModel $periodModel;
    protected \CodeIgniter\Database\BaseConnection $db;

    public function __construct()
    {
        $this->scheduleModel = new ScheduleModel();
        $this->entryModel    = new ScheduleEntryModel();
        $this->yearModel     = new AcademicYearModel();
        $this->periodModel   = new AcademicPeriodModel();
        $this->db            = \Config\Database::connect();
    }

    // ------------------------------------------------------------------
    // LIST semua jadwal
    // ------------------------------------------------------------------
    public function index()
    {
        $schedules = $this->scheduleModel->getWithRelations();

        foreach ($schedules as &$s) {
            $stats = $this->entryModel->getStats((int)$s['id']);
            $s['total_entries']   = $stats['total'];
            $s['total_generated'] = $stats['generated'];
            $s['total_manual']    = $stats['manual'];
            $s['total_locked']    = $stats['locked'];

            $targetSum = $this->db->table('schedule_loads')
                ->select('SUM(hours_per_week) as total')
                ->where('academic_year_id', $s['academic_year_id'])
                ->where('academic_period_id', $s['academic_period_id'])
                ->get()->getRowArray();
            $s['total_target'] = (int)($targetSum['total'] ?? 0);
        }

        return view('schedule-generator/index', [
            'title'     => 'Pembuat Jadwal Otomatis',
            'schedules' => $schedules,
        ]);
    }

    // ------------------------------------------------------------------
    // FORM buat jadwal baru
    // ------------------------------------------------------------------
    public function create()
    {
        return view('schedule-generator/create', [
            'title'   => 'Buat Jadwal Baru',
            'years'   => $this->yearModel->where('deleted_at', null)->orderBy('name', 'DESC')->findAll(),
            'periods' => $this->periodModel->getAllWithYear(),
        ]);
    }

    // ------------------------------------------------------------------
    // SIMPAN jadwal baru
    // ------------------------------------------------------------------
    public function store()
    {
        $rules = [
            'academic_year_id'   => 'required|is_natural_no_zero',
            'academic_period_id' => 'required|is_natural_no_zero',
            'name'               => 'required|max_length[100]',
            'active_days'        => 'required',
        ];

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

        $existing = $this->scheduleModel
            ->where('academic_year_id',   $this->request->getPost('academic_year_id'))
            ->where('academic_period_id', $this->request->getPost('academic_period_id'))
            ->first();

        if ($existing) {
            return redirect()->back()->withInput()
                             ->with('error', 'Jadwal untuk tahun ajaran dan periode ini sudah ada.');
        }

        $activeDays = implode(',', $this->request->getPost('active_days') ?? ['1','2','3','4','5']);

        $this->scheduleModel->insert([
            'academic_year_id'   => $this->request->getPost('academic_year_id'),
            'academic_period_id' => $this->request->getPost('academic_period_id'),
            'name'               => $this->request->getPost('name'),
            'active_days'        => $activeDays,
            'status'             => 'draft',
            'notes'              => $this->request->getPost('notes'),
        ]);

        return redirect()->to('/schedule-generator')
                         ->with('success', 'Jadwal berhasil dibuat. Silakan generate sekarang.');
    }

    // ------------------------------------------------------------------
    // HALAMAN DETAIL (WIZARD 7-LANGKAH)
    // ------------------------------------------------------------------
    public function detail(int $id)
    {
        $schedule = $this->scheduleModel->getWithRelations($id);
        if (!$schedule) {
            return redirect()->to('/schedule-generator')->with('error', 'Jadwal tidak ditemukan.');
        }

        $stats = $this->entryModel->getStats($id);
        $selectedStep = (int)($this->request->getGet('step') ?? ($stats['total'] > 0 ? 7 : 1));

        // Ambil data guru + status lock untuk tabel kolom kanan
        $teacherLockData = [];
        if ($stats['total'] > 0) {
            $rows = $this->entryModel->db->table('schedule_entries se')
                ->select([
                    'se.teacher_id',
                    't.name as teacher_name',
                    't.nip as teacher_nip',
                    't.user_id as teacher_user_id',
                    'COUNT(se.id) as total_entries',
                    'SUM(se.is_locked) as locked_entries',
                ])
                ->join('teachers t', 't.id = se.teacher_id', 'left')
                ->where('se.schedule_id', $id)
                ->groupBy('se.teacher_id')
                ->orderBy('t.name')
                ->get()->getResultArray();

            foreach ($rows as $row) {
                $total  = (int)$row['total_entries'];
                $locked = (int)$row['locked_entries'];
                $row['lock_status'] = $locked === 0 ? 'none'
                    : ($locked === $total ? 'all' : 'partial');
                $teacherLockData[] = $row;
            }
        }

        // Ambil data master untuk wizard
        $teachers = $this->db->table('teachers')
            ->select('id, name, nip, short_code, user_id')
            ->orderBy('name')
            ->get()->getResultArray();

        $subjects = $this->db->table('school_subjects')
            ->select('id, name, code')
            ->where('is_active', 1)
            ->orderBy('name')
            ->get()->getResultArray();

        $classrooms = $this->db->table('classrooms c')
            ->select('c.id, c.name, g.name as grade_name, g.id as grade_id')
            ->join('grades g', 'g.id = c.grade_id', 'left')
            ->where('c.deleted_at', null)
            ->orderBy('g.order')
            ->orderBy('c.name')
            ->get()->getResultArray();

        // Target jam per mapel
        $targets = $this->db->table('subject_target_hours')->get()->getResultArray();
        $subjectTargets = [];
        foreach ($targets as $t) {
            $subjectTargets[$t['subject_id']][$t['grade_id']] = (int)$t['target_hours'];
        }

        // Beban mengajar saat ini (schedule loads)
        $scheduleLoads = $this->db->table('schedule_loads sl')
            ->select('sl.id, sl.hours_per_week, sl.split_config, sl.teacher_id, sl.school_subject_id, sl.classroom_id, t.name as teacher_name, ss.name as subject_name, c.name as classroom_name, g.name as grade_name')
            ->join('teachers t', 't.id = sl.teacher_id')
            ->join('school_subjects ss', 'ss.id = sl.school_subject_id')
            ->join('classrooms c', 'c.id = sl.classroom_id')
            ->join('grades g', 'g.id = c.grade_id', 'left')
            ->where('sl.academic_year_id', $schedule['academic_year_id'])
            ->where('sl.academic_period_id', $schedule['academic_period_id'])
            ->orderBy('t.name')->orderBy('ss.name')->orderBy('g.order')->orderBy('c.name')
            ->get()->getResultArray();

        $settingModel = new \App\Models\ScheduleTeacherSettingModel();
        $globalMaxHours = $settingModel->getGlobalMaxHours();

        $timeslotModel = new \App\Models\ScheduleTimeslotModel();
        $activeDays = array_map('intval', explode(',', $schedule['active_days']));
        $timeslots = $timeslotModel
            ->whereIn('day', $activeDays)
            ->where('is_active', 1)
            ->orderBy('day')
            ->orderBy('slot_number')
            ->findAll();

        $gridByClassroom = $this->entryModel->getGridByClassroom($id);
        $gridByTeacher   = $this->entryModel->getGridByTeacher($id);
        $gridData        = $this->entryModel->getGridData($id);

        // Hitung total target (beban mengajar yang seharusnya)
        $totalTarget = $this->db->table('schedule_loads')
            ->select('SUM(hours_per_week) as total')
            ->where('academic_year_id', $schedule['academic_year_id'])
            ->where('academic_period_id', $schedule['academic_period_id'])
            ->get()
            ->getRowArray();
        $totalTarget = (int)($totalTarget['total'] ?? 0);

        return view('schedule-generator/detail', [
            'title'           => 'Detail Jadwal: ' . $schedule['name'],
            'schedule'        => $schedule,
            'stats'           => $stats,
            'teacherLockData' => $teacherLockData,
            'total_target'    => $totalTarget,
            'teachers'        => $teachers,
            'subjects'        => $subjects,
            'classrooms'      => $classrooms,
            'subjectTargets'  => $subjectTargets,
            'scheduleLoads'   => $scheduleLoads,
            'globalMaxHours'  => $globalMaxHours,
            'timeslots'       => $timeslots,
            'gridByClassroom' => $gridByClassroom,
            'gridByTeacher'   => $gridByTeacher,
            'gridData'        => $gridData,
            'selectedStep'    => $selectedStep,
        ]);
    }

    // ------------------------------------------------------------------
    // UPDATE METADATA JADWAL (Langkah 1 AJAX)
    // ------------------------------------------------------------------
    public function update(int $id)
    {
        $schedule = $this->scheduleModel->find($id);
        if (!$schedule) {
            if ($this->request->isAJAX()) {
                return $this->response->setJSON(['success' => false, 'message' => 'Jadwal tidak ditemukan.']);
            }
            return redirect()->back()->with('error', 'Jadwal tidak ditemukan.');
        }

        $rules = [
            'name'        => 'required|max_length[100]',
            'active_days' => 'required',
        ];

        if (!$this->validate($rules)) {
            $errors = implode(' ', $this->validator->getErrors());
            if ($this->request->isAJAX()) {
                return $this->response->setJSON(['success' => false, 'message' => $errors]);
            }
            return redirect()->back()->withInput()->with('error', $errors);
        }

        $activeDays = implode(',', $this->request->getPost('active_days') ?? ['1','2','3','4','5']);

        $this->scheduleModel->update($id, [
            'name'        => $this->request->getPost('name'),
            'active_days' => $activeDays,
            'notes'       => $this->request->getPost('notes'),
        ]);

        if ($this->request->isAJAX()) {
            return $this->response->setJSON([
                'success'     => true,
                'message'     => 'Informasi umum jadwal berhasil diperbarui.',
                'active_days' => $activeDays,
                'name'        => $this->request->getPost('name'),
            ]);
        }

        return redirect()->back()->with('success', 'Jadwal berhasil diperbarui.');
    }

    // ------------------------------------------------------------------
    // AMBIL SLOT WAKTU AKTIF (Langkah 2 AJAX)
    // ------------------------------------------------------------------
    public function getTimeslots(int $id)
    {
        if (!$this->request->isAJAX()) {
            return $this->response->setJSON([]);
        }

        $schedule = $this->scheduleModel->find($id);
        if (!$schedule) {
            return $this->response->setJSON(['success' => false, 'message' => 'Jadwal tidak ditemukan.']);
        }

        $activeDays = array_map('intval', explode(',', $schedule['active_days']));
        $timeslotModel = new \App\Models\ScheduleTimeslotModel();
        $timeslots = $timeslotModel->whereIn('day', $activeDays)->orderBy('day')->orderBy('slot_number')->findAll();

        return $this->response->setJSON([
            'success'     => true,
            'active_days' => $activeDays,
            'timeslots'   => $timeslots
        ]);
    }

    // ------------------------------------------------------------------
    // AMBIL KETERSEDIAAN GURU (Langkah 5 AJAX)
    // ------------------------------------------------------------------
    public function getTeacherAvailability(int $scheduleId, int $teacherId)
    {
        if (!$this->request->isAJAX()) {
            return $this->response->setJSON([]);
        }

        $schedule = $this->scheduleModel->find($scheduleId);
        if (!$schedule) {
            return $this->response->setJSON(['success' => false, 'message' => 'Jadwal tidak ditemukan.']);
        }

        $teacher = $this->db->table('teachers')->where('id', $teacherId)->get()->getRowArray();
        if (!$teacher) {
            return $this->response->setJSON(['success' => false, 'message' => 'Guru tidak ditemukan.']);
        }

        $timeslotModel = new \App\Models\ScheduleTimeslotModel();
        $activeDays    = array_map('intval', explode(',', $schedule['active_days']));
        $timeslots = $timeslotModel
            ->whereIn('day', $activeDays)
            ->where('is_active', 1)
            ->orderBy('day')
            ->orderBy('slot_number')
            ->findAll();

        $availabilityModel = new \App\Models\ScheduleAvailabilityModel();
        $blockedMap = $availabilityModel->getBlockedMap($teacherId);

        $settingModel = new \App\Models\ScheduleTeacherSettingModel();
        $setting = $settingModel->getByTeacher($teacherId);

        $globalMaxHours = $settingModel->getGlobalMaxHours();

        return $this->response->setJSON([
            'success'        => true,
            'teacher'        => $teacher,
            'timeslots'      => $timeslots,
            'blockedMap'     => $blockedMap,
            'setting'        => $setting,
            'globalMaxHours' => $globalMaxHours,
        ]);
    }

    // ------------------------------------------------------------------
    // SIMPAN BATAS JAM GLOBAL (Langkah 5 AJAX)
    // ------------------------------------------------------------------
    public function saveGlobalMaxHours()
    {
        if (!$this->request->isAJAX()) {
            return $this->response->setJSON(['success' => false]);
        }

        $raw = $this->request->getJSON(true);
        $maxHours = isset($raw['global_max_hours']) ? (int)$raw['global_max_hours'] : 6;

        if ($maxHours < 1 || $maxHours > 12) {
            return $this->response->setJSON(['success' => false, 'message' => 'Batas jam harus antara 1–12.']);
        }

        $settingModel = new \App\Models\ScheduleTeacherSettingModel();
        $settingModel->setGlobalMaxHours($maxHours);

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

    // ------------------------------------------------------------------
    // HALAMAN GRID JADWAL
    // ------------------------------------------------------------------
    public function view(int $id)
    {
        $schedule = $this->scheduleModel->getWithRelations($id);
        if (!$schedule) {
            return redirect()->to('/schedule-generator')->with('error', 'Jadwal tidak ditemukan.');
        }

        $timeslotModel = new \App\Models\ScheduleTimeslotModel();
        $activeDays    = array_map('intval', explode(',', $schedule['active_days']));

        $timeslots = $timeslotModel
            ->whereIn('day', $activeDays)
            ->where('is_active', 1)
            ->orderBy('day')
            ->orderBy('slot_number')
            ->findAll();

        $gridByClassroom = $this->entryModel->getGridByClassroom($id);
        $gridByTeacher   = $this->entryModel->getGridByTeacher($id);
        $gridData        = $this->entryModel->getGridData($id);

        return view('schedule-generator/view', [
            'title'           => 'Jadwal: ' . $schedule['name'],
            'schedule'        => $schedule,
            'timeslots'       => $timeslots,
            'gridByClassroom' => $gridByClassroom,
            'gridByTeacher'   => $gridByTeacher,
            'gridData'        => $gridData,
        ]);
    }

    // ------------------------------------------------------------------
    // PROSES GENERATE (AJAX POST) - DENGAN VALIDASI SEBELUMNYA
    // ------------------------------------------------------------------
    public function run(int $id)
    {
        set_time_limit(120); // 120 detik
        if (!$this->request->isAJAX()) {
            return redirect()->to('/schedule-generator');
        }

        $schedule = $this->scheduleModel->find($id);
        if (!$schedule) {
            return $this->response->setJSON(['success' => false, 'message' => 'Jadwal tidak ditemukan.']);
        }

        // VALIDASI SEBELUM GENERATE
        $validator = new \App\Services\SchedulePreValidator();
        $validation = $validator->validate(
            $id,
            (int)$schedule['academic_year_id'],
            (int)$schedule['academic_period_id']
        );

        if (!$validation['can_generate']) {
            // Format error messages dari object ke string untuk response
            $errorMessages = array_map(fn($e) => $e['message'] . (isset($e['detail']) ? ' — ' . $e['detail'] : ''), $validation['errors']);
            return $this->response->setJSON([
                'success'             => false,
                'message'             => 'Generate tidak dapat dilakukan karena masalah berikut:<br>' . implode('<br>', $errorMessages),
                'validation_errors'   => $validation['errors'],
                'validation_warnings' => $validation['warnings'],
            ]);
        }

        $hasWarnings = !empty($validation['warnings']);
        $keepManual = (bool)($this->request->getJSON(true)['keep_manual'] ?? false);

        try {
            $generator = new ScheduleGeneratorService();
            $result    = $generator->generate(
                scheduleId:  $id,
                yearId:      (int)$schedule['academic_year_id'],
                periodId:    (int)$schedule['academic_period_id'],
                activeDays:  $schedule['active_days'],
                keepManual:  $keepManual
            );

            $warnings = $validation['warnings'];
            if ($result['placed'] === $result['total']) {
                $warnings = array_filter($warnings, function($w) {
                    return is_array($w) ? (($w['code'] ?? '') !== 'HIGH_CAPACITY') : true;
                });
            }
            $hasWarnings = !empty($warnings);

            $response = [
                'success'      => true,
                'placed'       => $result['placed'],
                'total'        => $result['total'],
                'conflicts'    => $result['conflicts'],
                'has_conflict' => !empty($result['conflicts']),
                'has_warning'  => $hasWarnings,
                'warnings'     => $warnings,
                'message'      => $result['placed'] === $result['total']
                    ? "Berhasil menempatkan {$result['placed']} dari {$result['total']} jam pelajaran."
                    : "Selesai dengan " . count($result['conflicts']) . " konflik.",
            ];

            if ($hasWarnings) {
                // warnings adalah array of arrays — ambil field 'message' dari setiap item
                $warnTexts = array_map(function($w) {
                    return is_array($w) ? ($w['message'] ?? '') : (string)$w;
                }, $warnings);
                $response['message'] .= '<br><span class="text-warning">⚠️ ' . implode('; ', array_filter($warnTexts)) . '</span>';
            }

            return $this->response->setJSON($response);

        } catch (\Throwable $e) {
            log_message('error', 'ScheduleGenerator Error: ' . $e->getMessage());
            log_message('error', $e->getTraceAsString());
            
            return $this->response->setJSON([
                'success' => false,
                'message' => 'Error: ' . $e->getMessage(),
            ]);
        }
    }

    // ------------------------------------------------------------------
    // VALIDASI SEBELUM GENERATE (AJAX untuk Tab Validasi)
    // ------------------------------------------------------------------
    public function validateSchedule(int $id)
    {
        if (!$this->request->isAJAX()) {
            return redirect()->to('/schedule-generator');
        }
        
        try {
            $schedule = $this->scheduleModel->find($id);
            if (!$schedule) {
                return $this->response->setJSON(['success' => false, 'message' => 'Jadwal tidak ditemukan.']);
            }
            
            $validator = new \App\Services\SchedulePreValidator();
            $result = $validator->validate(
                $id,
                (int)$schedule['academic_year_id'],
                (int)$schedule['academic_period_id']
            );
            
            return $this->response->setJSON([
                'success' => true,
                'errors' => $result['errors'],
                'warnings' => $result['warnings'],
                'summary' => $result['summary'],
            ]);
            
        } catch (\Throwable $e) {
            log_message('error', 'ValidateSchedule Error: ' . $e->getMessage());
            log_message('error', $e->getTraceAsString());
            
            return $this->response->setJSON([
                'success' => false,
                'message' => 'Error: ' . $e->getMessage() . ' at line ' . $e->getLine()
            ]);
        }
    }

    // ------------------------------------------------------------------
    // LOCK / UNLOCK entry individual (AJAX)
    // ------------------------------------------------------------------
    public function entryLock(int $scheduleId, int $entryId, string $action)
    {
        if (!$this->request->isAJAX()) {
            return $this->response->setJSON(['success' => false, 'message' => 'Invalid request.']);
        }

        $entry = $this->entryModel->find($entryId);
        if (!$entry || (int)$entry['schedule_id'] !== $scheduleId) {
            return $this->response->setJSON(['success' => false, 'message' => 'Entry tidak ditemukan.']);
        }

        if ($action === 'lock') {
            $this->entryModel->lockEntry($entryId);
            return $this->response->setJSON(['success' => true, 'locked' => true,  'message' => 'Entry dikunci.']);
        } elseif ($action === 'unlock') {
            $this->entryModel->unlockEntry($entryId);
            return $this->response->setJSON(['success' => true, 'locked' => false, 'message' => 'Entry dibuka.']);
        }

        return $this->response->setJSON(['success' => false, 'message' => 'Aksi tidak valid.']);
    }

    // ------------------------------------------------------------------
    // LOCK semua entry guru tertentu (AJAX)
    // ------------------------------------------------------------------
    public function lockTeacher(int $scheduleId, int $teacherId, string $action)
    {
        if (!$this->request->isAJAX()) {
            return $this->response->setJSON(['success' => false]);
        }

        if ($action === 'lock') {
            $count = $this->entryModel->lockByTeacher($scheduleId, $teacherId);
            return $this->response->setJSON(['success' => true, 'count' => $count, 'message' => "{$count} entry guru dikunci."]);
        } elseif ($action === 'unlock') {
            $count = $this->entryModel->unlockByTeacher($scheduleId, $teacherId);
            return $this->response->setJSON(['success' => true, 'count' => $count, 'message' => "{$count} entry guru dibuka."]);
        }

        return $this->response->setJSON(['success' => false]);
    }

    // ------------------------------------------------------------------
    // PUBLISH jadwal
    // ------------------------------------------------------------------
    public function publish(int $id)
    {
        $schedule = $this->scheduleModel->find($id);
        if (!$schedule) {
            return redirect()->to('/schedule-generator')->with('error', 'Jadwal tidak ditemukan.');
        }

        $stats = $this->entryModel->getStats($id);
        if ($stats['total'] === 0) {
            return redirect()->to('/schedule-generator/detail/' . $id)
                             ->with('error', 'Jadwal belum memiliki entri. Generate dulu sebelum publish.');
        }

        $this->scheduleModel->update($id, ['status' => 'published']);
        return redirect()->to('/schedule-generator/detail/' . $id)
                         ->with('success', 'Jadwal berhasil dipublikasikan.');
    }

    // ------------------------------------------------------------------
    // HAPUS jadwal
    // ------------------------------------------------------------------
    public function delete(int $id)
    {
        $schedule = $this->scheduleModel->find($id);
        if (!$schedule) {
            return redirect()->to('/schedule-generator')->with('error', 'Jadwal tidak ditemukan.');
        }

        $this->entryModel->deleteAll($id);
        $this->scheduleModel->delete($id);

        return redirect()->to('/schedule-generator')->with('success', 'Jadwal berhasil dihapus.');
    }

    // ------------------------------------------------------------------
    // RESET entri
    // ------------------------------------------------------------------
    public function reset(int $id)
    {
        $this->entryModel->deleteGenerated($id);
        $this->scheduleModel->update($id, ['status' => 'draft']);

        return redirect()->to('/schedule-generator/detail/' . $id)
                         ->with('success', 'Hasil generate berhasil direset. Entri manual & locked dipertahankan.');
    }

    // ------------------------------------------------------------------
    // GET PERIODS (AJAX untuk form create)
    // ------------------------------------------------------------------
    public function getPeriods()
    {
        if (!$this->request->isAJAX()) {
            return $this->response->setJSON([]);
        }

        $yearId  = (int)$this->request->getGet('year_id');
        $periods = $this->periodModel->getByYear($yearId);

        return $this->response->setJSON($periods);
    }

    // ------------------------------------------------------------------
// CARI PELUANG (Acak/geser jadwal untuk membuka slot kosong)
// ------------------------------------------------------------------
public function findOpportunity(int $id)
{
    set_time_limit(180);
    
    if (!$this->request->isAJAX()) {
        return redirect()->to('/schedule-generator');
    }

    $schedule = $this->scheduleModel->find($id);
    if (!$schedule) {
        return $this->response->setJSON(['success' => false, 'message' => 'Jadwal tidak ditemukan.']);
    }

    try {
        $generator = new \App\Services\ScheduleGeneratorService();
        $result = $generator->findOpportunity(
            $id,
            (int)$schedule['academic_year_id'],
            (int)$schedule['academic_period_id'],
            $schedule['active_days']
        );

        return $this->response->setJSON([
            'success' => true,
            'slots_found' => $result['slots_found'],
            'conflicts_before' => $result['conflicts_before'],
            'conflicts_after' => $result['conflicts_after'],
        ]);
    } catch (\Throwable $e) {
        return $this->response->setJSON([
            'success' => false,
            'message' => 'Error: ' . $e->getMessage(),
        ]);
    }
}

// ------------------------------------------------------------------
// MASUKKAN BLOK GAGAL (Tempatkan blok yang gagal ke slot kosong)
// ------------------------------------------------------------------
public function insertFailed(int $id)
{
    set_time_limit(180);
    
    if (!$this->request->isAJAX()) {
        return redirect()->to('/schedule-generator');
    }

    $schedule = $this->scheduleModel->find($id);
    if (!$schedule) {
        return $this->response->setJSON(['success' => false, 'message' => 'Jadwal tidak ditemukan.']);
    }

    try {
        $generator = new \App\Services\ScheduleGeneratorService();
        $result = $generator->insertFailedBlocks(
            $id,
            (int)$schedule['academic_year_id'],
            (int)$schedule['academic_period_id'],
            $schedule['active_days']
        );

        return $this->response->setJSON([
            'success' => true,
            'inserted' => $result['inserted'],
            'remaining_conflicts' => $result['remaining_conflicts'],
        ]);
    } catch (\Throwable $e) {
        return $this->response->setJSON([
            'success' => false,
            'message' => 'Error: ' . $e->getMessage(),
        ]);
    }
}

public function findOpportunityAdvanced(int $id)
{
    set_time_limit(180);
    
    if (!$this->request->isAJAX()) {
        return redirect()->to('/schedule-generator');
    }

    $schedule = $this->scheduleModel->find($id);
    if (!$schedule) {
        return $this->response->setJSON(['success' => false, 'message' => 'Jadwal tidak ditemukan.']);
    }

    try {
        $generator = new \App\Services\ScheduleGeneratorService();
        $result = $generator->findOpportunityAdvanced(
            $id,
            (int)$schedule['academic_year_id'],
            (int)$schedule['academic_period_id'],
            $schedule['active_days']
        );

        return $this->response->setJSON([
            'success' => true,
            'slots_found' => $result['slots_found'],
            'blocks_moved' => $result['blocks_moved'],
            'conflicts_before' => $result['conflicts_before'],
            'conflicts_after' => $result['conflicts_after'],
        ]);
    } catch (\Throwable $e) {
        return $this->response->setJSON([
            'success' => false,
            'message' => 'Error: ' . $e->getMessage(),
        ]);
    }
}

// ------------------------------------------------------------------
// GET FAILED BLOCKS (untuk ditampilkan di detail)
// ------------------------------------------------------------------
public function getFailedBlocks(int $id)
{
    if (!$this->request->isAJAX()) {
        return redirect()->to('/schedule-generator');
    }

    $schedule = $this->scheduleModel->find($id);
    if (!$schedule) {
        return $this->response->setJSON(['success' => false, 'message' => 'Jadwal tidak ditemukan.']);
    }

    try {
        $yearId   = (int)$schedule['academic_year_id'];
        $periodId = (int)$schedule['academic_period_id'];

        // ── 1. Ambil active_days dari tabel schedules ─────────────────
        $activeDays = array_map('intval', explode(',', $schedule['active_days']));

        // ── 2. Hitung total slot KBM tersedia per hari aktif ─────────
        $totalKbmSlots = 0;
        $kbmPerDay     = [];
        foreach ($activeDays as $day) {
            $cnt = $this->db->table('schedule_timeslots')
                ->where('day', $day)
                ->where('slot_type', 'kbm')
                ->where('is_active', 1)
                ->countAllResults();
            $kbmPerDay[$day] = $cnt;
            $totalKbmSlots  += $cnt;
        }

        // ── 3. Hitung slot yang sudah terpakai per hari ───────────────
        $usedPerDay = [];
        $usedRows = $this->db->table('schedule_entries')
            ->select('day, COUNT(*) as cnt')
            ->where('schedule_id', $id)
            ->groupBy('day')
            ->get()->getResultArray();
        foreach ($usedRows as $row) {
            $usedPerDay[(int)$row['day']] = (int)$row['cnt'];
        }

        // Sisa slot KBM tersedia (total - yang sudah dipakai)
        $availableSlots = 0;
        foreach ($activeDays as $day) {
            $avail = ($kbmPerDay[$day] ?? 0) - ($usedPerDay[$day] ?? 0);
            $availableSlots += max(0, $avail);
        }

        // ── 4. Ambil semua beban mengajar ─────────────────────────────
        $loads = $this->db->table('schedule_loads sl')
            ->select('sl.id, sl.teacher_id, sl.hours_per_week, sl.split_config, t.name as teacher_name, ss.name as subject_name, c.name as classroom_name')
            ->join('teachers t', 't.id = sl.teacher_id')
            ->join('school_subjects ss', 'ss.id = sl.school_subject_id')
            ->join('classrooms c', 'c.id = sl.classroom_id')
            ->where('sl.academic_year_id', $yearId)
            ->where('sl.academic_period_id', $periodId)
            ->get()
            ->getResultArray();

        // ── 5. Hitung jam sudah terisi per load ───────────────────────
        $filledPerLoad = [];
        $entries = $this->db->table('schedule_entries')
            ->select('schedule_load_id, COUNT(*) as total')
            ->where('schedule_id', $id)
            ->groupBy('schedule_load_id')
            ->get()
            ->getResultArray();
        foreach ($entries as $e) {
            $filledPerLoad[$e['schedule_load_id']] = (int)$e['total'];
        }

        // ── 6. Hitung blocked slots per teacher ───────────────────────
        // Kumpulkan teacher_ids unik dari loads
        $teacherIds = array_unique(array_column($loads, 'teacher_id'));
        $blockedPerTeacher = [];
        if (!empty($teacherIds)) {
            $blockedRows = $this->db->table('schedule_availability')
                ->whereIn('teacher_id', $teacherIds)
                ->whereIn('day', $activeDays)
                ->get()->getResultArray();

            foreach ($blockedRows as $b) {
                $tid = (int)$b['teacher_id'];
                $day = (int)$b['day'];
                $slot = (int)$b['slot_number'];
                if ($slot === 0) {
                    // Seluruh hari diblokir
                    $blockedPerTeacher[$tid] = ($blockedPerTeacher[$tid] ?? 0) + ($kbmPerDay[$day] ?? 0);
                } else {
                    $blockedPerTeacher[$tid] = ($blockedPerTeacher[$tid] ?? 0) + 1;
                }
            }
        }

        // ── 7. Identifikasi blok gagal + klasifikasi ──────────────────
        $failedBlocks = [];
        foreach ($loads as $load) {
            $lid        = $load['id'];
            $totalHours = (int)$load['hours_per_week'];
            $blocks     = $this->parseSplitConfig($totalHours, $load['split_config'] ?? '');
            $totalNeeded = array_sum($blocks);
            $filled     = $filledPerLoad[$lid] ?? 0;

            if ($filled >= $totalNeeded) {
                continue;
            }

            $remaining  = $totalNeeded - $filled;
            $teacherId  = (int)$load['teacher_id'];
            $blockedCount = $blockedPerTeacher[$teacherId] ?? 0;

            // Klasifikasi kategori
            if ($blockedCount >= $totalHours) {
                $category    = 'TEACHER_BLOCKED';
                $suggestions = [
                    "Longgarkan ketersediaan {$load['teacher_name']} di menu Ketersediaan Guru",
                    "Naikkan max jam/hari guru ini",
                ];
            } elseif ($availableSlots < min($blocks)) {
                $category    = 'NO_SLOT';
                $suggestions = [
                    "Tambah slot KBM pada hari yang masih sedikit jam pelajarannya",
                    "Kurangi beban {$load['subject_name']} di kelas {$load['classroom_name']}",
                ];
            } else {
                $category    = 'CONSTRAINT';
                $suggestions = [
                    "Coba generate ulang — engine akan mencoba kombinasi berbeda",
                    "Periksa distribusi jam di tab Validasi",
                ];
            }

            foreach ($blocks as $size) {
                if ($remaining <= 0) break;
                if ($size <= $remaining) {
                    $failedBlocks[] = [
                        'teacher_name'   => $load['teacher_name'],
                        'subject_name'   => $load['subject_name'],
                        'classroom_name' => $load['classroom_name'],
                        'size'           => $size,
                        'category'       => $category,
                        'suggestions'    => $suggestions,
                    ];
                    $remaining -= $size;
                }
            }
        }

        $totalFailedHours = array_sum(array_column($failedBlocks, 'size'));

        return $this->response->setJSON([
            'success'       => true,
            'failed_blocks' => $failedBlocks,
            'total_hours'   => $totalFailedHours,
            'failed_count'  => count($failedBlocks),
        ]);

    } catch (\Throwable $e) {
        return $this->response->setJSON([
            'success' => false,
            'message' => 'Error: ' . $e->getMessage(),
        ]);
    }
}

private function parseSplitConfig(int $totalHours, string $splitConfig): array
{
    if (empty(trim($splitConfig))) {
        return [$totalHours];
    }
    
    $blocks = array_filter(
        array_map('intval', explode(',', $splitConfig)),
        fn($b) => $b > 0
    );
    
    if (empty($blocks) || array_sum($blocks) !== $totalHours) {
        return [$totalHours];
    }
    
    return array_values(array_slice($blocks, 0, 3));
}

// ------------------------------------------------------------------
// GESER BLOK 1 JAM YANG MENGHALANGI
// ------------------------------------------------------------------
public function shiftOneHourBlocks(int $id)
{
    set_time_limit(180);
    
    if (!$this->request->isAJAX()) {
        return redirect()->to('/schedule-generator');
    }

    $schedule = $this->scheduleModel->find($id);
    if (!$schedule) {
        return $this->response->setJSON(['success' => false, 'message' => 'Jadwal tidak ditemukan.']);
    }

    try {
        $generator = new \App\Services\ScheduleGeneratorService();
        $result = $generator->findAndShiftOneHourBlocks(
            $id,
            (int)$schedule['academic_year_id'],
            (int)$schedule['academic_period_id'],
            $schedule['active_days']
        );

        return $this->response->setJSON([
            'success' => true,
            'blocks_shifted' => $result['blocks_shifted'],
            'opportunities_found' => $result['opportunities_found'],
            'conflicts_before' => $result['conflicts_before'],
            'conflicts_after' => $result['conflicts_after'],
        ]);
    } catch (\Throwable $e) {
        return $this->response->setJSON([
            'success' => false,
            'message' => 'Error: ' . $e->getMessage(),
        ]);
    }
}

// ------------------------------------------------------------------
// GENERATE BERDASARKAN UKURAN BLOK (3, 2, atau 1)
// ------------------------------------------------------------------
public function runByBlockSize(int $id, int $blockSize)
{
    set_time_limit(180);
    
    if (!$this->request->isAJAX()) {
        return redirect()->to('/schedule-generator');
    }

    $schedule = $this->scheduleModel->find($id);
    if (!$schedule) {
        return $this->response->setJSON(['success' => false, 'message' => 'Jadwal tidak ditemukan.']);
    }

    try {
        $generator = new \App\Services\ScheduleGeneratorService();
        
        // Panggil method generateByBlockSize di service
        $result = $generator->generateByBlockSize(
            scheduleId:  $id,
            yearId:      (int)$schedule['academic_year_id'],
            periodId:    (int)$schedule['academic_period_id'],
            activeDays:  $schedule['active_days'],
            blockSizeFilter: $blockSize,
            keepManual:  false,
            resetFirst:  ($blockSize == 3) // reset hanya saat generate blok 3 jam
        );

        return $this->response->setJSON([
            'success' => true,
            'placed' => $result['placed'],
            'total' => $result['total'],
            'block_size' => $blockSize,
            'message' => "Berhasil menempatkan {$result['placed']} dari {$result['total']} jam (ukuran {$blockSize})",
        ]);

    } catch (\Throwable $e) {
        log_message('error', 'runByBlockSize Error: ' . $e->getMessage());
        return $this->response->setJSON([
            'success' => false,
            'message' => 'Error: ' . $e->getMessage(),
        ]);
    }
}

// ------------------------------------------------------------------
// HITUNG PELUANG BLOK 3 JAM
// ------------------------------------------------------------------
public function checkOpportunity3Jam(int $id)
{
    if (!$this->request->isAJAX()) {
        return redirect()->to('/schedule-generator');
    }

    $schedule = $this->scheduleModel->find($id);
    if (!$schedule) {
        return $this->response->setJSON(['success' => false, 'message' => 'Jadwal tidak ditemukan.']);
    }

    try {
        $generator = new \App\Services\ScheduleGeneratorService();
        $result = $generator->checkThreeHourOpportunity(
            $id,
            (int)$schedule['academic_year_id'],
            (int)$schedule['academic_period_id'],
            $schedule['active_days']
        );

        return $this->response->setJSON([
            'success' => true,
            'three_hour_blocks_needed' => $result['needed'],
            'consecutive_slots_available' => $result['available'],
            'is_ready' => $result['is_ready'],
            'message' => $result['message'],
        ]);
    } catch (\Throwable $e) {
        return $this->response->setJSON([
            'success' => false,
            'message' => 'Error: ' . $e->getMessage(),
        ]);
    }
}

// ------------------------------------------------------------------
// GENERATE ONLY 2 JAM (reset dulu)
// ------------------------------------------------------------------
public function generateOnly2Jam(int $id)
{
    set_time_limit(180);
    
    if (!$this->request->isAJAX()) {
        return redirect()->to('/schedule-generator');
    }

    $schedule = $this->scheduleModel->find($id);
    if (!$schedule) {
        return $this->response->setJSON(['success' => false, 'message' => 'Jadwal tidak ditemukan.']);
    }

    try {
        $generator = new \App\Services\ScheduleGeneratorService();
        
        // Reset dulu
        $generator->resetEntriesByBlockSize($id, 2);
        $generator->resetEntriesByBlockSize($id, 3);
        $generator->resetEntriesByBlockSize($id, 1);
        
        // Generate hanya blok 2 jam
        $result = $generator->generateByBlockSize(
            $id,
            (int)$schedule['academic_year_id'],
            (int)$schedule['academic_period_id'],
            $schedule['active_days'],
            2,  // blockSizeFilter = 2
            false,
            false
        );

        return $this->response->setJSON([
            'success' => true,
            'placed' => $result['placed'],
            'total' => $result['total'],
            'message' => "Blok 2 Jam: {$result['placed']} dari {$result['total']} jam berhasil.",
        ]);
    } catch (\Throwable $e) {
        return $this->response->setJSON([
            'success' => false,
            'message' => 'Error: ' . $e->getMessage(),
        ]);
    }
}

// ------------------------------------------------------------------
// REPAIR BLOK 2 (geser blok 2 jam yang menghalangi)
// ------------------------------------------------------------------
public function repairTwoHourBlocks(int $id)
{
    set_time_limit(180);
    
    if (!$this->request->isAJAX()) {
        return redirect()->to('/schedule-generator');
    }

    $schedule = $this->scheduleModel->find($id);
    if (!$schedule) {
        return $this->response->setJSON(['success' => false, 'message' => 'Jadwal tidak ditemukan.']);
    }

    try {
        $generator = new \App\Services\ScheduleGeneratorService();
        $result = $generator->repairTwoHourBlocksForThreeHour(
            $id,
            (int)$schedule['academic_year_id'],
            (int)$schedule['academic_period_id'],
            $schedule['active_days']
        );

        return $this->response->setJSON([
            'success' => true,
            'blocks_moved' => $result['blocks_moved'],
            'consecutive_before' => $result['consecutive_before'],
            'consecutive_after' => $result['consecutive_after'],
            'three_hour_needed' => $result['needed'],
            'is_ready' => $result['is_ready'],
            'message' => $result['message'],
        ]);
    } catch (\Throwable $e) {
        return $this->response->setJSON([
            'success' => false,
            'message' => 'Error: ' . $e->getMessage(),
        ]);
    }
}

// ------------------------------------------------------------------
// GENERATE ONLY 3 JAM (pertahankan yang sudah ada)
// ------------------------------------------------------------------
public function generateOnly3Jam(int $id)
{
    set_time_limit(180);
    
    if (!$this->request->isAJAX()) {
        return redirect()->to('/schedule-generator');
    }

    $schedule = $this->scheduleModel->find($id);
    if (!$schedule) {
        return $this->response->setJSON(['success' => false, 'message' => 'Jadwal tidak ditemukan.']);
    }

    try {
        $generator = new \App\Services\ScheduleGeneratorService();
        $result = $generator->generateByBlockSize(
            $id,
            (int)$schedule['academic_year_id'],
            (int)$schedule['academic_period_id'],
            $schedule['active_days'],
            3,
            false,
            false
        );

        return $this->response->setJSON([
            'success' => true,
            'placed' => $result['placed'],
            'total' => $result['total'],
            'message' => "Blok 3 Jam: {$result['placed']} dari {$result['total']} jam berhasil.",
        ]);
    } catch (\Throwable $e) {
        return $this->response->setJSON([
            'success' => false,
            'message' => 'Error: ' . $e->getMessage(),
        ]);
    }
}

// ------------------------------------------------------------------
// GENERATE ONLY 1 JAM (pertahankan yang sudah ada)
// ------------------------------------------------------------------
public function generateOnly1Jam(int $id)
{
    set_time_limit(180);
    
    if (!$this->request->isAJAX()) {
        return redirect()->to('/schedule-generator');
    }

    $schedule = $this->scheduleModel->find($id);
    if (!$schedule) {
        return $this->response->setJSON(['success' => false, 'message' => 'Jadwal tidak ditemukan.']);
    }

    try {
        $generator = new \App\Services\ScheduleGeneratorService();
        $result = $generator->generateByBlockSize(
            $id,
            (int)$schedule['academic_year_id'],
            (int)$schedule['academic_period_id'],
            $schedule['active_days'],
            1,
            false,
            false
        );

        return $this->response->setJSON([
            'success' => true,
            'placed' => $result['placed'],
            'total' => $result['total'],
            'message' => "Blok 1 Jam: {$result['placed']} dari {$result['total']} jam berhasil.",
        ]);
    } catch (\Throwable $e) {
        return $this->response->setJSON([
            'success' => false,
            'message' => 'Error: ' . $e->getMessage(),
        ]);
    }
}

// ------------------------------------------------------------------
// INFO PELUANG 3 JAM
// ------------------------------------------------------------------
public function getThreeHourOpportunity(int $id)
{
    if (!$this->request->isAJAX()) {
        return redirect()->to('/schedule-generator');
    }

    $schedule = $this->scheduleModel->find($id);
    if (!$schedule) {
        return $this->response->setJSON(['success' => false, 'message' => 'Jadwal tidak ditemukan.']);
    }

    try {
        $generator = new \App\Services\ScheduleGeneratorService();
        $result = $generator->getThreeHourOpportunity(
            $id,
            (int)$schedule['academic_year_id'],
            (int)$schedule['academic_period_id'],
            $schedule['active_days']
        );

        return $this->response->setJSON([
            'success' => true,
            'needed' => $result['needed'],
            'available' => $result['available'],
            'is_ready' => $result['is_ready'],
            'message' => $result['message'],
        ]);
    } catch (\Throwable $e) {
        return $this->response->setJSON([
            'success' => false,
            'message' => 'Error: ' . $e->getMessage(),
        ]);
    }
}

// ─── METHOD: moveEntry ───────────────────────────────────────────────────────
public function moveEntry(int $scheduleId)
{
    if (!$this->request->isAJAX()) {
        return $this->response->setJSON(['success' => false]);
    }
 
    $json       = $this->request->getJSON(true);
    $entryId    = (int)($json['entry_id']    ?? 0);
    $targetDay  = (int)($json['target_day']  ?? 0);
    $targetSlot = (int)($json['target_slot'] ?? 0);
 
    if (!$entryId || !$targetDay || !$targetSlot) {
        return $this->response->setJSON(['success' => false, 'message' => 'Data tidak lengkap.']);
    }
 
    // Ambil entry yang akan dipindah
    $entry = $this->entryModel->find($entryId);
    if (!$entry || (int)$entry['schedule_id'] !== $scheduleId) {
        return $this->response->setJSON(['success' => false, 'message' => 'Entry tidak ditemukan.']);
    }
 
    // Cek entry tidak terkunci
    if ((int)$entry['is_locked']) {
        return $this->response->setJSON(['success' => false, 'message' => 'Entry ini terkunci. Buka kunci terlebih dahulu.']);
    }
 
    // Cek slot target adalah KBM dan aktif
    $targetTimeslot = $this->db->table('schedule_timeslots')
        ->where('day', $targetDay)
        ->where('slot_number', $targetSlot)
        ->where('slot_type', 'kbm')
        ->where('is_active', 1)
        ->get()->getRowArray();
 
    if (!$targetTimeslot) {
        return $this->response->setJSON(['success' => false, 'message' => 'Slot tujuan bukan slot KBM aktif.']);
    }
 
    $teacherId   = (int)$entry['teacher_id'];
    $classroomId = (int)$entry['classroom_id'];
    $subjectId   = (int)$entry['school_subject_id'];
    $loadId      = (int)$entry['schedule_load_id'];
 
    // ── HC1: Guru sudah ada di slot tujuan? ──
    $teacherConflict = $this->db->table('schedule_entries se')
        ->select('se.id, ss.name as subject_name, c.name as classroom_name')
        ->join('school_subjects ss', 'ss.id = se.school_subject_id', 'left')
        ->join('classrooms c', 'c.id = se.classroom_id', 'left')
        ->where('se.schedule_id', $scheduleId)
        ->where('se.teacher_id', $teacherId)
        ->where('se.day', $targetDay)
        ->where('se.slot_number', $targetSlot)
        ->where('se.id !=', $entryId)
        ->get()->getRowArray();
 
    if ($teacherConflict) {
        return $this->response->setJSON([
            'success'   => false,
            'conflict'  => 'teacher',
            'message'   => "❌ Konflik Guru: {$entry['teacher_id']} sudah mengajar <strong>{$teacherConflict['subject_name']}</strong> di kelas <strong>{$teacherConflict['classroom_name']}</strong> pada slot ini.",
        ]);
    }
 
    // ── HC2: Kelas sudah ada pelajaran di slot tujuan? ──
    $classConflict = $this->db->table('schedule_entries se')
        ->select('se.id, ss.name as subject_name, t.name as teacher_name')
        ->join('school_subjects ss', 'ss.id = se.school_subject_id', 'left')
        ->join('teachers t', 't.id = se.teacher_id', 'left')
        ->where('se.schedule_id', $scheduleId)
        ->where('se.classroom_id', $classroomId)
        ->where('se.day', $targetDay)
        ->where('se.slot_number', $targetSlot)
        ->where('se.id !=', $entryId)
        ->get()->getRowArray();
 
    if ($classConflict) {
        return $this->response->setJSON([
            'success'   => false,
            'conflict'  => 'classroom',
            'message'   => "❌ Konflik Kelas: Sudah ada <strong>{$classConflict['subject_name']}</strong> (guru: {$classConflict['teacher_name']}) di kelas ini pada slot ini.",
        ]);
    }
 
    // ── HC3: Guru diblokir di slot tujuan? ──
    $isBlocked = $this->db->table('schedule_availability')
        ->where('teacher_id', $teacherId)
        ->groupStart()
            ->where('day', $targetDay)
            ->groupStart()
                ->where('slot_number', $targetSlot)
                ->orWhere('slot_number', 0) // whole day block
            ->groupEnd()
        ->groupEnd()
        ->countAllResults();
 
    if ($isBlocked) {
        return $this->response->setJSON([
            'success'  => false,
            'conflict' => 'availability',
            'message'  => "❌ Guru tidak tersedia: Guru telah memblokir slot ini di pengaturan ketersediaan.",
        ]);
    }
 
    // ── HC7: Mapel yang sama sudah ada di hari tujuan untuk kelas ini? ──
    $sameSubjectSameDay = $this->db->table('schedule_entries')
        ->where('schedule_id', $scheduleId)
        ->where('classroom_id', $classroomId)
        ->where('school_subject_id', $subjectId)
        ->where('day', $targetDay)
        ->where('id !=', $entryId)
        ->countAllResults();
 
    if ($sameSubjectSameDay > 0) {
        return $this->response->setJSON([
            'success'  => false,
            'conflict' => 'duplicate_subject',
            'message'  => "❌ Mapel Duplikat: Mata pelajaran yang sama sudah ada di hari ini untuk kelas ini. (HC7: mapel tidak boleh 2x sehari)",
        ]);
    }
 
    // ── HC: Blok dari load yang sama tidak boleh di hari yang sama ──
    $sameLoadSameDay = $this->db->table('schedule_entries')
        ->where('schedule_id', $scheduleId)
        ->where('schedule_load_id', $loadId)
        ->where('day', $targetDay)
        ->where('id !=', $entryId)
        ->countAllResults();
 
    if ($sameLoadSameDay > 0) {
        return $this->response->setJSON([
            'success'  => false,
            'conflict' => 'same_load_day',
            'message'  => "❌ Split Blok: Blok lain dari mata pelajaran yang sama (split yang sama) sudah ada di hari ini. Tiap blok harus di hari berbeda.",
        ]);
    }
 
    // ── Semua constraint OK — lakukan pemindahan ──
    $this->entryModel->update($entryId, [
        'day'         => $targetDay,
        'slot_number' => $targetSlot,
        'timeslot_id' => $targetTimeslot['id'],
        'is_manual'   => 1, // tandai sebagai manual
    ]);
 
    // Ambil nama hari & info untuk response
    $dayNames = ['','Senin','Selasa','Rabu','Kamis','Jumat','Sabtu'];
 
    return $this->response->setJSON([
        'success' => true,
        'message' => "Entry berhasil dipindah ke {$dayNames[$targetDay]} Slot {$targetSlot}.",
        'new_day'    => $targetDay,
        'new_slot'   => $targetSlot,
        'is_manual'  => 1,
    ]);
}

// ─── METHOD: getConflictDetail ───────────────────────────────────────────────
public function getConflictDetail(int $id)
{
    if (!$this->request->isAJAX()) {
        return redirect()->to('/schedule-generator');
    }
 
    $schedule = $this->scheduleModel->find($id);
    if (!$schedule) {
        return $this->response->setJSON(['success' => false, 'message' => 'Jadwal tidak ditemukan.']);
    }
 
    $yearId   = (int)$schedule['academic_year_id'];
    $periodId = (int)$schedule['academic_period_id'];
 
    // Ambil semua beban mengajar
    $loads = $this->db->table('schedule_loads sl')
        ->select('sl.id, sl.hours_per_week, sl.split_config, t.name as teacher_name, t.nip, ss.name as subject_name, c.name as classroom_name, g.name as grade_name')
        ->join('teachers t',         't.id = sl.teacher_id')
        ->join('school_subjects ss', 'ss.id = sl.school_subject_id')
        ->join('classrooms c',       'c.id = sl.classroom_id')
        ->join('grades g',           'g.id = c.grade_id', 'left')
        ->where('sl.academic_year_id',  $yearId)
        ->where('sl.academic_period_id', $periodId)
        ->get()->getResultArray();
 
    // Hitung jam terisi per load
    $filled = $this->db->table('schedule_entries')
        ->select('schedule_load_id, COUNT(*) as total')
        ->where('schedule_id', $id)
        ->groupBy('schedule_load_id')
        ->get()->getResultArray();
 
    $filledMap = [];
    foreach ($filled as $f) {
        $filledMap[$f['schedule_load_id']] = (int)$f['total'];
    }
 
    // Cari yang tidak 100%
    $conflicts  = [];
    $totalMissing = 0;
    foreach ($loads as $load) {
        $lid     = $load['id'];
        $needed  = (int)$load['hours_per_week'];
        $placed  = $filledMap[$lid] ?? 0;
        if ($placed < $needed) {
            $missing = $needed - $placed;
            $totalMissing += $missing;
            $conflicts[] = [
                'load_id'        => $lid,
                'teacher_name'   => $load['teacher_name'],
                'nip'            => $load['nip'],
                'subject_name'   => $load['subject_name'],
                'classroom_name' => ($load['grade_name'] ?? '') . ' ' . $load['classroom_name'],
                'hours_per_week' => $needed,
                'placed'         => $placed,
                'missing'        => $missing,
                'split_config'   => $load['split_config'] ?: (string)$needed,
            ];
        }
    }
 
    // Analisis penyebab per konflik
    $activeDays = array_map('intval', explode(',', $schedule['active_days']));
    $dayNames   = ['','Senin','Selasa','Rabu','Kamis','Jumat','Sabtu'];
 
    // Ambil slot KBM per hari
    $kbmPerDay = [];
    foreach ($activeDays as $day) {
        $kbmPerDay[$day] = $this->db->table('schedule_timeslots')
            ->where('day', $day)->where('slot_type', 'kbm')->where('is_active', 1)
            ->countAllResults();
    }
 
    // Ambil ketersediaan guru (blocked)
    foreach ($conflicts as &$c) {
        $tid = $this->db->table('teachers')->where('name', $c['teacher_name'])->get()->getRowArray()['id'] ?? 0;
 
        // Cek berapa hari guru ini diblokir
        $blockedDays = $this->db->table('schedule_availability')
            ->where('teacher_id', $tid)
            ->where('slot_number', 0)
            ->countAllResults();
 
        // Cek jam yang sudah terpakai guru ini
        $teacherHours = $this->db->table('schedule_entries se')
            ->select('se.day, COUNT(*) as cnt')
            ->where('se.schedule_id', $id)
            ->where('se.teacher_id', $tid)
            ->groupBy('se.day')
            ->get()->getResultArray();
 
        $usagePerDay = [];
        foreach ($teacherHours as $th) {
            $usagePerDay[$dayNames[$th['day']]] = (int)$th['cnt'];
        }
 
        // Cek max_hours_per_day guru
        $maxHours = $this->db->table('schedule_teacher_settings')
            ->select('max_hours_per_day')
            ->where('teacher_id', $tid)
            ->get()->getRowArray();
        $maxH = $maxHours['max_hours_per_day'] ?? null;
        if (!$maxH) {
            $global = $this->db->table('settings')
                ->where('setting_key', 'schedule_max_hours_per_day')
                ->get()->getRowArray();
            $maxH = $global['setting_value'] ?? 6;
        }
 
        // Cek load ini sudah ada di hari mana
        $existingDays = $this->db->table('schedule_entries')
            ->select('day, COUNT(*) as cnt')
            ->where('schedule_id', $id)
            ->where('schedule_load_id', $c['load_id'])
            ->groupBy('day')
            ->get()->getResultArray();
 
        $existingDayNames = [];
        foreach ($existingDays as $ed) {
            $existingDayNames[] = $dayNames[$ed['day']] . '(' . $ed['cnt'] . 'j)';
        }
 
        $c['teacher_id']      = $tid;
        $c['blocked_days']    = $blockedDays;
        $c['max_hours_day']   = $maxH;
        $c['usage_per_day']   = $usagePerDay;
        $c['already_in_days'] = $existingDayNames;
 
        // Diagnosis sederhana
        $diagnosis = [];
        $availDays = count($activeDays) - $blockedDays;
        if ($availDays < 1) {
            $diagnosis[] = '❌ Semua hari diblokir untuk guru ini';
        }
        // Cek apakah ada hari yang masih punya slot untuk guru ini
        $hasFreeSlot = false;
        foreach ($activeDays as $day) {
            $usage = 0;
            foreach ($teacherHours as $th) {
                if ($th['day'] == $day) { $usage = (int)$th['cnt']; break; }
            }
            if ($usage < (int)$maxH && $kbmPerDay[$day] > $usage) {
                // Cek load belum ada di hari ini
                $alreadyThisDay = false;
                foreach ($existingDays as $ed) {
                    if ($ed['day'] == $day) { $alreadyThisDay = true; break; }
                }
                if (!$alreadyThisDay) { $hasFreeSlot = true; break; }
            }
        }
        if (!$hasFreeSlot) {
            $diagnosis[] = '❌ Tidak ada hari yang tersedia (guru penuh atau load sudah di semua hari)';
        }
        if ($c['missing'] > 0 && $hasFreeSlot) {
            $diagnosis[] = '⚠️ Ada slot tersedia tapi engine tidak berhasil menempatkan — coba generate ulang';
        }
 
        $c['diagnosis'] = $diagnosis;
    }
    unset($c);
 
    return $this->response->setJSON([
        'success'       => true,
        'total_missing' => $totalMissing,
        'conflicts'     => $conflicts,
        'kbm_per_day'   => $kbmPerDay,
        'active_days'   => array_map(fn($d) => $dayNames[$d], $activeDays),
    ]);
}


}