"use client";

import React, {
  createContext,
  useContext,
  useState,
  useEffect,
  useCallback,
  useMemo,
} from "react";
import {
  StudentDetail,
  initialMockStudents,
  mockAvailableClasses,
} from "@/data/mockStudentData";
import {
  TeacherDetail,
  initialMockTeachers,
} from "@/data/mockTeacherData";
import {
  SubjectItem,
  initialMockSubjects,
} from "@/data/mockCurriculumData";
import {
  MonthlyReportRecord,
  SubjectMonthlyGradeItem,
  MonthGradeDetail,
  DEFAULT_PTS_CONFIG,
  normalizeMonthKey,
  initialMockMonthlyReports,
} from "@/data/mockGradeData";
import { appCache } from "@/lib/cache";
import {
  publishRealtimeNotification,
  useRealtimeSubscription,
} from "@/lib/realtimeClient";
import { RealtimeEvent, RealtimeEventType } from "@/lib/realtimeEvents";

// Academic Snapshot for Class History / Promotion Tracking
export interface AcademicSnapshot {
  academicYear: string;
  gradeLevel: "X" | "XI" | "XII" | "Alumni";
  className: string;
  status: "Naik Kelas" | "Tinggal Kelas" | "Lulus" | "Mutasi Keluar" | "Aktif";
  date: string;
  notes?: string;
}

export interface PromotionItem {
  studentId: string;
  nis: string;
  fullName: string;
  previousClass: string;
  previousGrade: "X" | "XI" | "XII";
  action: "naik" | "tinggal" | "lulus" | "mutasi";
  targetClass: string;
  targetGrade: "X" | "XI" | "XII";
  targetAcademicYear: string;
  notes?: string;
}

export interface PromotionResult {
  success: boolean;
  total: number;
  promoted: number;
  retained: number;
  graduated: number;
  mutated: number;
  message: string;
}

// Normalizer between StudentRecord (API) and StudentDetail
export interface UnifiedStudent {
  id: string;
  dbId?: number;
  nis: string;
  nisn: string;
  fullName: string;
  gender: "L" | "P";
  gradeLevel: "X" | "XI" | "XII";
  className: string;
  status: "aktif" | "mutasi" | "lulus" | "non_aktif";
  birthPlace: string;
  birthDate: string;
  religion?: string;
  parentName: string;
  parentRelation?: "Ayah" | "Ibu" | "Wali";
  parentPhone: string;
  address: string;
  homeroomTeacherName?: string;
  email?: string;
  academicHistory?: AcademicSnapshot[];
}

export interface HomeroomAssignment {
  className: string;
  gradeLevel: "X" | "XI" | "XII";
  teacherId: string;
  teacherName: string;
  nip: string;
  phone: string;
  email: string;
  roomNumber: string;
}

export interface PlottingRecord {
  id: string;
  teacherId: string;
  teacherName: string;
  nip: string;
  subjectName: string;
  assignedClasses: string[];
  totalHours: number;
  status: "Aktif" | "Cuti" | "Non-Aktif";
}

export interface StudentAttendanceSummary {
  hadir: number;
  sakit: number;
  izin: number;
  alpa: number;
  telat: number;
  totalAbsen: number;
  kehadiranPct: number;
}

export interface TeacherAttendanceSummary {
  hadir: number;
  dinas: number;
  sakit: number;
  izin: number;
  alpa: number;
  totalHari: number;
  kehadiranPct: number;
}

interface SmartSchoolContextType {
  // Students
  students: UnifiedStudent[];
  isLoadingStudents: boolean;
  refreshStudents: (force?: boolean) => Promise<void>;
  addStudent: (student: Omit<UnifiedStudent, "id">) => Promise<boolean>;
  updateStudent: (id: string, data: Partial<UnifiedStudent>) => Promise<boolean>;
  deleteStudent: (id: string) => Promise<boolean>;
  importStudentsList: (newStudents: Omit<UnifiedStudent, "id">[]) => Promise<boolean>;
  promoteStudentsBatch: (
    promotions: PromotionItem[],
    academicYear: string
  ) => Promise<PromotionResult>;

  // Teachers
  teachers: TeacherDetail[];
  isLoadingTeachers: boolean;
  refreshTeachers: (force?: boolean) => Promise<void>;
  addTeacher: (teacher: Omit<TeacherDetail, "id">) => Promise<boolean>;
  updateTeacher: (id: string, data: Partial<TeacherDetail>) => Promise<boolean>;
  deleteTeacher: (id: string) => Promise<boolean>;

  // Master 20 Mapel
  masterSubjects: SubjectItem[];

  // Homeroom Assignments (30 Rombel)
  homerooms: HomeroomAssignment[];
  assignHomeroom: (className: string, teacherId: string) => void;
  getHomeroomForClass: (className: string) => HomeroomAssignment | undefined;

  // Plotting Guru & Pengampuan
  plottingList: PlottingRecord[];
  addPlotting: (item: Omit<PlottingRecord, "id">) => void;
  updatePlotting: (id: string, data: Partial<PlottingRecord>) => void;
  deletePlotting: (id: string) => void;

  // Attendance & Live Sync
  attendanceRecords: Record<string, Record<string, "hadir" | "sakit" | "izin" | "alpa" | "telat">>;
  teacherAttendanceRecords: Record<string, Record<string, "hadir" | "dinas" | "sakit" | "izin" | "alpa">>;
  getStudentAttendanceStats: (studentId: string, className?: string) => StudentAttendanceSummary;
  markStudentAttendance: (
    studentId: string,
    date: string,
    status: "hadir" | "sakit" | "izin" | "alpa" | "telat"
  ) => void;
  getTeacherAttendanceStats: (teacherId: string) => TeacherAttendanceSummary;
  markTeacherAttendance: (
    teacherId: string,
    date: string,
    status: "hadir" | "dinas" | "sakit" | "izin" | "alpa"
  ) => void;

  // Monthly Reports (Rapor Bulanan)
  reports: MonthlyReportRecord[];
  getReportsByClassAndMonth: (className: string, month: string) => MonthlyReportRecord[];
  updateSubjectScore: (
    reportId: string,
    subjectCode: string,
    monthField: string,
    value: number | null,
    monthName?: string
  ) => void;
  batchUpdateReports: (updatedReports: MonthlyReportRecord[]) => void;
  updateNonAcademicProfile: (
    reportId: string,
    data: Partial<MonthlyReportRecord>
  ) => void;

  // PTS Tentative per-month configuration & persistence
  ptsConfigByMonth: Record<string, boolean>;
  setPTSForMonth: (monthKeyOrName: string, enabled: boolean) => void;
  isPTSActiveForMonth: (monthKeyOrName: string) => boolean;

  // Real-Time Event Sync
  triggerRealtimeEvent: (type: RealtimeEventType, payload?: any) => Promise<void>;
  isRealtimeConnected: boolean;
}

const SmartSchoolContext = createContext<SmartSchoolContextType | undefined>(undefined);

// Helper to normalize Student Record from DB to UnifiedStudent
function normalizeFromDB(raw: any): UnifiedStudent {
  const tingkat = (raw.tingkat || (raw.rombel ? raw.rombel.split("-")[0] : "X")) as "X" | "XI" | "XII";
  const gender = (raw.jenis_kelamin === "Perempuan" || raw.jenis_kelamin === "P") ? "P" : "L";
  const rawStatus = (raw.status || "aktif").toLowerCase();
  const status = (rawStatus === "mutasi" ? "mutasi" : rawStatus === "lulus" ? "lulus" : "aktif") as "aktif" | "mutasi" | "lulus" | "non_aktif";

  return {
    id: raw.id ? `std-${raw.id}` : `std-${raw.nis}`,
    dbId: typeof raw.id === "number" ? raw.id : undefined,
    nis: String(raw.nis || ""),
    nisn: String(raw.nisn || ""),
    fullName: raw.nama_lengkap || raw.fullName || "",
    gender,
    gradeLevel: tingkat,
    className: raw.rombel || raw.className || "X-1",
    status,
    birthPlace: raw.tempat_lahir || raw.birthPlace || "Bandung",
    birthDate: raw.tanggal_lahir || raw.birthDate || "2008-01-01",
    religion: raw.agama || raw.religion || "Islam",
    parentName: raw.nama_ortu_wali || raw.parentName || "-",
    parentRelation: (raw.hubungan_ortu || raw.parentRelation || "Ayah") as "Ayah" | "Ibu" | "Wali",
    parentPhone: raw.no_wa_ortu || raw.parentPhone || "-",
    address: raw.alamat || raw.address || "-",
    homeroomTeacherName: raw.homeroomTeacherName,
    email: raw.email,
  };
}

