<template>
<div class="timetablewrapper">
  <div class="timetable timetable-scrollcontent" :class="{
    'timetable-zerocolumn': (columnIDs?.length ?? 0) == 0,
  }" :style="{ '--column-num': columnIDs?.length ?? 0 }">
    <span></span>
    <div
      v-for="columnID, idx of columnIDs" :key="columnID"
      class="timetable-columnheader"
    >
      <slot name="columnheader" :columnID="columnID" :columnIndex="idx">
      {{ columnID }}
      </slot>
    </div>
    <span></span>
    <div class="timetable-timeheadercolumn timetable-timeheadercolumn-left">
      <time
        v-for="time, timeIdx of timeHeaders" :key="time.isoFormat()" :style="{
          top: `${getYCoordOfTime(time, timeIdx > 0) * 100}%`,
          transform: 'translateY(-50%)'
        }" :datetime="time.isoFormat()" class="timetable-timeheader"
      >
        {{ time.strftime('%H:%M') }}
      </time>
    </div>
    <div
      v-for="columnID, columnIndex of columnIDs" :key="columnID"
      @click="bodyColumnMouseEventHandler('click', columnID, $event)"
      @dblclick="bodyColumnMouseEventHandler('dblclick', columnID, $event)"
      @mousedown="bodyColumnMouseEventHandler('mousedown', columnID, $event)"
      @mousemove="bodyColumnMouseEventHandler('mousemove', columnID, $event)"
      @mouseup="bodyColumnMouseEventHandler('mouseup', columnID, $event)"
      class="timetable-bodycolumn"
    >
      <div
        v-for="time, timeIdx of timeHeaders" :key="time.isoFormat()"
        class="timetable-timetick" :style="{
          top: `${getYCoordOfTime(time, timeIdx > 0) * 100}%`
        }"
      ></div>
      <template v-for="[
        event, startCoord, endCoord,
      ] of events[columnIndex].map(x => [
        x, getYCoordOfTime(x.startTime), getYCoordOfTime(x.endTime, true),
      ])">
        <div v-if="endCoord - startCoord > 0" :style="{
          top: `${startCoord * 100}%`,
          height: `${(endCoord - startCoord) * 100}%`,
        }" class="timetable-event">
          <slot name="event" :event="event"></slot>
        </div>
      </template>
    </div>
    <div class="timetable-timeheadercolumn timetable-timeheadercolumn-right">
      <span
        v-for="time, timeIdx of timeHeaders" :key="time.isoFormat()" :style="{
          top: `${getYCoordOfTime(time, timeIdx > 0) * 100}%`,
          transform: 'translateY(-50%)'
        }" class="timetable-timeheader"
      >
        {{ time.strftime('%H:%M') }}
      </span>
    </div>
  </div>
</div>
</template>

<script>
import { computed, toRef } from 'vue';
import { Time } from '@silane/datetime';


function range(start, end=undefined, step=1) {
  if(end === undefined) {
    end = start;
    start = 0;
  }
  const length = Math.ceil((end - start) / step);
  return Array.from({length}, (_, idx) => start + idx * step);
}

