<?php

namespace App\Services\Attendance;

use App\Models\AttendanceData;
use App\Models\AttendanceDay;
use App\Models\AttendanceRecord;
use App\Models\Calendar;
use App\Models\ClassSection;
use App\Models\Configuration;
use App\Models\Student;
use App\Models\StudentClass;
use App\Models\TeacherClass;
use App\Models\User;
use App\Models\UserRole;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use RuntimeException;

class AttendanceQueryService
{
    public function __construct(
        protected Configuration $configuration,
        protected AttendanceData $attendanceData,
        protected AttendanceDay $attendanceDay,
        protected AttendanceRecord $attendanceRecord,
        protected Student $student,
        protected StudentClass $studentClass,
        protected ClassSection $classSection,
        protected TeacherClass $teacherClass,
        protected Calendar $calendar,
        protected User $user,
        protected UserRole $userRole,
        protected AttendanceService $attendanceService,
        protected SemesterRangeService $semesterRangeService,
    ) {}

    public function teacherGrid(string $semester, string $schoolYear, string $date, int $sectionCode): array
    {
        $bySection = $this->teacherClass->assignedBySectionForTerm($semester, $schoolYear);
        $sectionCodes = array_keys($bySection);

        $labels = [];
        if (!empty($sectionCodes)) {
            $rows = $this->classSection
                ->query()
                ->select(['class_section_id', 'class_section_name'])
                ->whereIn('class_section_id', $sectionCodes)
                ->orderBy('class_section_id')
                ->get()
                ->toArray();

            foreach ($rows as $row) {
                $code = (int)$row['class_section_id'];
                $name = trim((string)($row['class_section_name'] ?? ''));
                $labels[$code] = $name !== '' ? $name : 'Section #' . $code;
            }

            foreach ($sectionCodes as $code) {
                $labels[$code] ??= 'Section #' . $code;
            }
        }

        $grid = [];
        $locked = false;

        if ($sectionCode > 0) {
            $assigned = $this->teacherClass->assignedForSectionTerm($sectionCode, $semester, $schoolYear);
            $teacherIds = array_map(fn($r) => (int)$r['teacher_id'], $assigned);

            $statusMap = [];
            if (!empty($teacherIds)) {
                $rows = DB::table('staff_attendance as sa')
                    ->select('sa.user_id', 'sa.status', 'sa.reason')
                    ->where('sa.semester', $semester)
                    ->where('sa.school_year', $schoolYear)
                    ->whereDate('sa.date', $date)
                    ->whereIn('sa.user_id', $teacherIds)
                    ->get();

                foreach ($rows as $row) {
                    $statusMap[(int)$row->user_id] = [
                        'status' => $row->status,
                        'reason' => $row->reason,
                    ];
                }
            }

            foreach ($assigned as $teacher) {
                $teacherId = (int)$teacher['teacher_id'];
                $position = strtolower((string)($teacher['position'] ?? 'main'));
                $position = in_array($position, ['main', 'ta'], true) ? $position : 'main';
                $name = trim(($teacher['firstname'] ?? '') . ' ' . ($teacher['lastname'] ?? ''));
                $cell = $statusMap[$teacherId] ?? ['status' => null, 'reason' => null];

                $grid[] = [
                    'teacher_id' => $teacherId,
                    'name' => $name !== '' ? $name : 'User#' . $teacherId,
                    'position' => $position,
                    'status' => $cell['status'],
                    'reason' => $cell['reason'],
                ];
            }

            try {
                $locked = method_exists($this->attendanceDay, 'isFinalized')
                    ? $this->attendanceDay->isFinalized($sectionCode, $date, $semester, $schoolYear)
                    : false;
            } catch (\Throwable) {
                $locked = false;
            }
        }

        return [
            'semester' => $semester,
            'school_year' => $schoolYear,
            'date' => $date,
            'class_section_id' => $sectionCode,
            'sections' => $labels,
            'grid' => $grid,
            'locked' => $locked,
        ];
    }

