<!--
	SPDX-FileCopyrightText: © 2025 Hornwitser <code@hornwitser.no>
	SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
	<div>
		<table>
			<thead>
				<tr>
					<th>start</th>
					<th>end</th>
					<th>duration</th>
					<th>event</th>
					<th>location</th>
					<th>assigned</th>
					<th v-if="edit"></th>
				</tr>
			</thead>
			<tbody>
				<template v-if="edit">
					<tr
						v-for="es in eventSlots"
						:key='es.slot?.id ?? es.start.toMillis()'
						:class='{
							removed: es.type === "slot" && es.slot.deleted,
							gap: es.type === "gap",
						}'
					>
						<template v-if="es.type === 'gap'">
							<td colspan="2">
								{{ gapFormat(es) }}
								gap
							</td>
							<td>
								<input
									type="time"
									v-model="newEventDuration"
								>
							</td>
							<td>
								<SelectSingleEntity
									:entities="schedule.events"
									v-model="newEventId"
								/>
							</td>
							<td>
								<SelectMultiEntity
									:entities="schedule.locations"
									v-model="newEventLocationIds"
								/>
							</td>
							<td></td>
							<td>
								Add at
								<button
									type="button"
									@click="newEventSlot({ start: es.start })"
								>Start</button>
								<button
									type="button"
									@click="newEventSlot({ end: es.end })"
								>End</button>
							</td>
						</template>
						<template v-else-if='edit'>
							<td>
								<input
									type="datetime-local"
									:value="es.start.toFormat('yyyy-LL-dd\'T\'HH:mm')"
									@blur="editEventSlot(es, { start: ($event as any).target.value })"
								>
							</td>
							<td>
								<input
									type="time"
									:value="es.end.toFormat('HH:mm')"
									@input="editEventSlot(es, { end: ($event as any).target.value })"
								>
							</td>
							<td>
								<input
									type="time"
									:value='dropDay(es.end.diff(es.start)).toFormat("hh:mm")'
									@input="editEventSlot(es, { duration: ($event as any).target.value })"
								>
							</td>
							<td>
								<SelectSingleEntity
									:entities="schedule.events"
									:modelValue="es.slot.eventId"
									@update:modelValue="es.slot.setEventId($event)"
								/>
							</td>
							<td>
								<SelectMultiEntity
									:entities="schedule.locations"
									v-model="es.slot.locationIds"
								/>
							</td>
							<td>
								<SelectMultiEntity
									:entities="usersStore.users"
									v-model="es.slot.assigned"
								/>
							</td>
							<td>
								<button
									:disabled="es.slot.deleted"
									type="button"
									@click="es.slot.deleted = true"
								>Remove</button>
								<button
									v-if="es.slot.isModified()"
									type="button"
									@click="schedule.discardEventSlot(es.slot.id)"
								>Revert</button>
							</td>
						</template>
					</tr>
					<tr>
						<td>
							<input
								type="datetime-local"
								v-model="newEventStart"
							>
						</td>
						<td>
							<input
								type="time"
								v-model="newEventEnd"
							>
						</td>
						<td>
							<input
								type="time"
								v-model="newEventDuration"
							>
						</td>
						<td>
							<SelectSingleEntity
								:entities="schedule.events"
								v-model="newEventId"
							/>
						</td>
						<td>
							<SelectMultiEntity
								:entities="schedule.locations"
								v-model="newEventLocationIds"
							/>
						</td>
						<td></td>
						<td colspan="2">
							<button
								type="button"
								@click="newEventSlot()"
							>Add Event</button>
						</td>
					</tr>
				</template>
				<template v-else>
					<tr
						v-for="es in eventSlots"
						:key='es.slot?.id ?? es.start.toMillis()'
						:class='{
							gap: es.type === "gap",
						}'
					>
						<template v-if="es.type === 'gap'">
							<td colspan="2">
								{{ gapFormat(es) }}
								gap
							</td>
						</template>
						<template v-else>
							<td>{{ es.start.toFormat("yyyy-LL-dd HH:mm") }}</td>
							<td>{{ es.end.toFormat("HH:mm") }}</td>
							<td>{{ es.end.diff(es.start).toFormat('hh:mm') }}</td>
							<td>{{ es.event?.name }}</td>
							<td></td>
							<td><AssignedCrew :modelValue="es.slot.assigned" :edit="false" /></td>
						</template>
					</tr>
				</template>
			</tbody>
		</table>
	</div>
</template>

<script lang="ts" setup>
import { DateTime, Duration } from '~/shared/utils/luxon';
import type { Id } from '~/shared/types/common';
import { enumerate, pairs, toId } from '~/shared/utils/functions';

const props = defineProps<{
	edit?: boolean,
	locationId?: number,
	eventSlotFilter?: (slot: ClientScheduleEventSlot) => boolean,
}>();

interface EventSlot {
	type: "slot",
	event?: ClientScheduleEvent,
	slot: ClientScheduleEventSlot,
	start: DateTime,
	end: DateTime,
}

interface Gap {
	type: "gap",
	event?: undefined,
	slot?: undefined,
	start: DateTime,
	end: DateTime,
}

const accountStore = useAccountStore();
const usersStore = useUsersStore();
const schedule = await useSchedule();

const oneDayMs = 24 * 60 * 60 * 1000;
function dropDay(diff: Duration) {
	if (diff.toMillis() >= oneDayMs) {
		return diff.minus({ days: 1 });
	}
	return diff;
}

