<?php

namespace App\Controllers;

use App\Models\ClassProgressReportModel;
use App\Models\ClassProgressAttachmentModel;
use App\Models\ClassSectionModel;
use App\Models\CalendarModel;
use App\Models\ConfigurationModel;
use App\Models\StudentClassModel;
use App\Models\SubjectCurriculumModel;
use App\Services\SemesterRangeService;
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\HTTP\ResponseInterface;

class AdminProgressController extends BaseController
{
    protected ClassProgressReportModel $reportModel;
    protected ClassProgressAttachmentModel $attachmentModel;
    protected ClassSectionModel $classSectionModel;
    protected StudentClassModel $studentClassModel;
    protected CalendarModel $calendarModel;
    protected ConfigurationModel $configModel;
    protected SemesterRangeService $semesterRangeService;
    protected SubjectCurriculumModel $curriculumModel;

    public function __construct()
    {
        helper(['url', 'form']);
        $this->reportModel = new ClassProgressReportModel();
        $this->attachmentModel = new ClassProgressAttachmentModel();
        $this->classSectionModel = new ClassSectionModel();
        $this->studentClassModel = new StudentClassModel();
        $this->calendarModel = new CalendarModel();
        $this->configModel = new ConfigurationModel();
        $this->semesterRangeService = new SemesterRangeService($this->configModel);
        $this->curriculumModel = new SubjectCurriculumModel();
    }

    public function index()
    {
        $schoolYear = trim((string) ($this->request->getGet('school_year') ?? ''));
        if ($schoolYear === '') {
            $schoolYear = $this->currentSchoolYearName((string) ($this->configModel->getConfig('school_year') ?? ''));
        }

        $filters = [
            'school_year' => $schoolYear,
            'from' => (string) $this->request->getGet('from'),
            'to' => (string) $this->request->getGet('to'),
            'class_section_id' => (string) $this->request->getGet('class_section_id'),
            'status' => (string) $this->request->getGet('status'),
        ];

        $builder = $this->reportModel
            ->select('class_progress_reports.*, cs.class_section_name, CONCAT(IFNULL(u.firstname, ""), " ", IFNULL(u.lastname, "")) AS teacher_name')
            ->join('classSection cs', 'cs.class_section_id = class_progress_reports.class_section_id', 'left')
            ->join('users u', 'u.id = class_progress_reports.teacher_id', 'left');

        $this->applyProgressSchoolYearScope($builder, $schoolYear);

        if ($filters['from']) {
            $builder->where('week_start >=', $filters['from']);
        }
        if ($filters['to']) {
            $builder->where('week_end <=', $filters['to']);
        }
        if ($filters['class_section_id']) {
            $builder->where('class_progress_reports.class_section_id', (int) $filters['class_section_id']);
        }
        if ($filters['status']) {
            $builder->where('class_progress_reports.status', $filters['status']);
        }

        $rows = $builder->orderBy('week_start', 'DESC')->get()->getResultArray();
        $reportGroupsBySection = [];
        foreach ($rows as $row) {
            $row['status_label'] = ClassProgressController::STATUS_OPTIONS[$row['status']] ?? 'Unknown';
            $sectionId = (int) ($row['class_section_id'] ?? 0);
            $weekKey = (string) ($row['week_start'] ?? '');
            if ($sectionId === 0 || $weekKey === '') {
                continue;
            }
            if (! isset($reportGroupsBySection[$sectionId])) {
                $reportGroupsBySection[$sectionId] = [];
            }
            if (! isset($reportGroupsBySection[$sectionId][$weekKey])) {
                $reportGroupsBySection[$sectionId][$weekKey] = [
                    'week_start' => $row['week_start'],
                    'week_end' => $row['week_end'],
                    'class_section_name' => $row['class_section_name'] ?? '',
                    'reports' => [],
                ];
            }
            $reportGroupsBySection[$sectionId][$weekKey]['reports'][$row['subject']] = $row;
        }
        foreach ($reportGroupsBySection as $sectionId => $groups) {
            krsort($groups);
            $reportGroupsBySection[$sectionId] = $groups;
        }

        $classSections = $this->classSectionsForYear($schoolYear, $rows);
        $studentCounts = $this->studentClassModel->getStudentCountsBySection($schoolYear !== '' ? $schoolYear : null);
        $filteredSections = array_values(array_filter($classSections, function ($section) use ($studentCounts, $reportGroupsBySection) {
            $sectionId = (int) ($section['class_section_id'] ?? 0);
            return (isset($studentCounts[$sectionId]) && $studentCounts[$sectionId] > 0)
                || isset($reportGroupsBySection[$sectionId]);
        }));

        $filterStart = $this->normalizeDate($filters['from'] ?? '');
        $filterEnd = $this->normalizeDate($filters['to'] ?? '');
        [$dateList, $noSchoolDays, $totalPassedDays, $passedDatesSet] = $this->buildSemesterDates($schoolYear);
        [$expectedDays, $activeDatesSet] = $this->resolveExpectedDays(
            $dateList,
            $noSchoolDays,
            $totalPassedDays,
            $passedDatesSet,
            $filterStart,
            $filterEnd
        );
        $sectionStats = $this->buildSectionSubmissionStats($rows, $activeDatesSet, $expectedDays);
        $sectionSubjectCounts = $this->buildSectionSubjectCounts($rows);
        $lowProgressSectionIds = [];
        if ($expectedDays > 0) {
            foreach ($filteredSections as $section) {
                $sectionId = (int) ($section['class_section_id'] ?? 0);
                if ($sectionId === 0) {
                    continue;
                }
                $stat = $sectionStats[$sectionId] ?? null;
                $percent = $stat['percent'] ?? 0;
                if ($stat === null) {
                    $percent = 0;
                }
                if ($percent < 50) {
                    $lowProgressSectionIds[] = $sectionId;
                }
            }
        }

        return view('admin/class_progress_list', [
            'reportGroupsBySection' => $reportGroupsBySection,
            'filters' => $filters,
            'schoolYear' => $schoolYear,
            'schoolYears' => $this->availableSchoolYears($schoolYear),
            'classSections' => $filteredSections,
            'statusOptions' => ClassProgressController::STATUS_OPTIONS,
            'subjectSections' => ClassProgressController::SUBJECT_SECTIONS,
            'sectionStats' => $sectionStats,
            'sectionSubjectCounts' => $sectionSubjectCounts,
            'expectedDays' => $expectedDays,
            'lowProgressSectionIds' => $lowProgressSectionIds,
        ]);
    }