    public function teacherAttendanceFormData(?int $userId, int $requestedSectionId = 0): array
    {
        if (!$userId) {
            throw new RuntimeException('User not logged in.');
        }

        $teacher = $this->user->find($userId);
        $teacherName = $teacher
            ? trim(($teacher->firstname ?? '') . ' ' . ($teacher->lastname ?? ''))
            : 'Unknown Teacher';

        $semester = $this->attendanceService->currentSemester();
        $schoolYear = $this->attendanceService->currentSchoolYear();

        $assignments = $this->teacherClass->getClassAssignmentsByUserId($userId, $schoolYear, $semester);

        if (empty($assignments)) {
            throw new RuntimeException('You do not have an assigned class yet.');
        }

        $activeSection = null;
        foreach ($assignments as $assignment) {
            $cid = (int)($assignment['class_section_id'] ?? 0);
            $pk = (int)($assignment['class_section_pk'] ?? 0);

            if ($requestedSectionId > 0 && ($requestedSectionId === $cid || $requestedSectionId === $pk)) {
                $activeSection = $assignment;
                break;
            }
        }

        $activeSection ??= $assignments[0];

        $classSectionCode = (int)($activeSection['class_section_id'] ?? 0);
        $classSectionPk = (int)($activeSection['class_section_pk'] ?? 0);
        $classSectionId = $classSectionCode > 0 ? $classSectionCode : $classSectionPk;

        if ($classSectionId <= 0) {
            throw new RuntimeException('Unable to determine your class section.');
        }

        $today = Carbon::today();
        $anchorSunday = $today->dayOfWeek === Carbon::SUNDAY ? $today->copy() : $today->copy()->next(Carbon::SUNDAY);

        $sundayDates = [
            $anchorSunday->copy()->subWeeks(2)->toDateString(),
            $anchorSunday->copy()->subWeek()->toDateString(),
            $anchorSunday->toDateString(),
        ];
        $currentSunday = $sundayDates[2];

        $students = $this->student->getByClassAndYear($classSectionId, $schoolYear, $semester);
        if (empty($students) && $classSectionPk > 0 && $classSectionPk !== $classSectionId) {
            $students = $this->student->getByClassAndYear($classSectionPk, $schoolYear, $semester);
        }

        $attendanceRows = [];
        $lockedByStudent = [];

        $attendanceTable = $this->attendanceData->getTable();
        $dateInSundaysSql = 'DATE(`' . $attendanceTable . '`.`date`) IN (' .
            implode(',', array_fill(0, count($sundayDates), '?')) . ')';

        foreach ($students as $student) {
            $studentId = (int)$student['id'];

            $rows = AttendanceData::query()
                ->select(['date', 'status', 'is_reported', 'reason', 'modified_by'])
                ->where('student_id', $studentId)
                ->where('school_year', $schoolYear)
                ->where('semester', $semester)
                ->where(function ($q) use ($classSectionId, $classSectionPk) {
                    $q->where('class_section_id', $classSectionId);
                    if ($classSectionPk > 0 && $classSectionPk !== $classSectionId) {
                        $q->orWhere('class_section_id', $classSectionPk);
                    }
                })
                ->whereRaw($dateInSundaysSql, $sundayDates)
                ->get()
                ->map(fn($row) => (array)$row)
                ->all();

            $rows = $this->attendanceService->normalizeAttendanceEntries($rows);

            $recentRows = AttendanceData::query()
                ->select(['date', 'status'])
                ->where('student_id', $studentId)
                ->where('school_year', $schoolYear)
                ->where('semester', $semester)
                ->where(function ($q) use ($classSectionId, $classSectionPk) {
                    $q->where('class_section_id', $classSectionId);
                    if ($classSectionPk > 0 && $classSectionPk !== $classSectionId) {
                        $q->orWhere('class_section_id', $classSectionPk);
                    }
                })
                ->orderByDesc('date')
                ->limit(3)
                ->get()
                ->toArray();

            $recentHistory = [];
            foreach ($recentRows as $row) {
                $recentHistory[] = [
                    'date' => substr((string)($row['date'] ?? ''), 0, 10),
                    'status' => strtolower((string)($row['status'] ?? '')),
                ];
            }

            $snapshot = [];
            $rowsByDate = [];
            foreach ($rows as $entry) {
                $d = $this->calendarDateKey($entry['date'] ?? null);
                if ($d === '') {
                    continue;
                }
                $snapshot[$d] = $entry['status'] ?? null;
                $rowsByDate[$d] = $entry;
            }

            $statusHistory = [];
            foreach ($sundayDates as $date) {
                $statusHistory[$date] = array_key_exists($date, $snapshot)
                    ? $snapshot[$date]
                    : ($date === $currentSunday ? null : 'N/A');
            }

            // Recent list uses the same section filters but no Sunday window; if the windowed query
            // missed rows (SQL/driver quirks), still show statuses that match the visible columns.
            foreach ($recentRows as $row) {
                $d = $this->calendarDateKey($row['date'] ?? null);
                if ($d === '' || !in_array($d, $sundayDates, true)) {
                    continue;
                }
                $rowStatus = $row['status'] ?? null;
                if ($rowStatus === null || $rowStatus === '') {
                    continue;
                }
                $current = $statusHistory[$d] ?? null;
                if ($current === 'N/A' || $current === null) {
                    $statusHistory[$d] = $rowStatus;
                }
            }

            $todayRow = $rowsByDate[$currentSunday] ?? null;
            $lockInfo = $this->attendanceService->detectExternalSubmission($todayRow);
            $todayReason = $todayRow['reason'] ?? null;

            $attendanceRows[] = [
                'student' => $student,
                'sunday_statuses' => $statusHistory,
                'today' => $statusHistory[$currentSunday] ?? null,
                'today_reason' => $todayReason,
                'locked' => $lockInfo['locked'],
                'locked_source' => $lockInfo['source'],
                'recent_three' => $recentHistory,
            ];

            $lockedByStudent[$studentId] = [
                'locked' => $lockInfo['locked'],
                'source' => $lockInfo['source'],
                'reason' => $todayReason,
                'status' => $statusHistory[$currentSunday] ?? null,
            ];
        }

        $candidates = array_values(array_unique(array_filter(
            [$classSectionCode, $classSectionPk],
            static fn (int $v): bool => $v > 0,
        )));
        $alsoSectionId = null;
        foreach ($candidates as $cid) {
            if ($cid !== $classSectionId) {
                $alsoSectionId = $cid;
                break;
            }
        }

        $assigned = $this->teacherClass->assignedForSectionTerm(
            $classSectionId,
            $semester,
            $schoolYear,
            $alsoSectionId,
            true,
        );
        if ($assigned === []) {
            $assigned = $this->teacherClass->assignedForSectionTerm(
                $classSectionId,
                $semester,
                $schoolYear,
                $alsoSectionId,
                false,
            );
        }
        if ($assigned === []) {
            $assigned = $this->teacherRosterFromStaffAttendance(
                $sundayDates,
                $semester,
                $schoolYear,
                $classSectionId,
                $classSectionPk,
            );
        }
        if ($assigned === [] && $userId > 0) {
            $self = $this->user->query()->select(['id', 'firstname', 'lastname'])->find($userId);
            if ($self) {
                $assigned = [[
                    'class_section_id' => $classSectionId,
                    'teacher_id' => (int) $self->id,
                    'position' => 'main',
                    'firstname' => $self->firstname,
                    'lastname' => $self->lastname,
                ]];
            }
        }

        $teacherRows = [];

        if (!empty($assigned)) {
            $teacherIds = array_map(static fn($r) => (int)$r['teacher_id'], $assigned);

            $statusMap = [];
            if (!empty($teacherIds)) {
                $staffDateInSql = 'DATE(`sa`.`date`) IN (' .
                    implode(',', array_fill(0, count($sundayDates), '?')) . ')';
                $staffQ = DB::table('staff_attendance as sa')
                    ->select('sa.user_id', 'sa.date', 'sa.status', 'sa.reason')
                    ->where('sa.semester', $semester)
                    ->where('sa.school_year', $schoolYear)
                    ->whereIn('sa.user_id', $teacherIds)
                    ->whereRaw($staffDateInSql, $sundayDates);

                if (Schema::hasColumn('staff_attendance', 'class_section_id')) {
                    $staffQ->where(function ($w) use ($classSectionId, $classSectionPk) {
                        $w->whereNull('sa.class_section_id')
                            ->orWhere('sa.class_section_id', 0)
                            ->orWhere('sa.class_section_id', $classSectionId);
                        if ($classSectionPk > 0 && $classSectionPk !== $classSectionId) {
                            $w->orWhere('sa.class_section_id', $classSectionPk);
                        }
                    });
                }

                $rows = $staffQ->get();

                foreach ($rows as $row) {
                    $dk = $this->calendarDateKey($row->date ?? null);
                    if ($dk === '') {
                        continue;
                    }
                    $statusMap[(int)$row->user_id][$dk] = [
                        'status' => $row->status,
                        'reason' => $row->reason,
                    ];
                }
            }

            foreach ($assigned as $teacher) {
                $teacherId = (int)$teacher['teacher_id'];
                $position = strtolower((string)($teacher['position'] ?? 'main'));
                $position = in_array($position, ['main', 'ta'], true) ? $position : 'main';
                $name = trim(($teacher['firstname'] ?? '') . ' ' . ($teacher['lastname'] ?? ''));

                $history = [];
                foreach ($sundayDates as $date) {
                    $history[$date] = isset($statusMap[$teacherId][$date]['status'])
                        ? $statusMap[$teacherId][$date]['status']
                        : ($date === $currentSunday ? null : 'N/A');
                }

                $todayReason = null;
                if (isset($statusMap[$teacherId][$currentSunday]) && is_array($statusMap[$teacherId][$currentSunday])) {
                    $todayReason = $statusMap[$teacherId][$currentSunday]['reason'] ?? null;
                }

                $teacherRows[] = [
                    'teacher_id' => $teacherId,
                    'position' => $position,
                    'name' => $name !== '' ? $name : 'User#' . $teacherId,
                    'sunday_statuses' => $history,
                    'today' => $history[$currentSunday] ?? null,
                    'today_reason' => $todayReason,
                ];
            }
        }

        $noSchoolDays = [];
        $calendarTable = $this->calendar->getTable();
        $calDateInSql = 'DATE(`' . $calendarTable . '`.`date`) IN (' .
            implode(',', array_fill(0, count($sundayDates), '?')) . ')';
        $calendarRows = $this->calendar->query()
            ->where('no_school', 1)
            ->whereRaw($calDateInSql, $sundayDates)
            ->where('semester', $semester)
            ->where('school_year', $schoolYear)
            ->get()
            ->toArray();

        foreach ($calendarRows as $row) {
            $d = $this->calendarDateKey($row['date'] ?? null);
            if ($d === '') {
                continue;
            }
            $noSchoolDays[$d] = [
                'title' => $row['title'] ?? 'No School',
                'description' => $row['description'] ?? '',
            ];
        }

        return [
            'teacher_name' => $teacherName,
            'students' => $attendanceRows,
            'teachers' => $teacherRows,
            'class_section_id' => $classSectionId,
            'sunday_dates' => $sundayDates,
            'current_sunday' => $currentSunday,
            'enable_attendance' => (int)$this->configuration->getConfig('enable_attendance'),
            'can_edit' => ((int)$this->configuration->getConfig('enable_attendance') === 1),
            'class_id' => (int)$this->classSection->getClassId($classSectionId),
            'no_school_days' => $noSchoolDays,
            'today' => $currentSunday,
            'locked_by_student' => $lockedByStudent,
        ];
    }

