"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AttendanceService = void 0;
const common_1 = require("@nestjs/common");
const mongoose_1 = require("@nestjs/mongoose");
const attendance_schema_1 = require("./schemas/attendance.schema");
const mongoose_2 = require("mongoose");
const student_schema_1 = require("../students/schemas/student.schema");
const section_schema_1 = require("../sections/schemas/section.schema");
const regex_sanitizer_1 = require("../common/utils/regex-sanitizer");
let AttendanceService = class AttendanceService {
    model;
    studentModel;
    sectionModel;
    constructor(model, studentModel, sectionModel) {
        this.model = model;
        this.studentModel = studentModel;
        this.sectionModel = sectionModel;
    }
    isDateYYYYMMDD(s) {
        return /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/.test(s);
    }
    isFutureDate(dateStr) {
        const inputDate = new Date(dateStr);
        const today = new Date();
        today.setHours(0, 0, 0, 0);
        inputDate.setHours(0, 0, 0, 0);
        return inputDate > today;
    }
    async isTeacherAssignedToSection(teacherId, sectionId) {
        if (!sectionId)
            return false;
        const section = await this.sectionModel.findById(sectionId).lean();
        if (!section)
            return false;
        return String(section.assignedTeacherId || '') === String(teacherId);
    }
    async getStudentMapByIds(ids) {
        if (!ids.length)
            return new Map();
        const docs = await this.studentModel
            .find({ _id: { $in: ids } })
            .select({ _id: 1, classId: 1, sectionId: 1, academicYearId: 1 })
            .lean();
        const map = new Map();
        docs.forEach((s) => map.set(String(s._id), s));
        return map;
    }
    async resolveStudentSnap(studentId) {
        return this.studentModel
            .findById(studentId)
            .select({ classId: 1, sectionId: 1, academicYearId: 1 })
            .lean();
    }
    async markAttendance(dto, teacherId, headerAcademicYearId, userRole) {
        const { studentId, date, status } = dto;
        if (!this.isDateYYYYMMDD(date))
            throw new common_1.BadRequestException('Invalid date format (YYYY-MM-DD)');
        if (this.isFutureDate(date)) {
            throw new common_1.BadRequestException('Cannot mark attendance for future dates');
        }
        const s = await this.resolveStudentSnap(studentId);
        if (userRole !== 'admin' && s?.sectionId) {
            const isAssigned = await this.isTeacherAssignedToSection(teacherId, s.sectionId);
            if (!isAssigned) {
                throw new common_1.ForbiddenException('You are not assigned to this student\'s section');
            }
        }
        const ayId = headerAcademicYearId || s?.academicYearId;
        if (!ayId)
            throw new common_1.BadRequestException('academicYearId is required (via header or student record)');
        const update = {
            studentId,
            date,
            status: status,
            markedBy: teacherId,
            classId: s?.classId,
            sectionId: s?.sectionId,
            academicYearId: ayId,
        };
        return this.model.findOneAndUpdate({ studentId, date }, update, { upsert: true, new: true, setDefaultsOnInsert: true }).lean();
    }
    async bulkMarkAttendance(body, teacherId, headerAcademicYearId, userRole) {
        const { date, entries } = body;
        if (!this.isDateYYYYMMDD(date))
            throw new common_1.BadRequestException('Invalid date format (YYYY-MM-DD)');
        if (this.isFutureDate(date)) {
            throw new common_1.BadRequestException('Cannot mark attendance for future dates');
        }
        const ids = (entries || []).map(e => e.studentId);
        const studentMap = await this.getStudentMapByIds(ids);
        if (userRole !== 'admin') {
            const sectionIds = new Set();
            studentMap.forEach((s) => {
                if (s?.sectionId)
                    sectionIds.add(String(s.sectionId));
            });
            for (const sectionId of sectionIds) {
                const isAssigned = await this.isTeacherAssignedToSection(teacherId, sectionId);
                if (!isAssigned) {
                    throw new common_1.ForbiddenException('You are not assigned to one or more sections of these students');
                }
            }
        }
        const ops = (entries || []).map((r) => {
            const s = studentMap.get(String(r.studentId));
            const ayId = headerAcademicYearId || s?.academicYearId;
            if (!ayId) {
                return null;
            }
            return {
                updateOne: {
                    filter: { studentId: r.studentId, date },
                    update: {
                        studentId: r.studentId,
                        date,
                        status: r.status,
                        markedBy: teacherId,
                        classId: s?.classId,
                        sectionId: s?.sectionId,
                        academicYearId: ayId,
                    },
                    upsert: true,
                },
            };
        }).filter(Boolean);
        if (!ops.length)
            return { matched: 0, modified: 0, upserted: 0, skipped: entries?.length || 0 };
        const result = await this.model.bulkWrite(ops);
        return {
            matched: result.matchedCount || 0,
            modified: result.modifiedCount || 0,
            upserted: result.upsertedCount || 0,
            skipped: (entries?.length || 0) - ops.length,
        };
    }
    async getAttendance(filter) {
        const q = {};
        if (filter.classId)
            q.classId = filter.classId;
        if (filter.sectionId)
            q.sectionId = filter.sectionId;
        if (filter.academicYearId)
            q.academicYearId = filter.academicYearId;
        if (filter.date)
            q.date = filter.date;
        return this.model.find(q).sort({ date: -1 }).lean();
    }
    async getAdminSummary(filter) {
        const rec = await this.getAttendance(filter);
        const grouped = rec.reduce((acc, r) => {
            acc[r.date] = acc[r.date] || { date: r.date, present: 0, absent: 0, total: 0 };
            acc[r.date].total++;
            acc[r.date][r.status] = (acc[r.date][r.status] || 0) + 1;
            return acc;
        }, {});
        return Object.values(grouped).sort((a, b) => b.date.localeCompare(a.date));
    }
    async getReport(filter, period) {
        const rec = await this.getAttendance(filter);
        const buckets = {};
        const pad2 = (n) => String(n).padStart(2, '0');
        rec.forEach((r) => {
            const d = new Date(r.date);
            let key = '';
            let label = '';
            let sortKey = '';
            if (period === 'weekly') {
                const day = d.getDay();
                const mondayOffset = (day + 6) % 7;
                const weekStart = new Date(d);
                weekStart.setDate(d.getDate() - mondayOffset);
                const y = weekStart.getFullYear();
                const m = pad2(weekStart.getMonth() + 1);
                const dd = pad2(weekStart.getDate());
                key = `${y}-${m}-${dd}`;
                label = `Week of ${weekStart.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}`;
                sortKey = key;
            }
            else {
                const y = d.getFullYear();
                const m = pad2(d.getMonth() + 1);
                key = `${y}-${m}`;
                label = `${d.toLocaleString(undefined, { month: 'long' })} ${y}`;
                sortKey = key;
            }
            if (!buckets[key])
                buckets[key] = { label, present: 0, absent: 0, total: 0, sortKey };
            buckets[key].present += r.status === 'present' ? 1 : 0;
            buckets[key].absent += r.status === 'absent' ? 1 : 0;
            buckets[key].total++;
        });
        return Object.values(buckets)
            .sort((a, b) => a.sortKey.localeCompare(b.sortKey))
            .map(({ sortKey, ...rest }) => rest);
    }
    async getTeacherHistory(teacherId) {
        return this.model.find({ markedBy: teacherId }).sort({ date: -1 }).lean();
    }
    async getSectionDay(sectionId, date, academicYearId) {
        const q = { sectionId, date };
        if (academicYearId)
            q.academicYearId = academicYearId;
        return this.model.find(q).sort({ studentId: 1 }).lean();
    }
    async getStudentRange(studentId, range, academicYearId) {
        const q = { studentId };
        if (academicYearId)
            q.academicYearId = academicYearId;
        if (range?.from && range?.to) {
            q.date = { $gte: range.from, $lte: range.to };
        }
        else if (range?.from) {
            q.date = { $gte: range.from };
        }
        else if (range?.to) {
            q.date = { $lte: range.to };
        }
        return this.model.find(q).sort({ date: -1 }).lean();
    }
    async getStudentMonth(studentId, month, academicYearId) {
        const validatedMonth = (0, regex_sanitizer_1.validateMonth)(month);
        const q = { studentId, date: { $regex: `^${validatedMonth}-` } };
        if (academicYearId)
            q.academicYearId = academicYearId;
        const rows = await this.model.find({ ...q, status: 'present' }).select({ date: 1 }).lean();
        const dates = rows.map((r) => r.date);
        return { dates };
    }
    async getDailySummary(academicYearId) {
        const pipeline = [];
        if (academicYearId) {
            pipeline.push({ $match: { academicYearId } });
        }
        pipeline.push({
            $group: {
                _id: '$date',
                present: { $sum: { $cond: [{ $eq: ['$status', 'present'] }, 1, 0] } },
                absent: { $sum: { $cond: [{ $eq: ['$status', 'absent'] }, 1, 0] } },
                total: { $sum: 1 },
            },
        }, { $sort: { _id: -1 } }, { $limit: 30 });
        const result = await this.model.aggregate(pipeline);
        return result
            .map((e) => ({ date: e._id, present: e.present, absent: e.absent, total: e.total }))
            .sort((a, b) => a.date.localeCompare(b.date));
    }
};
exports.AttendanceService = AttendanceService;
exports.AttendanceService = AttendanceService = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, mongoose_1.InjectModel)(attendance_schema_1.Attendance.name)),
    __param(1, (0, mongoose_1.InjectModel)(student_schema_1.Student.name)),
    __param(2, (0, mongoose_1.InjectModel)(section_schema_1.Section.name)),
    __metadata("design:paramtypes", [mongoose_2.Model,
        mongoose_2.Model,
        mongoose_2.Model])
], AttendanceService);
//# sourceMappingURL=attendance.service.js.map