    private function classSectionsForYear(string $schoolYear, array $reportRows = []): array
    {
        $db = db_connect();

        if ($schoolYear !== '' && $db->fieldExists('school_year', 'classSection')) {
            $sections = $db->table('classSection')
                ->select('classSection.class_section_id, classSection.class_section_name, classes.class_name')
                ->join('classes', 'classSection.class_id = classes.id', 'left')
                ->where('classSection.school_year', $schoolYear)
                ->orderBy('classSection.class_section_name', 'ASC')
                ->get()
                ->getResultArray();
        } else {
            $sections = $this->classSectionModel->getClassSections();
        }

        $sectionsById = [];
        foreach ($sections as $section) {
            $sectionId = (int) ($section['class_section_id'] ?? 0);
            if ($sectionId > 0) {
                $sectionsById[$sectionId] = $section;
            }
        }

        foreach ($reportRows as $row) {
            $sectionId = (int) ($row['class_section_id'] ?? 0);
            if ($sectionId <= 0 || isset($sectionsById[$sectionId])) {
                continue;
            }

            $sectionsById[$sectionId] = [
                'class_section_id' => $sectionId,
                'class_section_name' => (string) ($row['class_section_name'] ?? ('Section #' . $sectionId)),
                'class_name' => '',
            ];
        }

        uasort($sectionsById, static function (array $a, array $b): int {
            return strnatcasecmp((string) ($a['class_section_name'] ?? ''), (string) ($b['class_section_name'] ?? ''));
        });

        return array_values($sectionsById);
    }