    public function buildDailyAttendanceData(string $termSemester, string $termYear): array
    {
        $effectiveTermYear = $termYear;

        $loadClassSections = function (string $schoolYear): array {
            if ($schoolYear === '') {
                return [];
            }

            return $this->classSection
                ->query()
                ->select('classSection.id', 'classSection.class_id', 'classSection.class_section_id', 'classSection.class_section_name')
                ->join('student_class as sc', 'sc.class_section_id', '=', 'classSection.class_section_id')
                ->where('sc.school_year', $schoolYear)
                ->groupBy('classSection.id', 'classSection.class_id', 'classSection.class_section_id', 'classSection.class_section_name')
                ->get()
                ->map(fn($row) => $row->toArray())
                ->all();
        };

        $classSections = $loadClassSections($effectiveTermYear);

        if ($classSections === []) {
            $fallbackYear = (string) DB::table('student_class')
                ->whereNotNull('school_year')
                ->where('school_year', '<>', '')
                ->orderByDesc('school_year')
                ->value('school_year');

            if ($fallbackYear !== '' && $fallbackYear !== $effectiveTermYear) {
                $effectiveTermYear = $fallbackYear;
                $classSections = $loadClassSections($effectiveTermYear);
            }
        }

        $attendanceData = [];
        $attendanceRecord = [];
        $studentsBySection = [];
        $grades = [];
        $datesBySection = [];
        $studentSchoolMap = [];

        $sectionCounts = $this->studentClass->getStudentCountsBySection($effectiveTermYear);

        foreach ($classSections as $classSection) {
            $secPk = (int)($classSection['id'] ?? 0);
            $secCodeRaw = (string)($classSection['class_section_id'] ?? '');
            $secCode = $secCodeRaw !== '' ? $secCodeRaw : (string)$secPk;
            $classId = (int)($classSection['class_id'] ?? 0);

            $studentsBySection[$secCode] = [];
            $datesBySection[$secCode] = [];
            $attendanceData[$secCode] = [];
            $attendanceRecord[$secCode] = [];

            $countForCode = (int)($sectionCounts[$secCode] ?? 0);
            $countForPk = (int)($sectionCounts[$secPk] ?? 0);
            $sectionHasStudents = ($countForCode + $countForPk) > 0;

            $students = $this->studentClass->getClassStudents($secCode, $effectiveTermYear);
            if (!$students && $secPk && (string)$secPk !== (string)$secCode) {
                $students = $this->studentClass->getClassStudents($secPk, $effectiveTermYear);
            }

            if (!$sectionHasStudents || !$students) {
                continue;
            }

            // Bulk-load student profiles for the whole section in one query
            $studentsArray = is_array($students) ? $students : $students->toArray();
            $studentIds = array_map(fn($sc) => (int)($sc['student_id'] ?? $sc->student_id ?? 0), $studentsArray);
            $studentProfiles = $this->student
                ->query()
                ->select(['id', 'firstname', 'lastname', 'school_id'])
                ->whereIn('id', $studentIds)
                ->where('is_active', 1)
                ->get()
                ->keyBy('id')
                ->map(fn($row) => $row->toArray())
                ->all();

            $activeIds = array_keys($studentProfiles);
            if (empty($activeIds)) {
                unset($studentsBySection[$secCode], $datesBySection[$secCode], $attendanceData[$secCode], $attendanceRecord[$secCode]);
                continue;
            }

            // Bulk-load all attendance entries for the section in one query
            $allEntries = AttendanceData::query()
                ->whereIn('student_id', $activeIds)
                ->where(function ($q) use ($secCode, $secPk) {
                    $q->where('class_section_id', $secCode);
                    if ($secPk && (string)$secPk !== (string)$secCode) {
                        $q->orWhere('class_section_id', $secPk);
                    }
                })
                ->when($termSemester !== '', fn($q) => $q->where('semester', $termSemester))
                ->when($effectiveTermYear !== '', fn($q) => $q->where('school_year', $effectiveTermYear))
                ->orderBy('date')
                ->get()
                ->map(fn($row) => $row->toArray())
                ->groupBy('student_id')
                ->map(fn($group) => $group->values()->all())
                ->all();

            // Bulk-load all attendance records (summaries) for the section in one query
            $allRecords = AttendanceRecord::query()
                ->whereIn('student_id', $activeIds)
                ->where(function ($q) use ($secCode, $secPk) {
                    $q->where('class_section_id', $secCode);
                    if ($secPk && (string)$secPk !== (string)$secCode) {
                        $q->orWhere('class_section_id', $secPk);
                    }
                })
                ->when($termSemester !== '', fn($q) => $q->where('semester', $termSemester))
                ->when($effectiveTermYear !== '', fn($q) => $q->where('school_year', $effectiveTermYear))
                ->get()
                ->keyBy('student_id')
                ->map(fn($row) => $row->toArray())
                ->all();

            $hasRoster = false;

            foreach ($studentIds as $studentId) {
                $student = $studentProfiles[$studentId] ?? null;
                if (!$student) {
                    continue;
                }

                $studentsBySection[$secCode][] = $student;
                $studentSchoolMap[$studentId] = (string)($student['school_id'] ?? '');
                $hasRoster = true;

                $entries = $this->attendanceService->normalizeAttendanceEntries(
                    array_map(fn($r) => (array)$r, $allEntries[$studentId] ?? [])
                );
                $attendanceData[$secCode][$studentId] = $entries;

                foreach ($entries as $entry) {
                    $d = (string)($entry['date'] ?? '');
                    if ($d !== '') {
                        $datesBySection[$secCode][$d] = true;
                    }
                }

                $attendanceRecord[$secCode][$studentId] = $allRecords[$studentId] ?? [
                    'total_presence' => 0,
                    'total_late' => 0,
                    'total_absence' => 0,
                    'total_attendance' => 0,
                ];
            }

            if (!$hasRoster) {
                unset($studentsBySection[$secCode], $datesBySection[$secCode], $attendanceData[$secCode], $attendanceRecord[$secCode]);
                continue;
            }

            $grades[$classId][] = $classSection;
        }

        foreach ($attendanceData as $secId => &$studentEntries) {
            foreach ($studentEntries as &$entries) {
                usort($entries, static fn($a, $b) => strcmp((string)($a['date'] ?? ''), (string)($b['date'] ?? '')));
            }
        }
        unset($studentEntries, $entries);

        foreach ($datesBySection as $sec => $set) {
            $arr = array_keys($set);
            sort($arr, SORT_STRING);
            $datesBySection[$sec] = $arr;
        }

        ksort($grades, SORT_NUMERIC);

        [$rangeStart, $rangeEnd] = $this->semesterRangeService->getSchoolYearRange($effectiveTermYear);
        $semesterNorm = $this->semesterRangeService->normalizeSemester($termSemester);

        if ($semesterNorm !== '') {
            $semRange = $this->semesterRangeService->getSemesterRange($effectiveTermYear, $semesterNorm);
            if ($semRange) {
                [$rangeStart, $rangeEnd] = $semRange;
            }
        }

        $dateList = $this->semesterRangeService->buildSundayList($rangeStart, $rangeEnd);

        $noSchoolDays = [];
        $events = $this->calendar->query()
            ->where('no_school', 1)
            ->where('date', '>=', $rangeStart)
            ->where('date', '<=', $rangeEnd)
            ->get()
            ->toArray();

        foreach ($events as $event) {
            $d = substr((string)($event['date'] ?? ''), 0, 10);
            if ($d !== '') {
                $noSchoolDays[$d] = trim((string)($event['title'] ?? 'No School')) ?: 'No School';
            }
        }

        $anchorSunday = now()->dayOfWeek === Carbon::SUNDAY
            ? now()->toDateString()
            : now()->next(Carbon::SUNDAY)->toDateString();

        $passedDatesSet = [];
        foreach ($dateList as $d) {
            if ($d <= $anchorSunday && empty($noSchoolDays[$d])) {
                $passedDatesSet[$d] = true;
            }
        }

        $totalPassedDays = count($passedDatesSet);

        if ($totalPassedDays > 0 && $effectiveTermYear !== '' && $termSemester !== '') {
            foreach ($attendanceData as $secCode => $studentEntries) {
                foreach ($studentEntries as $studentId => $entries) {
                    $statusByDate = [];
                    foreach ($entries as $entry) {
                        $d = substr((string)($entry['date'] ?? ''), 0, 10);
                        if ($d === '' || empty($passedDatesSet[$d])) {
                            continue;
                        }
                        $st = strtolower(trim((string)($entry['status'] ?? '')));
                        if (!in_array($st, ['present', 'absent', 'late'], true)) {
                            continue;
                        }
                        $statusByDate[$d] = $st;
                    }

                    $p = 0;
                    $l = 0;
                    $a = 0;

                    foreach ($statusByDate as $st) {
                        if ($st === 'present') {
                            $p++;
                        } elseif ($st === 'late') {
                            $l++;
                        } elseif ($st === 'absent') {
                            $a++;
                        }
                    }

                    $sum = $p + $l + $a;
                    $record = $attendanceRecord[$secCode][$studentId] ?? [];
                    $storedSum = (int)($record['total_presence'] ?? 0)
                        + (int)($record['total_late'] ?? 0)
                        + (int)($record['total_absence'] ?? 0);

                    if ($storedSum !== $totalPassedDays && $storedSum !== $sum) {
                        DB::table('attendance_record')
                            ->updateOrInsert(
                                [
                                    'student_id' => $studentId,
                                    'semester' => $termSemester,
                                    'school_year' => $effectiveTermYear,
                                ],
                                [
                                    'class_section_id' => $secCode,
                                    'school_id' => $studentSchoolMap[$studentId] ?? '',
                                    'total_presence' => $p,
                                    'total_late' => $l,
                                    'total_absence' => $a,
                                    'total_attendance' => $sum,
                                    'modified_by' => auth()->id(),
                                    'updated_at' => now(),
                                    'created_at' => now(),
                                ]
                            );

                        $attendanceRecord[$secCode][$studentId]['total_presence'] = $p;
                        $attendanceRecord[$secCode][$studentId]['total_late'] = $l;
                        $attendanceRecord[$secCode][$studentId]['total_absence'] = $a;
                        $attendanceRecord[$secCode][$studentId]['total_attendance'] = $sum;
                    }
                }
            }
        }

        $adminName = '';
        if (auth()->id()) {
            $u = $this->user->query()->select('firstname', 'lastname')->find(auth()->id());
            if ($u) {
                $adminName = trim(($u->firstname ?? '') . ' ' . ($u->lastname ?? ''));
            }
        }

        return [
            'attendanceData' => $attendanceData,
            'attendanceRecord' => $attendanceRecord,
            'studentsBySection' => $studentsBySection,
            'grades' => $grades,
            'datesBySection' => $datesBySection,
            'dateList' => $dateList,
            'noSchoolDays' => $noSchoolDays,
            'totalPassedDays' => $totalPassedDays,
            'semester' => $termSemester,
            'schoolYear' => $effectiveTermYear,
            'currentAdminName' => $adminName,
        ];
    }