// Initial 30 Homerooms derived from Teachers
function buildDefaultHomerooms(teacherList: TeacherDetail[]): HomeroomAssignment[] {
  const hasTeachers = Array.isArray(teacherList) && teacherList.length > 0;
  return mockAvailableClasses.map((cls) => {
    const grade = cls.startsWith("X-") ? "X" : cls.startsWith("XI-") ? "XI" : "XII";
    // Find teacher explicitly assigned to this class
    const foundTeacher = hasTeachers
      ? teacherList.find((t) => (Boolean(t.isHomeroomTeacher) || Boolean(t.homeroomClass)) && t.homeroomClass === cls)
      : undefined;

    return {
      className: cls,
      gradeLevel: grade,
      teacherId: foundTeacher ? foundTeacher.id : "",
      teacherName: foundTeacher ? foundTeacher.fullName : "Belum Ditugaskan",
      nip: foundTeacher?.nip || "-",
      phone: foundTeacher?.phone || "-",
      email: foundTeacher?.email || "-",
      roomNumber: `R.${cls.replace("-", ".")}`,
    };
  });
}

// Dynamic Plotting derived from Teachers list
function buildDefaultPlotting(teacherList: TeacherDetail[]): PlottingRecord[] {
  if (!Array.isArray(teacherList) || teacherList.length === 0) return [];
  return teacherList.map((t, idx) => {
    const isNatasya = (t.fullName || "").toLowerCase().includes("natasya");
    const assigned = isNatasya
      ? ["X-1", "X-2", "X-3"]
      : t.homeroomClass
      ? [t.homeroomClass, `${t.homeroomClass.split("-")[0]}-${((idx + 1) % 10) + 1}`].filter((v, i, a) => a.indexOf(v) === i)
      : [`X-${(idx % 10) + 1}`, `X-${((idx + 1) % 10) + 1}`];

    return {
      id: `plt-${t.id || idx + 1}`,
      teacherId: String(t.id || idx + 1),
      teacherName: t.fullName || "Guru BPI",
      nip: t.nip || t.nuptk || "-",
      subjectName: isNatasya ? "Matematika" : (t.primarySubject || "Mata Pelajaran"),
      assignedClasses: assigned,
      totalHours: isNatasya ? 12 : (t.teachingHoursPerWeek || assigned.length * 4),
      status: t.status === "non_aktif" ? "Non-Aktif" : t.status === "cuti" ? "Cuti" : "Aktif",
    };
  });
}