    private function applyProgressSchoolYearScope($builder, string $schoolYear): void
    {
        if ($schoolYear === '') {
            return;
        }

        $db = db_connect();
        $hasReportYear = $db->fieldExists('school_year', 'class_progress_reports');
        $hasSectionYear = $db->fieldExists('school_year', 'classSection');
        [$rangeStart, $rangeEnd] = $this->semesterRangeService->getSchoolYearRange($schoolYear);

        if ($hasReportYear) {
            if ($this->hasAnyExplicitProgressSchoolYearRows()) {
                $builder->where('class_progress_reports.school_year', $schoolYear);
                return;
            }

            if ($rangeStart !== '' && $rangeEnd !== '') {
                $builder
                    ->groupStart()
                        ->groupStart()
                            ->where('class_progress_reports.school_year IS NULL', null, false)
                            ->orWhere('class_progress_reports.school_year', '')
                        ->groupEnd()
                        ->where('class_progress_reports.week_start >=', $rangeStart)
                        ->where('class_progress_reports.week_start <=', $rangeEnd)
                    ->groupEnd();
                return;
            }

            $builder->where('class_progress_reports.school_year', $schoolYear);
            return;
        }

        if ($hasSectionYear) {
            $builder->where('cs.school_year', $schoolYear);
            return;
        }

        if ($rangeStart !== '' && $rangeEnd !== '') {
            $builder
                ->where('class_progress_reports.week_start >=', $rangeStart)
                ->where('class_progress_reports.week_start <=', $rangeEnd);
        }
    }

    private function hasAnyExplicitProgressSchoolYearRows(): bool
    {
        try {
            return db_connect()
                ->table('class_progress_reports')
                ->where('school_year IS NOT NULL', null, false)
                ->where('school_year !=', '')
                ->limit(1)
                ->get()
                ->getRowArray() !== null;
        } catch (\Throwable $e) {
            log_message('warning', 'Unable to check explicit progress school-year rows: {message}', [
                'message' => $e->getMessage(),
            ]);
            return true;
        }
    }

    private function availableSchoolYears(string $selectedYear): array
    {
        $years = [];
        $addYear = static function (mixed $value) use (&$years): void {
            $year = trim((string) $value);
            if ($year !== '' && ! in_array($year, $years, true)) {
                $years[] = $year;
            }
        };

        $addYear($selectedYear);
        $addYear($this->currentSchoolYearName((string) ($this->configModel->getConfig('school_year') ?? '')));

        $db = db_connect();
        foreach ([
            ['table' => 'school_years', 'column' => 'name'],
            ['table' => 'class_progress_reports', 'column' => 'school_year'],
            ['table' => 'classSection', 'column' => 'school_year'],
            ['table' => 'student_class', 'column' => 'school_year'],
        ] as $source) {
            try {
                if (! $db->tableExists($source['table']) || ! $db->fieldExists($source['column'], $source['table'])) {
                    continue;
                }

                $rows = $db->table($source['table'])
                    ->select($source['column'])
                    ->distinct()
                    ->where($source['column'] . ' IS NOT NULL', null, false)
                    ->orderBy($source['column'], 'DESC')
                    ->get()
                    ->getResultArray();

                foreach ($rows as $row) {
                    $addYear($row[$source['column']] ?? '');
                }
            } catch (\Throwable $e) {
                log_message('warning', 'Unable to load school-year options for admin progress: {message}', [
                    'message' => $e->getMessage(),
                ]);
            }
        }

        rsort($years, SORT_NATURAL);

        return $years;
    }

