import { useEffect, useMemo, useState, type FormEvent } from 'react'
import { Link, useParams, useSearchParams } from 'react-router-dom'
import { fetchAdministratorCalendarEvents, openProtectedApiFile } from '../../api/session'
import type { ExamDraftRow, SchoolCalendarEventRow } from '../../api/types'
import { TeacherShell } from './TeacherShell'
import { useTeacherResource } from './useTeacherResource'
import { postTeacherMultipart } from '../../api/teacherSession'
import { useAuth } from '../../auth/AuthProvider'
import { ApiHttpError, apiFetch } from '../../api/http'
import { useSchoolYearOptions } from '../../hooks/useSchoolYearOptions'

function TeacherApiDumpPage({
  title,
  subtitle,
  path,
}: {
  title: string
  subtitle?: string
  path: string
}) {
  const { data, loading, error } = useTeacherResource<unknown>(path)
  return (
    <TeacherShell title={title} subtitle={subtitle} loading={loading} error={error}>
      {data != null ? (
        <pre className="small bg-light p-3 rounded overflow-auto" style={{ maxHeight: 560 }}>
          {JSON.stringify(data, null, 2)}
        </pre>
      ) : null}
    </TeacherShell>
  )
}

function teacherRouteWithTerm(
  path: string,
  params: { classSectionId?: number; schoolYear?: string; semester?: string },
): string {
  const query = new URLSearchParams()
  if (params.classSectionId && params.classSectionId > 0) {
    query.set('class_section_id', String(params.classSectionId))
  }
  if (params.schoolYear) query.set('school_year', params.schoolYear)
  if (params.semester) query.set('semester', params.semester)
  const value = query.toString()
  return value ? `${path}?${value}` : path
}

type CompetitionIndexPayload = {
  success?: string
  error?: string
  hasClasses?: boolean
  activeClassName?: string
  schoolYear?: string
  semester?: string
  studentTotal?: number
  competitions?: Array<Record<string, unknown>>
  questionCounts?: Record<string, number>
  scoreCounts?: Record<string, number>
  sectionMap?: Record<string, string>
}

function formatCompetitionDate(value: unknown): string {
  if (value == null || value === '') return ''
  const s = String(value)
  const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(s)
  if (m) return `${m[2]}-${m[3]}-${m[1]}`
  return s
}

function normalizeCalendarDate(value: string | null | undefined): string {
  if (!value) return ''
  const match = /^(\d{4}-\d{2}-\d{2})/.exec(String(value))
  return match ? match[1] : String(value)
}

function formatCalendarLabel(value: string, options: Intl.DateTimeFormatOptions): string {
  if (!value) return ''
  return new Intl.DateTimeFormat('en-US', options).format(new Date(`${value}T12:00:00`))
}

function monthStart(value: Date): Date {
  return new Date(value.getFullYear(), value.getMonth(), 1)
}

function dateKey(value: Date): string {
  const y = value.getFullYear()
  const m = String(value.getMonth() + 1).padStart(2, '0')
  const d = String(value.getDate()).padStart(2, '0')
  return `${y}-${m}-${d}`
}

type TeacherCalendarDisplayEvent = SchoolCalendarEventRow & {
  start: string
  backgroundColor: string
}

type TeacherExamDraftsPayload = {
  ok?: boolean
  class_section_id?: number | string | null
  assignments?: Array<{
    class_section_id?: number | string | null
    class_section_name?: string | null
    [key: string]: unknown
  }>
  drafts?: ExamDraftRow[]
  legacy_exams?: ExamDraftRow[]
  exam_types?: string[]
  status_options?: string[]
  school_year?: string | null
  semester?: string | null
}

type TeacherExamDraftStoreResponse = {
  ok?: boolean
  draft?: ExamDraftRow
  message?: string
}

type FrontendMeResponse = {
  status?: boolean
  data?: {
    user?: {
      class_section_id?: number | null
      class_section_name?: string | null
    } | null
  } | null
}

const EXAM_DRAFT_MAX_BYTES = 12 * 1024 * 1024
const EXAM_DRAFT_ALLOWED_EXTENSIONS = ['doc', 'docx']

function teacherExamFilePath(kind: 'teacher' | 'final', filename?: string | null) {
  return filename ? `/api/v1/files/exams/${kind}/${encodeURIComponent(filename)}` : null
}

type TeacherAbsenceRow = {
  id?: number
  user_id?: number
  date?: string
  semester?: string | null
  school_year?: string | null
  status?: string | null
  reason?: string | null
}

type TeacherAbsenceFormPayload = {
  ok?: boolean
  teacher_name?: string | null
  semester?: string | null
  school_year?: string | null
  existing?: TeacherAbsenceRow[]
  available_dates?: string[]
}

type TeacherAbsenceSubmitResponse = {
  ok?: boolean
  message?: string
  saved?: number
  dates?: string[]
}

function formatTeacherAbsenceDate(value: string | null | undefined): string {
  if (!value) return ''
  const match = /^(\d{4})-(\d{2})-(\d{2})/.exec(String(value))
  if (!match) return String(value)
  return `${match[2]}-${match[3]}-${match[1]}`
}

function teacherAbsenceStatusBadgeClass(value: string | null | undefined): string {
  const status = String(value ?? '').trim().toLowerCase()
  if (status === 'absent') return 'danger'
  if (status === 'late') return 'warning text-dark'
  if (status === 'present') return 'success'
  return 'secondary'
}

type TeacherScoreComment = {
  comment?: string | null
  comment_review?: string | null
}

type TeacherScoresStudent = {
  student_id: number
  firstname?: string | null
  lastname?: string | null
  school_id?: string | null
  scores?: Record<string, string | number | null | undefined>
  comments?: Record<string, TeacherScoreComment> | null
}

type TeacherScoresPayload = {
  ok?: boolean
  students?: TeacherScoresStudent[]
  class_section_id?: number | string | null
  class_section_name?: string | null
  semester?: string | null
  school_year?: string | null
  scores_locked?: boolean
  missing_ok_map?: Record<string, Record<string, Record<string, boolean | number> | boolean | number>>
}

type TeacherScoresRow = {
  student_id: number
  firstname: string
  lastname: string
  school_id: string
  attendance: string
  homework: string
  project: string
  quiz: string
  participation: string
  ptap: string
  term_score: string
  semester_score: string
  ptap_comment: string
  ptap_comment_review: string
  term_comment: string
  term_comment_review: string
  ptap_missing_ok: boolean
  term_missing_ok: boolean
}

function teacherScoreDisplay(value: unknown): string {
  if (value == null || value === '') return ''
  return String(value)
}

function teacherSemesterKey(value: string | null | undefined): 'midterm' | 'final' | null {
  const semester = String(value ?? '').trim().toLowerCase()
  if (semester === 'fall') return 'midterm'
  if (semester === 'spring') return 'final'
  return null
}

function teacherScoreStartsWithName(value: string, firstName: string): boolean {
  const text = value.trim()
  const name = firstName.trim()
  if (text === '' || name === '') return true
  const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
  return new RegExp(`^${escaped}\\b`, 'i').test(text)
}

function teacherScoreCommentError(value: string, firstName: string): string | null {
  const text = value.trim()
  if (text === '') return null
  if (text.length < 100 || text.length > 350) {
    return `Comment for ${firstName || 'student'} must be between 100 and 350 characters.`
  }
  if (!teacherScoreStartsWithName(text, firstName)) {
    return `Comment for ${firstName || 'student'} must start with the student's first name.`
  }
  return null
}

function teacherScoreInRange(value: string): boolean {
  const text = value.trim()
  if (text === '') return false
  const num = Number(text)
  return Number.isFinite(num) && num >= 0 && num <= 100
}

