import {
  DateTime, time, timedelta, type Date,
} from '@silane/datetime';
import { computed, shallowRef, watch, type Ref } from 'vue';

import {
  comparerByKey, datetimeRange, dateAndTimeRangeToDateTimeRange,
} from '../../../../../../common/utils';
import { TimeTable } from '../../../../../../common/timetable';
import {
  resourceOrdering, type Resource,
} from '../../../../../../composables/resourcestore';
import type { ErrorHandler } from '../../../../../../composables/errorhandler';
import type { EventStore, Event } from '../../../../../../composables/eventstore';
import type { ResourceHour, ResourceHourStore } from '../../../../../../composables/resourcehourstore';


export default function(
  resource: Ref<Resource | null>, classroom: Ref<Resource>, dateRange: Ref<[Date, Date]>,
  errorHandler: ErrorHandler, eventStore: EventStore, resourceHourStore: ResourceHourStore,
) {
  const handleError = errorHandler.handleErrorAsync;

  const dates = computed(() => [...datetimeRange(
    ...dateRange.value, timedelta({ days: 1 }),
  )]);

  const allEvents = shallowRef<Event[]>([]);
  const load = handleError(async () => {
    allEvents.value = await eventStore.query({
      startTime: DateTime.combine(dateRange.value[0], time()),
      endTime: DateTime.combine(dateRange.value[1], time()),
    });
  });
  watch(dateRange, async () => {
    await load();
  }, { immediate: true });

  const events = computed(() => allEvents.value.filter(
    x => !x.eventChangeTo && [
      resource.value?.id ?? '', classroom.value.id,
    ].every(rId => x.resources.some(r => r.id === rId))
  ));

  const dailyEvents = computed(() => {
    const timetable = new TimeTable<Event>();
    for(const x of events.value) {
      timetable.add(x.startTime, x.endTime, x);
    }
    return dates.value.map(date => ({ date, items: timetable.get(
      ...dateAndTimeRangeToDateTimeRange(date, time(), time())
    ).filter(x => x.items.size).map(x => ({
      startTime: x.start.timetz(), endTime: x.end.timetz(),
      events: [...x.items],
    })) }));
  });

  const usedResources = computed(() => [...new Map([
    ...resource.value ? [[resource.value.id, resource.value] as const] : [],
    ...events.value.flatMap(e => e.resources.filter(
      x => x.type !== 'classroom'
    ).map(x => [x.id, x] as const))
  ])].map(x => x[1]).sort(comparerByKey(resourceOrdering)));

  const usedResourceHours = shallowRef<ResourceHour[][]>([]);
  const loadResourceHours = handleError(async () => {
    const range = {
      startTime: DateTime.combine(dateRange.value[0], time()),
      endTime: DateTime.combine(dateRange.value[1], time()),
    };
    usedResourceHours.value = await Promise.all(usedResources.value.map(
      x => resourceHourStore.query({ ...range, resource: x.id })
    ));
  });
  watch(usedResources, async (newValue, oldValue) => {
    if(newValue.length === oldValue?.length && newValue.every(
      (r, i) => r.id === oldValue[i].id
    )) return;
    await loadResourceHours();
  }, { immediate: true });

  const dailyUsedResourceHours = computed(
    () => usedResourceHours.value.flatMap((resourceHours, usedResourceIdx) => {
      const usedResource = usedResources.value[usedResourceIdx];
      const timetable = new TimeTable();
      for(const x of resourceHours) {
        timetable.add(x.startTime, x.endTime, true);
      }
      for(const x of allEvents.value.filter(x => (
        !x.eventChangeTo
        && x.resources.map(r => r.id).includes(usedResource.id)
        && !events.value.map(e => e.id).includes(x.id)
      ))) {
        timetable.remove(x.startTime, x.endTime, true);
      }
      return dates.value.map(date => ({ date, items: timetable.get(
        ...dateAndTimeRangeToDateTimeRange(date, time(), time())
      ).filter(x => x.items.size).map(x => ({
        startTime: x.start.timetz(), endTime: x.end.timetz(),
        resource: usedResource,
      })) }));
    })
  );

  return {
    refresh: async () => {
      await load();
      await loadResourceHours();
    }, dailyEvents, usedResources, dailyUsedResourceHours,
  };
}