    public function view($id)
    {
        $row = $this->reportModel
            ->select('class_progress_reports.*, cs.class_section_name, CONCAT(IFNULL(u.firstname, ""), " ", IFNULL(u.lastname, "")) AS teacher_name')
            ->join('classSection cs', 'cs.class_section_id = class_progress_reports.class_section_id', 'left')
            ->join('users u', 'u.id = class_progress_reports.teacher_id', 'left')
            ->find((int) $id);

        if (! $row) {
            throw new PageNotFoundException('Progress report not found.');
        }

        $row['status_label'] = ClassProgressController::STATUS_OPTIONS[$row['status']] ?? 'Unknown';
        $row['flags'] = $this->decodeFlags($row['flags_json']);

        $weeklyReports = $this->reportModel
            ->select('class_progress_reports.*')
            ->where('class_section_id', $row['class_section_id'])
            ->where('week_start', $row['week_start'])
            ->orderBy('subject', 'ASC')
            ->findAll();

        $attachmentMap = $this->loadAttachmentsForReports(array_column($weeklyReports, 'id'));
        foreach ($weeklyReports as &$report) {
            $report['attachments'] = $attachmentMap[$report['id']] ?? [];
            if (empty($report['attachments']) && ! empty($report['attachment_path'])) {
                $report['attachments'][] = [
                    'id' => $report['id'],
                    'name' => basename((string) $report['attachment_path']),
                    'legacy' => true,
                ];
            }
        }

        return view('admin/class_progress_view', [
            'row' => $row,
            'weeklyReports' => $weeklyReports,
            'subjectSections' => ClassProgressController::SUBJECT_SECTIONS,
        ]);
    }

    public function attachment($id)
    {
        $row = $this->reportModel->find((int)$id);
        if (! $row || empty($row['attachment_path'])) {
            throw new PageNotFoundException('Attachment not found.');
        }

        $file = $this->resolveAttachmentFile($row);
        if (! $file) {
            throw new PageNotFoundException('Attachment missing.');
        }

        return $this->response->download($file, null)->setFileName(basename($file));
    }

    public function attachmentFile($id)
    {
        $attachment = $this->attachmentModel->find((int) $id);
        if (! $attachment || empty($attachment['file_path'])) {
            throw new PageNotFoundException('Attachment not found.');
        }

        $file = $this->resolveAttachmentPath($attachment['file_path']);
        if (! $file) {
            throw new PageNotFoundException('Attachment missing.');
        }

        $downloadName = $attachment['original_name'] ?: basename($file);
        return $this->response->download($file, null)->setFileName($downloadName);
    }

    protected function resolveAttachmentFile(array $row): ?string
    {
        $path = trim((string) ($row['attachment_path'] ?? ''));
        if ($path === '') {
            return null;
        }
        return $this->resolveAttachmentPath($path);
    }

    protected function resolveAttachmentPath(string $path): ?string
    {
        $relative = preg_replace('#^writable/uploads/#', '', $path);
        $absolute = WRITEPATH . 'uploads/' . ltrim($relative, '/');
        return is_file($absolute) ? $absolute : null;
    }

    protected function loadAttachmentsForReports(array $reportIds): array
    {
        $reportIds = array_values(array_filter(array_map('intval', $reportIds)));
        if (empty($reportIds)) {
            return [];
        }

        $rows = $this->attachmentModel
            ->whereIn('report_id', $reportIds)
            ->orderBy('id', 'ASC')
            ->findAll();

        $map = [];
        foreach ($rows as $row) {
            $reportId = (int) ($row['report_id'] ?? 0);
            if ($reportId === 0) {
                continue;
            }
            $map[$reportId][] = [
                'id' => (int) $row['id'],
                'name' => $row['original_name'] ?: basename((string) $row['file_path']),
            ];
        }

        return $map;
    }

    protected function decodeFlags(?string $json): array
    {
        if (! $json) {
            return [];
        }

        $flags = json_decode($json, true);
        return is_array($flags) ? $flags : [];
    }

    protected function normalizeDate(string $value): string
    {
        $value = trim($value);
        if (! preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) {
            return '';
        }
        [$y, $m, $d] = array_map('intval', explode('-', $value));
        return checkdate($m, $d, $y) ? $value : '';
    }

