<template>
	<div>
		<table>
			<thead>
				<tr>
					<th>start</th>
					<th>end</th>
					<th>duration</th>
					<th>shift</th>
					<th>s</th>
					<th>role</th>
					<th>assigned</th>
					<th v-if="edit"></th>
				</tr>
			</thead>
			<tbody>
				<template v-if="edit">
					<tr
						v-for="ss in shiftSlots"
						:key='ss.slot?.id ?? ss.start.toMillis()'
						:class='{
							removed: ss.slot?.deleted || ss.shift?.deleted,
							gap: ss.type === "gap",
						}'
					>
						<template v-if="ss.type === 'gap'">
							<td colspan="2">
								{{ gapFormat(ss) }}
								gap
							</td>
							<td>
								<input
									type="time"
									v-model="newShiftDuration"
								>
							</td>
							<td>
								<input
									type="text"
									v-model="newShiftName"
								>
							</td>
							<td></td>
							<td>
								<select
									v-model="newShiftRoleId"
								>
									<option
										v-for="role in schedule.roles.values()"
										:key="role.id"
										:value="role.id"
										:disabled="role.deleted"
										:selected="role.id === newShiftRoleId"
									>{{ role.name }}</option>
								</select>
							</td>
							<td></td>
							<td>
								Add at
								<button
									type="button"
									@click="newShiftSlot({ start: ss.start })"
								>Start</button>
								<button
									type="button"
									@click="newShiftSlot({ end: ss.end })"
								>End</button>
							</td>
						</template>
						<template v-else-if='edit'>
							<td>
								<input
									type="datetime-local"
									:value="ss.start.toFormat('yyyy-LL-dd\'T\'HH:mm')"
									@blur="editShiftSlot(ss, { start: ($event as any).target.value })"
								>
							</td>
							<td>
								<input
									type="time"
									:value="ss.end.toFormat('HH:mm')"
									@input="editShiftSlot(ss, { end: ($event as any).target.value })"
								>
							</td>
							<td>
								<input
									type="time"
									:value='dropDay(ss.end.diff(ss.start)).toFormat("hh:mm")'
									@input="editShiftSlot(ss, { duration: ($event as any).target.value })"
								>
							</td>
							<td>
								<input
									type="text"
									v-model="ss.name"
								>
							</td>
							<td>{{ status(ss) }}</td>
							<td>
								<select
									v-model="ss.shift.roleId"
								>
									<option
										v-for="role in schedule.roles.values()"
										:key="role.id"
										:value="role.id"
										:disabled="role.deleted"
										:selected="role.id === ss.shift.roleId"
									>{{ role.name }}</option>
								</select>
							</td>
							<td>
								<AssignedCrew
									:edit="true"
									v-model="ss.slot.assigned"
								/>
							</td>
							<td>
								<button
									:disabled="ss.deleted"
									type="button"
									@click="ss.slot.deleted = true"
								>Remove</button>
								<button
									v-if="ss.slot.isModified()"
									type="button"
									@click="ss.shift.discardSlot(ss.slot.id)"
								>Revert</button>
							</td>
						</template>
					</tr>
					<tr>
						<td>
							<input
								type="datetime-local"
								v-model="newShiftStart"
							>
						</td>
						<td>
							<input
								type="time"
								v-model="newShiftEnd"
							>
						</td>
						<td>
							<input
								type="time"
								v-model="newShiftDuration"
							>
						</td>
						<td>
								<input
									type="text"
									v-model="newShiftName"
								>
						</td>
						<td colspan="3">
							<button
								type="button"
								@click="newShiftSlot()"
							>Add Shift</button>
						</td>
					</tr>
				</template>
				<template v-else>
					<tr
						v-for="ss in shiftSlots"
						:key='ss.slot?.id ?? ss.start.toMillis()'
						:class='{
							gap: ss.type === "gap",
						}'
					>
						<template v-if="ss.type === 'gap'">
							<td colspan="2">
								{{ gapFormat(ss) }}
								gap
							</td>
						</template>
						<template v-else>
							<td>{{ ss.start.toFormat("yyyy-LL-dd HH:mm") }}</td>
							<td>{{ ss.end.toFormat("HH:mm") }}</td>
							<td>{{ ss.end.diff(ss.start).toFormat('hh:mm') }}</td>
							<td>{{ ss.name }}</td>
							<td>{{ status(ss) }}</td>
							<td>{{ ss.roleId }}</td>
							<td><AssignedCrew :modelValue="ss.assigned" :edit="false" /></td>
						</template>
					</tr>
				</template>
			</tbody>
		</table>
	</div>
</template>

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

const props = defineProps<{
	edit?: boolean,
	roleId?: Id,
	shiftSlotFilter?: (slot: ClientScheduleShiftSlot) => boolean,
}>();

