<script lang="ts">
import { ref, computed, watch, shallowRef, markRaw } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { Date, DateTime, time, timedelta, add, cmp } from '@silane/datetime';

import { keys as injectionKeys, inject } from '../../../../injection';
import CopyEventsByDayOfWeekDialog from './index/_CopyEventsByDayOfWeekDialog.vue';
import UpdateEventsDialog from './index/_UpdateEventsDialog.vue';
import type { Event } from '../../../../composables/eventstore';
import { datetimeRange } from '../../../../common/utils.js';
import { getResourceDisplayText } from '../../../../common/zkan-utils.js';


function datetimeRangeToDateRange(range: [DateTime, DateTime]): [Date, Date] {
  if(cmp(range[1].time(), time()) === 0) {
    return [range[0].date(), range[1].date()];
  } else {
    return [range[0].date(), add(range[1].date(), timedelta({ days: 1 }))];
  }
}

function normalizeVueRouterQuery(query: ReturnType<typeof useRoute>['query']) {
  return Object.fromEntries(Object.entries(query).map(
    ([k, v]) => [k, Array.isArray(v) ? v : [v]]
  ));
}

export default {
  name: 'PageMasterTimetableEventsIndex',
  components: { CopyEventsByDayOfWeekDialog, UpdateEventsDialog },
  setup() {
    const today = Date.today();
    const route = useRoute();
    const router = useRouter();

    const handleError = inject(injectionKeys.errorHandler).handleErrorAsync;
    const colorLabelStore = inject(injectionKeys.colorLabelStore);
    const resourceStore = inject(injectionKeys.resourceStore);
    const eventStore = inject(injectionKeys.eventStore);
    const generalTimetableScope = inject(injectionKeys.generalTimetableScope);
    const links = inject(injectionKeys.links, null);

    const eventLinkFn = links?.eventLinkFn ?? (() => '');
    const eventChangeLinkFn = links?.eventChangeLinkFn ?? (() => '');

    const initialFilter = JSON.parse(
      normalizeVueRouterQuery(route.query).filter?.[0] ?? '{}'
    );

    const filterChecked = ref<boolean | null>(null);
    const filterEventChange = ref(initialFilter.eventChange ?? [
      'normal', 'cancelled', 'changeFrom', 'changeTo'
    ]);
    const filterType = ref(initialFilter.type ?? ['standard', 'lesson']);
    const filterTeacher = ref<string | null>(initialFilter.teacher ?? null);
    const filterStudent = ref<string | null>(initialFilter.student ?? null);
    const filterDateRange = ref(initialFilter.dateRange?.map(
      x => Date.fromISOFormat(x)
    ) ?? [markRaw(today), markRaw(add(today, timedelta({ days: 1 })))]);
    const filterDayofweek = ref(new Set(
      initialFilter.dayofweek ?? [0, 1, 2, 3, 4, 5, 6]
    ));
    const filterColor = ref<string | null>(initialFilter.color ?? null);
    const filterSubject = ref<string | null>(initialFilter.subject ?? null);
    const filterFrame = ref<string | null>(initialFilter.frame ?? null);

    const filterQueryString = computed(() => JSON.stringify({
      eventChange: filterEventChange.value, type: filterType.value,
      teacher: filterTeacher.value, student: filterStudent.value,
      dateRange: filterDateRange.value.map(x => x.isoFormat()),
      dayofweek: [...filterDayofweek.value], color: filterColor.value,
      subject: filterSubject.value, frame: filterFrame.value,
    }));

    watch(filterQueryString, () => {
      const qs = new URLSearchParams({
        filter: filterQueryString.value,
      });
      router.replace(`?${qs.toString()}`);
    });

    const downloadedEvents = shallowRef<Event[]>([]);
    const checkedEvents = ref(new Set<string>());

    const load = handleError(async () => {
      downloadedEvents.value = (await eventStore.query({
        startTime: DateTime.combine(
          generalTimetableScope.dateRange.value[0], time(0)
        ),
        endTime: DateTime.combine(
          generalTimetableScope.dateRange.value[1], time(0)
        ),
      })).sort((a, b) => cmp(a.startTime, b.startTime));
      checkedEvents.value.clear();
      filterDateRange.value = [
        markRaw(generalTimetableScope.dateRange.value[0]),
        markRaw(generalTimetableScope.dateRange.value[1]),
      ];
    });
    watch(generalTimetableScope.dateRange, load, { immediate: true });

    const filteredExceptDateEvents = computed(() => downloadedEvents.value.filter(
      x => (x.resources.map(x => x.id).includes(
        generalTimetableScope.classroom.value
      )) && (
        filterChecked.value == null ||
        checkedEvents.value.has(x.id) === filterChecked.value
      ) && (
        filterEventChange.value.some(tag => (tag === 'normal' && (
          x.eventChangeTo == null && x.eventChangeFrom == null
        )) || (tag === 'cancelled' && (
          x.eventChangeTo && x.eventChangeTo.toEvent == null
        )) || (tag === 'changeFrom' && x.eventChangeTo?.toEvent != null) || (
          tag === 'changeTo' && x.eventChangeFrom != null
        ))
      ) && filterType.value.includes(x.type) && (
        filterTeacher.value == null ||
        x.resources.map(x => x.id).includes(filterTeacher.value)
      ) && (
        filterStudent.value == null ||
        x.resources.map(x => x.id).includes(filterStudent.value)
      ) && (
        filterColor.value == null || x.color === filterColor.value
      ) && (filterSubject.value == null || (
        x.type === 'lesson' && x.subject === filterSubject.value
      )) && (filterFrame.value == null || (
        x.type === 'lesson' && x.frame === filterFrame.value
      ))
    ));
    const filteredEvents = computed(
      () => filteredExceptDateEvents.value.filter(x => (
        cmp(
          DateTime.combine(filterDateRange.value[0], time()), x.endTime
        ) < 0 && cmp(
          x.startTime, DateTime.combine(filterDateRange.value[1], time())
        ) < 0
      )).filter(x => filterDayofweek.value.has(x.startTime.weekday()))
    );

    const shownEventCount = ref(30);
    watch(filteredEvents, () => {
      shownEventCount.value = 30;
    }, { immediate: false });

    const eventDates = computed(() => [...new Set(
      filteredExceptDateEvents.value.flatMap(x => [...datetimeRange(
        ...datetimeRangeToDateRange([x.startTime, x.endTime]),
        timedelta({ days: 1 })
      )]).map(x => x.toOrdinal())
    )].map(x => Date.fromOrdinal(x)));
    const filterOptionsResources = computed(() => {
      const ids = new Set(
        filteredEvents.value.flatMap(x => x.resources).map(x => x.id)
      );
      return resourceStore.resources.value.filter(x => ids.has(x.id));
    });
    const filterOptionsColors = computed(() => [...new Set(
      filteredEvents.value.map(x => x.color)
    )].sort());
    const filterOptionsSubjects = computed(() => [...new Set(
      filteredEvents.value.map(
        x => x.type === 'lesson' ? x.subject : null
      ).filter((x): x is string => x != null)
    )].sort());
    const filterOptionsFrames = computed(() => [...new Set(
      filteredEvents.value.map(
        x => x.type === 'lesson' ? x.frame : null
      ).filter((x): x is string => x != null)
    )].sort());

    const shownDialog = ref<
      '' | 'deleteEvents' | 'updateEvents' | 'copyEventsByDayOfWeek'
    >('');

    const deleteEvents = handleError(async () => {
      await eventStore.mutate([...checkedEvents.value].map(x => ({
        opcode: 'remove_event', param: { id: x }
      })));
    });

    function dialogActionRun() {
      shownDialog.value = '';
      load();
    }

    return {
      generalTimetableScope, colorLabels: colorLabelStore.colorLabels,
      getResourceDisplayText, eventLinkFn, eventChangeLinkFn,
      events: downloadedEvents, eventDates,
      filterOptionsResources,
      filterOptionDateRange: generalTimetableScope.dateRange,
      filterOptionsColors, filterOptionsSubjects, filterOptionsFrames,
      filterChecked, filterEventChange, filterType,
      filterTeacher, filterStudent, filterDateRange, filterDayofweek,
      filterColor, filterSubject, filterFrame, filteredEvents, checkedEvents,
      checkedEventCount: computed(() => filteredEvents.value.filter(
        x => checkedEvents.value.has(x.id)
      ).length), shownDialog, deleteEvents, dialogActionRun, shownEventCount,
    };
  },
}
</script>