    protected function buildSemesterDates(?string $schoolYear = null): array
    {
        $schoolYear = trim((string) ($schoolYear ?? ''));
        if ($schoolYear === '') {
            $schoolYear = $this->currentSchoolYearName((string) ($this->configModel->getConfig('school_year') ?? ''));
        }

        $semester = (string) ($this->configModel->getConfig('semester') ?? '');
        $schoolYearForRange = $schoolYear !== '' ? $schoolYear : $this->currentSchoolYearName((string) ($this->configModel->getConfig('school_year') ?? ''));
        [$rangeStart, $rangeEnd] = $this->semesterRangeService->getSchoolYearRange($schoolYearForRange);
        $semesterNorm = $this->semesterRangeService->normalizeSemester($semester);
        if ($semesterNorm !== '' && $schoolYearForRange !== '') {
            $semRange = $this->semesterRangeService->getSemesterRange($schoolYearForRange, $semesterNorm);
            if ($semRange) {
                [$rangeStart, $rangeEnd] = $semRange;
            }
        }

        $dateList = [];
        try {
            $start = new \DateTimeImmutable($rangeStart);
            $end = new \DateTimeImmutable($rangeEnd);
            $cursor = $start;
            $w = (int) $cursor->format('w');
            if ($w !== 0) {
                $cursor = $cursor->modify('next sunday');
            }
            while ($cursor <= $end) {
                $dateList[] = $cursor->format('Y-m-d');
                $cursor = $cursor->modify('+7 days');
            }
        } catch (\Throwable $e) {
            $dateList = [];
        }

        $noSchoolDays = [];
        $events = [];
        try {
            $events = $this->calendarModel->getEvents();
        } catch (\Throwable $e) {
            $events = [];
        }
        foreach ($events as $event) {
            $d = substr((string) ($event['date'] ?? ''), 0, 10);
            if ($d === '' || empty($event['no_school'])) {
                continue;
            }
            if ($d < $rangeStart || $d > $rangeEnd) {
                continue;
            }
            $eventYear = trim((string) ($event['school_year'] ?? ''));
            if ($schoolYearForRange !== '' && $eventYear !== '' && $eventYear !== $schoolYearForRange) {
                continue;
            }
            $noSchoolDays[$d] = true;
        }

        $anchorSundayYmd = '';
        try {
            $tzName = (string) (config('School')->attendance['timezone'] ?? user_timezone());
            $tzObj = new \DateTimeZone($tzName ?: 'UTC');
        } catch (\Throwable $e) {
            try {
                $tzObj = new \DateTimeZone(user_timezone() ?: 'UTC');
            } catch (\Throwable $e2) {
                $tzObj = new \DateTimeZone('UTC');
            }
        }
        try {
            $nowDate = new \DateTime('now', $tzObj);
        } catch (\Throwable $e) {
            $nowDate = new \DateTime('now');
        }
        $weekday = (int) $nowDate->format('w');
        $anchorSundayYmd = $weekday === 0
            ? $nowDate->format('Y-m-d')
            : $nowDate->modify('next sunday')->format('Y-m-d');

        $passedDatesSet = [];
        if (! empty($dateList) && $anchorSundayYmd !== '') {
            foreach ($dateList as $d) {
                if ($d <= $anchorSundayYmd && empty($noSchoolDays[$d])) {
                    $passedDatesSet[$d] = true;
                }
            }
        }
        $totalPassedDays = count($passedDatesSet);

        return [$dateList, $noSchoolDays, $totalPassedDays, $passedDatesSet];
    }

    protected function resolveExpectedDays(
        array $dateList,
        array $noSchoolDays,
        int $totalPassedDays,
        array $passedDatesSet,
        string $filterStart,
        string $filterEnd
    ): array {
        $activeDatesSet = [];
        if ($filterStart === '' && $filterEnd === '') {
            $expectedDays = $totalPassedDays;
            if ($expectedDays > 0) {
                $activeDatesSet = $passedDatesSet;
            }
            return [$expectedDays, $activeDatesSet];
        }

        $expectedDays = 0;
        foreach ($dateList as $d) {
            if ($d === '') {
                continue;
            }
            if ($filterStart !== '' && $d < $filterStart) {
                continue;
            }
            if ($filterEnd !== '' && $d > $filterEnd) {
                continue;
            }
            if (! empty($noSchoolDays[$d])) {
                continue;
            }
            $activeDatesSet[$d] = true;
            $expectedDays++;
        }

        return [$expectedDays, $activeDatesSet];
    }

