(function () {
	'use strict';

	/*
	 * Change this to false after testing is complete.
	 */
	const DEBUG = false;

	/**
	 * Output a development log message.
	 *
	 * @param {string} message Log message.
	 * @param {*} data Optional supporting data.
	 *
	 * @returns {void}
	 */
	function debug(message, data) {
		if (!DEBUG) {
			return;
		}

		if (typeof data === 'undefined') {
			console.log(
				'TutorHelper Availability Planner: '
					+ message
			);

			return;
		}

		console.log(
			'TutorHelper Availability Planner: '
				+ message,
			data
		);
	}

	/**
	 * Output a warning message.
	 *
	 * @param {string} message Warning message.
	 * @param {*} data Optional supporting data.
	 *
	 * @returns {void}
	 */
	function warn(message, data) {
		if (typeof data === 'undefined') {
			console.warn(
				'TutorHelper Availability Planner: '
					+ message
			);

			return;
		}

		console.warn(
			'TutorHelper Availability Planner: '
				+ message,
			data
		);
	}

	/**
	 * Run an initialiser without preventing other scripts from loading.
	 *
	 * @param {Function} callback Initialiser callback.
	 * @param {string} name Component name.
	 *
	 * @returns {void}
	 */
	function safelyInitialise(callback, name) {
		try {
			const initialised = callback();

			if (initialised === false) {
				warn(
					name
						+ ' was not initialised because '
						+ 'required markup was missing.'
				);

				return;
			}

			debug(
				name
					+ ' initialised successfully.'
			);
		} catch (error) {
			console.error(
				'TutorHelper: failed to initialise '
					+ name
					+ '.',
				error
			);
		}
	}

	/**
	 * Parse a Y-m-d value into a UTC timestamp.
	 *
	 * @param {string} value Date in Y-m-d format.
	 *
	 * @returns {number|null}
	 */
	function getDateTimestamp(value) {
		if (typeof value !== 'string') {
			return null;
		}

		const parts = value.split('-');

		if (parts.length !== 3) {
			return null;
		}

		const year = Number(parts[0]);
		const month = Number(parts[1]);
		const day = Number(parts[2]);

		if (
			!Number.isInteger(year)
			|| !Number.isInteger(month)
			|| !Number.isInteger(day)
		) {
			return null;
		}

		const timestamp = Date.UTC(
			year,
			month - 1,
			day
		);

		const parsedDate = new Date(timestamp);

		if (
			parsedDate.getUTCFullYear() !== year
			|| parsedDate.getUTCMonth() !== month - 1
			|| parsedDate.getUTCDate() !== day
		) {
			return null;
		}

		return timestamp;
	}

	/**
	 * Format a Y-m-d value for display.
	 *
	 * @param {string} value Date in Y-m-d format.
	 *
	 * @returns {string}
	 */
	function formatRangeDate(value) {
		const timestamp = getDateTimestamp(value);

		if (timestamp === null) {
			return value;
		}

		return new Intl.DateTimeFormat(
			document.documentElement.lang
				|| 'en-GB',
			{
				weekday: 'short',
				day: 'numeric',
				month: 'short',
				year: 'numeric',
				timeZone: 'UTC'
			}
		).format(
			new Date(timestamp)
		);
	}

	/**
	 * Initialise the twelve-month Availability planner.
	 *
	 * @returns {boolean}
	 */
	function initialiseAvailabilityRangeSelection() {
		debug('Starting initialisation.');

		/*
		 * Find the primary planner controls first. These are required
		 * before any child controls can be queried from the form.
		 */
		const openButton = document.getElementById(
			'tutorhelper-start-range-selection'
		);

		const panel = document.getElementById(
			'tutorhelper-range-selection-panel'
		);

		const closeButton = document.getElementById(
			'tutorhelper-close-range-selection'
		);

		const clearButton = document.getElementById(
			'tutorhelper-clear-range-selection'
		);

		const submitButton = document.getElementById(
			'tutorhelper-save-range-selection'
		);

		const startInput = document.getElementById(
			'tutorhelper-range-start'
		);

		const endInput = document.getElementById(
			'tutorhelper-range-end'
		);

		const startOutput = document.getElementById(
			'tutorhelper-selected-start-date'
		);

		const endOutput = document.getElementById(
			'tutorhelper-selected-end-date'
		);

		const countOutput = document.getElementById(
			'tutorhelper-selected-day-count'
		);

		const workingDayFields = document.getElementById(
			'tutorhelper-working-day-fields'
		);

		const dateButtons = Array.from(
			document.querySelectorAll(
				'.tutorhelper-year-calendar__day[data-date]'
			)
		);

		/*
		 * Clear and Close are optional controls. Their absence must not
		 * prevent the main planner from loading.
		 */
		if (
			!openButton
			|| !panel
			|| !submitButton
			|| !startInput
			|| !endInput
			|| dateButtons.length === 0
		) {
			warn(
				'Required planner markup was not found.',
				{
					openButton: Boolean(openButton),
					panel: Boolean(panel),
					submitButton: Boolean(submitButton),
					startInput: Boolean(startInput),
					endInput: Boolean(endInput),
					dateButtonCount: dateButtons.length
				}
			);

			return false;
		}

		if (!panel.matches('form')) {
			warn(
				'The range-selection panel is not a form.',
				panel
			);

			return false;
		}

		const rangeForm = panel;

		/*
		 * Form-specific controls can safely be queried only after the
		 * form itself has been validated and assigned to rangeForm.
		 */
		const selectionSummary = rangeForm.querySelector(
			'.tutorhelper-range-selection__summary'
		);

		const typeInputs = Array.from(
			rangeForm.querySelectorAll(
				'input[name="tutorhelper_range_type"]'
			)
		);

		const editDateFields = document.getElementById(
			'tutorhelper-range-edit-dates'
		);

		const editStartDateInput = document.getElementById(
			'tutorhelper-range-edit-start'
		);

		const editEndDateInput = document.getElementById(
			'tutorhelper-range-edit-end'
		);

		const modeInput = document.getElementById(
			'tutorhelper-range-mode'
		);

		const recordTypeInput = document.getElementById(
			'tutorhelper-range-record-type'
		);

		const recordIdInput = document.getElementById(
			'tutorhelper-range-record-id'
		);

		const titleInput = document.getElementById(
			'tutorhelper-range-title'
		);

		const startTimeInput = document.getElementById(
			'tutorhelper-range-start-time'
		);

		const endTimeInput = document.getElementById(
			'tutorhelper-range-end-time'
		);

		const locationDetailsInput = document.getElementById(
			'tutorhelper-range-location-details'
		);

		const panelTitle = document.getElementById(
			'tutorhelper-range-selection-title'
		);

		const weekdayCheckboxes = workingDayFields
			? Array.from(
				workingDayFields.querySelectorAll(
					'input[name="weekdays[]"]'
				)
			)
			: [];

		const locationCheckboxes = workingDayFields
			? Array.from(
				workingDayFields.querySelectorAll(
					'input[name="location_types[]"]'
				)
			)
			: [];

		const bookingTypeCheckboxes = workingDayFields
			? Array.from(
				workingDayFields.querySelectorAll(
					'input[name="allowed_booking_type_ids[]"]'
				)
			)
			: [];

		const primaryLocation = workingDayFields
			? workingDayFields.querySelector(
				'select[name="location_type"]'
			)
			: null;

		const itemChooser = document.getElementById(
			'tutorhelper-calendar-item-chooser'
		);

		const itemChooserOptions = document.getElementById(
			'tutorhelper-calendar-item-chooser-options'
		);

		const itemChooserDate = document.getElementById(
			'tutorhelper-calendar-item-chooser-date'
		);

		const itemChooserCloseButtons = itemChooser
			? Array.from(
				itemChooser.querySelectorAll(
					'[data-tutorhelper-close-chooser]'
				)
			)
			: [];

		const deleteButton = document.getElementById(
			'tutorhelper-delete-range'
		);

		const workingDayCalendarLink = document.getElementById(
			'tutorhelper-working-day-calendar-link'
		);

		const workingDayCalendarEdit = document.getElementById(
			'tutorhelper-working-day-calendar-edit'
		);

		debug(
			'Element check.',
			{
				openButton: Boolean(openButton),
				panel: Boolean(panel),
				closeButton: Boolean(closeButton),
				clearButton: Boolean(clearButton),
				submitButton: Boolean(submitButton),
				startInput: Boolean(startInput),
				endInput: Boolean(endInput),
				rangeForm: Boolean(rangeForm),
				workingDayFields: Boolean(workingDayFields),
				selectionSummary: Boolean(selectionSummary),
				editDateFields: Boolean(editDateFields),
				deleteButton: Boolean(deleteButton),
				workingDayCalendarLink: Boolean(workingDayCalendarLink),
				dateButtonCount: dateButtons.length
			}
		);

		let isSelecting = false;
		let startDate = '';
		let endDate = '';

		/**
		 * Return the selected planner range type.
		 *
		 * @returns {string}
		 */
		function getSelectedType() {
			const selected = rangeForm.querySelector(
				'input[name="tutorhelper_range_type"]:checked'
			);

			return selected
				? selected.value
				: 'general_availability';
		}

		/**
		 * Return currently selected weekday values.
		 *
		 * @returns {string[]}
		 */
		function getCheckedWeekdays() {
			return weekdayCheckboxes
				.filter(function (checkbox) {
					return checkbox.checked;
				})
				.map(function (checkbox) {
					return checkbox.value;
				});
		}

		/**
		 * Return currently selected location values.
		 *
		 * @returns {string[]}
		 */
		function getCheckedLocations() {
			return locationCheckboxes
				.filter(function (checkbox) {
					return checkbox.checked;
				})
				.map(function (checkbox) {
					return checkbox.value;
				});
		}

		/**
		 * Return the inclusive number of selected dates.
		 *
		 * @returns {number}
		 */
		function getSelectedDayCount() {
			const startTimestamp = getDateTimestamp(startDate);
			const endTimestamp = getDateTimestamp(endDate);

			if (
				startTimestamp === null
				|| endTimestamp === null
			) {
				return 0;
			}

			return Math.floor(
				(endTimestamp - startTimestamp)
				/ 86400000
			) + 1;
		}

		/**
		 * Remove temporary date-selection classes.
		 *
		 * @returns {void}
		 */
		function clearDayStates() {
			dateButtons.forEach(function (button) {
				button.classList.remove(
					'is-range-start',
					'is-range-end',
					'is-in-selected-range',
					'is-preview-general_availability',
					'is-preview-outside_term_time',
					'is-preview-tutor_leave',
					'is-preview-public_holiday'
				);

				button.setAttribute(
					'aria-pressed',
					'false'
				);
			});
		}

		/**
		 * Show Working day fields only for Working day ranges.
		 *
		 * @returns {void}
		 */
		function updateRangeTypeFields() {
			if (!workingDayFields) {
				return;
			}

			const isWorkingDay =
				getSelectedType()
					=== 'general_availability';

			workingDayFields.hidden = !isWorkingDay;

			workingDayFields
				.querySelectorAll(
					'input, select, textarea'
				)
				.forEach(function (field) {
					field.disabled = !isWorkingDay;
				});

			debug(
				'Range type updated.',
				{
					type: getSelectedType(),
					workingDayFieldsVisible: isWorkingDay
				}
			);
		}

		/**
		 * Keep the primary location within the selected available
		 * locations.
		 *
		 * @returns {void}
		 */
		function synchronisePrimaryLocation() {
			if (!primaryLocation) {
				return;
			}

			const checkedLocations = getCheckedLocations();

			Array.from(
				primaryLocation.options
			).forEach(function (option) {
				option.disabled =
					!checkedLocations.includes(
						option.value
					);
			});

			if (
				!checkedLocations.includes(
					primaryLocation.value
				)
			) {
				primaryLocation.value =
					checkedLocations.length > 0
						? checkedLocations[0]
						: '';
			}

			debug(
				'Primary location synchronised.',
				{
					availableLocations: checkedLocations,
					primaryLocation: primaryLocation.value
				}
			);
		}

		/**
		 * Update date outputs and calendar preview classes.
		 *
		 * @returns {void}
		 */
		function updateSelectionDisplay() {
			clearDayStates();

			const startTimestamp = getDateTimestamp(startDate);
			const endTimestamp = getDateTimestamp(endDate);
			const selectedType = getSelectedType();

			startInput.value = startDate;
			endInput.value = endDate;

			if (startOutput) {
				startOutput.textContent = startDate
					? formatRangeDate(startDate)
					: 'Not selected';
			}

			if (endOutput) {
				endOutput.textContent = endDate
					? formatRangeDate(endDate)
					: 'Not selected';
			}

			if (countOutput) {
				countOutput.textContent = String(
					getSelectedDayCount()
				);
			}

			if (clearButton) {
				clearButton.disabled = startDate === '';
			}

			submitButton.disabled =
				startDate === ''
				|| endDate === '';

			dateButtons.forEach(function (button) {
				const dateValue = button.dataset.date;
				const timestamp = getDateTimestamp(dateValue);

				if (timestamp === null) {
					return;
				}

				if (dateValue === startDate) {
					button.classList.add(
						'is-range-start'
					);
				}

				if (dateValue === endDate) {
					button.classList.add(
						'is-range-end'
					);
				}

				const isSelected =
					startTimestamp !== null
					&& (
						endTimestamp === null
							? timestamp === startTimestamp
							: (
								timestamp >= startTimestamp
								&& timestamp <= endTimestamp
							)
					);

				if (!isSelected) {
					return;
				}

				button.classList.add(
					'is-in-selected-range',
					'is-preview-' + selectedType
				);

				button.setAttribute(
					'aria-pressed',
					'true'
				);
			});

			debug(
				'Selection display updated.',
				{
					type: selectedType,
					startDate: startDate,
					endDate: endDate,
					dayCount: getSelectedDayCount(),
					submitDisabled: submitButton.disabled
				}
			);
		}

		/**
		 * Reset the current selected range.
		 *
		 * @returns {void}
		 */
		function resetSelection() {
			startDate = '';
			endDate = '';

			updateSelectionDisplay();

			debug('Date selection cleared.');
		}

		/**
		 * Validate the Working day fields.
		 *
		 * @returns {boolean}
		 */
		function validateWorkingDay() {
			if (
				getSelectedType()
					!== 'general_availability'
			) {
				return true;
			}

			if (!workingDayFields) {
				window.alert(
					'Working day fields could not be loaded.'
				);

				return false;
			}

			const selectedWeekdays = getCheckedWeekdays();

			const startTime = workingDayFields.querySelector(
				'input[name="start_time"]'
			);

			const endTime = workingDayFields.querySelector(
				'input[name="end_time"]'
			);

			const selectedLocations = getCheckedLocations();

			if (selectedWeekdays.length === 0) {
				window.alert('Select at least one weekday.');
				return false;
			}

			if (
				!startTime
				|| !endTime
				|| startTime.value === ''
				|| endTime.value === ''
				|| endTime.value <= startTime.value
			) {
				window.alert(
					'Enter a valid start and end time.'
				);

				return false;
			}

			if (selectedLocations.length === 0) {
				window.alert(
					'Select at least one available location.'
				);

				return false;
			}

			if (
				!primaryLocation
				|| !selectedLocations.includes(
					primaryLocation.value
				)
			) {
				window.alert(
					'Choose a primary location from the '
						+ 'selected available locations.'
				);

				return false;
			}

			return true;
		}

		/**
		 * Reset record metadata to normal create mode.
		 *
		 * @returns {void}
		 */
		function enterCreateMode() {
			if (modeInput) {
				modeInput.value = 'create';
			}

			if (recordTypeInput) {
				recordTypeInput.value = '';
			}

			if (recordIdInput) {
				recordIdInput.value = '0';
			}

			if (deleteButton) {
				deleteButton.hidden = true;
			}

			if (workingDayCalendarLink) {
				workingDayCalendarLink.hidden = true;
			}

			if (panelTitle) {
				panelTitle.textContent =
					'Add a date range';
			}
						if (selectionSummary) {
				selectionSummary.hidden = false;
			}

			if (editDateFields) {
				editDateFields.hidden = true;
			}

			if (submitButton) {
				submitButton.textContent = 'Save date range';
			}

			typeInputs.forEach(function (input) {
				input.disabled = false;
			});

			debug('Planner switched to create mode.');
		}

		/**
		 * Close the calendar item chooser.
		 *
		 * @returns {void}
		 */
		function closeItemChooser() {
			if (!itemChooser) {
				return;
			}

			itemChooser.hidden = true;

			if (itemChooserOptions) {
				itemChooserOptions.innerHTML = '';
			}

			debug('Calendar item chooser closed.');
		}

		/**
		 * Show the item chooser for a calendar date.
		 *
		 * @param {string} date Calendar date.
		 * @param {Object[]} items Editable items.
		 *
		 * @returns {void}
		 */
		function openItemChooser(date, items) {
			if (
				!itemChooser
				|| !itemChooserOptions
			) {
				warn(
					'Calendar item chooser markup is missing.'
				);

				return;
			}

			itemChooserOptions.innerHTML = '';

			if (itemChooserDate) {
				itemChooserDate.textContent = formatRangeDate(date);
			}

			items.forEach(function (item) {
				const button = document.createElement('button');
				button.type = 'button';
				button.className =
					'button '
					+ 'tutorhelper-calendar-item-chooser__option';

				const title = item.title
					? item.title
					: 'Calendar entry';

				let typeLabel = item.range_type
					? item.range_type
					: '';

				switch (item.range_type) {
					case 'general_availability':
						typeLabel = 'Working day';
						break;

					case 'outside_term_time':
						typeLabel = 'Outside term time';
						break;

					case 'tutor_leave':
						typeLabel = 'Tutor on leave';
						break;

					case 'public_holiday':
						typeLabel = 'Public holiday';
						break;
				}

				button.textContent =
					typeLabel
					+ ' — '
					+ title;

				button.addEventListener(
					'click',
					function () {
						closeItemChooser();
						loadCalendarItem(item);
					}
				);

				itemChooserOptions.appendChild(button);
			});

			itemChooser.hidden = false;

			const firstOption = itemChooserOptions.querySelector(
				'button'
			);

			if (firstOption) {
				firstOption.focus();
			}

			debug(
				'Calendar item chooser opened.',
				items
			);
		}

		/**
		 * Switch the planner form into edit mode.
		 *
		 * @param {Object} data Loaded record data.
		 *
		 * @returns {void}
		 */
		function enterEditMode(data) {
			if (modeInput) {
				modeInput.value = 'edit';
			}

			if (recordTypeInput) {
				recordTypeInput.value = data.record_type || '';
			}

			if (recordIdInput) {
				recordIdInput.value = String(
					data.record_id || 0
				);
			}

			if (deleteButton) {
				deleteButton.hidden = false;
			}

			const isWorkingDay =
				data.record_type === 'availability';

			if (panelTitle) {
				panelTitle.textContent =
					isWorkingDay
						? 'Edit recurring Working Day'
						: 'Edit date range';
			}

			if (selectionSummary) {
				selectionSummary.hidden = true;
			}

			if (editDateFields) {
				editDateFields.hidden = false;
			}

			if (editStartDateInput) {
				editStartDateInput.value = data.start_date || '';
			}

			if (editEndDateInput) {
				editEndDateInput.value = data.end_date || '';
			}

			if (submitButton) {
				submitButton.textContent =
					isWorkingDay
						? 'Save recurring range'
						: 'Save changes';
			}

			

			if (workingDayCalendarLink) {
				workingDayCalendarLink.hidden =
					!isWorkingDay
					|| !data.occurrence_date;
			}

			if (
				isWorkingDay
				&& data.occurrence_date
				&& workingDayCalendarEdit
				&& typeof tutorhelperAvailabilityPlanner
					!== 'undefined'
				&& tutorhelperAvailabilityPlanner
					.calendarEditUrl
			) {
				const calendarUrl = new URL(
					tutorhelperAvailabilityPlanner.calendarEditUrl,
					window.location.origin
				);

				calendarUrl.searchParams.set(
					'availability_id',
					String(data.record_id)
				);

				calendarUrl.searchParams.set(
					'occurrence_date',
					data.occurrence_date
				);

				workingDayCalendarEdit.href =
					calendarUrl.toString();
			}

			/*
			 * Do not allow a stored record to change type.
			 */
			typeInputs.forEach(function (input) {
				input.disabled =
					input.value !== data.range_type;
			});

			debug(
				'Planner switched to edit mode.',
				{
					recordType: data.record_type,
					recordId: data.record_id,
					rangeType: data.range_type,
					occurrenceDate: data.occurrence_date || ''
				}
			);
		}

		/**
		 * Load one planner record from WordPress.
		 *
		 * @param {Object} item Calendar edit-item metadata.
		 *
		 * @returns {void}
		 */
		function loadCalendarItem(item) {
			if (
				typeof tutorhelperAvailabilityPlanner === 'undefined'
				|| !tutorhelperAvailabilityPlanner.ajaxUrl
				|| !tutorhelperAvailabilityPlanner.loadItemAction
				|| !tutorhelperAvailabilityPlanner.loadItemNonce
			) {
				window.alert(
					'The calendar entry could not be loaded.'
				);

				warn(
					'Availability Planner AJAX configuration is missing.'
				);

				return;
			}

			const requestData = new URLSearchParams();

			requestData.append(
				'action',
				tutorhelperAvailabilityPlanner.loadItemAction
			);

			requestData.append(
				'nonce',
				tutorhelperAvailabilityPlanner.loadItemNonce
			);

			requestData.append(
				'record_type',
				item.record_type
			);

			requestData.append(
				'record_id',
				String(item.record_id)
			);

			debug(
				'Loading calendar item.',
				item
			);

			fetch(
				tutorhelperAvailabilityPlanner.ajaxUrl,
				{
					method: 'POST',
					headers: {
						'Content-Type':
							'application/x-www-form-urlencoded; charset=UTF-8'
					},
					body: requestData.toString()
				}
			)
				.then(function (response) {
					return response.json();
				})
				.then(function (response) {
					if (
						!response
						|| !response.success
						|| !response.data
					) {
						const message =
							response
							&& response.data
							&& response.data.message
								? response.data.message
								: 'The calendar entry could not be loaded.';

						throw new Error(message);
					}

					const loadedData = response.data;
					loadedData.occurrence_date =
						item.occurrence_date || '';

					populateEditForm(loadedData);
				})
				.catch(function (error) {
					console.error(
						'TutorHelper: calendar item load failed.',
						error
					);

					window.alert(
						error.message
							|| 'The calendar entry could not be loaded.'
					);
				});
		}

		/**
		 * Populate the Availability Planner form for editing.
		 *
		 * @param {Object} data Loaded planner record.
		 *
		 * @returns {void}
		 */
		function populateEditForm(data) {
			if (
				!data
				|| !data.record_type
				|| !data.record_id
				|| !data.range_type
			) {
				warn(
					'Loaded calendar item is incomplete.',
					data
				);

				return;
			}

			typeInputs.forEach(function (input) {
				input.checked =
					input.value === data.range_type;
			});

			if (titleInput) {
				titleInput.value = data.title || '';
			}

			startDate = data.start_date || '';
			endDate = data.end_date || '';

			weekdayCheckboxes.forEach(function (checkbox) {
				checkbox.checked = false;
			});

			locationCheckboxes.forEach(function (checkbox) {
				checkbox.checked = false;
			});

			bookingTypeCheckboxes.forEach(function (checkbox) {
				checkbox.checked = false;
			});

			if (
				data.range_type === 'general_availability'
			) {
				const weekdays = Array.isArray(data.weekdays)
					? data.weekdays
					: [];

				weekdayCheckboxes.forEach(function (checkbox) {
					checkbox.checked = weekdays.includes(
						checkbox.value
					);
				});

				if (startTimeInput) {
					startTimeInput.value = data.start_time || '';
				}

				if (endTimeInput) {
					endTimeInput.value = data.end_time || '';
				}

				const locations = Array.isArray(data.location_types)
					? data.location_types
					: [];

				locationCheckboxes.forEach(function (checkbox) {
					checkbox.checked = locations.includes(
						checkbox.value
					);
				});

				if (primaryLocation) {
					primaryLocation.value = data.location_type || '';
				}

				if (locationDetailsInput) {
					locationDetailsInput.value =
						data.location_details || '';
				}

				const bookingTypeIds = Array.isArray(
					data.allowed_booking_type_ids
				)
					? data.allowed_booking_type_ids.map(
						function (value) {
							return Number(value);
						}
					)
					: [];

				bookingTypeCheckboxes.forEach(function (checkbox) {
					checkbox.checked = bookingTypeIds.includes(
						Number(checkbox.value)
					);
				});
			}

			enterEditMode(data);

			/*
			 * Editing an existing item is not calendar range-selection
			 * mode. The stored dates are loaded directly.
			 */
			isSelecting = false;
			panel.hidden = false;

			openButton.setAttribute(
				'aria-expanded',
				'true'
			);

			updateRangeTypeFields();
			synchronisePrimaryLocation();
			updateSelectionDisplay();

			panel.scrollIntoView({
				behavior: 'smooth',
				block: 'nearest'
			});

			if (titleInput) {
				titleInput.focus({
					preventScroll: true
				});
			}

			debug(
				'Existing calendar record loaded into form.',
				data
			);
		}

		/**
		 * Return editable records attached to one calendar button.
		 *
		 * @param {HTMLElement} button Calendar day button.
		 *
		 * @returns {Object[]}
		 */
		function getCalendarEditItems(button) {
			if (
				!button
				|| !button.dataset.editItems
			) {
				return [];
			}

			try {
				const items = JSON.parse(
					button.dataset.editItems
				);

				return Array.isArray(items)
					? items
					: [];
			} catch (error) {
				warn(
					'Calendar edit-item data could not be parsed.',
					error
				);

				return [];
			}
		}

		/*
		 * Item chooser for multiple instances in a day.
		 */
		itemChooserCloseButtons.forEach(function (button) {
			button.addEventListener(
				'click',
				closeItemChooser
			);
		});

		if (editStartDateInput) {
			editStartDateInput.addEventListener(
				'change',
				function () {
					startDate = editStartDateInput.value;
					updateSelectionDisplay();
				}
			);
		}

		if (editEndDateInput) {
			editEndDateInput.addEventListener(
				'change',
				function () {
					endDate = editEndDateInput.value;
					updateSelectionDisplay();
				}
			);
		}

		if (deleteButton) {
			deleteButton.addEventListener(
				'click',
				function (event) {
					if (
						!window.confirm(
							'Delete this complete date range?'
						)
					) {
						event.preventDefault();
					}
				}
			);
		}

		/*
		 * Open the date-range panel.
		 */
		openButton.addEventListener(
			'click',
			function () {
				debug('Add date range button clicked.');

				enterCreateMode();
				isSelecting = true;
				panel.hidden = false;

				openButton.setAttribute(
					'aria-expanded',
					'true'
				);

				updateRangeTypeFields();
				synchronisePrimaryLocation();
				updateSelectionDisplay();

				panel.scrollIntoView({
					behavior: 'smooth',
					block: 'nearest'
				});

				const selectedTypeInput = typeInputs.find(
					function (input) {
						return input.checked;
					}
				);

				if (selectedTypeInput) {
					selectedTypeInput.focus({
						preventScroll: true
					});
				}
			}
		);

		/*
		 * Close the date-range panel.
		 */
		if (closeButton) {
			closeButton.addEventListener(
				'click',
				function () {
					debug('Close button clicked.');

					isSelecting = false;
					panel.hidden = true;

					openButton.setAttribute(
						'aria-expanded',
						'false'
					);

					resetSelection();
					enterCreateMode();
					openButton.focus();
				}
			);
		}

		/*
		 * Clear the selected date range.
		 */
		if (clearButton) {
			clearButton.addEventListener(
				'click',
				function () {
					debug('Clear selection clicked.');
					resetSelection();
				}
			);
		}

		/*
		 * Select start and end dates from the calendar, or load an
		 * existing record when selection mode is closed.
		 */
		dateButtons.forEach(function (button) {
			button.addEventListener(
				'click',
				function () {
					if (!isSelecting) {
						const editItems = getCalendarEditItems(button);

						debug(
							'Calendar date clicked outside range-selection mode.',
							{
								date: button.dataset.date,
								itemCount: editItems.length,
								items: editItems
							}
						);

						if (editItems.length === 0) {
							return;
						}

						if (editItems.length === 1) {
							loadCalendarItem(editItems[0]);
							return;
						}

						openItemChooser(
							button.dataset.date,
							editItems
						);

						return;
					}

					const selectedDate = button.dataset.date;

					if (
						!selectedDate
						|| getDateTimestamp(selectedDate) === null
					) {
						warn(
							'Invalid calendar date button.',
							button
						);

						return;
					}

					if (
						startDate === ''
						|| endDate !== ''
					) {
						startDate = selectedDate;
						endDate = '';
					} else {
						const startTimestamp = getDateTimestamp(
							startDate
						);

						const selectedTimestamp = getDateTimestamp(
							selectedDate
						);

						if (
							startTimestamp === null
							|| selectedTimestamp === null
						) {
							return;
						}

						if (selectedTimestamp < startTimestamp) {
							endDate = startDate;
							startDate = selectedDate;
						} else {
							endDate = selectedDate;
						}
					}

					updateSelectionDisplay();

					debug(
						'Calendar date selected.',
						{
							clickedDate: selectedDate,
							startDate: startDate,
							endDate: endDate,
							weekdays: getCheckedWeekdays()
						}
					);
				}
			);
		});

		/*
		 * Update conditional fields and preview colours when the range
		 * type changes.
		 */
		typeInputs.forEach(function (input) {
			input.addEventListener(
				'change',
				function () {
					updateRangeTypeFields();
					updateSelectionDisplay();
				}
			);
		});

		weekdayCheckboxes.forEach(function (checkbox) {
			checkbox.addEventListener(
				'change',
				function () {
					debug(
						'Working day weekdays manually changed.',
						getCheckedWeekdays()
					);
				}
			);
		});

		locationCheckboxes.forEach(function (checkbox) {
			checkbox.addEventListener(
				'change',
				synchronisePrimaryLocation
			);
		});

		/*
		 * Validate and log the exact values that will be submitted.
		 */
		rangeForm.addEventListener(
			'submit',
			function (event) {
				/*
				 * Delete uses the same form but its own admin-post action.
				 * Do not block it with save-only field validation.
				 */
				if (
					deleteButton
					&& event.submitter === deleteButton
				) {
					debug('Delete range submission confirmed.');
					return;
				}

				const submittedFormData = new FormData(rangeForm);

				debug(
					'Form values before submission.',
					{
						rangeType: submittedFormData.get(
							'tutorhelper_range_type'
						),
						title: submittedFormData.get('title'),
						startDate: submittedFormData.get('start_date'),
						endDate: submittedFormData.get('end_date'),
						weekdays: submittedFormData.getAll('weekdays[]'),
						startTime: submittedFormData.get('start_time'),
						endTime: submittedFormData.get('end_time'),
						locations: submittedFormData.getAll('location_types[]'),
						primaryLocation: submittedFormData.get('location_type'),
						bookingTypeIds: submittedFormData.getAll(
							'allowed_booking_type_ids[]'
						)
					}
				);

				if (
					startDate === ''
					|| endDate === ''
				) {
					event.preventDefault();

					window.alert(
						'Select a start and end date.'
					);

					warn(
						'Submission prevented because '
							+ 'the range is incomplete.'
					);

					return;
				}

				const startTimestamp = getDateTimestamp(startDate);
				const endTimestamp = getDateTimestamp(endDate);

				if (
					startTimestamp === null
					|| endTimestamp === null
					|| endTimestamp < startTimestamp
				) {
					event.preventDefault();
					window.alert(
						'The end date must not be before the start date.'
					);
					return;
				}

				if (!validateWorkingDay()) {
					event.preventDefault();

					warn(
						'Submission prevented by '
							+ 'Working day validation.'
					);

					return;
				}

				debug(
					'Client-side validation passed. '
						+ 'Form will submit.'
				);
			}
		);

		/*
		 * Initial component state.
		 */
		openButton.setAttribute(
			'aria-expanded',
			'false'
		);

		enterCreateMode();
		updateRangeTypeFields();
		synchronisePrimaryLocation();
		updateSelectionDisplay();

		debug(
			'Planner listeners attached.',
			{
				typeInputCount: typeInputs.length,
				dateButtonCount: dateButtons.length,
				weekdayCheckboxCount: weekdayCheckboxes.length,
				locationCheckboxCount: locationCheckboxes.length
			}
		);

		return true;
	}

	/**
	 * Initialise all Availability planner components.
	 *
	 * @returns {void}
	 */
	function initialiseAvailabilityPlanner() {
		safelyInitialise(
			initialiseAvailabilityRangeSelection,
			'availability range selection'
		);
	}

	if (document.readyState === 'loading') {
		document.addEventListener(
			'DOMContentLoaded',
			initialiseAvailabilityPlanner
		);
	} else {
		initialiseAvailabilityPlanner();
	}
})();