/** `teacher/competition_scores/index.php` */
export function TeacherCompetitionScoresIndexPage() {
  const { data, loading, error } = useTeacherResource<CompetitionIndexPayload>('/competition-scores')
  const flashOk = data?.success
  const flashErr = data?.error

  return (
    <TeacherShell
      title="Competition Scores"
      subtitle="Enter scores for your class competitions."
      loading={loading}
      error={error}
      flash={flashOk ?? flashErr ?? undefined}
    >
      <div className="d-flex flex-wrap justify-content-between align-items-start mb-3 gap-2">
        <div />
        <div className="text-end small">
          {data?.activeClassName ? (
            <div className="fw-semibold">Class: {data.activeClassName}</div>
          ) : null}
          {data?.schoolYear ? (
            <div className="text-muted">
              {data.schoolYear} {data.semester ?? ''}
            </div>
          ) : null}
        </div>
      </div>

      {data?.hasClasses === false ? (
        <div className="alert alert-warning">No classes assigned to you for the current term.</div>
      ) : data && (!data.competitions || data.competitions.length === 0) ? (
        <div className="alert alert-info">No competitions available for this class right now.</div>
      ) : null}

      {data?.competitions && data.competitions.length > 0 ? (
        <div className="table-responsive">
          <table className="table table-bordered table-sm align-middle">
            <thead className="table-light">
              <tr>
                <th>Title</th>
                <th>Questions</th>
                <th>Scores Entered</th>
                <th>Dates</th>
                <th>Locked</th>
                <th>Actions</th>
              </tr>
            </thead>
            <tbody>
              {data.competitions.map((c) => {
                const compId = Number(c.id ?? 0)
                const sectionId = Number(c.class_section_id ?? 0)
                const sectionName =
                  sectionId > 0 ? (data.sectionMap?.[String(sectionId)] ?? sectionId) : 'All Classes'
                const qCount = data.questionCounts?.[String(compId)]
                const scoreCount = data.scoreCounts?.[String(compId)] ?? 0
                const studentTotal = data.studentTotal ?? 0
                const scoreLabel =
                  studentTotal > 0 ? `${scoreCount} / ${studentTotal}` : String(scoreCount)
                const isLocked = Boolean(c.is_locked)
                const startLabel = formatCompetitionDate(c.start_date)
                const endLabel = formatCompetitionDate(c.end_date)
                let dateLabel = '—'
                if (startLabel && endLabel) dateLabel = `${startLabel} - ${endLabel}`
                else if (startLabel) dateLabel = startLabel
                else if (endLabel) dateLabel = endLabel

                return (
                  <tr key={compId || JSON.stringify(c)}>
                    <td>{String(c.title ?? '')}</td>
                    <td>{qCount != null ? String(qCount) : '—'}</td>
                    <td>{scoreLabel}</td>
                    <td>{dateLabel}</td>
                    <td>{isLocked ? 'Yes' : 'No'}</td>
                    <td>
                      {isLocked ? (
                        <span className="text-muted">Locked</span>
                      ) : (
                        <Link to={`/app/teacher/competition-scores/${compId}`}>Enter scores</Link>
                      )}
                      <div className="small text-muted">{sectionName}</div>
                    </td>
                  </tr>
                )
              })}
            </tbody>
          </table>
        </div>
      ) : null}
    </TeacherShell>
  )
}

/** `teacher/competition_scores/scores.php` */
export function TeacherCompetitionScoresDetailPage() {
  const { competitionId } = useParams()
  const path =
    competitionId && competitionId !== 'undefined'
      ? `/competition-scores/${competitionId}`
      : '/competition-scores/0'
  return <TeacherApiDumpPage title="Competition scores entry" path={path} />
}

/** `teacher/absence_vacation.php` */
export function TeacherAbsenceVacationPage() {
  const [payload, setPayload] = useState<TeacherAbsenceFormPayload | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const [message, setMessage] = useState<string | null>(null)
  const [submitting, setSubmitting] = useState(false)
  const [selectedDates, setSelectedDates] = useState<string[]>([])
  const [reasonType, setReasonType] = useState('vacation')
  const [reason, setReason] = useState('')

  async function load() {
    try {
      setLoading(true)
      setError(null)
      const body = await apiFetch<TeacherAbsenceFormPayload>('/api/v1/teacher/absence-vacation')
      setPayload(body)
      setSelectedDates((current) => {
        if (current.length > 0) return current
        return (body.available_dates?.length ?? 0) > 0 ? [''] : []
      })
    } catch (e) {
      setError(e instanceof Error ? e.message : 'Unable to load absence request form.')
    } finally {
      setLoading(false)
    }
  }

  useEffect(() => {
    void load()
  }, [])

  const availableDates = payload?.available_dates ?? []
  const hasDates = availableDates.length > 0
  const usedDates = new Set(selectedDates.filter(Boolean))
  const subtitleParts = [`Logged in as: ${payload?.teacher_name?.trim() || 'Teacher'}`]
  if (payload?.semester || payload?.school_year) {
    subtitleParts.push(`Term: ${payload?.semester ?? ''}${payload?.semester && payload?.school_year ? ' — ' : ''}${payload?.school_year ?? ''}`)
  }

  function updateDateRow(index: number, value: string) {
    setSelectedDates((current) => current.map((item, itemIndex) => (itemIndex === index ? value : item)))
  }

  function addDateRow() {
    setSelectedDates((current) => {
      if (current.length >= availableDates.length) return current
      return [...current, '']
    })
  }

  function removeDateRow(index: number) {
    setSelectedDates((current) => current.filter((_, itemIndex) => itemIndex !== index))
  }

  async function submit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault()
    setSubmitting(true)
    setError(null)
    setMessage(null)

    const dates = Array.from(new Set(selectedDates.map((item) => item.trim()).filter(Boolean)))

    if (dates.length === 0) {
      setError('Select at least one Sunday date.')
      setSubmitting(false)
      return
    }

    if (!reason.trim()) {
      setError('Reason is required.')
      setSubmitting(false)
      return
    }

    try {
      const body = await apiFetch<TeacherAbsenceSubmitResponse>('/api/v1/teacher/absence-vacation', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          dates,
          reason_type: reasonType.trim() || null,
          reason: reason.trim(),
        }),
      })
      setMessage(body.message ?? 'Absence request submitted.')
      setReason('')
      setReasonType('vacation')
      setSelectedDates(hasDates ? [''] : [])
      await load()
    } catch (e) {
      setError(e instanceof Error ? e.message : 'Unable to submit absence request.')
    } finally {
      setSubmitting(false)
    }
  }

  return (
    <TeacherShell
      title="TimeOff Request"
      subtitle={subtitleParts.join(' · ')}
      loading={loading}
      error={error}
    >
      {message ? <div className="alert alert-success">{message}</div> : null}

      <div className="card shadow-sm mb-4">
        <div className="card-header bg-light">
          <strong>Submit Upcoming Absences</strong>
        </div>
        <div className="card-body">
          <form onSubmit={(event) => void submit(event)}>
            <div className="mb-3">
              <label className="form-label">Select Sunday Date(s)</label>
              <div className="d-flex flex-column gap-2">
                {hasDates ? (
                  selectedDates.map((value, index) => (
                    <div key={`absence-date-${index}`} className="d-flex align-items-center gap-2 flex-wrap">
                      <select
                        className="form-select"
                        style={{ maxWidth: 260 }}
                        value={value}
                        onChange={(event) => updateDateRow(index, event.target.value)}
                        required
                      >
                        <option value="" disabled>
                          Select Sunday date
                        </option>
                        {availableDates.map((date) => {
                          const takenByAnotherRow = usedDates.has(date) && value !== date
                          return (
                            <option key={date} value={date} disabled={takenByAnotherRow}>
                              {formatTeacherAbsenceDate(date)}
                            </option>
                          )
                        })}
                      </select>

                      {index === 0 ? (
                        <button
                          type="button"
                          className="btn btn-outline-secondary btn-sm"
                          onClick={addDateRow}
                          disabled={!hasDates || selectedDates.length >= availableDates.length}
                        >
                          Add another
                        </button>
                      ) : (
                        <button
                          type="button"
                          className="btn btn-outline-danger btn-sm"
                          onClick={() => removeDateRow(index)}
                        >
                          Remove
                        </button>
                      )}
                    </div>
                  ))
                ) : (
                  <div className="alert alert-info mb-0">
                    No future Sundays are available in the current term.
                  </div>
                )}
              </div>
              <div className="form-text">Future Sundays from September to May are available only.</div>
            </div>

            <div className="row g-3 mb-3">
              <div className="col-sm-6">
                <label className="form-label" htmlFor="teacher-absence-reason-type">
                  Reason Type
                </label>
                <select
                  id="teacher-absence-reason-type"
                  className="form-select"
                  value={reasonType}
                  onChange={(event) => setReasonType(event.target.value)}
                >
                  <option value="vacation">Vacation</option>
                  <option value="sick">Sick</option>
                  <option value="personal">Personal</option>
                  <option value="other">Other</option>
                </select>
              </div>

              <div className="col-sm-6">
                <label className="form-label" htmlFor="teacher-absence-reason">
                  Reason <span className="text-danger">*</span>
                </label>
                <input
                  id="teacher-absence-reason"
                  className="form-control"
                  type="text"
                  value={reason}
                  onChange={(event) => setReason(event.target.value)}
                  placeholder="Provide a reason"
                  required
                />
              </div>
            </div>

            <div className="d-flex justify-content-center">
              <button className="btn btn-primary px-5" type="submit" disabled={submitting || !hasDates}>
                {submitting ? 'Submitting…' : 'Submit Absence'}
              </button>
            </div>
          </form>
        </div>
      </div>

      <div className="card shadow-sm">
        <div className="card-header bg-light">
          <strong>Your Reported Status (This Term)</strong>
        </div>
        <div className="card-body p-0">
          <div className="table-responsive">
            <table className="table table-bordered table-striped m-0 align-middle">
              <thead className="table-light">
                <tr>
                  <th className="text-center" style={{ width: 80 }}>
                    #
                  </th>
                  <th>Date</th>
                  <th>Status</th>
                  <th>Reason</th>
                </tr>
              </thead>
              <tbody>
                {(payload?.existing ?? []).length > 0 ? (
                  (payload?.existing ?? []).map((row, index) => {
                    const status = String(row.status ?? '').trim().toLowerCase()
                    return (
                      <tr key={row.id ?? `${row.date ?? 'absence'}-${index}`}>
                        <td className="text-center">{index + 1}</td>
                        <td>{formatTeacherAbsenceDate(row.date)}</td>
                        <td>
                          <span className={`badge bg-${teacherAbsenceStatusBadgeClass(status)}`}>
                            {status ? `${status.charAt(0).toUpperCase()}${status.slice(1)}` : 'N/A'}
                          </span>
                        </td>
                        <td>{row.reason ?? ''}</td>
                      </tr>
                    )
                  })
                ) : (
                  <tr>
                    <td colSpan={4} className="text-center">
                      No reported entries yet.
                    </td>
                  </tr>
                )}
              </tbody>
            </table>
          </div>
        </div>
      </div>
    </TeacherShell>
  )
}

