<script lang="ts">
import {
  DateTime, Time, cmp, add, time, timedelta, type Date,
} from '@silane/datetime';
import {
  ref, computed, watch, shallowRef, triggerRef, onScopeDispose,
  type PropType, type Raw,
} from 'vue';

import { inject, keys as injectionKeys } from '../../injection';
import { TimeTable } from '../../common/timetable';
import {
  datetimeRange, dateAndTimeRangeToDateTimeRange,
} from '../../common/utils';

export function addGlobalEventListener(
    ...args: Parameters<typeof globalThis.addEventListener>
) {
    globalThis.addEventListener(...args);
    onScopeDispose(() => {
        globalThis.removeEventListener(...args);
    });
}

function roundTime(time: Time, deltaMinutes: number) {
  const totalMinutes = time.hour * 60 + time.minute;
  const roundedTotalMinutes = Math.round(
    totalMinutes / deltaMinutes
  ) * deltaMinutes;
  return new Time(
    Math.floor(roundedTotalMinutes / 60) % 24, roundedTotalMinutes % 60,
  );
}

export default {
  name: 'InputDailyTimeRanges',
  props: {
    dates: {
      type: Array as PropType<Date[]>, required: true,
    },
    modelValue: {
      type: Array as PropType<{ startTime: DateTime, endTime: DateTime }[]>,
      required: true,
    },
  },
  emits: {
    'update:modelValue': (
      payload: { startTime: DateTime, endTime: DateTime }[]
    ) => true,
  },
  setup(props, { emit }) {
    const links = inject(injectionKeys.links);

    const columns = computed(() => props.dates.map(x => ({
      data: x, columnID: x.isoFormat(),
    })));

    const timetable = shallowRef(new TimeTable());

    watch(() => props.modelValue, () => {
      timetable.value.clear();
      for(const range of props.modelValue) {
        timetable.value.add(range.startTime, range.endTime, true);
      }
      triggerRef(timetable);
    }, { immediate: true });

    const dailyResourceHours = computed(() => props.dates.flatMap(
      date => timetable.value.get(
        ...dateAndTimeRangeToDateTimeRange(date, time(), time())
      ).filter(x => x.items.has(true)).map(x => ({
        date, startTime: x.start.timetz(), endTime: x.end.timetz(),
      }))
    ));

    function emitUpdateModelValue() {
      emit('update:modelValue', timetable.value.get().filter(
        x => x.items.has(true)
      ).map(x => ({ startTime: x.start, endTime: x.end })));
    }

    let dateClipboard: Date | null = null;
    function copyDate(date: Date) {
      dateClipboard = date;
    }

    function pasteDate(date: Date) {
      if(!dateClipboard) return;
      timetable.value.remove(...dateAndTimeRangeToDateTimeRange(
        date, time(), time()
      ), true);
      for(const range of timetable.value.get(
        ...dateAndTimeRangeToDateTimeRange(dateClipboard, time(), time())
      ).filter(x => x.items.has(true))) {
        timetable.value.add(...dateAndTimeRangeToDateTimeRange(
          date, range.start.timetz(), range.end.timetz(),
        ), true);
      }
      emitUpdateModelValue();
    }

    function unfill(columnID: string, start: Time, end: Time) {
      const date = columns.value.find(x => x.columnID === columnID)!.data;
      timetable.value.remove(...dateAndTimeRangeToDateTimeRange(
        date, start, end,
      ), true);
      emitUpdateModelValue();
    }

    const draggingRange = ref<{
      startDate: Raw<Date>, endDate: Raw<Date>,
      startTime: Raw<Time>, endTime: Raw<Time>,
    } | null>(null);
    const draggingContext = shallowRef<{
      type: 'new' | 'resizehead' | 'resizetail'
    } | null>(null);
    function onResizepinchMouseDown(payload) {
      if(payload.originalEvent.button !== 0) return;
      payload.originalEvent.stopPropagation();
      payload.originalEvent.preventDefault();
      const date = columns.value.find(
        x => x.columnID === payload.event.columnID
      )!.data;
      let [startTime, endTime] = [
        payload.event.startTime, payload.event.endTime,
      ];
      if(payload.pinchType === 'head') {
        [startTime, endTime] = [endTime, startTime];
      }
      draggingRange.value = {
        startDate: date, endDate: date, startTime, endTime,
      }
      draggingContext.value = {
        type: payload.pinchType.type === 'head' ? 'resizehead' : 'resizetail',
      };
      timetable.value.remove(...dateAndTimeRangeToDateTimeRange(
        date, payload.event.startTime, payload.event.endTime
      ), true);
      triggerRef(timetable);
    }
    function onTimelineMouseDown(payload) {
      if(payload.originalEvent.button !== 0) return;
      payload.originalEvent.preventDefault();
      const date = columns.value.find(
        x => x.columnID === payload.columnID
      )!.data;
      draggingRange.value = {
        startDate: date, endDate: date,
        startTime: payload.time, endTime: payload.time,
      };
      draggingContext.value = { type: 'new' };
    }
    function onTimelineMouseMove(payload) {
      if(!draggingRange.value) return;
      payload.originalEvent.preventDefault();
      const date = columns.value.find(
        x => x.columnID === payload.columnID
      )!.data;
      draggingRange.value.endDate = date;
      draggingRange.value.endTime = payload.time;
    }
    const activeRanges = computed(() => {
      if(!draggingContext.value || !draggingRange.value) return [];
      const [startTime, endTime] = [
        draggingRange.value.startTime, draggingRange.value.endTime,
      ].sort(cmp);
      const [startDate, endDate] = draggingContext.value.type === 'new' ? [
        draggingRange.value.startDate, draggingRange.value.endDate,
      ].sort(cmp) : [
        draggingRange.value.startDate, draggingRange.value.startDate,
      ];
      return [...datetimeRange(
        startDate, add(endDate, timedelta({ days: 1 })),
        timedelta({ days: 1 }),
      )].map(date => ({
        date,
        startTime: roundTime(startTime, 10), endTime: roundTime(endTime, 10),
      }));
    });
    addGlobalEventListener('mouseup', () => {
      if(!activeRanges.value.length) return;
      for(const x of activeRanges.value) {
        const range = dateAndTimeRangeToDateTimeRange(
          x.date, x.startTime, x.endTime,
        );
        if(cmp(...range) >= 0) continue;
        timetable.value.add(...range, true);
      }
      draggingRange.value = null;
      draggingContext.value = null;
      triggerRef(timetable);
      emitUpdateModelValue();
    });

    const timetableEvents = computed(() => [
      ...dailyResourceHours.value.map(x => ({
        ...x, data: { type: 'filled' },
      })), ...activeRanges.value?.map(x => ({
        ...x, data: { type: 'active' },
      })) ?? []
    ].map(x => ({
      startTime: x.startTime, endTime: x.endTime,
      columnID: x.date.isoFormat(), data: x.data,
    })));

    return {
      time, links, columns, copyDate, pasteDate, unfill,
      onResizepinchMouseDown, onTimelineMouseDown, onTimelineMouseMove,
      timetableEvents,
    };
  },
}
</script>