    /**
     * Distinct user_ids with staff rows on the given Sundays, scoped by teacher_class section assignment
     * (works when staff_attendance has no class_section_id column).
     *
     * @return list<int>
     */
    private function distinctStaffAttendeeIdsViaTeacherClassJoin(
        array $sundayDates,
        string $semester,
        string $schoolYear,
        int $classSectionId,
        int $classSectionPk,
    ): array {
        $sectionIds = array_values(array_unique(array_filter(
            [$classSectionId, $classSectionPk],
            static fn (int $v): bool => $v > 0,
        )));
        if ($sundayDates === [] || $sectionIds === []) {
            return [];
        }

        $staffDateInSql = 'DATE(`sa`.`date`) IN (' .
            implode(',', array_fill(0, count($sundayDates), '?')) . ')';

        $run = static function (
            bool $requireTcSemester,
            bool $requireSaSemester,
        ) use ($sundayDates, $schoolYear, $semester, $staffDateInSql, $sectionIds): array {
            $q = DB::table('staff_attendance as sa')
                ->join('teacher_class as tc', function ($j) use ($sectionIds, $schoolYear, $requireTcSemester, $semester) {
                    $j->on('tc.teacher_id', '=', 'sa.user_id')
                        ->whereIn('tc.class_section_id', $sectionIds)
                        ->where('tc.school_year', '=', $schoolYear);
                    if ($requireTcSemester && trim($semester) !== '') {
                        $j->whereRaw('TRIM(tc.semester) = ?', [trim($semester)]);
                    }
                })
                ->where('sa.school_year', $schoolYear)
                ->whereRaw($staffDateInSql, $sundayDates);
            if ($requireSaSemester && trim($semester) !== '') {
                $q->whereRaw('TRIM(sa.semester) = ?', [trim($semester)]);
            }

            return $q->distinct()
                ->pluck('sa.user_id')
                ->map(static fn ($v): int => (int) $v)
                ->filter(static fn (int $v): bool => $v > 0)
                ->values()
                ->all();
        };

        $ids = $run(true, true);
        if ($ids === []) {
            $ids = $run(false, true);
        }
        if ($ids === []) {
            $ids = $run(false, false);
        }

        return $ids;
    }