/** `teacher/add_final_exam.php` */
export function TeacherAddFinalExamPage() {
  return <TeacherApiDumpPage title="Add final exam" path="/add-final-exam" />
}

/** `teacher/add_homework.php` */
export function TeacherAddHomeworkPage() {
  return <TeacherApiDumpPage title="Add homework" path="/add-homework" />
}

/** `teacher/add_midterm_exam.php` */
export function TeacherAddMidtermExamPage() {
  return <TeacherApiDumpPage title="Add midterm exam" path="/add-midterm-exam" />
}

/** `teacher/add_participation.php` */
export function TeacherAddParticipationPage() {
  return <TeacherApiDumpPage title="Add participation" path="/add-participation" />
}

/** `teacher/add_project.php` */
export function TeacherAddProjectPage() {
  return <TeacherApiDumpPage title="Add project" path="/add-project" />
}

/** `teacher/add_quiz.php` */
export function TeacherAddQuizPage() {
  return <TeacherApiDumpPage title="Add quiz" path="/add-quiz" />
}

/** `teacher/add_quiz_column_form.php` */
export function TeacherAddQuizColumnFormPage() {
  return <TeacherApiDumpPage title="Add quiz column" path="/add-quiz-column-form" />
}

/** `teacher/calendar.php` */
export function TeacherCalendarPage() {
  const [events, setEvents] = useState<TeacherCalendarDisplayEvent[]>([])
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const [selectedDate, setSelectedDate] = useState<string>('')
  const [visibleMonth, setVisibleMonth] = useState<Date>(() => monthStart(new Date()))

  useEffect(() => {
    let cancelled = false
    ;(async () => {
      setLoading(true)
      try {
        const res = await fetchAdministratorCalendarEvents({ audience: 'teacher' })
        if (cancelled) return
        const rows = (res.data?.events ?? []).map((row) => {
          const start = normalizeCalendarDate(row.start)
          return {
            ...row,
            start,
            backgroundColor:
              row.backgroundColor ??
              row.extendedProps?.backgroundColor ??
              row.extendedProps?.color ??
              '#3788d8',
          }
        })
        const firstDate =
          rows.map((row) => row.start).filter(Boolean).sort()[0] ?? normalizeCalendarDate(new Date().toISOString())
        setEvents(rows)
        setSelectedDate((current) => current || firstDate)
        setVisibleMonth(monthStart(new Date(`${firstDate}T12:00:00`)))
        setError(null)
      } catch (e) {
        if (!cancelled) {
          setEvents([])
          setError(e instanceof Error ? e.message : 'Failed to load calendar events.')
        }
      } finally {
        if (!cancelled) setLoading(false)
      }
    })()
    return () => {
      cancelled = true
    }
  }, [])

  const eventsByDate = useMemo(() => {
    const map = new Map<string, TeacherCalendarDisplayEvent[]>()
    for (const event of events) {
      if (!event.start) continue
      if (!map.has(event.start)) map.set(event.start, [])
      map.get(event.start)?.push(event)
    }
    for (const rows of map.values()) {
      rows.sort((left, right) => left.title.localeCompare(right.title))
    }
    return map
  }, [events])

  const selectedEvents = eventsByDate.get(selectedDate) ?? []
  const todayKey = normalizeCalendarDate(new Date().toISOString())

  const monthCells = useMemo(() => {
    const firstDay = monthStart(visibleMonth)
    const lastDay = new Date(visibleMonth.getFullYear(), visibleMonth.getMonth() + 1, 0)
    const leading = firstDay.getDay()
    const totalCells = Math.ceil((leading + lastDay.getDate()) / 7) * 7
    const startDate = new Date(visibleMonth.getFullYear(), visibleMonth.getMonth(), 1 - leading)

    return Array.from({ length: totalCells }, (_, index) => {
      const current = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate() + index)
      const key = dateKey(current)
      return {
        key,
        day: current.getDate(),
        inMonth: current.getMonth() === visibleMonth.getMonth(),
        events: eventsByDate.get(key) ?? [],
      }
    })
  }, [eventsByDate, visibleMonth])

  const monthEventCount = monthCells.reduce((sum, cell) => sum + (cell.inMonth ? cell.events.length : 0), 0)
  const noSchoolCount = events.filter((row) => Boolean(row.extendedProps?.no_school)).length
  const staffOnlyCount = events.filter(
    (row) =>
      Boolean(row.extendedProps?.notify_teacher) &&
      Boolean(row.extendedProps?.notify_admin) &&
      !Boolean(row.extendedProps?.notify_parent),
  ).length

  return (
    <TeacherShell
      title="Calendar"
      subtitle="Teacher-facing school events, staff-only dates, and no-school days."
      loading={loading}
      error={error}
    >
      <div className="teacher-calendar-shell">
        <section className="teacher-calendar-summary">
          <div className="teacher-calendar-stat">
            <span className="teacher-calendar-stat-value">{events.length}</span>
            <span className="teacher-calendar-stat-label">Total events</span>
          </div>
          <div className="teacher-calendar-stat">
            <span className="teacher-calendar-stat-value">{noSchoolCount}</span>
            <span className="teacher-calendar-stat-label">No-school days</span>
          </div>
          <div className="teacher-calendar-stat">
            <span className="teacher-calendar-stat-value">{staffOnlyCount}</span>
            <span className="teacher-calendar-stat-label">Staff-only events</span>
          </div>
        </section>

        <div className="teacher-calendar-layout">
          <section className="teacher-calendar-panel">
            <div className="teacher-calendar-toolbar">
              <div>
                <h2 className="h5 mb-1">
                  {visibleMonth.toLocaleDateString('en-US', { month: 'long', year: 'numeric' })}
                </h2>
                <p className="text-muted mb-0 small">
                  {monthEventCount} event{monthEventCount === 1 ? '' : 's'} this month
                </p>
              </div>
              <div className="teacher-calendar-controls">
                <button
                  type="button"
                  className="btn btn-outline-secondary btn-sm"
                  onClick={() =>
                    setVisibleMonth((current) => monthStart(new Date(current.getFullYear(), current.getMonth() - 1, 1)))
                  }
                >
                  Prev
                </button>
                <button
                  type="button"
                  className="btn btn-success btn-sm"
                  onClick={() => {
                    setSelectedDate(todayKey)
                    setVisibleMonth(monthStart(new Date()))
                  }}
                >
                  Today
                </button>
                <button
                  type="button"
                  className="btn btn-outline-secondary btn-sm"
                  onClick={() =>
                    setVisibleMonth((current) => monthStart(new Date(current.getFullYear(), current.getMonth() + 1, 1)))
                  }
                >
                  Next
                </button>
              </div>
            </div>

            <div className="teacher-calendar-legend small">
              <span><i className="teacher-calendar-dot" style={{ background: '#3788d8' }} /> General</span>
              <span><i className="teacher-calendar-dot" style={{ background: '#ffc107' }} /> No school</span>
              <span><i className="teacher-calendar-dot" style={{ background: '#28a745' }} /> Staff only</span>
              <span><i className="teacher-calendar-dot" style={{ background: '#6f42c1' }} /> Meeting</span>
            </div>

            <div className="teacher-calendar-grid" aria-label="Teacher calendar">
              {['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map((day) => (
                <div key={day} className="teacher-calendar-cell teacher-calendar-head">
                  {day}
                </div>
              ))}
              {monthCells.map((cell) => (
                <button
                  key={cell.key}
                  type="button"
                  className={[
                    'teacher-calendar-cell',
                    'teacher-calendar-day',
                    cell.inMonth ? '' : 'is-muted',
                    cell.key === todayKey ? 'is-today' : '',
                    cell.key === selectedDate ? 'is-selected' : '',
                  ]
                    .filter(Boolean)
                    .join(' ')}
                  onClick={() => {
                    setSelectedDate(cell.key)
                    if (!cell.inMonth) {
                      setVisibleMonth(monthStart(new Date(`${cell.key}T12:00:00`)))
                    }
                  }}
                >
                  <span className="teacher-calendar-day-number">{cell.day}</span>
                  <div className="teacher-calendar-events">
                    {cell.events.slice(0, 3).map((event) => (
                      <span
                        key={`${String(event.id ?? event.title)}-${event.start}`}
                        className="teacher-calendar-event-pill"
                        style={{
                          backgroundColor: `${event.backgroundColor}22`,
                          borderLeftColor: event.backgroundColor,
                        }}
                      >
                        {event.title}
                      </span>
                    ))}
                    {cell.events.length > 3 ? (
                      <span className="teacher-calendar-more">+{cell.events.length - 3} more</span>
                    ) : null}
                  </div>
                </button>
              ))}
            </div>
          </section>

          <aside className="teacher-calendar-panel">
            <div className="teacher-calendar-agenda-head">
              <div>
                <h2 className="h5 mb-1">Day agenda</h2>
                <p className="text-muted mb-0 small">
                  {selectedDate
                    ? formatCalendarLabel(selectedDate, {
                        weekday: 'long',
                        month: 'long',
                        day: 'numeric',
                        year: 'numeric',
                      })
                    : 'Select a date'}
                </p>
              </div>
              <span className="badge text-bg-light">
                {selectedEvents.length} event{selectedEvents.length === 1 ? '' : 's'}
              </span>
            </div>

            {selectedEvents.length === 0 ? (
              <p className="text-muted small mb-0">No events scheduled for this day.</p>
            ) : (
              <div className="teacher-calendar-agenda-list">
                {selectedEvents.map((event) => (
                  <article key={`${String(event.id ?? event.title)}-${event.start}`} className="teacher-calendar-agenda-item">
                    <div className="teacher-calendar-agenda-tag">
                      <i className="teacher-calendar-dot" style={{ background: event.backgroundColor }} />
                      <span>
                        {String(event.id ?? '').startsWith('pm-')
                          ? 'Meeting'
                          : event.extendedProps?.no_school
                            ? 'No School'
                            : event.extendedProps?.notify_teacher &&
                                event.extendedProps?.notify_admin &&
                                !event.extendedProps?.notify_parent
                              ? 'Staff Only'
                              : 'School Event'}
                      </span>
                    </div>
                    <h3 className="h6 mb-1">{event.title}</h3>
                    <p className="small text-muted mb-0">{event.description || 'No extra details provided.'}</p>
                  </article>
                ))}
              </div>
            )}
          </aside>
        </div>
      </div>
    </TeacherShell>
  )
}