const newEventStart = ref("");
const newEventDuration = ref("01:00");
const newEventEnd = computed({
	get: () => {
		try {
			return DateTime.fromISO(newEventStart.value, { zone: accountStore.activeTimezone, locale: accountStore.activeLocale })
				.plus(Duration.fromISOTime(newEventDuration.value, { locale: accountStore.activeLocale }))
				.toFormat("HH:mm")
		} catch (err) {
			return "";
		}
	},
	set: (value: string) => {
		const start = DateTime.fromISO(newEventStart.value, { zone: accountStore.activeTimezone, locale: accountStore.activeLocale });
		const end = endFromTime(start, value);
		newEventDuration.value = dropDay(end.diff(start)).toFormat("hh:mm");
	},
});
const newEventLocationIds = ref(new Set(props.locationId === undefined ? undefined : [props.locationId]));
watch(() => props.locationId, () => {
	newEventLocationIds.value = new Set(props.locationId === undefined ? undefined : [props.locationId]);
});

function endFromTime(start: DateTime, time: string) {
	let end = start.startOf("day").plus(Duration.fromISOTime(time, { locale: accountStore.activeLocale }));
	if (end.toMillis() <= start.toMillis()) {
		end = end.plus({ days: 1 });
	}
	return end;
}
function durationFromTime(time: string) {
	let duration = Duration.fromISOTime(time, { locale: accountStore.activeLocale });
	if (duration.toMillis() === 0) {
		duration = Duration.fromMillis(oneDayMs, { locale: accountStore.activeLocale });
	}
	return duration;
}
const newEventId = ref<Id>();

function editEventSlot(
	eventSlot: EventSlot,
	edits: {
		start?: string,
		end?: string,
		duration?: string,
	}
) {
	if (edits.start) {
		const start = DateTime.fromISO(edits.start, { zone: accountStore.activeTimezone, locale: accountStore.activeLocale });
		eventSlot.slot.start = start;
		eventSlot.slot.end = start.plus(eventSlot.end.diff(eventSlot.start));
	}
	if (edits.end !== undefined) {
		eventSlot.slot.end = endFromTime(eventSlot.start, edits.end);
	}
	if (edits.duration !== undefined) {
		eventSlot.slot.end = eventSlot.start.plus(durationFromTime(edits.duration));
	}
}
function newEventSlot(options: { start?: DateTime, end?: DateTime } = {}) {
	const event = schedule.value.events.get(newEventId.value!);
	if (!event) {
		alert("Invalid event");
		return;
	}
	let start;
	let end;
	const duration = durationFromTime(newEventDuration.value);
	if (!duration.isValid) {
		alert("Invalid duration");
		return;
	}
	if (options.start) {
		start = options.start;
		end = options.start.plus(duration);
	} else if (options.end) {
		end = options.end;
		start = options.end.minus(duration);
	} else {
		start = DateTime.fromISO(newEventStart.value, { zone: accountStore.activeTimezone, locale: accountStore.activeLocale });
		end = endFromTime(start, newEventEnd.value);
	}
	if (!start.isValid || !end.isValid) {
		alert("Invalid start and/or end time");
		return;
	}
	const slot = ClientScheduleEventSlot.create(
		schedule.value,
		schedule.value.nextClientId--,
		event.id,
		start,
		end,
		new Set(newEventLocationIds.value),
		false,
		new Set(),
		0,
	);
	schedule.value.eventSlots.set(slot.id, slot);
	event.slotIds.add(slot.id);
	newEventId.value = undefined;
}

const oneHourMs = 60 * 60 * 1000;
function gapFormat(gap: Gap) {
	let diff = gap.end.diff(gap.start);
	if (diff.toMillis() % oneHourMs !== 0)
		diff = diff.shiftTo("hours", "minutes");
	else
		diff = diff.shiftTo("hours");
	return diff.toHuman({ listStyle: "short", unitDisplay: "short" });
}

const eventSlots = computed(() => {
	const data: (EventSlot | Gap)[] = [];
	for (const slot of schedule.value.eventSlots.values()) {
		const event = schedule.value.events.get(slot.eventId!);
		if (event?.deleted)
			continue;
		if (props.eventSlotFilter && !props.eventSlotFilter(slot))
			continue;
		if (props.locationId !== undefined && !slot.locationIds.has(props.locationId))
			continue;
		data.push({
			type: "slot",
			event,
			slot,
			start: slot.start,
			end: slot.end,
		});
	}
	data.sort((a, b) => a.start.toMillis() - b.start.toMillis() || a.end.toMillis() - b.end.toMillis());

	// Insert gaps
	let maxEnd = 0;
	const gaps: [number, Gap][] = []
	for (const [index, [first, second]] of enumerate(pairs(data))) {
		maxEnd = Math.max(maxEnd, first.end.toMillis());
		if (maxEnd < second.start.toMillis()) {
			gaps.push([index, {
				type: "gap",
				start: DateTime.fromMillis(maxEnd, { locale: accountStore.activeLocale }),
				end: second.start,
			}]);
		}
	}
	gaps.reverse();
	for (const [index, gap] of gaps) {
		data.splice(index + 1, 0, gap);
	}
	return data;
});
</script>

<style scoped>
label {
	display: inline;
	padding-inline-end: 0.75em;
}
table {
	margin-block-start: 1rem;
	border-spacing: 0;
}
table th {
	text-align: left;
	border-bottom: 1px solid var(--foreground);
}
table :is(th, td) + :is(th, td) {
	padding-inline-start: 0.4em;
}
.gap {
	height: 1.8em;
}
.removed {
	background-color: color-mix(in oklab, var(--background), rgb(255, 0, 0) 40%);
}
.removed :is(td, input) {
	text-decoration: line-through;
}
</style>