<template>
<h1>イベント一覧</h1>
<router-link to="./new">イベント追加</router-link>
<form class="filteringform">
  <p>
    <label>
      選択状態
      <select v-model="filterChecked">
        <option :value="null">[無指定]</option>
        <option :value="false">未選択</option>
        <option :value="true">選択中</option>
      </select>
    </label>
  </p>
  <fieldset>
    <legend>振替</legend>
    <label>
      <input type="checkbox" v-model="filterEventChange" value="normal" />
      通常
    </label>
    <label>
      <input type="checkbox" v-model="filterEventChange" value="cancelled" />
      取消済み
    </label>
    <label>
      <input type="checkbox" v-model="filterEventChange" value="changeFrom" />
      振替元
    </label>
    <label>
      <input type="checkbox" v-model="filterEventChange" value="changeTo" />
      振替先
    </label>
  </fieldset>
  <fieldset>
    <legend>種別</legend>
    <label>
      <input type="checkbox" v-model="filterType" value="standard" /> 標準
    </label>
    <label>
      <input type="checkbox" v-model="filterType" value="lesson" /> 授業
    </label>
  </fieldset>
  <fieldset>
    <legend>日付</legend>
    <div style="
      display: grid; grid-template-columns: auto auto; align-items: center;
    ">
      <input-date-range
        v-model="filterDateRange" :highlighted-dates="eventDates"
        :min-date="filterOptionDateRange[0]"
        :max-date="filterOptionDateRange[1]"
      />
      <button
        type="button" @click="filterDateRange = filterOptionDateRange"
      >全範囲</button>
    </div>
    <label>
      <input type="checkbox" v-model="filterDayofweek" :value="0" /> 月
    </label>
    <label>
      <input type="checkbox" v-model="filterDayofweek" :value="1" /> 火
    </label>
    <label>
      <input type="checkbox" v-model="filterDayofweek" :value="2" /> 水
    </label>
    <label>
      <input type="checkbox" v-model="filterDayofweek" :value="3" /> 木
    </label>
    <label>
      <input type="checkbox" v-model="filterDayofweek" :value="4" /> 金
    </label>
    <label>
      <input type="checkbox" v-model="filterDayofweek" :value="5" /> 土
    </label>
    <label>
      <input type="checkbox" v-model="filterDayofweek" :value="6" /> 日
    </label>
  </fieldset>
  <fieldset>
    <legend>リソース</legend>
    <p>
      <label>
        講師
        <select v-model="filterTeacher">
          <option :value="null">[無指定]</option>
          <option v-for="r of filterOptionsResources.filter(
            x => x.type === 'teacher'
          )" :key="r.id" :value="r.id">{{ getResourceDisplayText(r) }}</option>
        </select>
      </label>
      <button
        type="button" @click="filterTeacher = null"
        v-if="filterTeacher != null"
      >指定解除</button>
    </p>
    <p>
      <label>
        生徒
        <select v-model="filterStudent">
          <option :value="null">[無指定]</option>
          <option v-for="r of filterOptionsResources.filter(
            x => x.type === 'student'
          )" :key="r.id" :value="r.id">{{ getResourceDisplayText(r) }}</option>
        </select>
      </label>
      <button
        type="button" @click="filterStudent = null"
        v-if="filterStudent != null"
      >指定解除</button>
    </p>
  </fieldset>
  <p>
    <label>
      色
      <select v-model="filterColor">
        <option :value="null">[無指定]</option>
        <option
          v-for="option of filterOptionsColors"
          :key="option" :value="option" :style="{ backgroundColor: option }"
        >{{
          colorLabels.find(x => x.color === option)?.label ?? option
        }}</option>
      </select>
    </label>
    <button
      type="button" @click="filterColor = null" v-if="filterColor != null"
    >指定解除</button>
  </p>
  <p>
    <label>
      教科
      <select v-model="filterSubject">
        <option :value="null">[無指定]</option>
        <option
          v-for="subject of filterOptionsSubjects"
          :key="subject" :value="subject"
        >{{ subject }}</option>
      </select>
    </label>
    <button
      type="button" @click="filterSubject = null" v-if="filterSubject != null"
    >指定解除</button>
  </p>
  <p>
    <label>
      枠
      <select v-model="filterFrame">
        <option :value="null">[無指定]</option>
        <option
          v-for="option of filterOptionsFrames" :key="option" :value="option"
        >{{ option }}</option>
      </select>
    </label>
    <button
      type="button" @click="filterFrame = null" v-if="filterFrame != null"
    >指定解除</button>
  </p>