<template>
<div class="inputdailytimerages">
  <time-table
    class="timeline" :startTime="time()" :endTime="time()"
    :columnIDs="columns.map(x => x.columnID)" :events="timetableEvents"
    @timelinemousedown="onTimelineMouseDown"
    @timelinemousemove="onTimelineMouseMove"
  >
    <template v-slot:columnheader="{ columnID, columnIndex }">
      <component :is="links?.dateTimeTableLinkFn(
        columns[columnIndex].data
      ) ? 'router-link' : 'span'" :to="links?.dateTimeTableLinkFn(
        columns[columnIndex].data
      )" class="dateheader" :class="{
        'dateheader-saturday': columns[columnIndex].data.weekday() === 5,
        'dateheader-sunday': columns[columnIndex].data.weekday() === 6,
      }">
        <time :datetime="columnID">
          {{ columns[columnIndex].data.strftime('%m/%d') }}<br />
          {{ ['月', '火', '水', '木', '金', '土', '日'][
            columns[columnIndex].data.weekday()
          ] }}
        </time>
      </component>
      <div>
        <button type="button" title="コピー" @click="
          copyDate(columns[columnIndex].data);
        ">🗊</button>
        <button type="button" title="ペースト" @click="
          pasteDate(columns[columnIndex].data);
        ">📋</button>
      </div>
    </template>
    <template v-slot:event="{ event }">
      <div class="event" :class="{
        filled: 'event-filled', active: 'event-active',
      }[event.data.type]" @mousedown.stopPropagation="() => false">
        <div
          v-if="event.data.type === 'filled'" class="event-resizepinchhead"
          @mousedown="onResizepinchMouseDown({
            pinchType: 'head', event, originalEvent: $event,
          });"
        ></div>
        <div>
          <date-time :value="event.startTime" />
        </div>
        <div>
          <button type="button" v-if="event.data.type === 'filled'" @click="
            unfill(event.columnID, event.startTime, event.endTime);
          " title="削除">❌</button>
        </div>
        <div>
          <date-time :value="event.endTime" />
        </div>
        <div
          v-if="event.data.type === 'filled'" class="event-resizepinchtail"
          @mousedown="onResizepinchMouseDown({
            pinchType: 'tail', event, originalEvent: $event,
          });"
        ></div>
      </div>
    </template>
  </time-table>
</div>
</template>

<style scoped>
.timeline {
  height: 100%;
}
.timeline::v-deep(.timetable-scrollcontent) {
  height: 100em;
}
.dateheader {
  color: black;
}
.dateheader-saturday {
  color: darkblue;
}
.dateheader-sunday {
  color: tomato;
}
.event {
  height: 100%;
  display: grid;
  grid-template-rows: auto 1fr auto;
  text-align: center;
}
.event-filled {
  background-color: skyblue;
}
.event-active {
  background-color: yellowgreen;
}
.event-resizepinchhead, .event-resizepinchtail {
  position: absolute;
  width: 100%;
  height: 1em;
  cursor: ns-resize;
}
.event-resizepinchhead {
  inset-block-start: 0;
}
.event-resizepinchtail {
  inset-block-end: 0;
}
</style>