/** `teacher/class_progress_history.php` */
export { TeacherClassProgressHistoryPage } from './TeacherClassProgressHistoryPage'

/** `teacher/class_progress_submit.php` */
export { TeacherClassProgressSubmitPage } from './TeacherClassProgressSubmitPage'

/** `teacher/class_progress_view.php` */
export function TeacherClassProgressViewPage() {
  return <TeacherApiDumpPage title="Class progress — view" path="/class-progress-view" />
}

/** `teacher/class_view.php` */
export { TeacherClassViewPage } from './TeacherClassViewPage'

/** `teacher/drafts.php` */
export function TeacherDraftsPage() {
  return <TeacherApiDumpPage title="Draft messages" path="/drafts" />
}

/** `teacher/exam_drafts.php` */
export function TeacherExamDraftsPage() {
  const { user } = useAuth()
  const [search, setSearch] = useSearchParams()
  const { data, loading, error, reload } = useTeacherResource<TeacherExamDraftsPayload>('/exam-drafts')
  const [message, setMessage] = useState<string | null>(null)
  const [submitError, setSubmitError] = useState<string | null>(null)
  const [submitting, setSubmitting] = useState(false)
  const [draftFile, setDraftFile] = useState<File | null>(null)
  const [sessionClassSectionId, setSessionClassSectionId] = useState<string>('')
  const [sessionClassSectionName, setSessionClassSectionName] = useState<string>('')
  const [form, setForm] = useState({
    exam_type: '',
    description: '',
  })

  const pageClassSectionId = search.get('class_section_id')?.trim() ?? ''
  const assignedSections = data?.assignments ?? []
  const authClassSectionId = sessionClassSectionId
  const authClassSectionName = sessionClassSectionName

  useEffect(() => {
    if (user?.class_section_id != null) {
      setSessionClassSectionId(String(user.class_section_id).trim())
      setSessionClassSectionName(user?.class_section_name != null ? String(user.class_section_name).trim() : '')
      return
    }

    let cancelled = false

    apiFetch<FrontendMeResponse>('/api/v1/frontend/me')
      .then((body) => {
        if (cancelled) return
        const nextId = body?.data?.user?.class_section_id
        const nextName = body?.data?.user?.class_section_name
        setSessionClassSectionId(nextId != null ? String(nextId).trim() : '')
        setSessionClassSectionName(nextName != null ? String(nextName).trim() : '')
      })
      .catch(() => {
        if (cancelled) return
        setSessionClassSectionId('')
        setSessionClassSectionName('')
      })

    return () => {
      cancelled = true
    }
  }, [user?.class_section_id, user?.class_section_name])

  const effectiveClassSectionId = useMemo(() => {
    if (pageClassSectionId) {
      const matched = assignedSections.find(
        (assignment) => String(assignment.class_section_id ?? '').trim() === pageClassSectionId,
      )
      if (matched) return pageClassSectionId
      return ''
    }

    if (authClassSectionId) {
      const matched = assignedSections.find(
        (assignment) => String(assignment.class_section_id ?? '').trim() === authClassSectionId,
      )
      if (matched) return authClassSectionId
    }

    const fromPayload = String(data?.class_section_id ?? '').trim()
    if (fromPayload) {
      const matched = assignedSections.find(
        (assignment) => String(assignment.class_section_id ?? '').trim() === fromPayload,
      )
      if (matched) return fromPayload
    }

    if (assignedSections.length > 0) {
      return String(assignedSections[0]?.class_section_id ?? '').trim()
    }

    return ''
  }, [assignedSections, authClassSectionId, data?.class_section_id, pageClassSectionId])

  const payload = useMemo<TeacherExamDraftsPayload | null>(() => {
    if (!data) return null
    return {
      ...data,
      class_section_id: effectiveClassSectionId || data.class_section_id || null,
    }
  }, [data, effectiveClassSectionId])

  const activeAssignment = useMemo(
    () =>
      assignedSections.find(
        (assignment) => String(assignment.class_section_id ?? '').trim() === effectiveClassSectionId,
      ) ?? null,
    [assignedSections, effectiveClassSectionId],
  )

  const activeClassSectionLabel =
    activeAssignment?.class_section_name?.trim() ||
    (effectiveClassSectionId === authClassSectionId ? authClassSectionName : '') ||
    'No class section selected'

  useEffect(() => {
    if (!payload) return
    setForm((current) => ({
      ...current,
      exam_type: current.exam_type || String(payload.exam_types?.[0] ?? ''),
    }))
  }, [payload])

  useEffect(() => {
    if (!effectiveClassSectionId) return
    if (pageClassSectionId === effectiveClassSectionId) return

    const next = new URLSearchParams(search)
    next.set('class_section_id', effectiveClassSectionId)
    setSearch(next, { replace: true })
  }, [effectiveClassSectionId, pageClassSectionId, search, setSearch])

  async function submitDraft(event: FormEvent) {
    event.preventDefault()
    setSubmitting(true)
    setSubmitError(null)
    setMessage(null)

    try {
      const submissionClassSectionId = String(payload?.class_section_id || effectiveClassSectionId || '').trim()

      if (!effectiveClassSectionId) {
        throw new Error(
          pageClassSectionId
            ? 'The class section from this page is not assigned to your account.'
            : 'Open this page with a class section selected before submitting an exam draft.',
        )
      }

      if (!submissionClassSectionId) {
        throw new Error('No class section is available for this submission.')
      }

      if (draftFile) {
        const ext = draftFile.name.split('.').pop()?.toLowerCase() ?? ''
        if (!EXAM_DRAFT_ALLOWED_EXTENSIONS.includes(ext)) {
          throw new Error('Draft file must be a .doc or .docx file.')
        }
        if (draftFile.size > EXAM_DRAFT_MAX_BYTES) {
          throw new Error('Draft file must be 12 MB or smaller.')
        }
      }

      const formData = new FormData()
      formData.set('class_section_id', submissionClassSectionId)
      if (form.exam_type.trim()) formData.set('exam_type', form.exam_type.trim())
      if (form.description.trim()) formData.set('description', form.description.trim())
      if (draftFile) formData.set('draft_file', draftFile)

      const result = await postTeacherMultipart<TeacherExamDraftStoreResponse>(
        '/api/v1/exams/drafts/teacher',
        formData,
      )
      setMessage(result.message ?? 'Exam draft submitted for review.')
      setDraftFile(null)
      setForm((current) => ({ ...current, description: '' }))
      reload()
    } catch (e) {
      setSubmitError(e instanceof Error ? e.message : 'Unable to submit exam draft.')
    } finally {
      setSubmitting(false)
    }
  }

  return (
    <TeacherShell
      title="Exam Drafts"
      subtitle="Submit exams for review and browse old exams saved for your assigned classes."
      loading={loading}
      error={error}
    >
      <div className="teacher-calendar-shell">
        {message ? <div className="alert alert-success mb-0">{message}</div> : null}
        {submitError ? <div className="alert alert-danger mb-0">{submitError}</div> : null}

        <section className="teacher-calendar-summary">
          <div className="teacher-calendar-stat">
            <span className="teacher-calendar-stat-value">{payload?.drafts?.length ?? 0}</span>
            <span className="teacher-calendar-stat-label">Submitted for review</span>
          </div>
          <div className="teacher-calendar-stat">
            <span className="teacher-calendar-stat-value">{payload?.legacy_exams?.length ?? 0}</span>
            <span className="teacher-calendar-stat-label">Old exams saved</span>
          </div>
          <div className="teacher-calendar-stat">
            <span className="teacher-calendar-stat-value">{payload?.assignments?.length ?? 0}</span>
            <span className="teacher-calendar-stat-label">Assigned classes</span>
          </div>
        </section>

        <div className="teacher-calendar-panel">
          <div className="d-flex flex-wrap justify-content-between gap-3 align-items-start mb-3">
            <div>
              <h2 className="h5 mb-1">Submit exam for review</h2>
              <p className="text-muted small mb-0">
                Upload a draft for one of your classes. Accepted file types: `.doc`, `.docx`, up to 12 MB.
              </p>
            </div>
            <div className="text-end small text-muted">
              <div>{activeClassSectionLabel}</div>
              <div>{payload?.semester ?? 'Current semester'}</div>
            </div>
          </div>

          <form className="row g-3" onSubmit={(event) => void submitDraft(event)}>
            <input type="hidden" name="class_section_id" value={String(payload?.class_section_id || effectiveClassSectionId || '')} />
            <div className="col-md-6">
              <label className="form-label">Exam type</label>
              <select
                className="form-select"
                value={form.exam_type}
                onChange={(event) => setForm((current) => ({ ...current, exam_type: event.target.value }))}
              >
                <option value="">Select exam type</option>
                {(payload?.exam_types ?? []).map((type) => (
                  <option key={type} value={type}>
                    {type}
                  </option>
                ))}
              </select>
            </div>

            <div className="col-md-6">
              <label className="form-label">Draft file</label>
              <input
                className="form-control"
                type="file"
                accept=".doc,.docx"
                onChange={(event) => {
                  const file = event.target.files?.[0] ?? null
                  setDraftFile(file)
                  if (!file) return

                  const ext = file.name.split('.').pop()?.toLowerCase() ?? ''
                  if (!EXAM_DRAFT_ALLOWED_EXTENSIONS.includes(ext)) {
                    setSubmitError('Draft file must be a .doc or .docx file.')
                    event.target.value = ''
                    setDraftFile(null)
                    return
                  }
                  if (file.size > EXAM_DRAFT_MAX_BYTES) {
                    setSubmitError('Draft file must be 12 MB or smaller.')
                    event.target.value = ''
                    setDraftFile(null)
                    return
                  }
                  setSubmitError(null)
                }}
              />
              <div className="form-text">Only Word documents are accepted by the review endpoint.</div>
            </div>

            {!effectiveClassSectionId ? (
              <div className="col-12">
                <div className="alert alert-warning mb-0 small">
                  No class section is available for your teacher session yet. Open the class page or re-login so the
                  portal can load your assigned section automatically.
                  <div className="mt-2">
                    <Link to="/app/teacher/class-view" className="btn btn-sm btn-outline-primary">
                      Go to class page
                    </Link>
                  </div>
                </div>
              </div>
            ) : null}

            <div className="col-12">
              <label className="form-label">Description</label>
              <textarea
                className="form-control"
                rows={3}
                value={form.description}
                onChange={(event) => setForm((current) => ({ ...current, description: event.target.value }))}
                placeholder="Add notes for the reviewer."
              />
            </div>

            <div className="col-12 d-flex justify-content-end">
              <button className="btn btn-success" type="submit" disabled={submitting || !effectiveClassSectionId}>
                {submitting ? 'Submitting…' : 'Submit for review'}
              </button>
            </div>
          </form>
        </div>

        <div className="teacher-calendar-panel">
          <div className="d-flex flex-wrap justify-content-between gap-3 align-items-start mb-3">
            <div>
              <h2 className="h5 mb-1">Submitted drafts</h2>
              <p className="text-muted small mb-0">
                {data?.school_year ?? 'Current year'} {data?.semester ? `· ${data.semester}` : ''}
              </p>
            </div>
            {(payload?.assignments?.length ?? 0) > 0 ? (
              <div className="text-end small text-muted">
                {(payload?.assignments ?? []).map((assignment, index) => (
                  <div key={String(assignment.class_section_id ?? assignment.class_section_name ?? index)}>
                    {assignment.class_section_name ?? `Class ${assignment.class_section_id ?? '—'}`}
                  </div>
                ))}
              </div>
            ) : null}
          </div>

          <div className="table-responsive">
            <table className="table table-striped align-middle mb-0">
              <thead>
                <tr>
                  <th>Title</th>
                  <th>Class</th>
                  <th>Type</th>
                  <th>Version</th>
                  <th>Status</th>
                  <th>Files</th>
                  <th>Review</th>
                </tr>
              </thead>
              <tbody>
                {loading ? (
                  <tr>
                    <td colSpan={7} className="text-muted">
                      Loading drafts…
                    </td>
                  </tr>
                ) : (payload?.drafts ?? []).length === 0 ? (
                  <tr>
                    <td colSpan={7} className="text-muted">
                      No exam drafts submitted yet.
                    </td>
                  </tr>
                ) : (
                  (payload?.drafts ?? []).map((draft) => {
                    const teacherFile = teacherExamFilePath('teacher', draft.teacher_file)
                    const finalFile = teacherExamFilePath('final', draft.final_pdf_file ?? draft.final_file)
                    return (
                      <tr key={draft.id}>
                        <td>
                          <div className="fw-semibold">{draft.draft_title ?? 'Untitled'}</div>
                          <div className="small text-muted">{draft.description ?? 'No description provided.'}</div>
                        </td>
                        <td>{draft.class_section_name ?? '—'}</td>
                        <td>{draft.exam_type ?? '—'}</td>
                        <td>{draft.version ?? 1}</td>
                        <td>
                          <span className="badge text-bg-secondary">{draft.status ?? '—'}</span>
                        </td>
                        <td>
                          <div className="d-flex flex-column gap-1 small">
                            {teacherFile ? (
                              <button
                                type="button"
                                className="btn btn-link p-0 text-start"
                                onClick={() => void openProtectedApiFile(teacherFile)}
                              >
                                {draft.teacher_filename ?? 'Teacher file'}
                              </button>
                            ) : (
                              <span className="text-muted">No uploaded draft</span>
                            )}
                            {finalFile ? (
                              <button
                                type="button"
                                className="btn btn-link p-0 text-start"
                                onClick={() => void openProtectedApiFile(finalFile)}
                              >
                                {draft.final_filename ?? 'Reviewed final'}
                              </button>
                            ) : null}
                          </div>
                        </td>
                        <td>
                          <div className="small">
                            <div>{draft.admin_comments ?? 'No admin comments yet.'}</div>
                            <div className="text-muted mt-1">
                              {draft.reviewed_at ? `Reviewed ${formatCompetitionDate(draft.reviewed_at)}` : 'Pending review'}
                            </div>
                          </div>
                        </td>
                      </tr>
                    )
                  })
                )}
              </tbody>
            </table>
          </div>
        </div>

        <div className="teacher-calendar-panel">
          <h2 className="h5 mb-3">Old exams</h2>
          {(payload?.legacy_exams ?? []).length === 0 ? (
            <p className="text-muted small mb-0">No old exams are saved for your assigned classes.</p>
          ) : (
            <div className="row g-3">
              {(payload?.legacy_exams ?? []).map((draft) => {
                const finalFile = teacherExamFilePath('final', draft.final_pdf_file ?? draft.final_file)
                return (
                  <div key={draft.id} className="col-12 col-xl-6">
                    <article className="teacher-calendar-agenda-item h-100">
                      <div className="teacher-calendar-agenda-tag">
                        <i className="teacher-calendar-dot" style={{ background: '#6f42c1' }} />
                        <span>Old exam</span>
                      </div>
                      <h3 className="h6 mb-1">{draft.draft_title ?? draft.exam_type ?? 'Old exam'}</h3>
                      <p className="small text-muted mb-2">
                        {draft.class_section_name ?? '—'} · {draft.exam_type ?? '—'}
                      </p>
                      <p className="small text-muted mb-2">{draft.description ?? 'No description provided.'}</p>
                      {finalFile ? (
                        <button
                          type="button"
                          className="btn btn-sm btn-outline-primary"
                          onClick={() => void openProtectedApiFile(finalFile)}
                        >
                          Download exam
                        </button>
                      ) : (
                        <span className="text-muted small">No file available.</span>
                      )}
                    </article>
                  </div>
                )
              })}
            </div>
          )}
        </div>
      </div>
    </TeacherShell>
  )
}