interface ShiftSlot {
	type: "slot",
	id: Id,
	deleted: boolean,
	shift: ClientScheduleShift,
	slot: ClientScheduleShiftSlot,
	name: string,
	roleId: Id,
	assigned: Set<Id>,
	start: DateTime,
	end: DateTime,
}

interface Gap {
	type: "gap",
	id?: undefined,
	shift?: undefined,
	slot?: undefined,
	name?: undefined,
	role?: undefined,
	start: DateTime,
	end: DateTime,
}

function status(shiftSlot: ShiftSlot) {
	if (
		!shiftSlot.shift
		|| shiftSlot.shift.name !== shiftSlot.name
	) {
		const shift = [...schedule.value.shifts.values()].find(shift => !shift.deleted && shift.name === shiftSlot.name);
		return shift ? "L" : "N";
	}
	return shiftSlot.shift.slots.size === 1 ? "" : shiftSlot.shift.slots.size;
}

const accountStore = useAccountStore();
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 newShiftStart = ref("");
const newShiftDuration = ref("01:00");
const newShiftEnd = computed({
	get: () => {
		try {
			return DateTime.fromISO(newShiftStart.value, { zone: accountStore.activeTimezone, locale: accountStore.activeLocale })
				.plus(Duration.fromISOTime(newShiftDuration.value, { locale: accountStore.activeLocale }))
				.toFormat("HH:mm")
		} catch (err) {
			return "";
		}
	},
	set: (value: string) => {
		const start = DateTime.fromISO(newShiftStart.value, { zone: accountStore.activeTimezone, locale: accountStore.activeLocale });
		const end = endFromTime(start, value);
		newShiftDuration.value = dropDay(end.diff(start)).toFormat("hh:mm");
	},
});
const newShiftRoleId = ref(props.roleId);
watch(() => props.roleId, () => {
	newShiftRoleId.value = props.roleId;
});

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 newShiftName = ref("");

function editShiftSlot(
	shiftSlot: ShiftSlot,
	edits: {
		start?: string,
		end?: string,
		duration?: string,
	}
) {
	if (edits.start) {
		const start = DateTime.fromISO(edits.start, { zone: accountStore.activeTimezone, locale: accountStore.activeLocale });
		shiftSlot.start = start;
		shiftSlot.end = start.plus(shiftSlot.slot.end.diff(shiftSlot.slot.start));
	}
	if (edits.end !== undefined) {
		shiftSlot.end = endFromTime(shiftSlot.start, edits.end);
	}
	if (edits.duration !== undefined) {
		shiftSlot.end = shiftSlot.start.plus(durationFromTime(edits.duration));
	}
}
function newShiftSlot(options: { start?: DateTime, end?: DateTime } = {}) {
	const name = newShiftName.value;
	const nameId = toId(name);
	const shift = [...schedule.value.shifts.values()].find(shift => toId(shift.name) === nameId);
	if (!shift) {
		alert("Invalid shift");
		return;
	}
	const role = schedule.value.roles.get(newShiftRoleId.value!);
	if (!role) {
		alert("Invalid role");
		return;
	}
	let start;
	let end;
	const duration = durationFromTime(newShiftDuration.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(newShiftStart.value, { zone: accountStore.activeTimezone, locale: accountStore.activeLocale });
		end = endFromTime(start, newShiftEnd.value);
	}
	if (!start.isValid || !end.isValid) {
		alert("Invalid start and/or end time");
		return;
	}
	const slot = ClientScheduleShiftSlot.create(
		schedule.value,
		schedule.value.nextClientId--,
		shift.id,
		start,
		end,
		new Set(),
	);
	schedule.value.shiftSlots.set(slot.id, slot);
	shift.slotIds.add(slot.id);
	newShiftName.value = "";
}

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 shiftSlots = computed(() => {
	const data: (ShiftSlot | Gap)[] = [];
	for (const shift of schedule.value.shifts.values()) {
		if (props.roleId !== undefined && shift.roleId !== props.roleId)
			continue;
		for (const slot of shift.slots.values()) {
			if (props.shiftSlotFilter && !props.shiftSlotFilter(slot))
				continue;
			data.push({
				type: "slot",
				id: slot.id,
				deleted: slot.deleted || shift.deleted,
				shift,
				slot,
				name: shift.name,
				roleId: shift.roleId,
				assigned: slot.assigned,
				start: slot.start,
				end: slot.end,
			});
		}
	}
	const byTime = (a: DateTime, b: DateTime) => a.toMillis() - b.toMillis();
	data.sort((a, b) => byTime(a.start, b.start) || byTime(a.end, b.end));

	// 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>