    protected function buildSectionSubmissionStats(array $rows, array $activeDatesSet, int $expectedDays): array
    {
        $submittedBySection = [];
        foreach ($rows as $row) {
            $sectionId = (int) ($row['class_section_id'] ?? 0);
            $weekStart = (string) ($row['week_start'] ?? '');
            if ($sectionId === 0 || $weekStart === '') {
                continue;
            }
            $submittedBySection[$sectionId][$weekStart] = true;
        }

        $stats = [];
        foreach ($submittedBySection as $sectionId => $weeks) {
            $submitted = count($weeks);
            $stats[$sectionId] = $this->buildSectionStat($submitted, $expectedDays);
        }

        return $stats;
    }

    protected function buildSectionSubjectCounts(array $rows): array
    {
        $allowedSubjects = [];
        foreach (ClassProgressController::SUBJECT_SECTIONS as $section) {
            $allowedSubjects[] = $section['db_subject'] ?? $section['label'] ?? '';
        }
        $allowedSubjects = array_values(array_filter($allowedSubjects));

        $counts = [];
        $sectionClassMap = [];
        foreach ($rows as $row) {
            $sectionId = (int) ($row['class_section_id'] ?? 0);
            $subject = (string) ($row['subject'] ?? '');
            if ($sectionId === 0 || $subject === '') {
                continue;
            }
            if (! empty($allowedSubjects) && ! in_array($subject, $allowedSubjects, true)) {
                continue;
            }
            if (! isset($sectionClassMap[$sectionId])) {
                $sectionClassMap[$sectionId] = $this->classSectionModel->getClassId($sectionId);
            }
        }

        $curriculumUnits = $this->buildCurriculumUnitMap(array_values(array_filter($sectionClassMap)));

        foreach ($rows as $row) {
            $sectionId = (int) ($row['class_section_id'] ?? 0);
            $subject = (string) ($row['subject'] ?? '');
            if ($sectionId === 0 || $subject === '') {
                continue;
            }
            if (! empty($allowedSubjects) && ! in_array($subject, $allowedSubjects, true)) {
                continue;
            }
            $subjectSlug = $this->resolveSubjectSlug($subject);
            $classId = $sectionClassMap[$sectionId] ?? null;
            $chapterToUnit = [];
            if ($classId && $subjectSlug && ! empty($curriculumUnits[$classId][$subjectSlug]['chapter_to_unit'])) {
                $chapterToUnit = $curriculumUnits[$classId][$subjectSlug]['chapter_to_unit'];
            }
            $unitKeys = $this->extractUnitKeys((string) ($row['unit_title'] ?? ''), $chapterToUnit);
            foreach ($unitKeys as $unitKey) {
                $key = $subjectSlug . '|' . $unitKey;
                $counts[$sectionId][$key] = true;
            }
        }

        $totals = [];
        foreach ($counts as $sectionId => $unitSet) {
            $totals[$sectionId] = count($unitSet);
        }

        return $totals;
    }

    protected function buildCurriculumUnitMap(array $classIds): array
    {
        $classIds = array_values(array_filter(array_map('intval', $classIds)));
        if (empty($classIds)) {
            return [];
        }

        $rows = $this->curriculumModel
            ->whereIn('class_id', $classIds)
            ->findAll();

        $map = [];
        foreach ($rows as $row) {
            $classId = (int) ($row['class_id'] ?? 0);
            $subject = (string) ($row['subject'] ?? '');
            $chapter = trim((string) ($row['chapter_name'] ?? ''));
            $unitNumber = $row['unit_number'] ?? null;
            if ($classId === 0 || $subject === '' || $chapter === '') {
                continue;
            }
            if ($unitNumber === null || $unitNumber === '') {
                continue;
            }
            $unitKey = (string) $unitNumber;
            $map[$classId][$subject]['chapter_to_unit'][$chapter] = $unitKey;
        }

        return $map;
    }