/** `teacher/homework_list.php` */
export function TeacherHomeworkListPage() {
  return <TeacherApiDumpPage title="Homework list" path="/homework-list" />
}

/** `teacher/scores.php` */
export function TeacherScoresPage() {
  const { user } = useAuth()
  const [search] = useSearchParams()
  const requestedSemester = search.get('semester')?.trim() ?? ''
  const requestedSchoolYear = search.get('school_year')?.trim() ?? ''
  const { selectedYear: defaultSchoolYear } = useSchoolYearOptions({
    preferredYear: requestedSchoolYear,
  })
  const effectiveRequestedSchoolYear = requestedSchoolYear || defaultSchoolYear

  const [payload, setPayload] = useState<TeacherScoresPayload | null>(null)
  const [rows, setRows] = useState<TeacherScoresRow[]>([])
  const [loading, setLoading] = useState(true)
  const [saving, setSaving] = useState(false)
  const [locking, setLocking] = useState(false)
  const [error, setError] = useState<string | null>(null)
  const [success, setSuccess] = useState<string | null>(null)
  const [warningLines, setWarningLines] = useState<string[]>([])
  const [tableSearch, setTableSearch] = useState('')

  const resolvedSemester = payload?.semester?.trim() || requestedSemester
  const resolvedSchoolYear = payload?.school_year?.trim() || effectiveRequestedSchoolYear
  const semesterKey = teacherSemesterKey(resolvedSemester)
  const termLabel = semesterKey === 'midterm' ? 'Midterm' : semesterKey === 'final' ? 'Final Exam' : 'Term Score'
  const termCommentLabel = semesterKey === 'midterm' ? 'Midterm Comment' : semesterKey === 'final' ? 'Final Comment' : 'Term Comment'
  const termCommentOverrideKey = semesterKey === 'midterm' ? 'midterm_comment' : semesterKey === 'final' ? 'final_comment' : ''
  const classSectionId = Number(payload?.class_section_id ?? 0)
  const classSectionName = String(payload?.class_section_name ?? '').trim()
  const scoresLocked = Boolean(payload?.scores_locked)

  function missingOkChecked(
    missingMap: TeacherScoresPayload['missing_ok_map'],
    field: string,
    studentId: number,
  ): boolean {
    const fieldMap = missingMap?.[field]
    if (!fieldMap || typeof fieldMap !== 'object') return false
    const studentEntry = fieldMap[String(studentId)]
    if (studentEntry === true || studentEntry === 1) return true
    if (studentEntry && typeof studentEntry === 'object') {
      const zero = (studentEntry as Record<string, unknown>)['0']
      return zero === true || zero === 1
    }
    return false
  }

  function buildRows(body: TeacherScoresPayload): TeacherScoresRow[] {
    const bodySemesterKey = teacherSemesterKey(body.semester)
    const bodyOverrideKey =
      bodySemesterKey === 'midterm' ? 'midterm_comment' : bodySemesterKey === 'final' ? 'final_comment' : ''
    return (body.students ?? []).map((student) => {
      const scoreMap = student.scores ?? {}
      const comments = student.comments ?? {}
      return {
        student_id: Number(student.student_id ?? 0),
        firstname: String(student.firstname ?? '').trim(),
        lastname: String(student.lastname ?? '').trim(),
        school_id: String(student.school_id ?? '').trim(),
        attendance: teacherScoreDisplay(scoreMap.attendance),
        homework: teacherScoreDisplay(scoreMap.homework),
        project: teacherScoreDisplay(scoreMap.project),
        quiz: teacherScoreDisplay(scoreMap.quiz),
        participation: teacherScoreDisplay(scoreMap.participation),
        ptap: teacherScoreDisplay(scoreMap.ptap),
        term_score: teacherScoreDisplay(
          bodySemesterKey === 'midterm' ? scoreMap.midterm_exam : bodySemesterKey === 'final' ? scoreMap.final_exam : '',
        ),
        semester_score: teacherScoreDisplay(scoreMap.semester),
        ptap_comment: String(comments.ptap?.comment ?? ''),
        ptap_comment_review: String(comments.ptap?.comment_review ?? ''),
        term_comment: String((bodySemesterKey ? comments[bodySemesterKey]?.comment : '') ?? ''),
        term_comment_review: String((bodySemesterKey ? comments[bodySemesterKey]?.comment_review : '') ?? ''),
        ptap_missing_ok: missingOkChecked(body.missing_ok_map, 'ptap_comment', Number(student.student_id ?? 0)),
        term_missing_ok: bodyOverrideKey
          ? missingOkChecked(body.missing_ok_map, bodyOverrideKey, Number(student.student_id ?? 0))
          : false,
      }
    })
  }

  function formatApiErrors(err: unknown, fallback: string): string {
    if (err instanceof ApiHttpError) {
      const body = err.body
      if (body && typeof body === 'object' && Array.isArray((body as { errors?: unknown }).errors)) {
        const errors = (body as { errors?: unknown[] }).errors
          ?.map((item) => String(item ?? '').trim())
          .filter(Boolean)
        if (errors && errors.length > 0) return errors.join(' ')
      }
      return err.message || fallback
    }
    return err instanceof Error ? err.message : fallback
  }

  async function load() {
    try {
      setLoading(true)
      setError(null)
      setWarningLines([])
      const params = new URLSearchParams()
      if (requestedSemester !== '') params.set('semester', requestedSemester)
      if (effectiveRequestedSchoolYear !== '') params.set('school_year', effectiveRequestedSchoolYear)
      const query = params.toString()
      const body = await apiFetch<TeacherScoresPayload>(`/api/v1/teacher/scores${query ? `?${query}` : ''}`)
      setPayload(body)
      setRows(buildRows(body))
    } catch (e) {
      setError(e instanceof Error ? e.message : 'Unable to load scores.')
    } finally {
      setLoading(false)
    }
  }

  useEffect(() => {
    void load()
  }, [effectiveRequestedSchoolYear, requestedSemester])

  const visibleRows = useMemo(() => {
    const needle = tableSearch.trim().toLowerCase()
    if (needle === '') return rows
    return rows.filter((row) =>
      `${row.school_id} ${row.firstname} ${row.lastname}`.toLowerCase().includes(needle),
    )
  }, [rows, tableSearch])

  const subtitleParts = [
    `Teacher: ${user?.name?.trim() || 'Teacher'}`,
    classSectionName || (classSectionId > 0 ? `Section ${classSectionId}` : 'No class assigned'),
  ]
  if (resolvedSemester) subtitleParts.push(`Current semester: ${resolvedSemester}`)
  if (resolvedSchoolYear) subtitleParts.push(resolvedSchoolYear)

  function updateRow(studentId: number, key: keyof TeacherScoresRow, value: string | boolean) {
    setRows((current) =>
      current.map((row) => (row.student_id === studentId ? { ...row, [key]: value } : row)),
    )
  }

  function collectCommentErrors(): string[] {
    const errors: string[] = []
    for (const row of rows) {
      const ptapError = teacherScoreCommentError(row.ptap_comment, row.firstname)
      if (ptapError) errors.push(ptapError)
      if (semesterKey) {
        const termError = teacherScoreCommentError(row.term_comment, row.firstname)
        if (termError) errors.push(termError)
      }
    }
    return errors
  }

  function buildMissingOkPayload(): Record<string, Record<string, number>> {
    const missingOk: Record<string, Record<string, number>> = {}
    for (const row of rows) {
      const next: Record<string, number> = {}
      if (row.ptap_missing_ok) next.ptap_comment = 1
      if (termCommentOverrideKey && row.term_missing_ok) next[termCommentOverrideKey] = 1
      if (Object.keys(next).length > 0) {
        missingOk[String(row.student_id)] = next
      }
    }
    return missingOk
  }

  async function saveComments(event: FormEvent<HTMLFormElement>) {
    event.preventDefault()
    if (!classSectionId || !resolvedSemester || !resolvedSchoolYear) return
    const validationErrors = collectCommentErrors()
    if (validationErrors.length > 0) {
      setWarningLines(validationErrors)
      setError('Please fix the comment validation errors before saving.')
      return
    }

    setSaving(true)
    setError(null)
    setSuccess(null)
    setWarningLines([])

    const comments: Record<string, Record<string, string>> = {}
    for (const row of rows) {
      comments[String(row.student_id)] = {
        ptap: row.ptap_comment,
      }
      if (semesterKey) {
        comments[String(row.student_id)][semesterKey] = row.term_comment
      }
    }

    try {
      await apiFetch('/api/v1/scores/comments', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          class_section_id: classSectionId,
          semester: resolvedSemester,
          school_year: resolvedSchoolYear,
          comments,
          missing_ok: buildMissingOkPayload(),
        }),
      })
      setSuccess('Comments saved.')
      await load()
    } catch (err) {
      setError(formatApiErrors(err, 'Unable to save comments.'))
    } finally {
      setSaving(false)
    }
  }

  async function submitSemesterScores() {
    if (!classSectionId || !resolvedSemester || !resolvedSchoolYear) return
    if (!window.confirm('Once you submit, you cannot add any scores or comments. Do you want to continue?')) {
      return
    }

    const invalidEntries: string[] = []
    const missingEntries: string[] = []
    for (const row of rows) {
      const studentName = `${row.firstname} ${row.lastname}`.trim() || `Student #${row.student_id}`
      const rowInvalid: string[] = []
      const rowMissing: string[] = []

      const scoreChecks: Array<[string, string]> = [
        ['Homework Avg', row.homework],
        ['Project Avg', row.project],
        ['Test/Quiz Avg', row.quiz],
        ['Participation', row.participation],
      ]
      if (semesterKey) scoreChecks.push([termLabel, row.term_score])

      for (const [label, value] of scoreChecks) {
        if (value.trim() === '') rowMissing.push(label)
        else if (!teacherScoreInRange(value)) rowInvalid.push(label)
      }

      if (row.ptap_comment.trim() === '' && !row.ptap_missing_ok) {
        rowMissing.push('PTAP Comment')
      }
      if (semesterKey && row.term_comment.trim() === '' && !row.term_missing_ok) {
        rowMissing.push(termCommentLabel)
      }

      const ptapError = teacherScoreCommentError(row.ptap_comment, row.firstname)
      if (ptapError) rowInvalid.push('PTAP Comment')
      if (semesterKey) {
        const termError = teacherScoreCommentError(row.term_comment, row.firstname)
        if (termError) rowInvalid.push(termCommentLabel)
      }

      if (rowInvalid.length > 0) invalidEntries.push(`${studentName}: ${rowInvalid.join(', ')}`)
      if (rowMissing.length > 0) missingEntries.push(`${studentName}: ${rowMissing.join(', ')}`)
    }

    if (invalidEntries.length > 0 || missingEntries.length > 0) {
      const lines: string[] = []
      if (invalidEntries.length > 0) {
        lines.push('Scores/comments are invalid for:')
        lines.push(...invalidEntries.map((item) => `- ${item}`))
      }
      if (missingEntries.length > 0) {
        lines.push('Missing entries (fill or check "Missing ok"):')
        lines.push(...missingEntries.map((item) => `- ${item}`))
      }
      setWarningLines(lines)
      setError('Please fix the missing or invalid entries before submitting.')
      return
    }

    setLocking(true)
    setError(null)
    setSuccess(null)
    setWarningLines([])

    try {
      await apiFetch('/api/v1/scores/lock', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          class_section_id: classSectionId,
          semester: resolvedSemester,
          school_year: resolvedSchoolYear,
          missing_ok: buildMissingOkPayload(),
        }),
      })
      setSuccess('Semester scores submitted and locked.')
      await load()
    } catch (err) {
      setError(formatApiErrors(err, 'Unable to submit semester scores.'))
    } finally {
      setLocking(false)
    }
  }

  return (
    <TeacherShell title="Students Class Scores" subtitle={subtitleParts.join(' · ')} loading={loading} error={error}>
      {success ? <div className="alert alert-success text-center">{success}</div> : null}
      {warningLines.length > 0 ? (
        <div className="alert alert-danger" role="alert">
          <p className="mb-2 text-center fw-semibold">
            Please fix the score/comment issues below before continuing.
          </p>
          <pre className="mb-0 small">{warningLines.join('\n')}</pre>
        </div>
      ) : null}

      {scoresLocked ? (
        <div className="alert alert-warning text-center">Scores are submitted and locked for this semester.</div>
      ) : null}

      <div className="text-center mb-3">
        <Link
          to={teacherRouteWithTerm('/app/teacher/select-semester', {
            schoolYear: resolvedSchoolYear,
          })}
          className="btn btn-outline-secondary btn-sm"
        >
          Choose another semester
        </Link>
      </div>

      {!loading && rows.length === 0 ? (
        <div className="alert alert-info">
          No students or score rows are available for this class and semester.
        </div>
      ) : null}

      {!loading && rows.length > 0 ? (
        <form onSubmit={(event) => void saveComments(event)}>
          <div className="d-flex flex-wrap justify-content-between align-items-center gap-3 mb-3">
            <div className="small text-muted">
              {classSectionName || `Section ${classSectionId || '—'}`} · {resolvedSemester || '—'} · {resolvedSchoolYear || '—'}
            </div>
            <div className="d-flex align-items-center gap-2">
              <label className="mb-0">Search:</label>
              <input
                className="form-control form-control-sm"
                style={{ width: 220 }}
                value={tableSearch}
                onChange={(event) => setTableSearch(event.target.value)}
              />
            </div>
          </div>

          <div className="table-responsive">
            <table className="table table-bordered table-striped w-100 align-middle">
              <thead className="table-light">
                <tr>
                  <th>First Name</th>
                  <th>Last Name</th>
                  <th className="text-center">Attendance</th>
                  <th className="text-center">Homework Avg</th>
                  <th className="text-center">Project Avg</th>
                  <th className="text-center">Test/Quiz Avg</th>
                  <th className="text-center">Participation</th>
                  <th className="text-center">PTAP</th>
                  <th>PTAP Comment</th>
                  <th>PTAP Comment Review</th>
                  {semesterKey ? <th className="text-center">{termLabel}</th> : null}
                  {semesterKey ? <th>{termCommentLabel}</th> : null}
                  {semesterKey ? <th>{termCommentLabel} Review</th> : null}
                  <th className="text-center">Semester Score</th>
                </tr>
              </thead>
              <tbody>
                {visibleRows.map((row) => (
                  <tr key={row.student_id}>
                    <td>{row.firstname || '—'}</td>
                    <td>{row.lastname || '—'}</td>
                    <td className={`text-center ${row.attendance ? '' : 'bg-light text-muted'}`}>{row.attendance || '—'}</td>
                    <td className={`text-center ${row.homework ? '' : 'bg-light text-muted'}`}>{row.homework || '—'}</td>
                    <td className={`text-center ${row.project ? '' : 'bg-light text-muted'}`}>{row.project || '—'}</td>
                    <td className={`text-center ${row.quiz ? '' : 'bg-light text-muted'}`}>{row.quiz || '—'}</td>
                    <td className={`text-center ${row.participation ? '' : 'bg-light text-muted'}`}>{row.participation || '—'}</td>
                    <td className={`text-center ${row.ptap ? '' : 'bg-light text-muted'}`}>{row.ptap || '—'}</td>
                    <td style={{ minWidth: 240 }}>
                      <textarea
                        className={`form-control form-control-sm ${row.ptap_comment.trim() === '' ? 'bg-light' : ''}`}
                        rows={3}
                        placeholder="PTAP Comment"
                        value={row.ptap_comment}
                        disabled={scoresLocked}
                        onChange={(event) => updateRow(row.student_id, 'ptap_comment', event.target.value)}
                      />
                      {row.ptap_comment.trim() === '' ? (
                        <label className="form-check-label small mt-1 d-inline-flex align-items-center gap-2">
                          <input
                            className="form-check-input mt-0"
                            type="checkbox"
                            checked={row.ptap_missing_ok}
                            disabled={scoresLocked}
                            onChange={(event) => updateRow(row.student_id, 'ptap_missing_ok', event.target.checked)}
                          />
                          <span>Missing ok</span>
                        </label>
                      ) : null}
                      <div className="small text-muted text-end">{row.ptap_comment.trim().length} / 350</div>
                    </td>
                    <td className={row.ptap_comment_review.trim() ? '' : 'bg-light text-muted'} style={{ minWidth: 180, whiteSpace: 'pre-wrap' }}>
                      {row.ptap_comment_review || '—'}
                    </td>
                    {semesterKey ? (
                      <td className={`text-center ${row.term_score ? '' : 'bg-light text-muted'}`}>{row.term_score || '—'}</td>
                    ) : null}
                    {semesterKey ? (
                      <td style={{ minWidth: 240 }}>
                        <textarea
                          className={`form-control form-control-sm ${row.term_comment.trim() === '' ? 'bg-light' : ''}`}
                          rows={3}
                          placeholder={termCommentLabel}
                          value={row.term_comment}
                          disabled={scoresLocked}
                          onChange={(event) => updateRow(row.student_id, 'term_comment', event.target.value)}
                        />
                        {row.term_comment.trim() === '' ? (
                          <label className="form-check-label small mt-1 d-inline-flex align-items-center gap-2">
                            <input
                              className="form-check-input mt-0"
                              type="checkbox"
                              checked={row.term_missing_ok}
                              disabled={scoresLocked}
                              onChange={(event) => updateRow(row.student_id, 'term_missing_ok', event.target.checked)}
                            />
                            <span>Missing ok</span>
                          </label>
                        ) : null}
                        <div className="small text-muted text-end">{row.term_comment.trim().length} / 350</div>
                      </td>
                    ) : null}
                    {semesterKey ? (
                      <td className={row.term_comment_review.trim() ? '' : 'bg-light text-muted'} style={{ minWidth: 180, whiteSpace: 'pre-wrap' }}>
                        {row.term_comment_review || '—'}
                      </td>
                    ) : null}
                    <td className={`text-center ${row.semester_score ? '' : 'bg-light text-muted'}`}>{row.semester_score || '—'}</td>
                  </tr>
                ))}
                {visibleRows.length === 0 ? (
                  <tr>
                    <td colSpan={semesterKey ? 13 : 10} className="text-center text-muted">
                      No students match the current search.
                    </td>
                  </tr>
                ) : null}
              </tbody>
            </table>
          </div>

          <div className="d-flex flex-wrap gap-2 mt-3">
            <Link
              to={teacherRouteWithTerm('/app/teacher/add-homework', {
                classSectionId,
                schoolYear: resolvedSchoolYear,
                semester: resolvedSemester,
              })}
              className="btn btn-secondary"
            >
              Add Homework
            </Link>
            <Link
              to={teacherRouteWithTerm('/app/teacher/add-quiz', {
                classSectionId,
                schoolYear: resolvedSchoolYear,
                semester: resolvedSemester,
              })}
              className="btn btn-secondary"
            >
              Add Quiz
            </Link>
            <Link
              to={teacherRouteWithTerm('/app/teacher/add-project', {
                classSectionId,
                schoolYear: resolvedSchoolYear,
                semester: resolvedSemester,
              })}
              className="btn btn-secondary"
            >
              Add Project
            </Link>
            <Link
              to={teacherRouteWithTerm('/app/teacher/add-participation', {
                classSectionId,
                schoolYear: resolvedSchoolYear,
                semester: resolvedSemester,
              })}
              className="btn btn-secondary"
            >
              Add Participation
            </Link>
            {semesterKey === 'midterm' ? (
              <Link
                to={teacherRouteWithTerm('/app/teacher/add-midterm-exam', {
                  classSectionId,
                  schoolYear: resolvedSchoolYear,
                  semester: resolvedSemester,
                })}
                className="btn btn-secondary"
              >
                Add Midterm
              </Link>
            ) : null}
            {semesterKey === 'final' ? (
              <Link
                to={teacherRouteWithTerm('/app/teacher/add-final-exam', {
                  classSectionId,
                  schoolYear: resolvedSchoolYear,
                  semester: resolvedSemester,
                })}
                className="btn btn-secondary"
              >
                Add Final
              </Link>
            ) : null}

            <button type="submit" className="btn btn-info" disabled={scoresLocked || saving || locking}>
              {saving ? 'Saving…' : 'Save Comments'}
            </button>
            <button
              type="button"
              className="btn btn-outline-danger"
              disabled={scoresLocked || saving || locking}
              onClick={() => void submitSemesterScores()}
            >
              {scoresLocked ? 'Scores Locked' : locking ? 'Submitting…' : 'Submit Semester Scores'}
            </button>
          </div>
        </form>
      ) : null}
    </TeacherShell>
  )
}

/** `teacher/showupdate_attendance.php` */
export { TeacherShowupdateAttendancePage } from './TeacherShowupdateAttendancePage'

/** `teacher/teacher_assignment.php` */
export function TeacherAssignmentPage() {
  return <TeacherApiDumpPage title="Assignments" path="/teacher-assignment" />
}

/** `teacher/teacher_contactus.php` */
export function TeacherContactUsPage() {
  return <TeacherApiDumpPage title="Contact us" path="/teacher-contactus" />
}

/** `teacher/teacher_support.php` */
export function TeacherSupportPage() {
  return <TeacherApiDumpPage title="Support" path="/teacher-support" />
}
