import { apiUrl } from '../lib/apiOrigin'
import { ApiHttpError, apiFetch, getStoredToken } from './http'
import type {
  AdministratorAbsenceFormResponse,
  AdministratorAbsenceSubmitResponse,
  AdministratorAdminAttendanceResponse,
  AttendanceCommentTemplatesResponse,
  AttendanceViolationsResponse,
  EarlyDismissalsResponse,
  ParentAttendanceAdminReportRow,
  StaffMonthlyOverviewPayload,
  TeacherStaffAttendanceResponse,
  AdministratorDailyAttendanceResponse,
  AdministratorDashboardMetricsResponse,
  AdministratorDashboardSearchResponse,
  ApiEnvelope,
  BroadcastEmailOptionsResponse,
  BroadcastEmailSendResponse,
  ClassSectionShowResponse,
  ClassSectionsResponse,
  DashboardPayload,
  FamilyAdminIndexResponse,
  GradingOverviewResponse,
  IpBansResponse,
  LandingPageResponse,
  LoginResponse,
  LoginSuccess,
  LateSlipLogsResponse,
  NavBuilderDataResponse,
  NavBuilderDeleteResponse,
  NavBuilderSaveResponse,
  NavMenuResponse,
  ParentAttendanceResponse,
  ParentAttendanceReportFormResponse,
  ParentEnrollmentsResponse,
  ParentEventsOverviewResponse,
  ParentInvoicesResponse,
  ParentProfileResponse,
  ParentRegistrationOverviewResponse,
  ParentEmergencyContactsResponse,
  ParentAttendanceReportsListResponse,
  SchoolCalendarListResponse,
  SchoolCalendarOptionsResponse,
  SchoolCalendarShowResponse,
  ParentAuthorizedUsersApiResponse,
  ParentMessagesResponse,
  NotificationAlertsResponse,
  PaypalTransactionsResponse,
  PrintNotificationRecipientsResponse,
  SchoolCalendarEventsResponse,
  StudentAssignmentsResponse,
  StudentScoresResponse,
  ReportCardAcknowledgementResponse,
  ReportCardMetaResponse,
  RemovedStudentsResponse,
  ClassProgressGroupsResponse,
  ClassProgressMetaResponse,
  ClassProgressShowResponse,
  ParentProfileRecord,
  ContactSubmitResponse,
  CompetitionScoresEditResponse,
  CompetitionWinnerAdminActionResponse,
  CompetitionWinnerAdminFormResponse,
  CompetitionWinnerAdminIndexResponse,
  CompetitionWinnerAdminPreviewResponse,
  CompetitionWinnerAdminScoresResponse,
  CompetitionWinnerAdminWinnersResponse,
  CompetitionWinnerExportQuizResponse,
  CompetitionScoresIndexResponse,
  ExamDraftAdminResponse,
  PublicCompetitionIndexResponse,
  PublicCompetitionShowResponse,
  StudentDirectoryResponse,
  StudentScoreCardResponse,
  StudentEmergencyContactResponse,
  StudentParentResponse,
  StudentPromotionTotalsResponse,
  SubjectCurriculumResponse,
  TeacherClassAssignmentsResponse,
  TeacherSubmissionsResponse,
  EmergencyContactGroupsResponse,
  EmergencyContactShowResponse,
  UserListRow,
  UsersResponse,
  ClassPrepIndexResponse,
  ClassPrepPrintResponse,
  CommunicationsComposeResponse,
  CommunicationsPreviewResponse,
  ConfigurationListResponse,
  DiscountApplyContextResponse,
  DiscountsListResponse,
  FamilyListItem,
  GuardianListItem,
  RegisteredNewStudentsResponse,
  EnrollmentWithdrawalPageResponse,
  ExpensesListResponse,
  ExpenseCreateFormResponse,
  ExpenseDetail,
  ExpenseEditFormResponse,
  ExpenseUserOption,
  FamilyLegacyImportMetaResponse,
  FamilyLegacyImportResult,
  FamilySearchResponse,
} from './types'

type LoginApiObject = Record<string, unknown>

function isObject(value: unknown): value is LoginApiObject {
  return value !== null && typeof value === 'object' && !Array.isArray(value)
}

function stringValue(value: unknown): string | null {
  return typeof value === 'string' && value.trim() !== '' ? value.trim() : null
}

function numberValue(value: unknown): number {
  if (typeof value === 'number' && Number.isFinite(value)) return value
  if (typeof value === 'string' && value.trim() !== '' && Number.isFinite(Number(value))) {
    return Number(value)
  }
  return 0
}

function rolesToMap(value: unknown): Record<string, boolean> {
  if (Array.isArray(value)) {
    return Object.fromEntries(
      value
        .map((role) => String(role ?? '').trim())
        .filter(Boolean)
        .map((role) => [role, true]),
    )
  }

  if (isObject(value)) {
    return Object.fromEntries(
      Object.entries(value)
        .filter(([role, enabled]) => role.trim() !== '' && Boolean(enabled))
        .map(([role]) => [role, true]),
    )
  }

  return {}
}

function normalizeLoginSuccess(body: unknown): LoginSuccess | null {
  if (!isObject(body)) return null

  const data = isObject(body.data) ? body.data : null
  const jwt = data && isObject(data.jwt) ? data.jwt : null
  const sanctum = data && isObject(data.sanctum) ? data.sanctum : null

  const token =
    stringValue(body.token) ??
    stringValue(body.access_token) ??
    (data ? stringValue(data.token) : null) ??
    (data ? stringValue(data.access_token) : null) ??
    (jwt ? stringValue(jwt.access_token) : null) ??
    (sanctum ? stringValue(sanctum.access_token) : null)

  const rawUser =
    (isObject(body.user) ? body.user : null) ??
    (data && isObject(data.user) ? data.user : null)

  if (!token || !rawUser) return null

  const firstName = stringValue(rawUser.firstname) ?? stringValue(rawUser.first_name) ?? ''
  const lastName = stringValue(rawUser.lastname) ?? stringValue(rawUser.last_name) ?? ''
  const name =
    stringValue(rawUser.name) ??
    [firstName, lastName].filter(Boolean).join(' ').trim() ??
    stringValue(rawUser.email) ??
    'User'

  return {
    status: true,
    token,
    user: {
      id: numberValue(rawUser.id),
      name,
      roles: rolesToMap(rawUser.roles),
      class_section_id:
        rawUser.class_section_id === null || rawUser.class_section_id === undefined
          ? null
          : numberValue(rawUser.class_section_id),
      class_section_name: stringValue(rawUser.class_section_name),
    },
  }
}

export async function loginRequest(
  email: string,
  password: string,
): Promise<LoginResponse> {
  const loginPaths = ['/api/v1/auth/login', '/api/v1/login', '/api/login']
  let body: unknown = null
  let lastError: unknown = null

  for (const path of loginPaths) {
    try {
      body = await apiFetch<unknown>(
        path,
        {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ email, password }),
        },
        { attachAuth: false },
      )
      lastError = null
      break
    } catch (error) {
      lastError = error
      if (!(error instanceof ApiHttpError) || error.status !== 404) {
        throw error
      }
    }
  }

  if (lastError) throw lastError

  if (isObject(body)) {
    const explicitFailure =
      body.status === false ||
      body.success === false ||
      String(body.result ?? '').toLowerCase() === 'false'

    if (explicitFailure) {
      return { status: false, message: stringValue(body.message) ?? 'Login failed.' }
    }
  }

  const normalized = normalizeLoginSuccess(body)
  if (normalized) return normalized

  throw new Error('Invalid login response from API. Expected a token and user object.')
}

export async function fetchDashboardRoute(): Promise<ApiEnvelope<DashboardPayload>> {
  return apiFetch<ApiEnvelope<DashboardPayload>>('/api/v1/dashboard/route')
}

export async function postRoleSwitch(role: string): Promise<ApiEnvelope<{
  role?: string
  dashboard_route?: string
}>> {
  return apiFetch<ApiEnvelope<{ role?: string; dashboard_route?: string }>>('/api/v1/role-switcher/switch', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ role }),
  })
}

export async function fetchLandingAdminDashboard(kind: 'admin' | 'administrator'): Promise<LandingPageResponse> {
  return apiFetch<LandingPageResponse>(`/api/v1/landing/${kind}`)
}

export async function fetchAdministratorDashboardMetrics(): Promise<AdministratorDashboardMetricsResponse> {
  return apiFetch<AdministratorDashboardMetricsResponse>('/api/v1/administrator/dashboard/metrics')
}