export default {
  name: 'TimeTable',
  props: {
    startTime: {
      type: /** @type {import('vue').PropType<Time>} */(Time),
      required: true,
    },
    endTime: {
      type: /** @type {import('vue').PropType<Time>} */(Time),
      required: true,
    },
    columnIDs: {
      type: /** @type {import('vue').PropType<string[]>} */ (Array),
      required: true,
    },
    events: {
      type: /** @type {import('vue').PropType<{
        columnID: string,
        startTime: import('@silane/datetime').Time,
        endTime: import('@silane/datetime').Time,
        data?: unknown,
      }[]>} */ (Array),
      default: [],
    }
  },
  emits: {
    timelineclick: /** @type {(payload: {
      columnID: string, time: import('@silane/datetime').Time,
      originalEvent: Event,
    }) => boolean} */(() => true),
  },
  emits: [
    'timelineclick', 'timelinedblclick',
    'timelinemousedown', 'timelinemousemove', 'timelinemouseup'
  ],
  setup(props, { emit }) {
    const timeHeaderInterval = 30;

    const startTime = computed(() => Math.floor(
      (props.startTime.hour * 60 + props.startTime.minute) / timeHeaderInterval
    ) * timeHeaderInterval);
    const endTime = computed(() => Math.ceil(
      (props.endTime.hour * 60 + props.endTime.minute) / timeHeaderInterval
    ) * timeHeaderInterval || 24 * 60);

    const timeHeaders = computed(() => range(
      startTime.value, endTime.value + 1, timeHeaderInterval
    ).map(x => new Time(Math.floor(x / 60) % 24, x % 60)));

    const events = computed(() => {
      const ret = props.columnIDs.map(
        () => /** @type {typeof props.events} */([])
      );
      for(let event of props.events) {
        const columnIndex = props.columnIDs.indexOf(event.columnID);
        if(columnIndex < 0) continue;
        ret[columnIndex].push(event);
      }
      return ret;
    });

    const getYCoordOfTime = computed(() => (time, end=false) => {
      let minutes = time.hour * 60 + time.minute;
      if(end && minutes === 0) {
        minutes = 24 * 60;
      }
      return Math.max(Math.min(
        (minutes - startTime.value) / (endTime.value - startTime.value), 1
      ), 0);
    });

    function bodyColumnMouseEventHandler(eventName, columnID, e) {
      const targetRect = e.currentTarget.getBoundingClientRect();
      const yCoord = Math.max(Math.min(
        (e.clientY - targetRect.top) / targetRect.height, 1
      ), 0);
      const minutes = startTime.value + yCoord * (
        endTime.value - startTime.value
      );
      const time = new Time(
        Math.floor(minutes / 60) % 24, Math.floor(minutes % 60)
      );
      emit(`timeline${eventName}`, { columnID, time, originalEvent: e });
    }

    return {
      columnIDs: toRef(props, 'columnIDs'), events,
      timeHeaders, getYCoordOfTime, bodyColumnMouseEventHandler,
    };
  },
}
</script>

<style scoped>
.timetablewrapper {
  overflow: auto;
  border: solid 3px black;
}
.timetable {
  box-sizing: border-box;
  width: fit-content;
  min-width: 100%;
  min-height: 100%;
  display: grid;
  grid-template-columns: 3em repeat(var(--column-num), 1fr) 3em;
  grid-template-rows: auto 1fr;
  padding-block-end: 1em;
}
.timetable.timetable-zerocolumn {
  grid-template-columns: 3em 3em;
}
.timetable-columnheader {
  position: sticky;
  top: 0;
  z-index: 1;
  text-align: center;
  border-inline-start: solid 1px hsl(0, 0%, 75%);
  border-inline-end: solid 1px hsl(0, 0%, 75%);
  border-block-end: solid 1px hsl(0, 0%, 75%);
  background-color: #FFF;
}
.timetable-timeheadercolumn {
  position: sticky;
  z-index: 1;
}
.timetable-timeheadercolumn-left {
  inset-inline-start: 0;
  text-align: end;
}
.timetable-timeheadercolumn-right {
  inset-inline-end: 0;
  text-align: start;
}
.timetable-timeheadercolumn-left .timetable-timeheader {
  inset-inline-start: 0;
}
.timetable-timeheadercolumn-right .timetable-timeheader {
  inset-inline-end: 0;
}
.timetable-timeheader {
  position: absolute;
  width: 100%;
}
.timetable-bodycolumn {
  position: relative;
  border-inline-start: solid 1px hsl(0, 0%, 75%);
  border-inline-end: solid 1px hsl(0, 0%, 75%);
}
.timetable-timetick {
  position: absolute;
  width: 100%;
  border-block-start: solid 1px hsl(0, 0%, 75%);
}
.timetable-event {
  position: absolute;
  width: 100%;
}

@media print {
  .timetable-columnheader {
    position: relative;
  }
  .timetable-timeheadercolumn {
    position: relative;
  }
}
</style>