    /**
     * When teacher_class has no rows, build a roster from staff_attendance for this section/window.
     *
     * @return array<int, array{class_section_id:int, teacher_id:int, position:string, firstname:?string, lastname:?string}>
     */
    private function teacherRosterFromStaffAttendance(
        array $sundayDates,
        string $semester,
        string $schoolYear,
        int $classSectionId,
        int $classSectionPk,
    ): array {
        if ($sundayDates === []) {
            return [];
        }

        $staffDateInSql = 'DATE(`sa`.`date`) IN (' .
            implode(',', array_fill(0, count($sundayDates), '?')) . ')';

        $ids = [];

        if (Schema::hasColumn('staff_attendance', 'class_section_id')) {
            $applySectionScope = function ($query) use ($classSectionId, $classSectionPk): void {
                $query->where(function ($w) use ($classSectionId, $classSectionPk) {
                    $w->whereNull('sa.class_section_id')
                        ->orWhere('sa.class_section_id', 0)
                        ->orWhere('sa.class_section_id', $classSectionId);
                    if ($classSectionPk > 0 && $classSectionPk !== $classSectionId) {
                        $w->orWhere('sa.class_section_id', $classSectionPk);
                    }
                });
            };

            $base = function (bool $useSemester) use ($sundayDates, $schoolYear, $semester, $staffDateInSql, $applySectionScope) {
                $q = DB::table('staff_attendance as sa')
                    ->where('sa.school_year', $schoolYear)
                    ->whereRaw($staffDateInSql, $sundayDates);
                if ($useSemester && trim($semester) !== '') {
                    $q->whereRaw('TRIM(sa.semester) = ?', [trim($semester)]);
                }
                $applySectionScope($q);

                return $q->distinct()->pluck('sa.user_id')->map(static fn ($v) => (int) $v)->filter(static fn (int $v) => $v > 0)->values()->all();
            };

            $ids = $base(true);
            if ($ids === []) {
                $ids = $base(false);
            }
        }

        if ($ids === []) {
            $ids = $this->distinctStaffAttendeeIdsViaTeacherClassJoin(
                $sundayDates,
                $semester,
                $schoolYear,
                $classSectionId,
                $classSectionPk,
            );
        }

        if ($ids === []) {
            return [];
        }

        $users = $this->user->query()
            ->select(['id', 'firstname', 'lastname'])
            ->whereIn('id', $ids)
            ->orderBy('firstname')
            ->orderBy('lastname')
            ->get();

        $out = [];
        foreach ($users as $u) {
            $out[] = [
                'class_section_id' => $classSectionId,
                'teacher_id' => (int) $u->id,
                'position' => 'main',
                'firstname' => $u->firstname,
                'lastname' => $u->lastname,
            ];
        }

        return $out;
    }

    /**
     * Normalize DB datetimes / date strings to Y-m-d so they match {@see $sundayDates} keys.
     */
    private function calendarDateKey(mixed $value): string
    {
        $s = trim((string)($value ?? ''));
        if ($s === '') {
            return '';
        }
        if (preg_match('/^(\d{4}-\d{2}-\d{2})/', $s, $m)) {
            return $m[1];
        }
        try {
            return Carbon::parse($s)->toDateString();
        } catch (\Throwable) {
            return '';
        }
    }
}
