<script setup lang="ts">
import {
  ClassroomComplianceAPIClient,
  getDefaultEntry,
  type EntriesPayload,
  type EntriesPayloadStudent,
  type EntriesResponseStudent,
} from '@/api/classroom_compliance'
import { useAuthStore } from '@/stores/auth'
import { computed, onMounted, ref, watch, type Ref } from 'vue'
import EntryTableCell from '@/apps/classroom_compliance/entries_table/EntryTableCell.vue'
import StudentScoreCell from '@/apps/classroom_compliance/entries_table/StudentScoreCell.vue'
import DateHeaderCell from '@/apps/classroom_compliance/entries_table/DateHeaderCell.vue'
import StudentNameCell from '@/apps/classroom_compliance/entries_table/StudentNameCell.vue'

const authStore = useAuthStore()
const props = defineProps<{
  classId: number
}>()
const apiClient = new ClassroomComplianceAPIClient(authStore)

enum TableView {
  FULL = "Full",
  GRADING = "Grading",
  WHITEBOARD = "Whiteboard"
}

const students: Ref<EntriesResponseStudent[]> = ref([])
const sortingChoice: Ref<string> = ref('name')
const selectedView: Ref<TableView> = ref(TableView.FULL)

const lastSaveState: Ref<string | null> = ref(null)
const lastSaveStateTimestamp: Ref<number> = ref(0)

const dates: Ref<string[]> = ref([])
const toDate: Ref<Date> = ref(new Date())
const fromDate: Ref<Date> = ref(new Date())

const entriesChangedSinceLastSave = computed(() => {
  const studentsClone: EntriesResponseStudent[] = JSON.parse(JSON.stringify(students.value))
  studentsClone.sort(sortEntriesByName)
  return lastSaveState.value === null || lastSaveState.value !== JSON.stringify(studentsClone)
})
const assignedDesks = computed(() => {
  return students.value.length > 0 && students.value.some(s => s.deskNumber > 0)
})

onMounted(async () => {
  showThisWeek()
  watch(sortingChoice, () => {
    if (sortingChoice.value === 'name') {
      students.value.sort(sortEntriesByName)
    } else if (sortingChoice.value === 'desk') {
      students.value.sort(sortEntriesByDeskNumber)
    }
    saveSortPreference()
  })
  attemptSortByStoredPreference()
})

async function loadEntries() {
  const entries = await apiClient
    .getEntries(props.classId, fromDate.value, toDate.value)
    .handleErrorsWithAlert()
  if (entries) {
    lastSaveState.value = JSON.stringify(entries.students)
    lastSaveStateTimestamp.value = Date.now()
    if (sortingChoice.value === 'name') {
      entries.students.sort(sortEntriesByName)
    } else if (sortingChoice.value === 'desk') {
      entries.students.sort(sortEntriesByDeskNumber)
    }
    students.value = entries.students
    dates.value = entries.dates
  } else {
    students.value = []
    lastSaveState.value = null
    lastSaveStateTimestamp.value = Date.now()
    dates.value = []
  }
}

function sortEntriesByName(a: EntriesResponseStudent, b: EntriesResponseStudent): number {
  if (a.name < b.name) return -1
  if (a.name > b.name) return 1
  return 0
}

function sortEntriesByDeskNumber(a: EntriesResponseStudent, b: EntriesResponseStudent): number {
  if (a.deskNumber < b.deskNumber) return -1
  if (a.deskNumber > b.deskNumber) return 1
  return sortEntriesByName(a, b)
}

function attemptSortByStoredPreference() {
  const storedPreferences = localStorage.getItem(`entries-table.${props.classId}.preferences`)
  if (storedPreferences !== null) {
    const preferences: Record<string, string> = JSON.parse(storedPreferences)
    if ('sort' in preferences) {
      sortingChoice.value = preferences['sort']
    }
  }
}

function saveSortPreference() {
  const preferences = {
    'sort': sortingChoice.value
  }
  localStorage.setItem(`entries-table.${props.classId}.preferences`, JSON.stringify(preferences))
}

function shiftDateRange(days: number) {
  toDate.value.setDate(toDate.value.getDate() + days)
  fromDate.value.setDate(fromDate.value.getDate() + days)
}

async function showPreviousWeek() {
  shiftDateRange(-7)
  await loadEntries()
}