</form>
<hr />
<section>
  <app-dialog
    :open="shownDialog === 'deleteEvents'"
    @update:open="$event ? null : shownDialog = ''"
  >
    <h1>削除</h1>
    <button type="button" @click="
      async () => (await deleteEvents(), dialogActionRun())
    ">{{ checkedEvents.size }}件のイベントの削除を実行</button>
  </app-dialog>
</section>
<section>
  <app-dialog
    :open="shownDialog === 'updateEvents'"
    @update:open="$event ? null : shownDialog = ''"
  >
    <update-events-dialog :source-events="
      [...checkedEvents].map(x => events.find(e => e.id === x)!)
    " @run="dialogActionRun()" />
  </app-dialog>
</section>
<section>
  <app-dialog
    :open="shownDialog === 'copyEventsByDayOfWeek'"
    @update:open="$event ? null : shownDialog = ''"
  >
    <copy-events-by-day-of-week-dialog :source-events="
      [...checkedEvents].map(x => events.find(e => e.id === x)!)
    " @run="dialogActionRun()" />
  </app-dialog>
</section>
<div class="eventchecktoolbar">
  <p>
    <label>
      <input
        type="checkbox" :checked="checkedEventCount > 0"
        :indeterminate="
          0 < checkedEventCount && checkedEventCount < filteredEvents.length
        "
        @change="($event.currentTarget as HTMLInputElement).checked
          ? filteredEvents.forEach(x => checkedEvents.add(x.id))
          : filteredEvents.forEach(x => checkedEvents.delete(x.id))
        "
      />
      全選択 / 全解除
    </label>
  </p>
  <p v-if="checkedEvents.size">
    <span>{{ checkedEvents.size }}件を選択中</span>
    <button type="button" @click="checkedEvents.clear()">選択リセット</button>
    <button type="button" @click="shownDialog = 'deleteEvents'">削除</button>
    <button
      type="button" @click="shownDialog = 'updateEvents'"
    >変更</button>
    <button
      type="button" @click="shownDialog = 'copyEventsByDayOfWeek'"
    >曜日別複製</button>
  </p>