    protected function resolveSubjectSlug(string $subject): ?string
    {
        foreach (ClassProgressController::SUBJECT_SECTIONS as $slug => $section) {
            $dbSubject = (string) ($section['db_subject'] ?? '');
            $label = (string) ($section['label'] ?? '');
            if ($subject === $dbSubject || $subject === $label) {
                return $slug;
            }
        }
        return null;
    }

    protected function countUnitSegments(string $unitTitle, array $chapterToUnit): int
    {
        $unitTitle = trim($unitTitle);
        if ($unitTitle === '') {
            return 0;
        }
        $parts = array_filter(array_map('trim', explode(';', $unitTitle)), static fn ($part) => $part !== '');
        if (! $parts) {
            return 0;
        }

        $seen = [];
        foreach ($parts as $part) {
            [$unitPart, $chapterPart] = $this->splitUnitChapterSegment($part);
            $key = $this->resolveUnitKey($unitPart, $chapterPart, $chapterToUnit);
            if ($key === '') {
                $key = $part;
            }
            if (isset($seen[$key])) {
                continue;
            }
            $seen[$key] = true;
        }

        return count($seen);
    }

    protected function splitUnitChapterSegment(string $segment): array
    {
        $segment = trim($segment);
        if ($segment === '') {
            return ['', ''];
        }
        $pos = strrpos($segment, '/');
        if ($pos === false) {
            return [$segment, ''];
        }
        $unitPart = trim(substr($segment, 0, $pos));
        $chapterPart = trim(substr($segment, $pos + 1));
        return [$unitPart, $chapterPart];
    }

    protected function extractUnitKeys(string $unitTitle, array $chapterToUnit): array
    {
        $unitTitle = trim($unitTitle);
        if ($unitTitle === '') {
            return [];
        }
        $parts = array_filter(array_map('trim', explode(';', $unitTitle)), static fn ($part) => $part !== '');
        if (! $parts) {
            return [];
        }

        $keys = [];
        foreach ($parts as $part) {
            [$unitPart, $chapterPart] = $this->splitUnitChapterSegment($part);
            $key = $this->resolveUnitKey($unitPart, $chapterPart, $chapterToUnit);
            if ($key === '') {
                continue;
            }
            $keys[$key] = true;
        }

        return array_keys($keys);
    }

    protected function resolveUnitKey(string $unitPart, string $chapterPart, array $chapterToUnit): string
    {
        if ($chapterPart !== '' && ! empty($chapterToUnit[$chapterPart])) {
            return (string) $chapterToUnit[$chapterPart];
        }
        if (empty($chapterToUnit)) {
            if (preg_match('/\bunit\s*(\d+)\b/i', $unitPart, $matches)) {
                return (string) $matches[1];
            }
            return '';
        }
        if (preg_match('/\bunit\s*(\d+)\b/i', $unitPart, $matches)) {
            return (string) $matches[1];
        }
        if ($unitPart !== '') {
            return $unitPart;
        }
        if ($chapterPart !== '') {
            return $chapterPart;
        }
        return '';
    }

    protected function buildSectionStat(int $submitted, int $expectedDays): array
    {
        $percent = $expectedDays > 0 ? round(($submitted * 100) / $expectedDays, 1) : 0.0;
        if ($expectedDays === 0) {
            $badgeClass = 'bg-secondary';
            $labelClass = 'text-muted';
        } elseif ($percent >= 100) {
            $badgeClass = 'bg-success';
            $labelClass = 'text-success';
        } elseif ($percent >= 60) {
            $badgeClass = 'bg-warning text-dark';
            $labelClass = 'text-warning';
        } elseif ($percent >= 40) {
            $badgeClass = 'bg-orange text-dark';
            $labelClass = 'text-warning';
        } else {
            $badgeClass = 'bg-danger';
            $labelClass = 'text-danger';
        }

        return [
            'submitted' => $submitted,
            'expected' => $expectedDays,
            'percent' => $percent,
            'badgeClass' => $badgeClass,
            'labelClass' => $labelClass,
        ];
    }
}