async function showThisWeek() {
  const today = new Date()
  today.setHours(0, 0, 0, 0)
  // First set the to-date to the next upcoming end-of-week (Friday).
  toDate.value = new Date()
  toDate.value.setHours(0, 0, 0, 0)
  if (today.getDay() >= 1 && today.getDay() <= 5) {
    // If we're anywhere in the week, shift up to Friday.
    const dayDiff = 5 - today.getDay()
    toDate.value.setDate(today.getDate() + dayDiff)
  } else {
    // If it's Saturday or Sunday, shift back to the previous Friday.
    if (today.getDay() === 6) {
      toDate.value.setDate(today.getDate() - 1)
    } else {
      toDate.value.setDate(today.getDate() - 2)
    }
  }

  // Then set the from-date to the Monday of that week.
  fromDate.value = new Date()
  fromDate.value.setHours(0, 0, 0, 0)
  fromDate.value.setDate(toDate.value.getDate() - 4)
  await loadEntries()
}

async function showNextWeek() {
  shiftDateRange(7)
  await loadEntries()
}

async function saveEdits() {
  if (!lastSaveState.value) {
    console.warn('No lastSaveState, cannot determine what edits were made.')
    await loadEntries()
    return
  }

  const payload: EntriesPayload = { students: [] }
  // Get a list of edits which have changed.
  const lastSaveStateObj: EntriesResponseStudent[] = JSON.parse(lastSaveState.value)
  for (let i = 0; i < students.value.length; i++) {
    const student: EntriesResponseStudent = students.value[i]
    const studentPayload: EntriesPayloadStudent = { id: student.id, entries: {} }
    for (const [dateStr, entry] of Object.entries(student.entries)) {
      const lastSaveStateEntry = lastSaveStateObj[i].entries[dateStr]
      if (JSON.stringify(lastSaveStateEntry) !== JSON.stringify(entry)) {
        studentPayload.entries[dateStr] = JSON.parse(JSON.stringify(entry))
      }
    }
    if (Object.keys(studentPayload.entries).length > 0) {
      payload.students.push(studentPayload)
    }
  }
  await apiClient.saveEntries(props.classId, payload).handleErrorsWithAlert()
  await loadEntries()
}

async function discardEdits() {
  if (lastSaveState.value) {
    students.value = JSON.parse(lastSaveState.value)
  } else {
    await loadEntries()
  }
}

function addAllEntriesForDate(dateStr: string) {
  for (let i = 0; i < students.value.length; i++) {
    const student = students.value[i]
    if (student.removed) continue
    if (!(dateStr in student.entries) || student.entries[dateStr] === null) {
      student.entries[dateStr] = getDefaultEntry(dateStr)
    }
  }
}
</script>
<template>
  <div>
    <div class="button-bar">
      <button type="button" @click="showPreviousWeek">Previous Week</button>
      <button type="button" @click="showThisWeek">This Week</button>
      <button type="button" @click="showNextWeek">Next Week</button>
      <button type="button" @click="saveEdits" :disabled="!entriesChangedSinceLastSave">
        Save
      </button>
      <button type="button" @click="discardEdits" :disabled="!entriesChangedSinceLastSave">
        Discard Edits
      </button>

      <select style="margin-left: 0.5em;" v-model="sortingChoice">
        <option value="name">Sort by Name</option>
        <option value="desk">Sort by Desk</option>
      </select>

      <select v-model="selectedView">
        <option :value="TableView.FULL">Full View</option>
        <option :value="TableView.GRADING">Grading View</option>
        <option :value="TableView.WHITEBOARD">Whiteboard View</option>
      </select>
    </div>

    <table class="entries-table" :class="{ 'entries-table-reduced-view': selectedView !== TableView.FULL }">
      <thead>
        <tr>
          <th>Student</th>
          <th v-if="assignedDesks">Desk</th>
          <DateHeaderCell v-for="date in selectedView === TableView.FULL ? dates : []" :key="date" :date-str="date"
            @add-all-entries-clicked="addAllEntriesForDate(date)" />
          <th v-if="selectedView !== TableView.WHITEBOARD">Score</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="student in students" :key="student.id">
          <StudentNameCell :student="student" :class-id="classId" />
          <!-- Desk Number: -->
          <td v-if="assignedDesks" v-text="student.deskNumber"></td>
          <!-- A cell for each entry in the table's date range: -->
          <EntryTableCell v-for="(entry, date) in selectedView === TableView.FULL ? student.entries : []" :key="date"
            v-model="student.entries[date]" :date-str="date" :last-save-state-timestamp="lastSaveStateTimestamp" />
          <!-- Score cell: -->
          <StudentScoreCell :score="student.score" v-if="selectedView !== TableView.WHITEBOARD" />
        </tr>
      </tbody>
    </table>
  </div>
</template>
<style scoped>
.entries-table {
  margin-top: 0.5em;
  margin-bottom: 1em;
  width: 100%;
}

.entries-table-reduced-view {
  width: auto;
  max-width: 100%;
}

.entries-table,
.entries-table th,
.entries-table td {
  border: 1px solid black;
  border-collapse: collapse;
}
</style>