</div>
<p v-show="!filteredEvents.length">イベントはありません。</p>
<DataGrid
  v-show="filteredEvents.length"
  :modelValue="filteredEvents.slice(0, shownEventCount)"
  :readonly="true"
  :itemKey="'id'"
  :headers="[
    {text: 'ID', value: 'id'},
    {text: '振替'},
    {text: '種別', value: 'type'},
    {text: '時刻', value: 'startTime'},
    {text: '使用リソース', value: 'resources'},
    {text: 'メモ', value: 'memo'},
    {text: '色', value: 'color'},
    {text: '教科', value: 'subject'},
    {text: '枠', value: 'frame'},
    {text: '操作'},
  ]"
>
  <template v-slot:td-ID="{data}">
    <label>
      <input type="checkbox" v-model="checkedEvents" :value="data" />
      <span :id="'event-'+data" :title="data">
        {{ `...${data.slice(-4)}` }}
      </span>
    </label>
  </template>
  <template v-slot:td-振替="{item}">
    <span>
      <template v-if="item.eventChangeTo">
        <template v-if="item.eventChangeTo.toEvent">
          →<EventLink
            v-if="item.eventChangeTo.toEvent"
            :event="item.eventChangeTo.toEvent"
          >振替先</EventLink>
        </template>
        <template v-else>取消済み</template>
      </template>
      <template v-if="item.eventChangeFrom">
        ←<EventLink
          :event="item.eventChangeFrom.fromEvent"
        >振替元</EventLink>
      </template>
    </span>
  </template>
  <template v-slot:td-種別="{data}">
    {{ { standard: '標準', lesson: '授業' }[data] }}
  </template>
  <template v-slot:td-時刻="{item}">
    <template v-if="item.timeLabel">
      <DateTime :value="item.startTime.date()" /> {{ item.timeLabel.label }}
    </template>
    <template v-else>
      <template v-if="
        item.startTime.date().isoFormat() === item.endTime.date().isoFormat()
      ">
        <DateTime :value="item.startTime.date()" />
        <DateTime :value="item.startTime.timetz()" /> - <DateTime :value="item.endTime.timetz()" />
      </template>
      <template v-else>
        <DateTime :value="item.startTime" /> - <DateTime :value="item.endTime" />
      </template>
    </template>
  </template>
  <template v-slot:td-使用リソース="{data}">
    <ul>
      <li v-for="resource of data.filter(
        x => x.id !== generalTimetableScope.classroom.value
      )" :key="resource.id">
        {{ getResourceDisplayText(resource) }}
      </li>
    </ul>
  </template>
  <template v-slot:td-メモ="{data}">{{ data }}</template>
  <template v-slot:td-色="{data}">
    <span class="colorbox" :style="{'--colorbox-color': data}"></span>
    {{ colorLabels.find(x => x.color === data)?.label ?? data }}
  </template>
  <template v-slot:td-教科="{data}">{{ data ?? 'N/A' }}</template>
  <template v-slot:td-枠="{data}">{{ data ?? 'N/A' }}</template>
  <template v-slot:td-操作="{item}">
    <span>
      <router-link :to="`./${item.id}/edit`">編集</router-link>
      <router-link :to="`./${item.id}/index`">振替</router-link>
      <!-- <router-link
        v-if="eventLinkFn(item)" :to="eventLinkFn(item)"
      >編集</router-link> -->
      <!-- <router-link
        v-if="eventChangeLinkFn(item)" :to="eventChangeLinkFn(item)"
      >振替</router-link> -->
    </span>
  </template>
</DataGrid>
<button
  type="button" v-show="filteredEvents.length > shownEventCount"
  @click="shownEventCount += 30"
>続きを表示</button>
</template>

<style scoped>
.colorbox {
  display: inline-block;
  width: 1em;
  height: 1em;
  border: solid 1px #000000;
  vertical-align: middle;
  background-color: var(--colorbox-color);
}

.filteringform {
  display: flex;
  flex-wrap: wrap;
}
.eventchecktoolbar {
  display: flex;
  gap: 1em;
}
</style>