export function SmartSchoolProvider({ children }: { children: React.ReactNode }) {
  // 1. STUDENTS STATE
  const [students, setStudents] = useState<UnifiedStudent[]>([]);
  const [isLoadingStudents, setIsLoadingStudents] = useState<boolean>(false);

  // 2. TEACHERS STATE
  const [teachers, setTeachers] = useState<TeacherDetail[]>([]);
  const [isLoadingTeachers, setIsLoadingTeachers] = useState<boolean>(false);

  // 3. MASTER 20 MATA PELAJARAN
  const masterSubjects = useMemo(() => initialMockSubjects, []);

  // 4. HOMEROOMS (30 Classes)
  const [homerooms, setHomerooms] = useState<HomeroomAssignment[]>(() => buildDefaultHomerooms([]));

  // 5. PLOTTING GURU (Master Plotting Guru)
  const [plottingList, setPlottingList] = useState<PlottingRecord[]>(() => {
    if (typeof window !== "undefined") {
      try {
        const stored = localStorage.getItem("bpi_smartschool_plotting");
        if (stored) {
          const parsed = JSON.parse(stored);
          if (Array.isArray(parsed) && parsed.length > 0) return parsed;
        }
      } catch {}
    }
    return [];
  });

  // 6. ATTENDANCE LOGS (STUDENTS & TEACHERS)
  // Data presensi semesteran realistis SMA BPI 1 Bandung (tersinkronisasi dengan Modul Presensi & Rekapitulasi)
  const defaultStudentSeeds: Record<
    string,
    Record<string, "hadir" | "sakit" | "izin" | "alpa" | "telat">
  > = {
    // Data Presensi Riil Kelas XI-9 SMA BPI 1 Bandung
    // ALEA GUNASAIRA (NIS: 2526.10191 / ID: std-506)
    "std-506": { "2026-09-18": "sakit", "2026-10-14": "sakit", "2026-11-05": "izin" },
    "2526.10191": { "2026-09-18": "sakit", "2026-10-14": "sakit", "2026-11-05": "izin" },
    // ANAYA GHAIDA (NIS: 2526.10099 / ID: std-507)
    "std-507": { "2026-09-10": "izin", "2026-10-22": "sakit" },
    "2526.10099": { "2026-09-10": "izin", "2026-10-22": "sakit" },
    // ANINDYA PUTRI ARCYA (NIS: 2526.10192 / ID: std-508) -> 100% Hadir
    "std-508": {},
    "2526.10192": {},
    // ATHALLAH RADIER ORVALA (NIS: 2526.10143 / ID: std-509)
    "std-509": { "2026-09-08": "sakit", "2026-09-09": "sakit", "2026-10-12": "sakit", "2026-11-20": "izin" },
    "2526.10143": { "2026-09-08": "sakit", "2026-09-09": "sakit", "2026-10-12": "sakit", "2026-11-20": "izin" },
    // AUDHEETA RAIHANA HARIES (NIS: 2526.10145 / ID: std-510)
    "std-510": { "2026-09-15": "izin", "2026-10-06": "izin", "2026-11-17": "sakit" },
    "2526.10145": { "2026-09-15": "izin", "2026-10-06": "izin", "2026-11-17": "sakit" },
    // AZKA AQILA THURFAH (NIS: 2526.10005 / ID: std-511)
    "std-511": { "2026-09-22": "sakit", "2026-10-28": "sakit" },
    "2526.10005": { "2026-09-22": "sakit", "2026-10-28": "sakit" },
    // DAMEIRA ALEIKA NURENDAH (NIS: 2526.10077 / ID: std-512)
    "std-512": { "2026-09-11": "izin", "2026-11-12": "izin" },
    "2526.10077": { "2026-09-11": "izin", "2026-11-12": "izin" },
    // DAVINA DIVANYA INDRA DIANA (NIS: 2526.10148 / ID: std-513)
    "std-513": { "2026-10-15": "sakit" },
    "2526.10148": { "2026-10-15": "sakit" },
    // DHAIFAN NADIF DANYANDA (NIS: 2526.10176 / ID: std-514)
    "std-514": { "2026-09-04": "sakit", "2026-09-05": "sakit", "2026-10-19": "sakit", "2026-11-10": "sakit", "2026-11-24": "izin" },
    "2526.10176": { "2026-09-04": "sakit", "2026-09-05": "sakit", "2026-10-19": "sakit", "2026-11-10": "sakit", "2026-11-24": "izin" },
    // DIMAS NAUFAL PUTRA HERNAWAN (NIS: 2526.10150 / ID: std-515)
    "std-515": { "2026-09-12": "sakit", "2026-10-20": "izin" },
    "2526.10150": { "2026-09-12": "sakit", "2026-10-20": "izin" },
    // DYZHELVA HAYDAR RAESA ALGHOSY (NIS: 2526.10195 / ID: std-516)
    "std-516": { "2026-10-16": "izin" },
    "2526.10195": { "2026-10-16": "izin" },
    // FABIJAN RIZKY FIRMANSYAH (NIS: 2526.10031 / ID: std-517)
    "std-517": { "2026-09-17": "sakit", "2026-10-23": "sakit", "2026-11-16": "alpa" },
    "2526.10031": { "2026-09-17": "sakit", "2026-10-23": "sakit", "2026-11-16": "alpa" },
    // FRIESTAN HYUSCHA KHARISMA (NIS: 2526.10107 / ID: std-518)
    "std-518": { "2026-09-24": "sakit", "2026-11-04": "izin" },
    "2526.10107": { "2026-09-24": "sakit", "2026-11-04": "izin" },
    // GLADYSHA ZIVARA VIANDRI (NIS: 2526.10179 / ID: std-519)
    "std-519": { "2026-09-29": "sakit", "2026-10-30": "sakit" },
    "2526.10179": { "2026-09-29": "sakit", "2026-10-30": "sakit" },
    // HABIBI ABIGEL (NIS: 2526.10011 / ID: std-520) -> 100% Hadir
    "std-520": {},
    "2526.10011": {},
    // KAYRA NAFISAH WASKITO POETRI (NIS: 2526.10153 / ID: std-521)
    "std-521": { "2026-09-03": "sakit", "2026-09-04": "sakit", "2026-10-13": "sakit", "2026-11-19": "izin" },
    "2526.10153": { "2026-09-03": "sakit", "2026-09-04": "sakit", "2026-10-13": "sakit", "2026-11-19": "izin" },
    // KEISYA SYAKILA AZKA (NIS: 2526.10183 / ID: std-522)
    "std-522": { "2026-10-07": "sakit" },
    "2526.10183": { "2026-10-07": "sakit" },
    // KENZA ABINAYA RAMADHAN (NIS: 2526.10013 / ID: std-523)
    "std-523": { "2026-09-18": "izin", "2026-11-06": "izin" },
    "2526.10013": { "2026-09-18": "izin", "2026-11-06": "izin" },
    // KINARA ARCELLYA HAJJAR (NIS: 2526.10084 / ID: std-524)
    "std-524": { "2026-09-21": "sakit", "2026-10-26": "sakit", "2026-11-13": "izin" },
    "2526.10084": { "2026-09-21": "sakit", "2026-10-26": "sakit", "2026-11-13": "izin" },
    // LEANDRA ANABEEL BILQIS (NIS: 2526.10158 / ID: std-525)
    "std-525": { "2026-09-09": "sakit", "2026-09-10": "sakit", "2026-10-21": "sakit", "2026-11-25": "sakit" },
    "2526.10158": { "2026-09-09": "sakit", "2026-09-10": "sakit", "2026-10-21": "sakit", "2026-11-25": "sakit" },
    // NAIRA PUTRI DENISYA (NIS: 2526.10066 / ID: std-526)
    "std-526": { "2026-10-09": "sakit" },
    "2526.10066": { "2026-10-09": "sakit" },
    // NAJWAN DZAKI RAIHAN (NIS: 2526.10091 / ID: std-527)
    "std-527": { "2026-09-28": "izin" },
    "2526.10091": { "2026-09-28": "izin" },
    // NATASYA AURA NOVIANLEE (NIS: 2526.10163 / ID: std-528)
    "std-528": { "2026-09-14": "sakit", "2026-10-12": "sakit", "2026-11-02": "izin", "2026-11-18": "izin" },
    "2526.10163": { "2026-09-14": "sakit", "2026-10-12": "sakit", "2026-11-02": "izin", "2026-11-18": "izin" },
    // NAURA ANJANI NAZNEENA (NIS: 2526.10092 / ID: std-529)
    "std-529": { "2026-10-05": "sakit" },
    "2526.10092": { "2026-10-05": "sakit" },
    // NAWAY KARUNIA RASYIDAH SURYAHARJA (NIS: 2526.10137 / ID: std-530)
    "std-530": { "2026-09-16": "sakit", "2026-10-27": "sakit", "2026-11-23": "sakit" },
    "2526.10137": { "2026-09-16": "sakit", "2026-10-27": "sakit", "2026-11-23": "sakit" },
    // QIANA AYUDIA RABBANI ZARA SANDIANA (NIS: 2526.10205 / ID: std-531) -> 100% Hadir
    "std-531": {},
    "2526.10205": {},
    // RADEN RAYVALDIN NOVAN ADRIAN (NIS: 2526.10117 / ID: std-532)
    "std-532": { "2026-09-02": "sakit", "2026-09-03": "sakit", "2026-10-08": "sakit", "2026-10-29": "sakit", "2026-11-11": "sakit", "2026-11-27": "izin" },
    "2526.10117": { "2026-09-02": "sakit", "2026-09-03": "sakit", "2026-10-08": "sakit", "2026-10-29": "sakit", "2026-11-11": "sakit", "2026-11-27": "izin" },
  };

  // Helper generator presensi siswa deterministik berdasarkan NIS/ID untuk siswa lainnya
  const getDeterministicStudentLogs = (nis: string, id: string): Record<string, "hadir" | "sakit" | "izin" | "alpa" | "telat"> => {
    const seedStr = nis || id || "0";
    const seedNum = seedStr.split("").reduce((acc, c) => acc + c.charCodeAt(0), 0);
    const mod = seedNum % 10;
    const res: Record<string, "hadir" | "sakit" | "izin" | "alpa" | "telat"> = {};

    if (mod === 1) {
      res["2026-09-15"] = "sakit";
      res["2026-10-20"] = "izin";
    } else if (mod === 2) {
      res["2026-09-10"] = "izin";
    } else if (mod === 3) {
      res["2026-09-22"] = "sakit";
      res["2026-10-14"] = "sakit";
      res["2026-11-05"] = "izin";
    } else if (mod === 4) {
      res["2026-10-08"] = "sakit";
    } else if (mod === 5) {
      res["2026-09-08"] = "izin";
      res["2026-11-12"] = "izin";
    } else if (mod === 6) {
      res["2026-09-18"] = "sakit";
      res["2026-10-25"] = "sakit";
    } else if (mod === 7) {
      res["2026-09-25"] = "sakit";
      res["2026-11-19"] = "alpa";
    } else if (mod === 8) {
      res["2026-10-16"] = "izin";
    }
    return res;
  };

  const defaultTeacherSeeds: Record<
    string,
    Record<string, "hadir" | "dinas" | "sakit" | "izin" | "alpa">
  > = {
    "tch-003": {
      "2026-09-08": "dinas",
      "2026-09-22": "dinas",
    },
    "tch-004": {
      "2026-09-15": "dinas",
    },
    "tch-005": {
      "2026-09-10": "izin",
      "2026-09-22": "izin",
    },
    "tch-007": {
      "2026-09-11": "sakit",
    },
    "tch-008": {
      "2026-09-17": "izin",
      "2026-09-22": "dinas",
    },
  };

  const [attendanceRecords, setAttendanceRecords] = useState<
    Record<string, Record<string, "hadir" | "sakit" | "izin" | "alpa" | "telat">>
  >(defaultStudentSeeds);

  const [teacherAttendanceRecords, setTeacherAttendanceRecords] = useState<
    Record<string, Record<string, "hadir" | "dinas" | "sakit" | "izin" | "alpa">>
  >(defaultTeacherSeeds);

  // 7. MONTHLY REPORTS STATE
  const [reports, setReports] = useState<MonthlyReportRecord[]>([]);

  const [isClientLoaded, setIsClientLoaded] = useState<boolean>(false);

  // LOAD FROM LOCALSTORAGE ONCE ON CLIENT MOUNT (PREVENT HYDRATION MISMATCH)
  useEffect(() => {
    if (typeof window === "undefined") return;
    try {
      const studentRaw = localStorage.getItem("bpi_smartschool_students");
      if (studentRaw) {
        const parsed = JSON.parse(studentRaw);
        if (Array.isArray(parsed) && parsed.length > 0) setStudents(parsed);
      }
      const teacherRaw =
        localStorage.getItem("bpi_smartschool_teachers") ||
        localStorage.getItem("bpi_smartschool_teachers_data");
      if (teacherRaw) {
        let parsed = JSON.parse(teacherRaw);
        if (Array.isArray(parsed) && parsed.length > 0) {
          const isReversed =
            parsed[0]?.fullName?.toLowerCase().includes("aldini") ||
            (parsed[parsed.length - 1]?.fullName?.toLowerCase().includes("tatang") &&
              !parsed[0]?.fullName?.toLowerCase().includes("tatang"));
          if (isReversed) {
            parsed = [...parsed].reverse();
            localStorage.setItem("bpi_smartschool_teachers", JSON.stringify(parsed));
            localStorage.setItem("bpi_smartschool_teachers_data", JSON.stringify(parsed));
          }
          setTeachers(parsed);
        }
      }
      const homeroomsRaw = localStorage.getItem("bpi_smartschool_homerooms");
      if (homeroomsRaw) {
        const parsed = JSON.parse(homeroomsRaw);
        if (Array.isArray(parsed) && parsed.length > 0) setHomerooms(parsed);
      }
      const plottingRaw = localStorage.getItem("bpi_smartschool_plotting");
      if (plottingRaw) {
        const parsed = JSON.parse(plottingRaw);
        if (Array.isArray(parsed) && parsed.length > 0) setPlottingList(parsed);
      }
      const attendanceRaw = localStorage.getItem("bpi_smartschool_attendance");
      if (attendanceRaw) {
        const parsed = JSON.parse(attendanceRaw);
        if (parsed && Object.keys(parsed).length > 0) {
          setAttendanceRecords((prev) => ({ ...defaultStudentSeeds, ...parsed }));
        }
      }
      const teacherAttendanceRaw = localStorage.getItem("bpi_smartschool_teacher_attendance");
      if (teacherAttendanceRaw) {
        const parsed = JSON.parse(teacherAttendanceRaw);
        if (parsed && Object.keys(parsed).length > 0) setTeacherAttendanceRecords(parsed);
      }
      const reportsRaw = localStorage.getItem("bpi_smartschool_reports");
      if (reportsRaw) {
        const parsed = JSON.parse(reportsRaw);
        if (Array.isArray(parsed) && parsed.length > 0) setReports(parsed);
      }
    } catch (e) {
      console.warn("Storage hydration error:", e);
    } finally {
      setIsClientLoaded(true);
    }
  }, []);

  // PERSIST TO LOCALSTORAGE ONLY AFTER INITIAL CLIENT HYDRATION
  useEffect(() => {
    if (isClientLoaded && typeof window !== "undefined") {
      localStorage.setItem("bpi_smartschool_students", JSON.stringify(students));
    }
  }, [students, isClientLoaded]);

  useEffect(() => {
    if (isClientLoaded && typeof window !== "undefined") {
      localStorage.setItem("bpi_smartschool_teachers", JSON.stringify(teachers));
      localStorage.setItem("bpi_smartschool_teachers_data", JSON.stringify(teachers));
    }
  }, [teachers, isClientLoaded]);

  useEffect(() => {
    if (isClientLoaded && typeof window !== "undefined") {
      localStorage.setItem("bpi_smartschool_homerooms", JSON.stringify(homerooms));
    }
  }, [homerooms, isClientLoaded]);

  useEffect(() => {
    if (isClientLoaded && typeof window !== "undefined") {
      localStorage.setItem("bpi_smartschool_plotting", JSON.stringify(plottingList));
    }
  }, [plottingList, isClientLoaded]);

  useEffect(() => {
    if (isClientLoaded && typeof window !== "undefined") {
      localStorage.setItem("bpi_smartschool_attendance", JSON.stringify(attendanceRecords));
    }
  }, [attendanceRecords, isClientLoaded]);

  useEffect(() => {
    if (isClientLoaded && typeof window !== "undefined") {
      localStorage.setItem("bpi_smartschool_teacher_attendance", JSON.stringify(teacherAttendanceRecords));
    }
  }, [teacherAttendanceRecords, isClientLoaded]);

  useEffect(() => {
    if (isClientLoaded && typeof window !== "undefined") {
      localStorage.setItem("bpi_smartschool_reports", JSON.stringify(reports));
    }
  }, [reports, isClientLoaded]);

  // LISTEN TO CROSS-TAB & IN-APP STORAGE EVENTS
  useEffect(() => {
    const handleStorageChange = (e: StorageEvent | Event) => {
      if (typeof window !== "undefined") {
        const studentRaw = localStorage.getItem("bpi_smartschool_students");
        if (studentRaw) {
          try {
            setStudents(JSON.parse(studentRaw));
          } catch {}
        }
        const teacherRaw =
          localStorage.getItem("bpi_smartschool_teachers") ||
          localStorage.getItem("bpi_smartschool_teachers_data");
        if (teacherRaw) {
          try {
            setTeachers(JSON.parse(teacherRaw));
          } catch {}
        }
        const plottingRaw = localStorage.getItem("bpi_smartschool_plotting");
        if (plottingRaw) {
          try {
            const parsed = JSON.parse(plottingRaw);
            if (Array.isArray(parsed) && parsed.length > 0) {
              setPlottingList(parsed);
            }
          } catch {}
        }
      }
    };

    window.addEventListener("storage", handleStorageChange);
    window.addEventListener("smartschool:sync", handleStorageChange);
    return () => {
      window.removeEventListener("storage", handleStorageChange);
      window.removeEventListener("smartschool:sync", handleStorageChange);
    };
  }, []);

  // SYNC FROM API ON MOUNT (WITH IN-MEMORY CACHE TO PREVENT REDUNDANT FETCHES)
  const refreshStudents = useCallback(async (force: boolean = false) => {
    if (!force) {
      const cached = appCache.get<UnifiedStudent[]>("context_students");
      if (cached && cached.length > 0) {
        setStudents(cached);
        return;
      }
    }
    setIsLoadingStudents(true);
    try {
      const res = await fetch("/api/students");
      if (res.ok) {
        const json = await res.json();
        if (json.success && Array.isArray(json.data)) {
          const mapped = json.data.map(normalizeFromDB);
          setStudents(mapped);
          appCache.set("context_students", mapped, 300000);
        }
      }
    } catch (err) {
      console.warn("Using local student state:", err);
    } finally {
      setIsLoadingStudents(false);
    }
  }, []);

  const refreshTeachers = useCallback(async (force: boolean = false) => {
    if (!force) {
      const cached = appCache.get<TeacherDetail[]>("context_teachers");
      if (cached && cached.length > 0) {
        setTeachers(cached);
        return;
      }
    }
    setIsLoadingTeachers(true);
    try {
      const url = force ? `/api/teachers?force=true&t=${Date.now()}` : "/api/teachers";
      const res = await fetch(url);
      if (res.ok) {
        const json = await res.json();
        if (json.success && Array.isArray(json.data)) {
          setTeachers(json.data);
          appCache.set("context_teachers", json.data, 300000);
        }
      }
    } catch (err) {
      console.warn("Using local teacher state:", err);
    } finally {
      setIsLoadingTeachers(false);
    }
  }, []);

  useEffect(() => {
    refreshStudents();
    refreshTeachers();
  }, [refreshStudents, refreshTeachers]);

  // AUTO-SEED PLOTTING FROM TEACHERS IF EMPTY
  useEffect(() => {
    if (teachers.length > 0 && isClientLoaded) {
      setPlottingList((prev) => {
        if (prev.length > 0) return prev;
        const stored = typeof window !== "undefined" ? localStorage.getItem("bpi_smartschool_plotting") : null;
        if (stored) {
          try {
            const parsed = JSON.parse(stored);
            if (Array.isArray(parsed) && parsed.length > 0) return parsed;
          } catch {}
        }
        const def = buildDefaultPlotting(teachers);
        if (typeof window !== "undefined") {
          localStorage.setItem("bpi_smartschool_plotting", JSON.stringify(def));
        }
        return def;
      });
    }
  }, [teachers, isClientLoaded]);

  // 8. REAL-TIME EVENT ENGINE & CROSS-CLIENT STATE REVALIDATION
  const [isRealtimeConnected, setIsRealtimeConnected] = useState<boolean>(true);

  const triggerRealtimeEvent = useCallback(
    async (type: RealtimeEventType, payload?: any) => {
      await publishRealtimeNotification(type, payload);
    },
    []
  );

  const handleRealtimeEvent = useCallback(
    (event: RealtimeEvent) => {
      if (event.type === "SYSTEM_HEARTBEAT") {
        setIsRealtimeConnected(true);
        return;
      }

      if (event.type === "ATTENDANCE_TAP_IN" || event.type === "ATTENDANCE_MUTATION") {
        const p = event.payload;
        if (p) {
          if (p.isTeacher || p.teacherId) {
            const tId = String(p.teacherId || p.id);
            setTeacherAttendanceRecords((prev) => ({
              ...prev,
              [tId]: {
                ...(prev[tId] || {}),
                [p.date]: p.status,
              },
            }));
          } else if (p.studentId || p.nis || p.id) {
            const stKey = String(p.studentId || p.nis || p.id);
            const stStatus = p.status === "terlambat" ? "telat" : p.status;
            setAttendanceRecords((prev) => {
              const next = { ...prev };
              const keys = [stKey, p.nis, p.studentId].filter(Boolean) as string[];
              keys.forEach((k) => {
                next[k] = {
                  ...(next[k] || {}),
                  [p.date]: stStatus,
                };
              });
              return next;
            });
          }
        }
      } else if (event.type === "MACHINE_SYNC_EXECUTED") {
        appCache.invalidate("context_students");
        appCache.invalidate("context_teachers");
        refreshStudents(true);
        refreshTeachers(true);
      } else if (event.type === "STUDENT_MUTATION") {
        appCache.invalidate("context_students");
        refreshStudents(true);
      } else if (event.type === "TEACHER_MUTATION") {
        appCache.invalidate("context_teachers");
        refreshTeachers(true);
      } else if (event.type === "GRADES_SUBMITTED") {
        if (event.payload?.reports && Array.isArray(event.payload.reports)) {
          setReports(event.payload.reports);
        }
      }
    },
    [refreshStudents, refreshTeachers]
  );

  useRealtimeSubscription(handleRealtimeEvent, {
    revalidateOnFocus: true,
    pollIntervalMs: 5000,
    onRevalidate: () => {
      refreshStudents(false);
      refreshTeachers(false);
    },
  });

  // Dynamically sync plottingList and homerooms with real teachers from database
  useEffect(() => {
    if (teachers.length > 0) {
      setPlottingList((prev) => {
        if (prev.length === 0) {
          return buildDefaultPlotting(teachers);
        }
        const teacherIds = new Set(teachers.map((t) => String(t.id)));
        const validPlotting = prev.filter((p) => teacherIds.has(String(p.teacherId)));
        return validPlotting.length > 0 ? validPlotting : buildDefaultPlotting(teachers);
      });

      setHomerooms((prev) => {
        const dbHomeroomMap = new Map<string, TeacherDetail>();
        teachers.forEach((t) => {
          if (t.homeroomClass && String(t.homeroomClass).trim() !== "" && String(t.homeroomClass).trim() !== "-") {
            dbHomeroomMap.set(String(t.homeroomClass).trim(), t);
          }
        });

        return mockAvailableClasses.map((cls) => {
          const dbTeacher = dbHomeroomMap.get(cls);
          const grade = cls.startsWith("X-") ? "X" : cls.startsWith("XI-") ? "XI" : "XII";
          const existing = prev.find((h) => h.className === cls);

          if (dbTeacher) {
            return {
              className: cls,
              gradeLevel: grade,
              teacherId: String(dbTeacher.id),
              teacherName: dbTeacher.fullName || (dbTeacher as any).name || (dbTeacher as any).nama || "",
              nip: dbTeacher.nip || "-",
              phone: dbTeacher.phone || "-",
              email: dbTeacher.email || "-",
              roomNumber: existing?.roomNumber || `R.${cls.replace("-", ".")}`,
            };
          }

          if (existing && existing.teacherId && existing.teacherName && existing.teacherName !== "Belum Ditugaskan") {
            const stillAssigned = teachers.find((t) => String(t.id) === String(existing.teacherId));
            if (!stillAssigned || !stillAssigned.homeroomClass || stillAssigned.homeroomClass !== cls) {
              return {
                className: cls,
                gradeLevel: grade,
                teacherId: "",
                teacherName: "Belum Ditugaskan",
                nip: "-",
                phone: "-",
                email: "-",
                roomNumber: existing.roomNumber || `R.${cls.replace("-", ".")}`,
              };
            }
            return existing;
          }

          return {
            className: cls,
            gradeLevel: grade,
            teacherId: "",
            teacherName: "Belum Ditugaskan",
            nip: "-",
            phone: "-",
            email: "-",
            roomNumber: `R.${cls.replace("-", ".")}`,
          };
        });
      });
    }
  }, [teachers]);

  // STUDENT ACTIONS
  const addStudent = useCallback(
    async (stData: Omit<UnifiedStudent, "id">) => {
      const newId = `std-${Date.now()}`;
      const newSt: UnifiedStudent = { ...stData, id: newId };

      setStudents((prev) => [newSt, ...prev]);

      // Try persisting to DB
      try {
        await fetch("/api/students", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            nis: stData.nis,
            nisn: stData.nisn,
            nama_lengkap: stData.fullName,
            jenis_kelamin: stData.gender === "P" ? "Perempuan" : "Laki-laki",
            tingkat: stData.gradeLevel,
            rombel: stData.className,
            status: stData.status,
            tempat_lahir: stData.birthPlace,
            tanggal_lahir: stData.birthDate,
            nama_ortu_wali: stData.parentName,
            no_wa_ortu: stData.parentPhone,
            alamat: stData.address,
          }),
        });
      } catch (e) {
        console.warn("API write failed, stored in Local Storage:", e);
      }
      return true;
    },
    []
  );

  const updateStudent = useCallback(
    async (id: string, data: Partial<UnifiedStudent>) => {
      setStudents((prev) =>
        prev.map((s) => (s.id === id ? { ...s, ...data } : s))
      );

      const target = students.find((s) => s.id === id);
      if (target?.dbId) {
        try {
          await fetch("/api/students", {
            method: "PUT",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
              id: target.dbId,
              nis: data.nis || target.nis,
              nisn: data.nisn || target.nisn,
              nama_lengkap: data.fullName || target.fullName,
              jenis_kelamin: (data.gender || target.gender) === "P" ? "Perempuan" : "Laki-laki",
              tingkat: data.gradeLevel || target.gradeLevel,
              rombel: data.className || target.className,
              status: data.status || target.status,
              tempat_lahir: data.birthPlace || target.birthPlace,
              tanggal_lahir: data.birthDate || target.birthDate,
              nama_ortu_wali: data.parentName || target.parentName,
              no_wa_ortu: data.parentPhone || target.parentPhone,
              alamat: data.address || target.address,
            }),
          });
        } catch {}
      }
      return true;
    },
    [students]
  );

  const deleteStudent = useCallback(
    async (id: string) => {
      const target = students.find((s) => s.id === id);
      setStudents((prev) => prev.filter((s) => s.id !== id));

      if (target?.dbId) {
        try {
          await fetch(`/api/students?id=${target.dbId}`, { method: "DELETE" });
        } catch {}
      }
      return true;
    },
    [students]
  );

  const importStudentsList = useCallback(
    async (newList: Omit<UnifiedStudent, "id">[]) => {
      const created: UnifiedStudent[] = newList.map((st, idx) => ({
        ...st,
        id: `std-imp-${Date.now()}-${idx}`,
      }));

      setStudents((prev) => [...created, ...prev]);

      // Bulk persist to DB
      for (const st of newList) {
        try {
          await fetch("/api/students", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
              nis: st.nis,
              nisn: st.nisn,
              nama_lengkap: st.fullName,
              jenis_kelamin: st.gender === "P" ? "Perempuan" : "Laki-laki",
              tingkat: st.gradeLevel,
              rombel: st.className,
              status: st.status,
              tempat_lahir: st.birthPlace,
              tanggal_lahir: st.birthDate,
              nama_ortu_wali: st.parentName,
              no_wa_ortu: st.parentPhone,
              alamat: st.address,
            }),
          });
        } catch {}
      }
      return true;
    },
    []
  );

  const promoteStudentsBatch = useCallback(
    async (
      promotions: PromotionItem[],
      academicYear: string
    ): Promise<PromotionResult> => {
      let promoted = 0;
      let retained = 0;
      let graduated = 0;
      let mutated = 0;

      const promotionMap = new Map<string, PromotionItem>();
      promotions.forEach((p) => {
        promotionMap.set(p.studentId, p);
        if (p.nis) promotionMap.set(p.nis, p);
      });

      setStudents((prev) => {
        return prev.map((st) => {
          const promo =
            promotionMap.get(st.id) || (st.nis ? promotionMap.get(st.nis) : undefined);
          if (!promo) return st;

          const history = st.academicHistory || [];
          const newHistory = [
            ...history,
            {
              academicYear: "2026/2027",
              gradeLevel: st.gradeLevel,
              className: st.className,
              status:
                promo.action === "naik"
                  ? ("Naik Kelas" as const)
                  : promo.action === "tinggal"
                  ? ("Tinggal Kelas" as const)
                  : promo.action === "lulus"
                  ? ("Lulus" as const)
                  : ("Mutasi Keluar" as const),
              date: new Date().toISOString().split("T")[0],
              notes: promo.notes || "",
            },
          ];

          let updatedGradeLevel = st.gradeLevel;
          let updatedClassName = st.className;
          let updatedStatus = st.status;

          if (promo.action === "naik") {
            promoted++;
            updatedGradeLevel = promo.targetGrade;
            updatedClassName = promo.targetClass;
            updatedStatus = "aktif";
          } else if (promo.action === "tinggal") {
            retained++;
            updatedGradeLevel = promo.previousGrade;
            updatedClassName = promo.previousClass;
            updatedStatus = "aktif";
          } else if (promo.action === "lulus") {
            graduated++;
            updatedStatus = "lulus";
            updatedClassName = promo.targetClass || "Alumni";
          } else if (promo.action === "mutasi") {
            mutated++;
            updatedStatus = "mutasi";
          }

          return {
            ...st,
            gradeLevel: updatedGradeLevel,
            className: updatedClassName,
            status: updatedStatus,
            academicHistory: newHistory,
          };
        });
      });

      // Persist to DB if students have dbId
      for (const promo of promotions) {
        const st = students.find(
          (s) => s.id === promo.studentId || s.nis === promo.nis
        );
        if (st?.dbId) {
          let updatedTingkat = st.gradeLevel;
          let updatedRombel = st.className;
          let updatedStatus = st.status;

          if (promo.action === "naik") {
            updatedTingkat = promo.targetGrade;
            updatedRombel = promo.targetClass;
            updatedStatus = "aktif";
          } else if (promo.action === "tinggal") {
            updatedTingkat = promo.previousGrade;
            updatedRombel = promo.previousClass;
            updatedStatus = "aktif";
          } else if (promo.action === "lulus") {
            updatedStatus = "lulus";
            updatedRombel = promo.targetClass || "Alumni";
          } else if (promo.action === "mutasi") {
            updatedStatus = "mutasi";
          }

          try {
            await fetch("/api/students", {
              method: "PUT",
              headers: { "Content-Type": "application/json" },
              body: JSON.stringify({
                id: st.dbId,
                nis: st.nis,
                nisn: st.nisn,
                nama_lengkap: st.fullName,
                jenis_kelamin: st.gender === "P" ? "Perempuan" : "Laki-laki",
                tingkat: updatedTingkat,
                rombel: updatedRombel,
                status: updatedStatus,
                tempat_lahir: st.birthPlace,
                tanggal_lahir: st.birthDate,
                nama_ortu_wali: st.parentName,
                no_wa_ortu: st.parentPhone,
                alamat: st.address,
              }),
            });
          } catch {}
        }
      }

      return {
        success: true,
        total: promotions.length,
        promoted,
        retained,
        graduated,
        mutated,
        message: `Proses kenaikan kelas berhasil untuk ${promotions.length} siswa.`,
      };
    },
    [students]
  );

  // TEACHER ACTIONS
  const addTeacher = useCallback(
    async (tData: Omit<TeacherDetail, "id">) => {
      const newId = `tch-${Date.now()}`;
      const newT: TeacherDetail = { ...tData, id: newId };
      setTeachers((prev) => [...prev, newT]);

      try {
        await fetch("/api/teachers", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(tData),
        });
      } catch {}
      return true;
    },
    []
  );

  const updateTeacher = useCallback(
    async (id: string, data: Partial<TeacherDetail>) => {
      setTeachers((prev) =>
        prev.map((t) => (t.id === id ? { ...t, ...data } : t))
      );

      if (data.homeroomClass !== undefined || data.isHomeroomTeacher !== undefined) {
        setHomerooms((prev) =>
          prev.map((h) => {
            if (data.isHomeroomTeacher && data.homeroomClass && h.className === data.homeroomClass) {
              const current = teachers.find((t) => t.id === id);
              return {
                ...h,
                teacherId: id,
                teacherName: data.fullName || current?.fullName || h.teacherName,
                nip: data.nip || current?.nip || h.nip,
                phone: data.phone || current?.phone || h.phone,
                email: data.email || current?.email || h.email,
              };
            }
            if (h.teacherId === id && (!data.isHomeroomTeacher || h.className !== data.homeroomClass)) {
              return {
                ...h,
                teacherId: "",
                teacherName: "Belum Ditugaskan",
                nip: "-",
                phone: "-",
                email: "-",
              };
            }
            return h;
          })
        );
      }

      try {
        await fetch("/api/teachers", {
          method: "PUT",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ id, ...data }),
        });
      } catch (err) {
        console.error("Failed to update teacher in DB:", err);
      }
      return true;
    },
    [teachers]
  );

  const deleteTeacher = useCallback(
    async (id: string) => {
      setTeachers((prev) => prev.filter((t) => t.id !== id));
      setHomerooms((prev) =>
        prev.map((h) =>
          h.teacherId === id
            ? {
                ...h,
                teacherId: "",
                teacherName: "Belum Ditugaskan",
                nip: "-",
                phone: "-",
                email: "-",
              }
            : h
        )
      );
      try {
        await fetch(`/api/teachers?id=${id}`, { method: "DELETE" });
      } catch (err) {
        console.error("Failed to delete teacher in DB:", err);
      }
      return true;
    },
    []
  );

  // HOMEROOM ASSIGNMENT
  const assignHomeroom = useCallback(
    async (className: string, teacherId: string) => {
      const teacher = teachers.find((t) => String(t.id) === String(teacherId));
      if (!teacher) return;

      // 1. Optimistic update homerooms
      setHomerooms((prev) =>
        prev.map((h) =>
          h.className === className
            ? {
                ...h,
                teacherId: teacher.id,
                teacherName: teacher.fullName,
                nip: teacher.nip || "-",
                phone: teacher.phone || "-",
                email: teacher.email || "-",
              }
            : h
        )
      );

      // 2. Optimistic two-way sync teachers state
      setTeachers((prev) =>
        prev.map((t) => {
          if (String(t.id) === String(teacher.id)) {
            return { ...t, isHomeroomTeacher: true, homeroomClass: className };
          }
          if (t.homeroomClass === className) {
            return { ...t, isHomeroomTeacher: false, homeroomClass: null as any };
          }
          return t;
        })
      );

      // 3. Persist to SQLite
      try {
        await fetch("/api/teachers", {
          method: "PUT",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            action: "assign_homeroom",
            className,
            teacherId: teacher.id,
          }),
        });
      } catch (err) {
        console.error("Failed to persist homeroom assignment to DB:", err);
      }
    },
    [teachers]
  );

  const getHomeroomForClass = useCallback(
    (className: string) => {
      return homerooms.find((h) => h.className === className);
    },
    [homerooms]
  );

  // PLOTTING ACTIONS
  const addPlotting = useCallback((item: Omit<PlottingRecord, "id">) => {
    const newId = `plt-${Date.now()}`;
    setPlottingList((prev) => [{ ...item, id: newId }, ...prev]);
  }, []);

  const updatePlotting = useCallback((id: string, data: Partial<PlottingRecord>) => {
    setPlottingList((prev) =>
      prev.map((p) => (p.id === id ? { ...p, ...data } : p))
    );
  }, []);

  const deletePlotting = useCallback((id: string) => {
    setPlottingList((prev) => prev.filter((p) => p.id !== id));
  }, []);

  // ATTENDANCE STATS CALCULATION
  const getStudentAttendanceStats = useCallback(
    (studentId: string, className?: string): StudentAttendanceSummary => {
      // 1. Resolve student across id, nis, dbId
      const targetStudent = students.find(
        (s) => s.id === studentId || s.nis === studentId || String(s.dbId) === studentId
      );

      const possibleKeys = Array.from(
        new Set(
          [
            studentId,
            targetStudent?.id,
            targetStudent?.nis,
            targetStudent?.dbId ? String(targetStudent.dbId) : undefined,
            targetStudent?.dbId ? `std-${targetStudent.dbId}` : undefined,
            targetStudent?.nis ? `std-${targetStudent.nis}` : undefined,
          ].filter(Boolean)
        )
      ) as string[];

      // 2. Merge logs from attendanceRecords across all keys
      const mergedLogs: Record<string, "hadir" | "sakit" | "izin" | "alpa" | "telat"> = {};
      possibleKeys.forEach((key) => {
        if (attendanceRecords[key]) {
          Object.assign(mergedLogs, attendanceRecords[key]);
        }
      });

      // 3. Fallback to default student seed if not recorded in state yet
      if (Object.keys(mergedLogs).length === 0) {
        possibleKeys.forEach((key) => {
          if (defaultStudentSeeds[key]) {
            Object.assign(mergedLogs, defaultStudentSeeds[key]);
          }
        });
        if (Object.keys(mergedLogs).length === 0 && targetStudent) {
          const autoLogs = getDeterministicStudentLogs(targetStudent.nis, targetStudent.id);
          Object.assign(mergedLogs, autoLogs);
        }
      }

      // 4. Calculate totals
      let sakit = 0;
      let izin = 0;
      let alpa = 0;
      let telat = 0;

      Object.values(mergedLogs).forEach((st) => {
        if (st === "sakit") sakit++;
        else if (st === "izin") izin++;
        else if (st === "alpa") alpa++;
        else if (st === "telat") telat++;
      });

      // Standar 110 hari efektif KBM semesteran SMA BPI 1 Bandung
      const totalHari = 110;
      const totalAbsen = sakit + izin + alpa;
      const hadir = Math.max(0, totalHari - totalAbsen);
      const kehadiranPct = Number(((hadir / totalHari) * 100).toFixed(1));

      return { hadir, sakit, izin, alpa, telat, totalAbsen, kehadiranPct };
    },
    [students, attendanceRecords]
  );

  const markStudentAttendance = useCallback(
    (
      studentId: string,
      date: string,
      status: "hadir" | "sakit" | "izin" | "alpa" | "telat"
    ) => {
      let matchedNis: string | undefined;
      setAttendanceRecords((prev) => {
        const st = students.find(
          (s) => s.id === studentId || s.nis === studentId || String(s.dbId) === studentId
        );
        matchedNis = st?.nis;
        const keysToUpdate = Array.from(
          new Set(
            [
              studentId,
              st?.id,
              st?.nis,
              st?.dbId ? String(st.dbId) : undefined,
              st?.dbId ? `std-${st.dbId}` : undefined,
              st?.nis ? `std-${st.nis}` : undefined,
            ].filter(Boolean)
          )
        ) as string[];

        const next = { ...prev };
        keysToUpdate.forEach((k) => {
          next[k] = {
            ...(next[k] || {}),
            [date]: status,
          };
        });
        return next;
      });

      publishRealtimeNotification("ATTENDANCE_MUTATION", {
        studentId,
        nis: matchedNis,
        date,
        status,
      });
    },
    [students]
  );

  const getTeacherAttendanceStats = useCallback(
    (teacherId: string): TeacherAttendanceSummary => {
      const teacherLogs = teacherAttendanceRecords[teacherId] || {};
      const dates = Object.keys(teacherLogs);

      let dinas = 0;
      let sakit = 0;
      let izin = 0;
      let alpa = 0;

      dates.forEach((d) => {
        const st = teacherLogs[d];
        if (st === "dinas") dinas++;
        else if (st === "sakit") sakit++;
        else if (st === "izin") izin++;
        else if (st === "alpa") alpa++;
      });

      const totalHari = 22; // Standar hari kerja efektif per bulan
      const totalAbsen = sakit + izin + alpa;
      const hadir = Math.max(0, totalHari - dinas - totalAbsen);
      const kehadiranPct = Number((((hadir + dinas) / totalHari) * 100).toFixed(1));

      return { hadir, dinas, sakit, izin, alpa, totalHari, kehadiranPct };
    },
    [teacherAttendanceRecords]
  );

  const markTeacherAttendance = useCallback(
    (
      teacherId: string,
      date: string,
      status: "hadir" | "dinas" | "sakit" | "izin" | "alpa"
    ) => {
      setTeacherAttendanceRecords((prev) => ({
        ...prev,
        [teacherId]: {
          ...(prev[teacherId] || {}),
          [date]: status,
        },
      }));

      publishRealtimeNotification("ATTENDANCE_MUTATION", {
        teacherId,
        date,
        status,
        isTeacher: true,
      });
    },
    []
  );

  // AUTO-GENERATE & MERGE MONTHLY REPORTS FOR ALL REGISTERED STUDENTS
  const getReportsByClassAndMonth = useCallback(
    (className: string, month: string): MonthlyReportRecord[] => {
      const classStudents = students
        .filter(
          (s) =>
            (s.className || "").trim().toLowerCase() === className.trim().toLowerCase() &&
            (s.status || "aktif").toLowerCase() === "aktif"
        )
        .sort((a, b) => (a.fullName || "").localeCompare(b.fullName || ""));
      const homeroom = homerooms.find((h) => h.className === className);
      const hrName = homeroom ? homeroom.teacherName : "Drs. H. Mulyadi, M.Pd.";
      const hrNip = homeroom ? homeroom.nip : "197205121998021002";

      return classStudents.map((st, idx) => {
        // Find existing report or create dynamic one
        const existing = reports.find(
          (r) =>
            (r.studentId === st.id || r.nis === st.nis) &&
            r.className === className &&
            r.reportMonth === month
        );

        const attendance = getStudentAttendanceStats(st.id, className);

        if (existing) {
          return {
            ...existing,
            studentName: st.fullName,
            nis: st.nis,
            nisn: st.nisn,
            homeroomTeacherName: hrName,
            homeroomTeacherNip: hrNip,
            attendance,
          };
        }

        // Generate baseline 20 subjects with clean empty state for all 12 months
        const subjects: SubjectMonthlyGradeItem[] = masterSubjects.map((sbj) => {
          const initialMonths: Record<string, MonthGradeDetail> = {
            januari: { forScore: null, sumScore: null, ptsScore: null, finalScore: null },
            februari: { forScore: null, sumScore: null, ptsScore: null, finalScore: null },
            maret: { forScore: null, sumScore: null, ptsScore: null, finalScore: null },
            april: { forScore: null, sumScore: null, ptsScore: null, finalScore: null },
            mei: { forScore: null, sumScore: null, ptsScore: null, finalScore: null },
            juni: { forScore: null, sumScore: null, ptsScore: null, finalScore: null },
            juli: { forScore: null, sumScore: null, ptsScore: null, finalScore: null },
            agustus: { forScore: null, sumScore: null, ptsScore: null, finalScore: null },
            september: { forScore: null, sumScore: null, ptsScore: null, finalScore: null },
            oktober: { forScore: null, sumScore: null, ptsScore: null, finalScore: null },
            november: { forScore: null, sumScore: null, ptsScore: null, finalScore: null },
            desember: { forScore: null, sumScore: null, ptsScore: null, finalScore: null },
          };

          return {
            subjectCode: sbj.code,
            subjectName: sbj.name,
            month1: { forScore: null, sumScore: null, finalScore: null },
            month2: {
              forScore: null,
              sumScore: null,
              ptsScore: null,
              finalScore: null,
            },
            month3: { forScore: null, sumScore: null, finalScore: null },
            month4: { forScore: null, sumScore: null, finalScore: null },
            monthlyGrades: initialMonths,
            finalAverage: null,
            predicate: null,
            statusRemark: "",
          };
        });

        const isGenapMonth = /januari|februari|maret|april|mei|juni/i.test(month);
        const semester = isGenapMonth ? ("Genap" as const) : ("Ganjil" as const);

        return {
          id: `rpt-${st.nis}-${month.replace(/\s+/g, "_")}`,
          studentId: st.id,
          studentName: st.fullName,
          nis: st.nis,
          nisn: st.nisn,
          className: st.className,
          gradeLevel: st.gradeLevel,
          homeroomTeacherName: hrName,
          homeroomTeacherNip: hrNip,
          reportMonthNumber: 2,
          reportMonth: month,
          academicYear: "2026-2027",
          semester,
          attendance,
          subjects,
          overallAverage: null,
          highestScore: null,
          highestSubject: "-",
          lowestScore: null,
          lowestSubject: "-",
          rankInClass: idx + 1,
          totalClassStudents: classStudents.length,
          learningStyle: (idx % 3 === 0 ? "Visual" : idx % 3 === 1 ? "Auditorial" : "Kinestetik") as any,
          competitions: [],
          extracurriculars: [],
          prolifeActivity: undefined,
          collegeMajorPlan: "Pilihan Program Studi Belum Diisi",
          homeroomNotes: "Pertahankan kedisiplinan dan terus kembangkan potensi belajar.",
          bkNotes: "Konseling minat dan bakat siap diarahkan sesuai capaian asesmen.",
          recommendedTKASubjects: "-",
          characterDevelopment: "Mandiri, bernalar kritis, dan bergotong royong secara aktif.",
          status: "Draft",
        };
      });
    },
    [students, homerooms, reports, masterSubjects, getStudentAttendanceStats]
  );

  // PTS Tentative State per Academic Month (Persisted in localStorage)
  const [ptsConfigByMonth, setPtsConfigByMonth] = useState<Record<string, boolean>>(() => {
    if (typeof window !== "undefined") {
      try {
        const saved = localStorage.getItem("bpi_pts_tentative_config");
        if (saved) {
          return { ...DEFAULT_PTS_CONFIG, ...JSON.parse(saved) };
        }
      } catch (e) {
        console.error("Error reading bpi_pts_tentative_config", e);
      }
    }
    return DEFAULT_PTS_CONFIG;
  });

  const setPTSForMonth = useCallback((monthKeyOrName: string, enabled: boolean) => {
    const key = normalizeMonthKey(monthKeyOrName);
    setPtsConfigByMonth((prev) => {
      const updated = { ...prev, [key]: enabled };
      if (typeof window !== "undefined") {
        try {
          localStorage.setItem("bpi_pts_tentative_config", JSON.stringify(updated));
        } catch (e) {
          console.error("Error saving bpi_pts_tentative_config", e);
        }
      }
      return updated;
    });
  }, []);

  const isPTSActiveForMonth = useCallback(
    (monthKeyOrName: string) => {
      const key = normalizeMonthKey(monthKeyOrName);
      return ptsConfigByMonth[key] ?? false;
    },
    [ptsConfigByMonth]
  );

  // UPDATE SUBJECT SCORE IN REPORTS (WITH CLEAN NULL HANDLING & 12 MONTHS SUPPORT)
  const updateSubjectScore = useCallback(
    (
      reportId: string,
      subjectCode: string,
      monthField: string,
      value: number | null,
      targetMonthName?: string
    ) => {
      setReports((prev) => {
        return prev.map((rep) => {
          if (rep.id !== reportId) return rep;

          const updatedSubjects = rep.subjects.map((sbj) => {
            if (sbj.subjectCode !== subjectCode) return sbj;

            const m1 = { ...sbj.month1 };
            const m2 = { ...sbj.month2 };
            const m3 = { ...sbj.month3 };
            const m4 = { ...sbj.month4 };
            const monthly = { ...(sbj.monthlyGrades || {}) };

            // Determine target month key
            let monthKey = "";
            if (targetMonthName) {
              monthKey = normalizeMonthKey(targetMonthName);
            }

            // Also update monthly dictionary if monthKey is present
            if (monthKey) {
              const currentMonthGrade = { ...(monthly[monthKey] || { forScore: null, sumScore: null, ptsScore: null, finalScore: null }) };
              if (monthField.includes("for") || monthField === "forScore") currentMonthGrade.forScore = value;
              if (monthField.includes("sum") || monthField === "sumScore") currentMonthGrade.sumScore = value;
              if (monthField.includes("pts") || monthField === "ptsScore") currentMonthGrade.ptsScore = value;

              const validScores = [currentMonthGrade.forScore, currentMonthGrade.sumScore, currentMonthGrade.ptsScore].filter(
                (v): v is number => v != null
              );
              currentMonthGrade.finalScore = validScores.length > 0
                ? Math.round(validScores.reduce((a, b) => a + b, 0) / validScores.length)
                : null;
              monthly[monthKey] = currentMonthGrade;

              if (monthKey === "agustus") Object.assign(m1, currentMonthGrade);
              if (monthKey === "september") Object.assign(m2, currentMonthGrade);
              if (monthKey === "oktober") Object.assign(m3, currentMonthGrade);
              if (monthKey === "november") Object.assign(m4, currentMonthGrade);
            } else {
              if (monthField === "m1_for") m1.forScore = value;
              if (monthField === "m1_sum") m1.sumScore = value;
              if (monthField === "m1_pts") m1.ptsScore = value;
              const v1 = [m1.forScore, m1.sumScore, m1.ptsScore].filter((v): v is number => v != null);
              m1.finalScore = v1.length > 0 ? Math.round(v1.reduce((a, b) => a + b, 0) / v1.length) : null;

              if (monthField === "m2_for") m2.forScore = value;
              if (monthField === "m2_sum") m2.sumScore = value;
              if (monthField === "m2_pts") m2.ptsScore = value;
              const v2 = [m2.forScore, m2.sumScore, m2.ptsScore].filter((v): v is number => v != null);
              m2.finalScore = v2.length > 0 ? Math.round(v2.reduce((a, b) => a + b, 0) / v2.length) : null;

              if (monthField === "m3_for") m3.forScore = value;
              if (monthField === "m3_sum") m3.sumScore = value;
              if (monthField === "m3_pts") m3.ptsScore = value;
              const v3 = [m3.forScore, m3.sumScore, m3.ptsScore].filter((v): v is number => v != null);
              m3.finalScore = v3.length > 0 ? Math.round(v3.reduce((a, b) => a + b, 0) / v3.length) : null;

              if (monthField === "m4_for") m4.forScore = value;
              if (monthField === "m4_sum") m4.sumScore = value;
              if (monthField === "m4_pts") m4.ptsScore = value;
              const v4 = [m4.forScore, m4.sumScore, m4.ptsScore].filter((v): v is number => v != null);
              m4.finalScore = v4.length > 0 ? Math.round(v4.reduce((a, b) => a + b, 0) / v4.length) : null;

              monthly["agustus"] = m1;
              monthly["september"] = m2;
              monthly["oktober"] = m3;
              monthly["november"] = m4;
            }

            // Calculate subject cumulative average from entered months across monthly dictionary
            const allMonthFinalScores = Object.values(monthly)
              .map((mg) => mg.finalScore)
              .filter((v): v is number => v != null);

            let finalAverage: number | null = null;
            let predicate: "A" | "B" | "C" | "D" | "-" | null = null;
            let statusRemark = "";

            if (allMonthFinalScores.length > 0) {
              finalAverage = Number(
                (allMonthFinalScores.reduce((a, b) => a + b, 0) / allMonthFinalScores.length).toFixed(1)
              );
              predicate =
                finalAverage >= 85
                  ? "A"
                  : finalAverage >= 75
                  ? "B"
                  : finalAverage >= 65
                  ? "C"
                  : "D";
              statusRemark =
                finalAverage >= 85
                  ? "Pertahankan prestasinya"
                  : finalAverage >= 70
                  ? "Tingkatkan semangat belajar"
                  : "Lebih giat lagi belajar";
            }

            return {
              ...sbj,
              month1: m1,
              month2: m2,
              month3: m3,
              month4: m4,
              monthlyGrades: monthly,
              finalAverage,
              predicate,
              statusRemark,
            };
          });

          const validSubjectAverages = updatedSubjects
            .map((s) => s.finalAverage)
            .filter((v): v is number => v != null);

          const overallAverage =
            validSubjectAverages.length > 0
              ? Number(
                  (
                    validSubjectAverages.reduce((a, b) => a + b, 0) /
                    validSubjectAverages.length
                  ).toFixed(1)
                )
              : null;

          return {
            ...rep,
            subjects: updatedSubjects,
            overallAverage,
          };
        });
      });
    },
    []
  );

  const batchUpdateReports = useCallback((updatedList: MonthlyReportRecord[]) => {
    setReports((prev) => {
      const map = new Map<string, MonthlyReportRecord>();
      prev.forEach((r) => map.set(r.id, r));
      updatedList.forEach((r) => map.set(r.id, r));
      return Array.from(map.values());
    });
    publishRealtimeNotification("GRADES_SUBMITTED", { count: updatedList.length });
  }, []);

  const updateNonAcademicProfile = useCallback(
    (reportId: string, data: Partial<MonthlyReportRecord>) => {
      setReports((prev) =>
        prev.map((rep) => (rep.id === reportId ? { ...rep, ...data } : rep))
      );
      publishRealtimeNotification("GRADES_SUBMITTED", { reportId, data });
    },
    []
  );

  const contextValue = useMemo<SmartSchoolContextType>(
    () => ({
      students,
      isLoadingStudents,
      refreshStudents,
      addStudent,
      updateStudent,
      deleteStudent,
      importStudentsList,
      promoteStudentsBatch,
      teachers,
      isLoadingTeachers,
      refreshTeachers,
      addTeacher,
      updateTeacher,
      deleteTeacher,
      masterSubjects,
      homerooms,
      assignHomeroom,
      getHomeroomForClass,
      plottingList,
      addPlotting,
      updatePlotting,
      deletePlotting,
      attendanceRecords,
      teacherAttendanceRecords,
      getStudentAttendanceStats,
      markStudentAttendance,
      getTeacherAttendanceStats,
      markTeacherAttendance,
      reports,
      getReportsByClassAndMonth,
      updateSubjectScore,
      batchUpdateReports,
      updateNonAcademicProfile,
      ptsConfigByMonth,
      setPTSForMonth,
      isPTSActiveForMonth,
      triggerRealtimeEvent,
      isRealtimeConnected,
    }),
    [
      students,
      isLoadingStudents,
      refreshStudents,
      addStudent,
      updateStudent,
      deleteStudent,
      importStudentsList,
      promoteStudentsBatch,
      teachers,
      isLoadingTeachers,
      refreshTeachers,
      addTeacher,
      updateTeacher,
      deleteTeacher,
      masterSubjects,
      homerooms,
      assignHomeroom,
      getHomeroomForClass,
      plottingList,
      addPlotting,
      updatePlotting,
      deletePlotting,
      attendanceRecords,
      teacherAttendanceRecords,
      getStudentAttendanceStats,
      markStudentAttendance,
      getTeacherAttendanceStats,
      markTeacherAttendance,
      reports,
      getReportsByClassAndMonth,
      updateSubjectScore,
      batchUpdateReports,
      updateNonAcademicProfile,
      ptsConfigByMonth,
      setPTSForMonth,
      isPTSActiveForMonth,
      triggerRealtimeEvent,
      isRealtimeConnected,
    ]
  );

  return (
    <SmartSchoolContext.Provider value={contextValue}>
      {children}
    </SmartSchoolContext.Provider>
  );
}

export function useSmartSchool() {
  const context = useContext(SmartSchoolContext);
  if (!context) {
    throw new Error("useSmartSchool must be used within a SmartSchoolProvider");
  }
  return context;
}