export async function toggleGradingLock(payload: {
  class_section_id: number
  semester: string
  school_year: string
}): Promise<{ ok?: boolean; locked?: boolean }> {
  return apiFetch<{ ok?: boolean; locked?: boolean }>('/api/v1/grading/locks/toggle', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function lockAllGradingScores(payload: {
  semester: string
  school_year: string
}): Promise<{ ok?: boolean; locked_count?: number }> {
  return apiFetch<{ ok?: boolean; locked_count?: number }>('/api/v1/grading/locks/all', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function refreshGradingScores(payload: {
  class_section_id: number
  semester: string
  school_year: string
}): Promise<{ ok?: boolean }> {
  return apiFetch<{ ok?: boolean }>('/api/v1/grading/refresh', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function fetchConfigurationByKey(configKey: string): Promise<{ ok?: boolean; config?: { id?: number; config_key?: string; config_value?: string | null } }> {
  return apiFetch<{ ok?: boolean; config?: { id?: number; config_key?: string; config_value?: string | null } }>(
    `/api/v1/configuration/key/${encodeURIComponent(configKey)}`,
  )
}

export async function updateConfigurationValue(configId: number, payload: {
  config_key: string
  config_value: string
}): Promise<{ ok?: boolean; config?: { id?: number; config_key?: string; config_value?: string | null } }> {
  return apiFetch<{ ok?: boolean; config?: { id?: number; config_key?: string; config_value?: string | null } }>(
    `/api/v1/configuration/${configId}`,
    {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    },
  )
}

export async function searchAdministratorDashboard(query: string): Promise<AdministratorDashboardSearchResponse> {
  const q = query.trim()
  const qs = q ? `?query=${encodeURIComponent(q)}` : ''
  return apiFetch<AdministratorDashboardSearchResponse>(`/api/v1/administrator/dashboard/user-search${qs}`)
}

export async function fetchAdministratorAbsenceForm(): Promise<AdministratorAbsenceFormResponse> {
  return apiFetch<AdministratorAbsenceFormResponse>('/api/v1/administrator/absence')
}

export async function submitAdministratorAbsence(payload: {
  dates: string[]
  reason_type?: string
  reason: string
}): Promise<AdministratorAbsenceSubmitResponse> {
  return apiFetch<AdministratorAbsenceSubmitResponse>('/api/v1/administrator/absence', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

/** Laravel often wraps JSON in `{ data: { ... } }`; some clients emit camelCase keys. */
function unwrapApiDataLayer(body: unknown): unknown {
  if (body === null || body === undefined || typeof body !== 'object') return body
  const o = body as Record<string, unknown>
  if ('data' in o && o.data !== null && typeof o.data === 'object') return o.data
  return body
}

function normalizeAdministratorDailyAttendanceResponse(raw: unknown): AdministratorDailyAttendanceResponse {
  const body = unwrapApiDataLayer(raw) as Record<string, unknown>
  const d = { ...(body as unknown as AdministratorDailyAttendanceResponse) }
  if (d.grades === undefined && body.Grades != null) d.grades = body.Grades as AdministratorDailyAttendanceResponse['grades']
  if (d.students_by_section === undefined && body.studentsBySection != null) {
    d.students_by_section = body.studentsBySection as AdministratorDailyAttendanceResponse['students_by_section']
  }
  if (d.attendance_data === undefined && body.attendanceData != null) {
    d.attendance_data = body.attendanceData as AdministratorDailyAttendanceResponse['attendance_data']
  }
  if (d.attendance_record === undefined && body.attendanceRecord != null) {
    d.attendance_record = body.attendanceRecord as AdministratorDailyAttendanceResponse['attendance_record']
  }
  if (d.dates_by_section === undefined && body.datesBySection != null) {
    d.dates_by_section = body.datesBySection as AdministratorDailyAttendanceResponse['dates_by_section']
  }
  if (d.date_list === undefined && body.dateList != null) {
    d.date_list = body.dateList as AdministratorDailyAttendanceResponse['date_list']
  }
  if (d.school_year === undefined && body.schoolYear != null) {
    d.school_year = String(body.schoolYear)
  }
  if (d.total_passed_days === undefined && body.totalPassedDays != null) {
    d.total_passed_days = Number(body.totalPassedDays)
  }
  return d
}

export async function fetchAdministratorDailyAttendance(
  params?: { semester?: string | null; schoolYear?: string | null },
): Promise<AdministratorDailyAttendanceResponse> {
  const query = new URLSearchParams()
  if (params?.semester) query.set('semester', params.semester)
  if (params?.schoolYear) query.set('school_year', params.schoolYear)
  const qs = query.size > 0 ? `?${query.toString()}` : ''
  const raw = await apiFetch<unknown>(`/api/v1/attendance/admin/daily${qs}`)
  return normalizeAdministratorDailyAttendanceResponse(raw)
}

export async function addAdministratorAttendanceEntry(payload: {
  class_section_id: number
  student_id: number
  school_id?: string | null
  status: 'present' | 'absent' | 'late'
  date?: string | null
  reason?: string | null
  class_id?: number | null
  is_reported?: 'yes' | 'no' | null
}): Promise<{ message?: string }> {
  return apiFetch<{ message?: string }>('/api/v1/attendance/admin/add-entry', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function updateAdministratorAttendance(payload: {
  class_section_id: number
  student_id: number
  school_id?: string | null
  status: 'present' | 'absent' | 'late'
  date: string
  reason?: string | null
  class_id?: number | null
  semester?: string | null
  school_year?: string | null
  is_reported?: string | null
}): Promise<{ message?: string }> {
  return apiFetch<{ message?: string }>('/api/v1/attendance/admin/update', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function fetchAdministratorAdminAttendance(
  params?: { date?: string | null; semester?: string | null; schoolYear?: string | null },
): Promise<AdministratorAdminAttendanceResponse> {
  const query = new URLSearchParams()
  if (params?.date) query.set('date', params.date)
  if (params?.semester) query.set('semester', params.semester)
  if (params?.schoolYear) query.set('school_year', params.schoolYear)
  const qs = query.size > 0 ? `?${query.toString()}` : ''
  return apiFetch<AdministratorAdminAttendanceResponse>(`/api/v1/attendance/staff/admins${qs}`)
}

export async function saveAdministratorAdminAttendance(payload: {
  date: string
  semester: string
  school_year: string
  admins: Record<
    string,
    {
      status?: string | null
      reason?: string | null
      role_name?: string | null
      role_slug?: string | null
    }
  >
}): Promise<{ message?: string }> {
  return apiFetch<{ message?: string }>('/api/v1/attendance/staff/admins/save', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function fetchTeacherStaffAttendance(params?: {
  date?: string | null
  semester?: string | null
  schoolYear?: string | null
  classSectionId?: number | null
}): Promise<TeacherStaffAttendanceResponse> {
  const query = new URLSearchParams()
  if (params?.date) query.set('date', params.date)
  if (params?.semester) query.set('semester', params.semester)
  if (params?.schoolYear) query.set('school_year', params.schoolYear)
  if (params?.classSectionId != null && params.classSectionId > 0) {
    query.set('class_section_id', String(params.classSectionId))
  }
  const qs = query.size > 0 ? `?${query.toString()}` : ''
  return apiFetch<TeacherStaffAttendanceResponse>(`/api/v1/attendance/staff/teachers${qs}`)
}

export async function saveTeacherStaffAttendance(payload: {
  date: string
  semester: string
  school_year: string
  class_section_id: number
  teachers: Record<
    string,
    {
      status?: string | null
      reason?: string | null
    }
  >
}): Promise<{ message?: string }> {
  return apiFetch<{ message?: string }>('/api/v1/attendance/staff/teachers/save', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function fetchStaffMonthlyAttendanceOverview(params?: {
  semester?: string | null
  schoolYear?: string | null
}): Promise<StaffMonthlyOverviewPayload> {
  const query = new URLSearchParams()
  if (params?.semester) query.set('semester', params.semester)
  if (params?.schoolYear) query.set('school_year', params.schoolYear)
  const qs = query.size > 0 ? `?${query.toString()}` : ''
  const raw = await apiFetch<unknown>(`/api/v1/attendance/staff/month${qs}`)
  return unwrapApiDataLayer(raw) as StaffMonthlyOverviewPayload
}

/** Matches CI `teacher_attendance_month` save cell (form body). */
export async function saveStaffMonthlyAttendanceCell(payload: Record<string, string>): Promise<{
  ok?: boolean
  csrf_token?: string
  csrf_hash?: string
}> {
  const body = new URLSearchParams()
  for (const [k, v] of Object.entries(payload)) body.set(k, v)
  return apiFetch('/api/v1/attendance/staff/cell', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: body.toString(),
  })
}

export async function fetchAttendanceViolationsPending(params?: {
  schoolYear?: string | null
  semester?: string | null
}): Promise<AttendanceViolationsResponse> {
  const query = new URLSearchParams()
  if (params?.schoolYear) query.set('school_year', params.schoolYear)
  if (params?.semester) query.set('semester', params.semester)
  const qs = query.size > 0 ? `?${query.toString()}` : ''
  const raw = await apiFetch<unknown>(`/api/v1/attendance-tracking/pending-violations${qs}`)
  return unwrapApiDataLayer(raw) as AttendanceViolationsResponse
}

export async function fetchAttendanceViolationsNotified(params?: {
  schoolYear?: string | null
  semester?: string | null
}): Promise<AttendanceViolationsResponse> {
  const query = new URLSearchParams()
  if (params?.schoolYear) query.set('school_year', params.schoolYear)
  if (params?.semester) query.set('semester', params.semester)
  const qs = query.size > 0 ? `?${query.toString()}` : ''
  const raw = await apiFetch<unknown>(`/api/v1/attendance-tracking/notified-violations${qs}`)
  return unwrapApiDataLayer(raw) as AttendanceViolationsResponse
}

export async function fetchParentAttendanceReportsAdmin(params?: {
  schoolYear?: string | null
  semester?: string | null
  start?: string | null
  end?: string | null
}): Promise<{ rows?: ParentAttendanceAdminReportRow[] }> {
  const query = new URLSearchParams()
  if (params?.schoolYear) query.set('school_year', params.schoolYear)
  if (params?.semester) query.set('semester', params.semester)
  if (params?.start) query.set('start', params.start)
  if (params?.end) query.set('end', params.end)
  const qs = query.size > 0 ? `?${query.toString()}` : ''
  return apiFetch<{ rows?: ParentAttendanceAdminReportRow[] }>(
    `/api/v1/attendance/parent-reports${qs}`,
  )
}

export async function fetchEarlyDismissals(params?: {
  schoolYear?: string | null
  semester?: string | null
}): Promise<EarlyDismissalsResponse> {
  const query = new URLSearchParams()
  if (params?.schoolYear) query.set('school_year', params.schoolYear)
  if (params?.semester) query.set('semester', params.semester)
  const qs = query.size > 0 ? `?${query.toString()}` : ''
  return apiFetch<EarlyDismissalsResponse>(`/api/v1/attendance/early-dismissals${qs}`)
}

export async function fetchEarlyDismissalStudentOptions(): Promise<{
  students?: Array<{
    id?: number
    firstname?: string
    lastname?: string
    class_section_name?: string | null
  }>
}> {
  return apiFetch('/api/v1/attendance/early-dismissals/student-options')
}

export async function createEarlyDismissal(payload: {
  student_id: number
  date: string
  dismiss_time: string
  reason?: string | null
}): Promise<{ message?: string }> {
  return apiFetch('/api/v1/attendance/early-dismissals', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

/** Multipart signature upload per report date — matches CI `attendance/early-dismissals/signature`. */
export async function uploadEarlyDismissalSignature(formData: FormData): Promise<{ message?: string }> {
  const token = getStoredToken()
  const headers = new Headers({ Accept: 'application/json' })
  if (token) headers.set('Authorization', `Bearer ${token}`)
  const res = await fetch(apiUrl('/api/v1/attendance/early-dismissals/signature'), {
    method: 'POST',
    body: formData,
    headers,
  })
  const body: unknown = await res.json().catch(() => null)
  if (!res.ok) {
    const msg =
      typeof body === 'object' && body !== null && 'message' in body
        ? String((body as { message?: unknown }).message ?? '')
        : ''
    throw new Error(msg || `HTTP ${res.status}`)
  }
  return body as { message?: string }
}

export async function fetchAttendanceCommentTemplates(): Promise<AttendanceCommentTemplatesResponse> {
  return apiFetch<AttendanceCommentTemplatesResponse>('/api/v1/attendance-templates')
}

export async function saveAttendanceCommentTemplate(payload: Record<string, string>): Promise<{
  status?: string
}> {
  const body = new URLSearchParams()
  for (const [k, v] of Object.entries(payload)) body.set(k, v)
  return apiFetch('/api/v1/attendance-templates/save', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: body.toString(),
  })
}

export async function deleteAttendanceCommentTemplate(id: number): Promise<{ status?: string }> {
  const body = new URLSearchParams()
  body.set('id', String(id))
  return apiFetch('/api/v1/attendance-templates/delete', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: body.toString(),
  })
}

export async function saveAttendanceViolationNote(payload: Record<string, string>): Promise<{ message?: string }> {
  const body = new URLSearchParams()
  for (const [k, v] of Object.entries(payload)) body.set(k, v)
  return apiFetch('/api/v1/attendance-tracking/save-notification-note', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: body.toString(),
  })
}

export async function sendAttendanceViolationEmail(payload: Record<string, string>): Promise<{ message?: string }> {
  const body = new URLSearchParams()
  for (const [k, v] of Object.entries(payload)) body.set(k, v)
  return apiFetch('/api/v1/attendance-tracking/send-manual-email', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: body.toString(),
  })
}

export async function fetchAttendanceComposeEmailContext(params: {
  studentId: string | number
  code: string
  variant?: string | null
  incidentDate?: string | null
}): Promise<Record<string, unknown>> {
  const q = new URLSearchParams()
  q.set('student_id', String(params.studentId))
  q.set('code', params.code)
  if (params.variant) q.set('variant', params.variant)
  if (params.incidentDate) q.set('incident_date', params.incidentDate)
  return apiFetch(`/api/v1/attendance-tracking/compose?${q.toString()}`)
}

export async function fetchAttendanceStudentViolationsView(
  studentId: string | number,
  query?: { code?: string | null; incident_date?: string | null; include_notified?: string | null },
): Promise<Record<string, unknown>> {
  const q = new URLSearchParams()
  if (query?.code) q.set('code', query.code)
  if (query?.incident_date) q.set('incident_date', query.incident_date)
  if (query?.include_notified) q.set('include_notified', query.include_notified)
  const qs = q.size > 0 ? `?${q.toString()}` : ''
  return apiFetch(`/api/v1/attendance-tracking/student-case/${studentId}${qs}`)
}

export async function fetchAttendanceTrackingStudents(): Promise<{
  students?: Array<Record<string, unknown>>
  title?: string
}> {
  return apiFetch('/api/v1/attendance/tracking')
}

export async function recordAttendanceTrackingEntry(payload: Record<string, unknown>): Promise<{ message?: string }> {
  return apiFetch('/api/v1/attendance/tracking/record', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function fetchBroadcastEmailOptions(): Promise<BroadcastEmailOptionsResponse> {
  return apiFetch<BroadcastEmailOptionsResponse>('/api/v1/broadcast-email/options')
}

export async function sendBroadcastEmail(payload: {
  from_key: string
  mode: 'personalized' | 'standard'
  subject: string
  body_html: string
  wrap_layout?: boolean
  preheader?: string
  cta_text?: string
  cta_url?: string
  test_email?: string
  send_test_only?: boolean
  parent_ids?: number[]
}): Promise<BroadcastEmailSendResponse> {
  return apiFetch<BroadcastEmailSendResponse>('/api/v1/broadcast-email/send', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function fetchSchoolCalendarOptions(): Promise<SchoolCalendarOptionsResponse> {
  return apiFetch<SchoolCalendarOptionsResponse>('/api/v1/settings/school-calendar/options')
}

export async function fetchAdministratorCalendarEvents(params?: {
  schoolYear?: string | null
  semester?: string | null
  audience?: 'admin' | 'teacher' | 'parent' | null
}): Promise<SchoolCalendarListResponse> {
  const query = new URLSearchParams()
  if (params?.schoolYear) query.set('school_year', params.schoolYear)
  if (params?.semester) query.set('semester', params.semester)
  if (params?.audience) query.set('audience', params.audience)
  const qs = query.size > 0 ? `?${query.toString()}` : ''
  return apiFetch<SchoolCalendarListResponse>(`/api/v1/settings/school-calendar/events${qs}`)
}

export async function fetchAdministratorCalendarEvent(eventId: number): Promise<SchoolCalendarShowResponse> {
  return apiFetch<SchoolCalendarShowResponse>(`/api/v1/settings/school-calendar/events/${eventId}`)
}

export async function createAdministratorCalendarEvent(payload: {
  title: string
  description?: string
  event_type?: string
  date: string
  notify_parent?: boolean
  notify_teacher?: boolean
  notify_admin?: boolean
  no_school?: boolean
  school_year?: string
  semester?: string
  send_email_parent?: boolean
  send_email_teacher?: boolean
  send_email_admin?: boolean
}): Promise<SchoolCalendarShowResponse> {
  return apiFetch<SchoolCalendarShowResponse>('/api/v1/settings/school-calendar/events', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function updateAdministratorCalendarEvent(
  eventId: number,
  payload: Record<string, unknown>,
): Promise<SchoolCalendarShowResponse> {
  return apiFetch<SchoolCalendarShowResponse>(`/api/v1/settings/school-calendar/events/${eventId}`, {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function deleteAdministratorCalendarEvent(eventId: number): Promise<ApiEnvelope<null>> {
  return apiFetch<ApiEnvelope<null>>(`/api/v1/settings/school-calendar/events/${eventId}`, {
    method: 'DELETE',
  })
}

export async function fetchStudentAssignments(
  schoolYear?: string | null,
): Promise<StudentAssignmentsResponse> {
  const query = schoolYear ? `?school_year=${encodeURIComponent(schoolYear)}` : ''
  return apiFetch<StudentAssignmentsResponse>(`/api/v1/students/assignments${query}`)
}

export async function fetchClassAssignmentOverview(
  schoolYear?: string | null,
  semester?: string | null,
): Promise<{
  message?: string
  data?: {
    classSections?: Array<{
      class_section_id?: number | null
      class_section_name?: string | null
      main_teachers?: string[]
      teacher_assistants?: string[]
      students?: Array<{
        id?: number
        firstname?: string | null
        lastname?: string | null
        age?: number | string | null
        gender?: string | null
        photo_consent?: boolean | number | null
        tuition_paid?: boolean | number | null
        school_id?: string | null
      }>
      semester?: string | null
      school_year?: string | null
      description?: string | null
    }>
    schoolYears?: string[]
    schoolYear?: string | null
    selectedYear?: string | null
    selectedSemester?: string | null
    semester?: string | null
    current_school_year?: string | null
  }
}> {
  const query = new URLSearchParams()
  if (schoolYear) query.set('school_year', schoolYear)
  if (semester) query.set('semester', semester)
  const qs = query.size > 0 ? `?${query.toString()}` : ''
  return apiFetch(`/api/v1/assignments${qs}`)
}

export async function assignStudentClass(payload: {
  student_id: number
  class_section_ids?: number[]
  class_section_id?: number[]
  is_event_only?: boolean
}): Promise<{ ok: boolean; message?: string }> {
  return apiFetch<{ ok: boolean; message?: string }>('/api/v1/students/assign-class', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      student_id: payload.student_id,
      class_section_id: payload.class_section_id ?? payload.class_section_ids ?? [],
      is_event_only: payload.is_event_only ?? false,
    }),
  })
}

export async function removeStudentClass(payload: {
  student_id: number
  class_section_id: number
}): Promise<{ ok: boolean; message?: string }> {
  return apiFetch<{ ok: boolean; message?: string }>('/api/v1/students/remove-class', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function fetchRemovedStudents(
  schoolYear?: string | null,
): Promise<RemovedStudentsResponse> {
  const query = schoolYear ? `?school_year=${encodeURIComponent(schoolYear)}` : ''
  return apiFetch<RemovedStudentsResponse>(`/api/v1/students/removed${query}`)
}

export async function setStudentActive(payload: {
  student_id: number
  is_active: boolean
}): Promise<{ ok: boolean; message?: string }> {
  return apiFetch<{ ok: boolean; message?: string }>('/api/v1/students/set-active', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function autoDistributeStudents(payload: {
  class_id?: number
  class_section_id?: number
  students_per_section: number
  school_year?: string | null
}): Promise<ApiEnvelope<{ sections?: Array<Record<string, unknown>>; message?: string }>> {
  return apiFetch<ApiEnvelope<{ sections?: Array<Record<string, unknown>>; message?: string }>>(
    '/api/v1/students/auto-distribute',
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    },
  )
}

export async function fetchStudentPromotionTotals(
  schoolYear?: string | null,
): Promise<StudentPromotionTotalsResponse> {
  const query = schoolYear ? `?school_year=${encodeURIComponent(schoolYear)}` : ''
  return apiFetch<StudentPromotionTotalsResponse>(`/api/v1/students/promotion-totals${query}`)
}

export async function fetchStudentDirectory(
  schoolYear?: string | null,
): Promise<StudentDirectoryResponse> {
  const query = schoolYear ? `?school_year=${encodeURIComponent(schoolYear)}` : ''
  return apiFetch<StudentDirectoryResponse>(`/api/v1/students${query}`)
}

export async function updateStudentProfile(
  studentId: number,
  payload: Record<string, unknown>,
): Promise<{ ok: boolean; message?: string }> {
  return apiFetch<{ ok: boolean; message?: string }>(`/api/v1/students/${studentId}`, {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function fetchStudentParent(studentId: number): Promise<StudentParentResponse> {
  return apiFetch<StudentParentResponse>(`/api/v1/students/${studentId}/parents`)
}

export async function fetchStudentEmergencyContacts(
  studentId: number,
): Promise<StudentEmergencyContactResponse> {
  return apiFetch<StudentEmergencyContactResponse>(`/api/v1/students/${studentId}/emergency-contacts`)
}

export async function fetchTeacherClassAssignments(
  schoolYear?: string | null,
): Promise<TeacherClassAssignmentsResponse> {
  const query = schoolYear ? `?school_year=${encodeURIComponent(schoolYear)}` : ''
  return apiFetch<TeacherClassAssignmentsResponse>(
    `/api/v1/administrator/teacher-class/assignments${query}`,
  )
}

export async function assignTeacherClass(payload: {
  teacher_id: number
  class_section_id: number
  teacher_role?: string
  school_year?: string
}): Promise<{ ok: boolean; message?: string; position?: string | null }> {
  return apiFetch<{ ok: boolean; message?: string; position?: string | null }>(
    '/api/v1/administrator/teacher-class/assign',
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    },
  )
}

export async function deleteTeacherClassAssignment(payload: {
  teacher_id: number
  class_section_id: number
  position: 'main' | 'ta'
  school_year?: string
}): Promise<{ ok: boolean; message?: string }> {
  return apiFetch<{ ok: boolean; message?: string }>(
    '/api/v1/administrator/teacher-class/delete',
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    },
  )
}

export async function fetchTeacherSubmissions(
  searchParams?: URLSearchParams,
): Promise<TeacherSubmissionsResponse> {
  const q = searchParams?.toString()
  return apiFetch<TeacherSubmissionsResponse>(
    `/api/v1/administrator/teacher-submissions${q ? `?${q}` : ''}`,
  )
}

export async function fetchPublishedCompetitions(): Promise<PublicCompetitionIndexResponse> {
  return apiFetch<PublicCompetitionIndexResponse>('/api/winners/competitions', {}, { attachAuth: false })
}

export async function fetchPublishedCompetition(id: number): Promise<PublicCompetitionShowResponse> {
  return apiFetch<PublicCompetitionShowResponse>(`/api/winners/competitions/${id}`, {}, { attachAuth: false })
}

export async function fetchCompetitionScoresIndex(
  classSectionId?: number | null,
): Promise<CompetitionScoresIndexResponse> {
  const query = classSectionId ? `?class_section_id=${encodeURIComponent(String(classSectionId))}` : ''
  return apiFetch<CompetitionScoresIndexResponse>(`/api/v1/competition-scores${query}`)
}

export async function fetchCompetitionScoresDetail(
  id: number,
  classSectionId?: number | null,
): Promise<CompetitionScoresEditResponse> {
  const query = classSectionId ? `?class_section_id=${encodeURIComponent(String(classSectionId))}` : ''
  return apiFetch<CompetitionScoresEditResponse>(`/api/v1/competition-scores/${id}${query}`)
}

export async function fetchAdminCompetitionWinnersIndex(): Promise<CompetitionWinnerAdminIndexResponse> {
  return apiFetch<CompetitionWinnerAdminIndexResponse>('/api/v1/competition-winners')
}

export async function fetchAdminCompetitionWinnerCreate(): Promise<CompetitionWinnerAdminFormResponse> {
  return apiFetch<CompetitionWinnerAdminFormResponse>('/api/v1/competition-winners/create')
}

export async function fetchAdminCompetitionWinnerSettings(id: number): Promise<CompetitionWinnerAdminFormResponse> {
  return apiFetch<CompetitionWinnerAdminFormResponse>(`/api/v1/competition-winners/${id}/settings`)
}

export async function saveAdminCompetitionWinner(
  payload: {
    title: string
    class_section_id?: number | null
    semester?: string | null
    school_year?: string | null
    start_date?: string | null
    end_date?: string | null
    winner_overrides?: Record<string, number | string | null>
    question_counts?: Record<string, number | string | null>
    prizes?: Record<string, Record<string, number | string | null>>
  },
  id?: number,
): Promise<CompetitionWinnerAdminActionResponse> {
  return apiFetch<CompetitionWinnerAdminActionResponse>(id ? `/api/v1/competition-winners/${id}` : '/api/v1/competition-winners', {
    method: id ? 'PUT' : 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function fetchAdminCompetitionWinnerScores(
  id: number,
  classSectionId?: number | null,
): Promise<CompetitionWinnerAdminScoresResponse> {
  const query = classSectionId ? `?class_section_id=${encodeURIComponent(String(classSectionId))}` : ''
  return apiFetch<CompetitionWinnerAdminScoresResponse>(`/api/v1/competition-winners/${id}/scores${query}`)
}

export async function saveAdminCompetitionWinnerScores(
  id: number,
  payload: { class_section_id?: number | null; scores: Record<string, number | string> },
): Promise<{ ok: boolean; message?: string; savedCount?: number }> {
  return apiFetch<{ ok: boolean; message?: string; savedCount?: number }>(
    `/api/v1/competition-winners/${id}/scores`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    },
  )
}

export async function fetchAdminCompetitionWinnerPreview(id: number): Promise<CompetitionWinnerAdminPreviewResponse> {
  return apiFetch<CompetitionWinnerAdminPreviewResponse>(`/api/v1/competition-winners/${id}/preview`)
}

export async function fetchAdminCompetitionWinnerWinners(id: number): Promise<CompetitionWinnerAdminWinnersResponse> {
  return apiFetch<CompetitionWinnerAdminWinnersResponse>(`/api/v1/competition-winners/${id}/winners`)
}

export async function publishAdminCompetitionWinner(id: number): Promise<CompetitionWinnerAdminActionResponse> {
  return apiFetch<CompetitionWinnerAdminActionResponse>(`/api/v1/competition-winners/${id}/publish`, {
    method: 'POST',
  })
}

export async function lockAdminCompetitionWinner(id: number): Promise<CompetitionWinnerAdminActionResponse> {
  return apiFetch<CompetitionWinnerAdminActionResponse>(`/api/v1/competition-winners/${id}/lock`, {
    method: 'POST',
  })
}

export async function unlockAdminCompetitionWinner(id: number): Promise<CompetitionWinnerAdminActionResponse> {
  return apiFetch<CompetitionWinnerAdminActionResponse>(`/api/v1/competition-winners/${id}/unlock`, {
    method: 'POST',
  })
}

export async function exportAdminCompetitionWinnerQuiz(
  id: number,
  payload?: { class_section_id?: number | null; semester?: string | null; school_year?: string | null },
): Promise<CompetitionWinnerExportQuizResponse> {
  return apiFetch<CompetitionWinnerExportQuizResponse>(`/api/v1/competition-winners/${id}/export-quiz`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload ?? {}),
  })
}

export async function saveCompetitionScores(
  id: number,
  payload: { class_section_id?: number | null; scores: Record<string, number | string> },
): Promise<{ ok: boolean; message?: string; savedCount?: number; invalidScores?: string[] }> {
  return apiFetch<{ ok: boolean; message?: string; savedCount?: number; invalidScores?: string[] }>(
    `/api/v1/competition-scores/${id}`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    },
  )
}

export async function notifyTeacherSubmissions(payload: {
  notify: number[]
  missing_items?: Record<string, string[]>
}): Promise<{ message?: string; sent?: number; failed?: number }> {
  return apiFetch<{ message?: string; sent?: number; failed?: number }>(
    '/api/v1/administrator/teacher-submissions/notify',
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    },
  )
}

export async function fetchSubjectCurriculum(schoolYear?: string | null): Promise<SubjectCurriculumResponse> {
  const query = schoolYear ? `?school_year=${encodeURIComponent(schoolYear)}` : ''
  return apiFetch<SubjectCurriculumResponse>(`/api/v1/subjects/curriculum${query}`)
}

export async function createSubjectCurriculum(payload: {
  class_id: number
  subject: string
  unit_number?: number | null
  unit_title?: string | null
  chapter_name: string
  school_year?: string | null
}): Promise<ApiEnvelope<{ entry?: unknown }>> {
  return apiFetch<ApiEnvelope<{ entry?: unknown }>>('/api/v1/subjects/curriculum', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function updateSubjectCurriculum(
  id: number,
  payload: Record<string, unknown>,
): Promise<ApiEnvelope<{ entry?: unknown }>> {
  return apiFetch<ApiEnvelope<{ entry?: unknown }>>(`/api/v1/subjects/curriculum/${id}`, {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function deleteSubjectCurriculum(id: number): Promise<ApiEnvelope<null>> {
  return apiFetch<ApiEnvelope<null>>(`/api/v1/subjects/curriculum/${id}`, {
    method: 'DELETE',
  })
}

export async function fetchClassSections(params?: {
  search?: string
  schoolYear?: string | null
  semester?: string | null
  withStudents?: boolean
  page?: number
  perPage?: number
}): Promise<ClassSectionsResponse> {
  const query = new URLSearchParams()
  if (params?.search) query.set('search', params.search)
  if (params?.schoolYear) query.set('school_year', params.schoolYear)
  if (params?.semester) query.set('semester', params.semester)
  if (params?.withStudents) query.set('with_students', '1')
  if (params?.page) query.set('page', String(params.page))
  if (params?.perPage) query.set('per_page', String(params.perPage))
  const qs = query.size > 0 ? `?${query.toString()}` : ''
  return apiFetch<ClassSectionsResponse>(`/api/v1/class-sections${qs}`)
}

export async function fetchClassSection(sectionId: number): Promise<ClassSectionShowResponse> {
  return apiFetch<ClassSectionShowResponse>(`/api/v1/class-sections/${sectionId}`)
}

export async function createClassSection(payload: {
  class_id: number
  class_section_id: number
  class_section_name: string
  semester: string
  school_year: string
}): Promise<ClassSectionShowResponse> {
  return apiFetch<ClassSectionShowResponse>('/api/v1/class-sections', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function updateClassSection(
  sectionId: number,
  payload: Partial<{
    class_id: number
    class_section_id: number
    class_section_name: string
    semester: string
    school_year: string
  }>,
): Promise<ClassSectionShowResponse> {
  return apiFetch<ClassSectionShowResponse>(`/api/v1/class-sections/${sectionId}`, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function deleteClassSection(sectionId: number): Promise<ApiEnvelope<null>> {
  return apiFetch<ApiEnvelope<null>>(`/api/v1/class-sections/${sectionId}`, {
    method: 'DELETE',
  })
}

export async function fetchExamDraftAdminData(): Promise<ExamDraftAdminResponse> {
  return apiFetch<ExamDraftAdminResponse>('/api/v1/exams/drafts/admin')
}

export async function openProtectedApiFile(path: string): Promise<void> {
  const headers = new Headers()
  const token = getStoredToken()
  if (token) headers.set('Authorization', `Bearer ${token}`)

  const res = await fetch(apiUrl(path), { headers })
  if (!res.ok) {
    const body: unknown = await res.json().catch(() => null)
    const msg =
      typeof body === 'object' && body !== null && 'message' in body
        ? String((body as { message?: unknown }).message ?? '')
        : ''
    throw new Error(msg || `HTTP ${res.status}`)
  }

  const blob = await res.blob()
  const objectUrl = URL.createObjectURL(blob)
  const link = document.createElement('a')
  link.href = objectUrl
  link.target = '_blank'
  link.rel = 'noreferrer'
  document.body.appendChild(link)
  link.click()
  link.remove()
  window.setTimeout(() => URL.revokeObjectURL(objectUrl), 60_000)
}

async function fetchMultipartJson<T>(
  path: string,
  formData: FormData,
  init: { method?: 'POST' | 'PUT' | 'PATCH' } = {},
): Promise<T> {
  const headers = new Headers({ Accept: 'application/json' })
  const token = getStoredToken()
  if (token) headers.set('Authorization', `Bearer ${token}`)
  const res = await fetch(apiUrl(path), {
    method: init.method ?? 'POST',
    headers,
    body: formData,
  })
  const body: unknown = await res.json().catch(() => null)
  if (!res.ok) {
    let msg =
      typeof body === 'object' && body !== null && 'message' in body
        ? String((body as { message?: unknown }).message ?? '')
        : ''

    if (
      msg === 'Validation failed.' &&
      typeof body === 'object' &&
      body !== null &&
      'errors' in body
    ) {
      const errors = (body as { errors?: Record<string, unknown> }).errors
      if (errors && typeof errors === 'object') {
        const firstField = Object.values(errors)[0]
        if (Array.isArray(firstField) && firstField.length > 0) {
          msg = String(firstField[0] ?? msg)
        }
      }
    }

    throw new Error(msg || `HTTP ${res.status}`)
  }
  return body as T
}

export async function uploadLegacyExamDraft(payload: {
  class_section_id: number
  school_year: string
  semester: string
  exam_type?: string
  old_exam_file: File
}): Promise<{ ok: boolean; message?: string }> {
  const formData = new FormData()
  formData.set('class_section_id', String(payload.class_section_id))
  formData.set('school_year', payload.school_year)
  formData.set('semester', payload.semester)
  if (payload.exam_type) formData.set('exam_type', payload.exam_type)
  formData.set('old_exam_file', payload.old_exam_file)
  return fetchMultipartJson('/api/v1/exams/drafts/admin/legacy', formData)
}

export async function reviewExamDraft(payload: {
  draft_id: number
  review_status?: string
  admin_comments?: string
  final_file?: File | null
}): Promise<{ ok: boolean; message?: string }> {
  const formData = new FormData()
  formData.set('draft_id', String(payload.draft_id))
  if (payload.review_status) formData.set('review_status', payload.review_status)
  if (payload.admin_comments) formData.set('admin_comments', payload.admin_comments)
  if (payload.final_file) formData.set('final_file', payload.final_file)
  return fetchMultipartJson('/api/v1/exams/drafts/admin/review', formData)
}

export async function fetchGradingOverview(params?: {
  classId?: number | null
  semester?: string | null
  schoolYear?: string | null
}): Promise<GradingOverviewResponse> {
  const query = new URLSearchParams()
  if (params?.classId) query.set('class_id', String(params.classId))
  if (params?.semester) query.set('semester', params.semester)
  if (params?.schoolYear) query.set('school_year', params.schoolYear)
  const qs = query.size > 0 ? `?${query.toString()}` : ''
  return apiFetch<GradingOverviewResponse>(`/api/v1/grading/overview${qs}`)
}

export async function fetchIpBans(params?: {
  status?: 'all' | 'active'
  page?: number
  perPage?: number
}): Promise<IpBansResponse> {
  const query = new URLSearchParams()
  if (params?.status) query.set('status', params.status)
  if (params?.page) query.set('page', String(params.page))
  if (params?.perPage) query.set('per_page', String(params.perPage))
  const qs = query.size > 0 ? `?${query.toString()}` : ''
  return apiFetch<IpBansResponse>(`/api/v1/ip-bans${qs}`)
}

export async function banIp(payload: {
  id?: number
  ip?: string
  hours?: number
}): Promise<ApiEnvelope<{ ban?: unknown }>> {
  return apiFetch<ApiEnvelope<{ ban?: unknown }>>('/api/v1/ip-bans/ban', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function unbanIp(payload: {
  id?: number
  ip?: string
  all?: boolean
}): Promise<ApiEnvelope<{ ban?: unknown; count?: number }>> {
  return apiFetch<ApiEnvelope<{ ban?: unknown; count?: number }>>('/api/v1/ip-bans/unban', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function fetchLateSlipLogs(params?: {
  schoolYear?: string | null
  semester?: string | null
  q?: string
  dateFrom?: string
  dateTo?: string
  page?: number
  perPage?: number
}): Promise<LateSlipLogsResponse> {
  const query = new URLSearchParams()
  if (params?.schoolYear) query.set('school_year', params.schoolYear)
  if (params?.semester) query.set('semester', params.semester)
  if (params?.q) query.set('q', params.q)
  if (params?.dateFrom) query.set('date_from', params.dateFrom)
  if (params?.dateTo) query.set('date_to', params.dateTo)
  if (params?.page) query.set('page', String(params.page))
  if (params?.perPage) query.set('per_page', String(params.perPage))
  const qs = query.size > 0 ? `?${query.toString()}` : ''
  return apiFetch<LateSlipLogsResponse>(`/api/v1/attendance/late-slip-logs${qs}`)
}

export async function deleteLateSlipLog(id: number): Promise<ApiEnvelope<null>> {
  return apiFetch<ApiEnvelope<null>>(`/api/v1/attendance/late-slip-logs/${id}`, {
    method: 'DELETE',
  })
}

export async function fetchUsers(params?: {
  sort?: string
  order?: 'asc' | 'desc'
}): Promise<UsersResponse> {
  const query = new URLSearchParams()
  if (params?.sort) query.set('sort', params.sort)
  if (params?.order) query.set('order', params.order)
  const qs = query.size > 0 ? `?${query.toString()}` : ''
  return apiFetch<UsersResponse>(`/api/v1/users${qs}`)
}

let discountParentSchoolIdMapPromise: Promise<Map<string, string>> | null = null

function userRowHasParentRole(row: UserListRow): boolean {
  return (row.roles ?? []).some((role) => {
    const label =
      typeof role === 'string' ? role : `${role.name ?? ''} ${role.slug ?? ''}`
    return label.toLowerCase().includes('parent')
  })
}

async function fetchDiscountParentSchoolIdMap(): Promise<Map<string, string>> {
  if (!discountParentSchoolIdMapPromise) {
    discountParentSchoolIdMapPromise = (async () => {
      const data = await fetchUsers({ sort: 'lastname', order: 'asc' })
      const map = new Map<string, string>()

      for (const row of data.users ?? []) {
        if (!userRowHasParentRole(row)) continue
        const user = row.user
        const userId = user?.id
        const schoolId = user?.school_id
        if (userId == null || schoolId == null) continue
        const normalizedSchoolId = String(schoolId).trim()
        if (normalizedSchoolId === '') continue
        map.set(String(userId), normalizedSchoolId)
      }

      return map
    })().catch((error) => {
      discountParentSchoolIdMapPromise = null
      throw error
    })
  }

  return discountParentSchoolIdMapPromise
}

async function enrichDiscountApplyContext(
  context: DiscountApplyContextResponse,
): Promise<DiscountApplyContextResponse> {
  const parents = context.parents ?? []
  if (parents.length === 0) return context

  try {
    const schoolIdMap = await fetchDiscountParentSchoolIdMap()
    return {
      ...context,
      parents: parents.map((parent) => {
        if (typeof parent.school_id === 'string' && parent.school_id.trim() !== '') {
          return parent
        }

        const schoolId = schoolIdMap.get(String(parent.id))
        if (!schoolId) return parent
        return { ...parent, school_id: schoolId }
      }),
    }
  } catch {
    return context
  }
}

function unwrapEnvelopeData(body: unknown): unknown {
  if (!body || typeof body !== 'object') return body
  const o = body as Record<string, unknown>
  if ('data' in o && o.data != null && typeof o.data === 'object') return o.data
  return body
}

function normalizeUserDetailResponse(body: unknown): UserListRow {
  const raw = unwrapEnvelopeData(body)
  if (!raw || typeof raw !== 'object') throw new Error('Invalid user response')
  const o = raw as Record<string, unknown>

  if (o.user != null && typeof o.user === 'object') {
    return {
      user: o.user as UserListRow['user'],
      roles: Array.isArray(o.roles) ? (o.roles as UserListRow['roles']) : undefined,
      role_ids: Array.isArray(o.role_ids) ? (o.role_ids as number[]) : undefined,
      parent_type: typeof o.parent_type === 'string' ? o.parent_type : undefined,
      second_from_parents:
        typeof o.second_from_parents === 'string' ? o.second_from_parents : undefined,
    }
  }

  if (o.id != null || o.email != null || o.firstname != null) {
    return { user: o as UserListRow['user'] }
  }

  throw new Error('Invalid user response')
}

/** GET `/api/v1/users/:id` (falls back to user list row if show route is missing). */
export async function fetchUser(userId: number): Promise<UserListRow> {
  try {
    const body = await apiFetch<unknown>(`/api/v1/users/${userId}`)
    return normalizeUserDetailResponse(body)
  } catch (e) {
    if (e instanceof ApiHttpError && (e.status === 404 || e.status === 405)) {
      const data = await fetchUsers({ sort: 'lastname', order: 'asc' })
      const row = data.users?.find((r) => Number(r.user?.id) === userId)
      if (row) return row
    }
    throw e
  }
}

export async function deleteUser(userId: number): Promise<{ ok: boolean; message?: string }> {
  return apiFetch<{ ok: boolean; message?: string }>(`/api/v1/users/${userId}`, {
    method: 'DELETE',
  })
}

export async function saveUser(
  userId: number,
  payload: Record<string, unknown>,
): Promise<{ ok?: boolean; message?: string }> {
  try {
    return await apiFetch(`/api/v1/users/${userId}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    })
  } catch (error) {
    if (error instanceof ApiHttpError && error.status === 422) {
      const body = error.body
      if (body && typeof body === 'object') {
        const errors = (body as { errors?: unknown }).errors
        if (errors && typeof errors === 'object') {
          for (const value of Object.values(errors as Record<string, unknown>)) {
            if (Array.isArray(value) && value.length > 0) {
              throw new Error(String(value[0]))
            }
            if (typeof value === 'string' && value.trim() !== '') {
              throw new Error(value)
            }
          }
        }
      }
    }
    throw error
  }
}

/**
 * Returns whether `email` can be used (not taken by another user).
 * Expected Laravel route: `GET /api/v1/users/email-availability?email=...&except_user_id=...`
 * 200 body: `{ "available": true }` or `{ "available": false, "message": "..." }` (or under `data`).
 */
export async function checkUserEmailAvailable(
  exceptUserId: number,
  email: string,
): Promise<{ available: boolean; message?: string }> {
  const q = new URLSearchParams()
  q.set('email', email.trim())
  q.set('except_user_id', String(exceptUserId))
  const body = await apiFetch<unknown>(`/api/v1/users/email-availability?${q.toString()}`)
  const o = (body && typeof body === 'object' ? body : {}) as Record<string, unknown>
  const d =
    o.data && typeof o.data === 'object' && o.data !== null
      ? (o.data as Record<string, unknown>)
      : o
  const av = d.available
  if (av === false) {
    return {
      available: false,
      message: typeof d.message === 'string' ? d.message : undefined,
    }
  }
  if (av === true) {
    return { available: true, message: typeof d.message === 'string' ? d.message : undefined }
  }
  return { available: true }
}

/**
 * Account settings: ask the API to email a confirmation link to `email`.
 * The stored login email must not change until the user completes that flow (`confirmUserEmailChange`).
 *
 * Expected Laravel route: `POST /api/v1/users/{user}/email-change-request` with JSON `{ "email": "..." }`.
 */
export async function requestUserEmailChange(
  userId: number,
  email: string,
): Promise<{ ok?: boolean; message?: string }> {
  return apiFetch(`/api/v1/users/${userId}/email-change-request`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email: email.trim() }),
  })
}

/**
 * Completes a pending email change (token from the confirmation email).
 * Expected Laravel route: `POST /api/v1/users/email-change/confirm` with JSON `{ "token": "..." }`.
 */
export async function confirmUserEmailChange(
  token: string,
): Promise<{ ok?: boolean; message?: string }> {
  return apiFetch('/api/v1/users/email-change/confirm', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ token: token.trim() }),
  })
}

export async function createUserRecord(
  payload: Record<string, unknown>,
): Promise<{ ok?: boolean; id?: number; message?: string }> {
  return apiFetch('/api/v1/users', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export type LoginActivityRow = {
  user_id?: number | string
  email?: string | null
  login_time?: string | null
  logout_time?: string | null
  ip_address?: string | null
  user_agent?: string | null
}

export type LoginActivityResponse = {
  activities?: LoginActivityRow[]
  pagination?: {
    currentPage?: number
    pageCount?: number
    perPage?: number
    total?: number
  }
}

export async function fetchLoginActivity(params?: {
  page?: number
  per_page?: number
  school_year?: string
  semester?: string
}): Promise<LoginActivityResponse> {
  const query = new URLSearchParams()
  if (params?.page) query.set('page', String(params.page))
  if (params?.per_page) query.set('per_page', String(params.per_page))
  if (params?.school_year) query.set('school_year', params.school_year)
  if (params?.semester) query.set('semester', params.semester)
  const qs = query.size > 0 ? `?${query.toString()}` : ''
  return apiFetch<LoginActivityResponse>(`/api/v1/administrator/login-activity${qs}`)
}

export async function fetchNotificationAlerts(): Promise<NotificationAlertsResponse> {
  return apiFetch<NotificationAlertsResponse>('/api/v1/administrator/notifications/alerts')
}

export async function saveNotificationAlerts(
  subjects: Record<string, string[]>,
): Promise<{ message?: string }> {
  return apiFetch<{ message?: string }>('/api/v1/administrator/notifications/alerts', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ subjects }),
  })
}

export async function fetchPrintNotificationRecipients(): Promise<PrintNotificationRecipientsResponse> {
  return apiFetch<PrintNotificationRecipientsResponse>(
    '/api/v1/administrator/notifications/print-recipients',
  )
}

export async function savePrintNotificationRecipients(
  notify: Record<string, number | boolean>,
): Promise<{ message?: string }> {
  return apiFetch<{ message?: string }>(
    '/api/v1/administrator/notifications/print-recipients',
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ notify }),
    },
  )
}

export async function fetchFamilyAdminIndex(params?: {
  studentId?: number | null
  guardianId?: number | null
}): Promise<FamilyAdminIndexResponse> {
  const query = new URLSearchParams()
  if (params?.studentId) query.set('student_id', String(params.studentId))
  if (params?.guardianId) query.set('guardian_id', String(params.guardianId))
  const qs = query.size > 0 ? `?${query.toString()}` : ''
  return apiFetch<FamilyAdminIndexResponse>(`/api/v1/family-admin${qs}`)
}

/** Optional JSON search (`family/search` parity). Falls back to client-side lists when unavailable. */
export async function fetchFamilySearchSuggestions(q: string): Promise<FamilySearchResponse> {
  const query = q.trim()
  if (!query) return { items: [] }
  try {
    return await apiFetch<FamilySearchResponse>(
      `/api/v1/family-admin/search?q=${encodeURIComponent(query)}`,
    )
  } catch {
    return { items: [] }
  }
}

export async function sendFamilyComposeEmail(payload: {
  to: string
  subject: string
  html: string
  return_url?: string
}): Promise<{ ok?: boolean; message?: string }> {
  return apiFetch(`/api/v1/family-admin/compose-email/send`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

/** GET metadata for legacy family import (`routes/api.php` + JWT Bearer). */
export async function fetchFamilyLegacyImportMeta(): Promise<FamilyLegacyImportMetaResponse> {
  const raw = await apiFetch<
    FamilyLegacyImportMetaResponse | ApiEnvelope<FamilyLegacyImportMetaResponse>
  >('/api/v1/families/import-legacy')
  if (raw && typeof raw === 'object' && 'data' in raw && (raw as ApiEnvelope<FamilyLegacyImportMetaResponse>).data) {
    return (raw as ApiEnvelope<FamilyLegacyImportMetaResponse>).data as FamilyLegacyImportMetaResponse
  }
  return raw as FamilyLegacyImportMetaResponse
}

/**
 * POST multipart legacy family import. Typical Laravel field: `file` (UploadedFile).
 * Append optional `dry_run` = `1` when supported.
 */
export async function submitFamilyLegacyImport(formData: FormData): Promise<FamilyLegacyImportResult> {
  const raw = await fetchMultipartJson<
    FamilyLegacyImportResult | ApiEnvelope<FamilyLegacyImportResult>
  >('/api/v1/families/import-legacy', formData)
  if (
    raw &&
    typeof raw === 'object' &&
    'data' in raw &&
    (raw as ApiEnvelope<FamilyLegacyImportResult>).data != null
  ) {
    return (raw as ApiEnvelope<FamilyLegacyImportResult>).data as FamilyLegacyImportResult
  }
  return raw as FamilyLegacyImportResult
}

export async function importLegacySecondParents(): Promise<{
  message?: string
  data?: {
    ok?: boolean
    message?: string
    created_users?: number
    guardians_linked?: number
    skipped?: number
    total_source?: number
  } | null
}> {
  return apiFetch('/api/v1/families/import-legacy-second-parents', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({}),
  })
}

export async function fetchPaypalTransactions(params?: {
  q?: string
  perPage?: number
}): Promise<PaypalTransactionsResponse> {
  const query = new URLSearchParams()
  if (params?.q) query.set('q', params.q)
  if (params?.perPage) query.set('per_page', String(params.perPage))
  const qs = query.size > 0 ? `?${query.toString()}` : ''
  return apiFetch<PaypalTransactionsResponse>(`/api/v1/paypal-transactions${qs}`)
}

export async function fetchPaypalTransactionsCsv(params?: { q?: string }): Promise<Blob> {
  const headers = new Headers({ Accept: 'text/csv' })
  const token = getStoredToken()
  if (token) headers.set('Authorization', `Bearer ${token}`)
  const query = new URLSearchParams()
  if (params?.q) query.set('q', params.q)
  const suffix = query.size > 0 ? `?${query.toString()}` : ''
  const res = await fetch(apiUrl(`/api/v1/paypal-transactions/csv${suffix}`), { headers })
  if (!res.ok) throw new Error(`HTTP ${res.status}`)
  return res.blob()
}

export async function fetchNavMenu(): Promise<NavMenuResponse> {
  return apiFetch<NavMenuResponse>('/api/v1/nav-builder/menu')
}

export async function fetchNavBuilderData(): Promise<NavBuilderDataResponse> {
  return apiFetch<NavBuilderDataResponse>('/api/v1/nav-builder/data')
}

export async function saveNavBuilderItem(payload: {
  id?: number | null
  menu_parent_id?: number | null
  label: string
  url?: string | null
  icon_class?: string | null
  target?: string | null
  sort_order?: number
  is_enabled?: boolean
  roles?: number[]
}): Promise<NavBuilderSaveResponse> {
  return apiFetch<NavBuilderSaveResponse>('/api/v1/nav-builder', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function deleteNavBuilderItem(id: number): Promise<NavBuilderDeleteResponse> {
  return apiFetch<NavBuilderDeleteResponse>(`/api/v1/nav-builder/${id}`, {
    method: 'DELETE',
  })
}

export async function reorderNavBuilderItems(orders: Record<string, number>): Promise<ApiEnvelope<{ ok: true }>> {
  return apiFetch<ApiEnvelope<{ ok: true }>>('/api/v1/nav-builder/reorder', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ orders }),
  })
}

export async function fetchParentAttendance(
  schoolYear: string | null,
): Promise<ParentAttendanceResponse> {
  const q = schoolYear ? `?school_year=${encodeURIComponent(schoolYear)}` : ''
  return apiFetch<ParentAttendanceResponse>(`/api/v1/parents/attendance${q}`)
}

export async function fetchParentProfile(): Promise<ParentProfileResponse> {
  return apiFetch<ParentProfileResponse>('/api/v1/parents/profile')
}

export async function fetchParentEnrollments(
  schoolYear: string | null,
): Promise<ParentEnrollmentsResponse> {
  const q = schoolYear ? `?school_year=${encodeURIComponent(schoolYear)}` : ''
  return apiFetch<ParentEnrollmentsResponse>(`/api/v1/parents/enrollments${q}`)
}

export async function fetchParentInvoices(
  schoolYear: string | null,
): Promise<ParentInvoicesResponse> {
  const q = schoolYear ? `?school_year=${encodeURIComponent(schoolYear)}` : ''
  return apiFetch<ParentInvoicesResponse>(`/api/v1/parents/invoices${q}`)
}

export async function fetchParentEventsOverview(): Promise<ParentEventsOverviewResponse> {
  return apiFetch<ParentEventsOverviewResponse>('/api/v1/parents/events')
}

export async function fetchParentRegistrationOverview(): Promise<ParentRegistrationOverviewResponse> {
  return apiFetch<ParentRegistrationOverviewResponse>('/api/v1/parents/registration')
}

export async function fetchAttendanceReportForm(): Promise<ParentAttendanceReportFormResponse> {
  return apiFetch<ParentAttendanceReportFormResponse>(
    '/api/v1/parents/attendance-reports/form',
  )
}

export async function submitContactMessage(payload: {
  name: string
  email: string
  subject: string
  message: string
}): Promise<ContactSubmitResponse> {
  return apiFetch<ContactSubmitResponse>('/api/v1/contact', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function fetchParentEmergencyContacts(): Promise<ParentEmergencyContactsResponse> {
  return apiFetch<ParentEmergencyContactsResponse>('/api/v1/parents/emergency-contacts')
}

export async function fetchParentAttendanceReportsList(
  query?: Record<string, string>,
): Promise<ParentAttendanceReportsListResponse> {
  const q =
    query && Object.keys(query).length > 0
      ? `?${new URLSearchParams(query).toString()}`
      : ''
  return apiFetch<ParentAttendanceReportsListResponse>(
    `/api/v1/parents/attendance-reports${q}`,
  )
}

export async function fetchParentAuthorizedUsers(): Promise<ParentAuthorizedUsersApiResponse> {
  return apiFetch<ParentAuthorizedUsersApiResponse>('/api/v1/parents/authorized-users')
}

export async function inviteParentAuthorizedUser(email: string): Promise<ContactSubmitResponse> {
  return apiFetch<ContactSubmitResponse>('/api/v1/parents/authorized-users', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email }),
  })
}

export async function fetchParentMessagesInbox(): Promise<ParentMessagesResponse> {
  return apiFetch<ParentMessagesResponse>('/api/v1/messages/inbox')
}

export async function fetchSchoolCalendarEvents(): Promise<SchoolCalendarEventsResponse> {
  return apiFetch<SchoolCalendarEventsResponse>(
    '/api/v1/settings/school-calendar/events?audience=parent',
  )
}

export async function fetchStudentScores(studentId: number): Promise<StudentScoresResponse> {
  return apiFetch<StudentScoresResponse>(`/api/v1/students/${studentId}/scores`)
}

export async function fetchReportCardMeta(): Promise<ReportCardMetaResponse> {
  return apiFetch<ReportCardMetaResponse>('/api/v1/reports/report-cards/meta')
}

export async function fetchReportCardAcknowledgement(
  studentId: number,
  schoolYear?: string | null,
  semester?: string | null,
): Promise<ReportCardAcknowledgementResponse> {
  const query = new URLSearchParams({ student_id: String(studentId) })
  if (schoolYear) query.set('school_year', schoolYear)
  if (semester) query.set('semester', semester)
  return apiFetch<ReportCardAcknowledgementResponse>(
    `/api/v1/reports/report-cards/acknowledgement?${query.toString()}`,
  )
}

export async function fetchClassProgressGroups(params?: {
  classSectionId?: number | null
  status?: string | null
  weekStart?: string | null
  weekEnd?: string | null
  subject?: string | null
  page?: number
  perPage?: number
}): Promise<ClassProgressGroupsResponse> {
  const query = new URLSearchParams()
  query.set('group_by_week', '1')
  if (params?.classSectionId) query.set('class_section_id', String(params.classSectionId))
  if (params?.status) query.set('status', params.status)
  if (params?.weekStart) query.set('week_start', params.weekStart)
  if (params?.weekEnd) query.set('week_end', params.weekEnd)
  if (params?.subject) query.set('subject', params.subject)
  if (params?.page) query.set('page', String(params.page))
  if (params?.perPage) query.set('per_page', String(params.perPage))
  return apiFetch<ClassProgressGroupsResponse>(`/api/v1/class-progress?${query.toString()}`)
}

export async function fetchClassProgressDetail(id: number): Promise<ClassProgressShowResponse> {
  return apiFetch<ClassProgressShowResponse>(`/api/v1/class-progress/${id}`)
}

export async function fetchClassProgressMeta(params?: {
  classId?: number | null
  sundayCount?: number | null
}): Promise<ClassProgressMetaResponse> {
  const query = new URLSearchParams()
  if (params?.classId) query.set('class_id', String(params.classId))
  if (params?.sundayCount) query.set('sunday_count', String(params.sundayCount))
  const qs = query.size > 0 ? `?${query.toString()}` : ''
  return apiFetch<ClassProgressMetaResponse>(`/api/v1/class-progress/meta${qs}`)
}

export async function fetchStudentScoreCard(studentId: number): Promise<StudentScoreCardResponse> {
  return apiFetch<StudentScoreCardResponse>(`/api/v1/students/${studentId}/score-card`)
}

/** Students the current user may open on the score-card list (CI `StudentController::scoreCardList`). */
export type ScoreCardSelectableStudent = {
  id: number
  school_id?: string | null
  firstname?: string | null
  lastname?: string | null
  /** When present, client may filter to the teacher’s active section (see `StudentScoreCardListPage`). */
  class_section_ids?: number[] | null
}

/**
 * Teachers should pass `class_section_id` + `school_year` so the list matches CI `scoreCardList`
 * (students in that section for the year, not the whole school).
 */
export async function fetchScoreCardSelectableStudents(params?: {
  classSectionId?: number | null
  schoolYear?: string | null
}): Promise<{ students: ScoreCardSelectableStudent[] }> {
  const q = new URLSearchParams()
  if (params?.classSectionId != null && params.classSectionId > 0) {
    q.set('class_section_id', String(params.classSectionId))
  }
  if (params?.schoolYear != null && String(params.schoolYear).trim() !== '') {
    q.set('school_year', String(params.schoolYear).trim())
  }
  const qs = q.size > 0 ? `?${q.toString()}` : ''
  return apiFetch<{ students: ScoreCardSelectableStudent[] }>(`/api/v1/students/score-card/selectable${qs}`)
}

export async function fetchAdministratorEmergencyContacts(params?: {
  parentId?: number | null
  parentIds?: number[]
}): Promise<EmergencyContactGroupsResponse> {
  const query = new URLSearchParams()
  if (params?.parentId) query.set('parent_id', String(params.parentId))
  for (const id of params?.parentIds ?? []) {
    query.append('parent_ids[]', String(id))
  }
  const qs = query.size > 0 ? `?${query.toString()}` : ''
  return apiFetch<EmergencyContactGroupsResponse>(`/api/v1/administrator/emergency-contacts${qs}`)
}

export async function fetchAdministratorEmergencyContact(
  contactId: number,
  parentId: number,
): Promise<EmergencyContactShowResponse> {
  return apiFetch<EmergencyContactShowResponse>(
    `/api/v1/administrator/emergency-contacts/${contactId}?parent_id=${encodeURIComponent(String(parentId))}`,
  )
}

export async function updateAdministratorEmergencyContact(
  contactId: number,
  payload: {
    parent_id: number
    name: string
    cellphone: string
    email?: string | null
    relation?: string | null
  },
): Promise<EmergencyContactShowResponse> {
  return apiFetch<EmergencyContactShowResponse>(`/api/v1/administrator/emergency-contacts/${contactId}`, {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function deleteAdministratorEmergencyContact(
  contactId: number,
  parentId: number,
): Promise<{ ok: true }> {
  return apiFetch<{ ok: true }>(
    `/api/v1/administrator/emergency-contacts/${contactId}?parent_id=${encodeURIComponent(String(parentId))}`,
    { method: 'DELETE' },
  )
}

export async function updateParentProfile(
  payload: Required<
    Pick<
      ParentProfileRecord,
      'firstname' | 'lastname' | 'cellphone' | 'address_street' | 'city' | 'state' | 'zip'
    >
  >,
): Promise<ParentProfileResponse> {
  return apiFetch<ParentProfileResponse>('/api/v1/parents/profile', {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function updateParentStudent(
  studentId: number,
  payload: Record<string, unknown>,
): Promise<{ ok: true }> {
  return apiFetch<{ ok: true }>(`/api/v1/parents/students/${studentId}`, {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function storeParentRegistration(payload: {
  students?: Record<string, unknown>[]
  emergency_contacts?: Record<string, unknown>[]
}): Promise<{ ok: true; created_student_ids?: number[] }> {
  return apiFetch<{ ok: true; created_student_ids?: number[] }>('/api/v1/parents/registration', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function storeParentEmergencyContact(payload: {
  name: string
  cellphone: string
  email?: string
  relation?: string
}): Promise<ParentEmergencyContactsResponse> {
  return apiFetch<ParentEmergencyContactsResponse>('/api/v1/parents/emergency-contacts', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function updateParentEmergencyContact(
  contactId: number,
  payload: { name: string; cellphone: string; email?: string; relation?: string },
): Promise<ParentEmergencyContactsResponse> {
  return apiFetch<ParentEmergencyContactsResponse>(`/api/v1/parents/emergency-contacts/${contactId}`, {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function submitParentAttendanceReport(payload: Record<string, unknown>): Promise<{
  ok: true
  inserted?: number
  blocked?: number
}> {
  return apiFetch('/api/v1/parents/attendance-reports', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function fetchReportCardPdf(studentId: number): Promise<Blob> {
  const headers = new Headers({ Accept: 'application/pdf' })
  const token = getStoredToken()
  if (token) headers.set('Authorization', `Bearer ${token}`)
  const res = await fetch(apiUrl(`/api/v1/reports/report-cards/students/${studentId}`), {
    headers,
  })
  if (!res.ok) throw new Error(`HTTP ${res.status}`)
  return res.blob()
}

function unwrapData<T extends object>(body: T & { data?: unknown }): T {
  if (body && typeof body === 'object' && 'data' in body && body.data && typeof body.data === 'object') {
    return body.data as T
  }
  return body
}

export async function fetchClassPrepIndex(params?: {
  school_year?: string
  semester?: string
}): Promise<ClassPrepIndexResponse> {
  const qs = new URLSearchParams()
  if (params?.school_year) qs.set('school_year', params.school_year)
  if (params?.semester) qs.set('semester', params.semester)
  const suffix = qs.toString() ? `?${qs}` : ''
  const body = await apiFetch<ClassPrepIndexResponse & { data?: ClassPrepIndexResponse }>(
    `/api/v1/class-prep${suffix}`,
  )
  return unwrapData(body)
}

export async function saveClassPrepAdjustment(payload: {
  class_section_id: number | string
  school_year: string
  semester?: string
  adjustments: Record<string, number>
}): Promise<{ ok?: boolean; message?: string }> {
  return apiFetch(`/api/v1/class-prep/adjustments`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function fetchClassPrepPrintPayload(params: {
  sectionId: string
  schoolYear: string
  semester?: string
}): Promise<ClassPrepPrintResponse> {
  const qs = new URLSearchParams()
  if (params.semester) qs.set('semester', params.semester)
  const suffix = qs.toString() ? `?${qs}` : ''
  const body = await apiFetch<ClassPrepPrintResponse & { data?: ClassPrepPrintResponse }>(
    `/api/v1/class-prep/sections/${encodeURIComponent(params.sectionId)}/print/${encodeURIComponent(params.schoolYear)}${suffix}`,
  )
  return unwrapData(body)
}

export async function fetchCommunicationsCompose(): Promise<CommunicationsComposeResponse> {
  const body = await apiFetch<CommunicationsComposeResponse & { data?: CommunicationsComposeResponse }>(
    '/api/v1/communications/compose',
  )
  return unwrapData(body)
}

export async function fetchStudentFamilies(studentId: number): Promise<{ data: FamilyListItem[] }> {
  return apiFetch(`/api/v1/students/${studentId}/families`)
}

export async function fetchFamilyGuardians(familyId: number): Promise<{ data: GuardianListItem[] }> {
  return apiFetch(`/api/v1/families/${familyId}/guardians`)
}

export async function previewCommunicationsEmail(payload: {
  student_id: number
  family_id: number
  template_key: string
  vars?: Record<string, string>
}): Promise<CommunicationsPreviewResponse> {
  return apiFetch(`/api/v1/communications/preview`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function sendCommunicationsEmail(payload: {
  student_id: number
  family_id: number
  template_key: string
  subject: string
  body: string
  recipients: string[]
  cc?: string[]
  bcc?: string[]
}): Promise<{ ok?: boolean; message?: string }> {
  return apiFetch(`/api/v1/communications/send`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function fetchConfigurations(): Promise<ConfigurationListResponse> {
  const body = await apiFetch<ConfigurationListResponse & { data?: ConfigurationListResponse }>(
    '/api/v1/configuration',
  )
  return unwrapData(body)
}

export async function createConfigurationEntry(payload: {
  config_key: string
  config_value: string
}): Promise<{ ok?: boolean }> {
  return apiFetch(`/api/v1/configuration`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function updateConfigurationEntry(
  id: number | string,
  payload: { config_key: string; config_value: string },
): Promise<{ ok?: boolean }> {
  return apiFetch(`/api/v1/configuration/${encodeURIComponent(String(id))}`, {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function deleteConfigurationEntry(id: number | string): Promise<{ ok?: boolean }> {
  return apiFetch(`/api/v1/configuration/${encodeURIComponent(String(id))}`, {
    method: 'DELETE',
  })
}

export async function fetchDiscountsList(params?: {
  school_year?: string
  semester?: string
}): Promise<DiscountsListResponse> {
  const qs = new URLSearchParams()
  if (params?.school_year) qs.set('school_year', params.school_year)
  if (params?.semester) qs.set('semester', params.semester)
  const suffix = qs.toString() ? `?${qs}` : ''
  const body = await apiFetch<DiscountsListResponse & { data?: DiscountsListResponse }>(
    `/api/v1/discounts${suffix}`,
  )
  return unwrapData(body)
}

export async function fetchDiscountApplyContext(params?: {
  school_year?: string
  semester?: string
}): Promise<DiscountApplyContextResponse> {
  const qs = new URLSearchParams()
  if (params?.school_year) qs.set('school_year', params.school_year)
  if (params?.semester) qs.set('semester', params.semester)
  const suffix = qs.toString() ? `?${qs}` : ''
  try {
    const body = await apiFetch<DiscountApplyContextResponse & { data?: DiscountApplyContextResponse }>(
      `/api/v1/discounts/options${suffix}`,
    )
    return enrichDiscountApplyContext(unwrapData(body))
  } catch (err: unknown) {
    if (!(err instanceof ApiHttpError) || err.status !== 404) throw err
  }

  const body = await apiFetch<DiscountApplyContextResponse & { data?: DiscountApplyContextResponse }>(
    `/api/v1/discounts/apply-context${suffix}`,
  )
  return enrichDiscountApplyContext(unwrapData(body))
}

export async function createDiscountVoucher(payload: Record<string, unknown>): Promise<{ ok?: boolean; message?: string }> {
  return apiFetch(`/api/v1/discounts`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function applyDiscountVoucher(payload: {
  voucher_id: number | string
  parent_ids: (number | string)[]
  allow_additional?: boolean
  school_year?: string
  semester?: string
}): Promise<{ ok?: boolean; message?: string }> {
  return apiFetch(`/api/v1/discounts/apply`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function reverseDiscountApplication(payload: {
  voucher_id?: number | string
  parent_ids: (number | string)[]
  reason?: string
  school_year?: string
  semester?: string
}): Promise<{ ok?: boolean; message?: string }> {
  return apiFetch(`/api/v1/discounts/reverse`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

function normalizeEnrollmentWithdrawalResponse(
  d: EnrollmentWithdrawalPageResponse,
): {
  students: EnrollmentWithdrawalPageResponse['students']
  classes: EnrollmentWithdrawalPageResponse['classes']
  schoolYears: string[]
  selectedYear: string
  currentYear: string
  semester: string
  missingYear: boolean
  isCurrentYear: boolean
} {
  const selectedYear = String(d.selectedYear ?? d.selected_year ?? '')
  const currentYear = String(d.currentYear ?? d.current_year ?? '')
  const semester = String(d.semester ?? '')
  const missingYear = Boolean(d.missingYear ?? d.missing_year)
  let isCurrentYear = d.isCurrentYear ?? d.is_current_year
  if (isCurrentYear === undefined)
    isCurrentYear = selectedYear !== '' && currentYear !== '' && selectedYear === currentYear
  const schoolYearsRaw = d.schoolYears ?? d.school_years
  const schoolYears = Array.isArray(schoolYearsRaw) ? schoolYearsRaw.map((y) => String(y)) : []
  return {
    students: d.students ?? [],
    classes: d.classes ?? [],
    schoolYears,
    selectedYear,
    currentYear,
    semester,
    missingYear,
    isCurrentYear: Boolean(isCurrentYear),
  }
}

/** Admin — registered students (legacy new-students). */
export async function fetchRegisteredNewStudents(params?: {
  school_year?: string
  semester?: string
}): Promise<{
  new_students: RegisteredNewStudentsResponse['new_students']
  total_new?: number
  school_years?: string[]
}> {
  const qs = new URLSearchParams()
  if (params?.school_year) qs.set('school_year', params.school_year)
  if (params?.semester) qs.set('semester', params.semester)
  const suffix = qs.toString() ? `?${qs}` : ''
  const body = await apiFetch<
    RegisteredNewStudentsResponse & { data?: RegisteredNewStudentsResponse }
  >(`/api/v1/administrator/enroll-withdrawal/new-students${suffix}`)
  const d = unwrapData(body)
  return {
    new_students: d.new_students ?? [],
    total_new: d.total_new,
    school_years: d.school_years,
  }
}

/** Admin — enrollment & withdrawal grid (legacy enrollment_withdrawal). */
export async function fetchEnrollmentWithdrawalPage(params?: {
  schoolYear?: string
  semester?: string
}): Promise<ReturnType<typeof normalizeEnrollmentWithdrawalResponse>> {
  const qs = new URLSearchParams()
  if (params?.schoolYear) qs.set('schoolYear', params.schoolYear)
  if (params?.semester) qs.set('semester', params.semester)
  const suffix = qs.toString() ? `?${qs}` : ''
  const body = await apiFetch<
    EnrollmentWithdrawalPageResponse & { data?: EnrollmentWithdrawalPageResponse }
  >(`/api/v1/administrator/enroll-withdrawal${suffix}`)
  const d = unwrapData(body)
  return normalizeEnrollmentWithdrawalResponse(d)
}

export async function postEnrollmentWithdrawalAssignClass(payload: {
  student_id: number
  parent_id: number
  class_section_id: number | string
}): Promise<{ ok?: boolean; message?: string }> {
  return apiFetch(`/api/v1/administrator/enroll-withdrawal`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

export async function postEnrollmentWithdrawalStatusBatch(payload: {
  updates: { student_id: number; enrollment_status: string }[]
  school_year?: string
  semester?: string
}): Promise<{ ok?: boolean; message?: string }> {
  return apiFetch(`/api/v1/administrator/enroll-withdrawal/update-statuses`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      enrollment_status: payload.updates.map((row) => row.enrollment_status),
    }),
  })
}

export async function postGenerateInvoiceForParent(parentId: number): Promise<unknown> {
  return apiFetch(`/api/v1/administrator/invoices/generate-for-parent`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ parent_id: parentId }),
  })
}

export async function fetchExpensesList(params?: {
  school_year?: string
  semester?: string
}): Promise<ExpensesListResponse> {
  const qs = new URLSearchParams()
  if (params?.school_year) qs.set('school_year', params.school_year)
  if (params?.semester) qs.set('semester', params.semester)
  const suffix = qs.toString() ? `?${qs}` : ''
  const body = await apiFetch<ExpensesListResponse & { data?: ExpensesListResponse }>(
    `/api/v1/administrator/expenses${suffix}`,
  )
  const d = unwrapData(body as ExpensesListResponse & { data?: ExpensesListResponse })
  return {
    expenses: d.expenses ?? [],
    school_year: d.school_year,
    semester: d.semester,
    school_years: d.school_years,
  }
}

function normalizeExpenseFormOptions(
  body: Partial<ExpenseCreateFormResponse> & {
    retailors?: string[]
    staff?: ExpenseUserOption[]
  },
): ExpenseCreateFormResponse {
  return {
    retailors: body.retailors ?? [],
    users: body.users ?? body.staff ?? [],
  }
}

export async function fetchExpenseCreateForm(): Promise<ExpenseCreateFormResponse> {
  const body = await apiFetch<ExpenseCreateFormResponse & { data?: ExpenseCreateFormResponse }>(
    '/api/v1/administrator/expenses/options',
  )
  return normalizeExpenseFormOptions(
    unwrapData(
      body as ExpenseCreateFormResponse & {
        data?: ExpenseCreateFormResponse
        retailors?: string[]
        staff?: ExpenseUserOption[]
      },
    ),
  )
}

export async function createExpense(
  formData: FormData,
): Promise<{ ok?: boolean; message?: string }> {
  return fetchMultipartJson('/api/v1/administrator/expenses', formData)
}

export async function fetchExpenseEdit(expenseId: number): Promise<ExpenseEditFormResponse> {
  const [expenseBody, optionsBody] = await Promise.all([
    apiFetch<{ expense?: ExpenseDetail; data?: { expense?: ExpenseDetail } }>(
      `/api/v1/administrator/expenses/${expenseId}`,
    ),
    apiFetch<ExpenseCreateFormResponse & { data?: ExpenseCreateFormResponse }>(
      '/api/v1/administrator/expenses/options',
    ),
  ])
  const expenseData = unwrapData(
    expenseBody as { expense?: ExpenseDetail; data?: { expense?: ExpenseDetail } },
  )
  const optionsData = normalizeExpenseFormOptions(
    unwrapData(
      optionsBody as ExpenseCreateFormResponse & {
        data?: ExpenseCreateFormResponse
        retailors?: string[]
        staff?: ExpenseUserOption[]
      },
    ),
  )
  return {
    expense: expenseData.expense ?? (expenseData as ExpenseDetail),
    retailors: optionsData.retailors,
    users: optionsData.users,
    receipt_url: expenseData.expense?.receipt_url ?? null,
  }
}

export async function updateExpense(
  expenseId: number,
  formData: FormData,
): Promise<{ ok?: boolean; message?: string }> {
  const payload = new FormData()
  formData.forEach((value, key) => {
    payload.append(key, value)
  })
  payload.set('_method', 'PUT')
  return fetchMultipartJson(`/api/v1/administrator/expenses/${expenseId}`, payload)
}

export async function updateExpenseStatus(payload: {
  id: number
  status: 'approved' | 'denied'
}): Promise<{ success?: boolean; error?: string }> {
  return apiFetch(`/api/v1/administrator/expenses/${payload.id}/status`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ status: payload.status }),
  })
}
