(function () {
	'use strict';

	/**
	 * Return the Add Booking configuration.
	 *
	 * @returns {Object|null}
	 */
	function getSettings() {
		if (
			typeof window.tutorhelperTutorBooking === 'undefined'
		) {
			return null;
		}

		return window.tutorhelperTutorBooking;
	}

	/**
	 * Escape text before inserting it into generated HTML.
	 *
	 * @param {string} value Text value.
	 *
	 * @returns {string}
	 */
	function escapeHtml(value) {
		const element = document.createElement('div');

		element.textContent = value || '';

		return element.innerHTML;
	}

	/**
	 * Initialise searchable TutorHelper student selection.
	 */
	function initialiseStudentSearch() {
		const wrapper = document.querySelector(
			'[data-student-search]'
		);

		const settings = getSettings();

		if (
			!wrapper
			|| !settings
			|| !settings.studentSearch
			|| typeof window.jQuery === 'undefined'
		) {
			return;
		}

		const $ = window.jQuery;

		const searchSettings =
			settings.studentSearch;

		const input = wrapper.querySelector(
			'[data-student-search-input]'
		);

		const valueInput = wrapper.querySelector(
			'[data-student-value]'
		);

		const results = wrapper.querySelector(
			'[data-student-results]'
		);

		const status = wrapper.querySelector(
			'[data-student-status]'
		);

		const selection = wrapper.querySelector(
			'[data-student-selection]'
		);

		const spinner = wrapper.querySelector(
			'[data-student-spinner]'
		);

		const form = wrapper.closest('form');

		if (
			!input
			|| !valueInput
			|| !results
			|| !selection
			|| !form
		) {
			return;
		}

		const minimumCharacters = Math.max(
			2,
			parseInt(
				searchSettings.minimumCharacters,
				10
			) || 2
		);

		const searchDelay = Math.max(
			150,
			parseInt(
				searchSettings.searchDelay,
				10
			) || 300
		);

		let searchTimer = null;
		let searchRequest = null;

		/**
		 * Update accessible status text.
		 *
		 * @param {string} message Status text.
		 * @param {boolean} isError Whether this is an error.
		 */
		function setStatus(message, isError) {
			if (!status) {
				return;
			}

			status.textContent = message || '';

			status.classList.toggle(
				'is-error',
				Boolean(isError)
			);
		}

		/**
		 * Set loading state.
		 *
		 * @param {boolean} loading Whether a request is active.
		 */
		function setLoading(loading) {
			if (spinner) {
				spinner.classList.toggle(
					'is-active',
					Boolean(loading)
				);
			}

			input.setAttribute(
				'aria-busy',
				loading ? 'true' : 'false'
			);
		}

		/**
		 * Close search results.
		 */
		function closeResults() {
			results.hidden = true;
			results.innerHTML = '';

			input.setAttribute(
				'aria-expanded',
				'false'
			);
		}

		/**
		 * Clear the selected student.
		 */
		function clearSelection() {
			valueInput.value = '';

			valueInput.dispatchEvent(
				new Event(
					'change',
					{
						bubbles: true
					}
				)
			);

			selection.innerHTML =
				'<p class="'
				+ 'tutorhelper-booking-student-search__empty'
				+ '">No student selected.</p>';

			input.value = '';
			input.disabled = false;
			input.setCustomValidity('');
			input.focus();

			setStatus('', false);
		}

		/**
		 * Display the selected student.
		 *
		 * @param {Object} student Selected student.
		 */
		function selectStudent(student) {
			const studentId = parseInt(
				student.id,
				10
			);

			if (
				!Number.isInteger(studentId)
				|| studentId < 1
			) {
				return;
			}

			valueInput.value = String(studentId);

			valueInput.dispatchEvent(
				new Event(
					'change',
					{
						bubbles: true
					}
				)
			);

			selection.innerHTML =
				'<div class="'
				+ 'tutorhelper-booking-student-search__selected'
				+ '">'
				+ '<div>'
				+ '<strong>'
				+ escapeHtml(student.name)
				+ '</strong>'
				+ '<span>'
				+ escapeHtml(student.email)
				+ '</span>'
				+ '<small>'
				+ escapeHtml(student.username)
				+ '</small>'
				+ '</div>'
				+ '<button type="button" '
				+ 'class="button" '
				+ 'data-clear-student>'
				+ escapeHtml(searchSettings.change)
				+ '</button>'
				+ '</div>';

			const clearButton = selection.querySelector(
				'[data-clear-student]'
			);

			if (clearButton) {
				clearButton.addEventListener(
					'click',
					clearSelection
				);
			}

			input.value = '';
			input.disabled = true;
			input.setCustomValidity('');

			closeResults();

			setStatus(
				searchSettings.selected,
				false
			);
		}

		/**
		 * Render AJAX search results.
		 *
		 * @param {Object[]} students Student results.
		 */
		function renderResults(students) {
			results.innerHTML = '';

			if (
				!Array.isArray(students)
				|| students.length === 0
			) {
				closeResults();

				setStatus(
					searchSettings.noResults,
					false
				);

				return;
			}

			students.forEach(function (student) {
				const button =
					document.createElement('button');

				button.type = 'button';

				button.className =
					'tutorhelper-booking-student-search__result';

				button.setAttribute(
					'role',
					'option'
				);

				button.innerHTML =
					'<strong>'
					+ escapeHtml(student.name)
					+ '</strong>'
					+ '<span>'
					+ escapeHtml(student.email)
					+ '</span>'
					+ '<small>'
					+ escapeHtml(student.username)
					+ '</small>';

				button.addEventListener(
					'click',
					function () {
						selectStudent(student);
					}
				);

				results.appendChild(button);
			});

			results.hidden = false;

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

			setStatus('', false);
		}

		/**
		 * Run the AJAX search.
		 *
		 * @param {string} term Search term.
		 */
		function searchStudents(term) {
			if (searchRequest) {
				searchRequest.abort();
			}

			setLoading(true);

			setStatus(
				searchSettings.searching,
				false
			);

			searchRequest = $.ajax({
				url: settings.ajaxUrl,
				method: 'POST',

				data: {
					action:
						searchSettings.action,

					nonce:
						searchSettings.nonce,

					search:
						term
				}
			})
				.done(function (response) {
					if (
						!response
						|| !response.success
						|| !response.data
					) {
						renderResults([]);

						return;
					}

					renderResults(
						response.data.students || []
					);
				})
				.fail(function (request, statusText) {
					if (statusText === 'abort') {
						return;
					}

					closeResults();

					setStatus(
						searchSettings.failed,
						true
					);
				})
				.always(function () {
					setLoading(false);
					searchRequest = null;
				});
		}

		input.addEventListener('input', function () {
			const term = input.value.trim();

			window.clearTimeout(searchTimer);

			if (term.length < minimumCharacters) {
				if (searchRequest) {
					searchRequest.abort();
				}

				closeResults();
				setStatus('', false);
				setLoading(false);

				return;
			}

			searchTimer = window.setTimeout(
				function () {
					searchStudents(term);
				},
				searchDelay
			);
		});

		input.addEventListener('keydown', function (event) {
			if (
				event.key === 'Escape'
				&& !results.hidden
			) {
				closeResults();
			}
		});

		document.addEventListener('click', function (event) {
			if (
				!results.hidden
				&& !wrapper.contains(event.target)
			) {
				closeResults();
			}
		});

		form.addEventListener('submit', function (event) {
			const studentId = parseInt(
				valueInput.value,
				10
			);

			if (
				!Number.isInteger(studentId)
				|| studentId < 1
			) {
				event.preventDefault();

				input.disabled = false;

				input.setCustomValidity(
					searchSettings.selectStudent
				);

				input.reportValidity();
				input.focus();

				return;
			}

			input.setCustomValidity('');
		});
	}

	/**
	 * Parse an H:i value into minutes after midnight.
	 *
	 * @param {string} value Time value.
	 *
	 * @returns {number|null}
	 */
	function parseTime(value) {
        if (
            typeof value !== 'string'
            || !/^\d{2}:\d{2}$/.test(value)
        ) {
            return null;
        }

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

        const hours = parseInt(parts[0], 10);
        const minutes = parseInt(parts[1], 10);

        if (
            !Number.isInteger(hours)
            || !Number.isInteger(minutes)
            || hours < 0
            || hours > 23
            || minutes < 0
            || minutes > 59
        ) {
            return null;
        }

        return (hours * 60) + minutes;
    }

	/**
	 * Format minutes after midnight as H:i.
	 *
	 * @param {number} totalMinutes Minutes after midnight.
	 *
	 * @returns {string}
	 */
	function formatTime(totalMinutes) {
		const normalised =
			((totalMinutes % 1440) + 1440) % 1440;

		const hours = Math.floor(
			normalised / 60
		);

		const minutes =
			normalised % 60;

		return String(hours).padStart(2, '0')
			+ ':'
			+ String(minutes).padStart(2, '0');
	}

	/**
	 * Format minor currency units.
	 *
	 * @param {number} minorUnits Amount in minor units.
	 *
	 * @returns {string}
	 */
	function formatPrice(minorUnits) {
		const settings = getSettings();

		if (!settings || !settings.currency) {
			return String(minorUnits);
		}

		const currency =
			settings.currency;

		const decimals = Math.max(
			0,
			parseInt(
				currency.decimals,
				10
			) || 0
		);

		const divisor =
			Math.pow(10, decimals);

		const number =
			(minorUnits / divisor).toLocaleString(
				document.documentElement.lang
					|| undefined,
				{
					minimumFractionDigits:
						decimals,

					maximumFractionDigits:
						decimals
				}
			);

		switch (currency.symbolPosition) {
			case 'before_space':
				return currency.symbol
					+ ' '
					+ number;

			case 'after':
				return number
					+ currency.symbol;

			case 'after_space':
				return number
					+ ' '
					+ currency.symbol;

			case 'before':
			default:
				return currency.symbol
					+ number;
		}
	}

	/**
	 * Initialise Booking Type suggestions and preview.
	 */
	function initialiseBookingPreview() {
		const form = document.querySelector(
			'.tutorhelper-tutor-booking__form'
		);

		if (!form) {
			return;
		}

        const isEditMode =
	        form.dataset.mode === 'edit';

		const bookingType = form.querySelector(
			'[name="booking_type_id"]'
		);

		const studentInput = form.querySelector(
			'[name="student_user_id"]'
		);

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

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

		const location = form.querySelector(
			'[name="location_type"]'
		);

		const locationDetails = form.querySelector(
			'[name="location_details"]'
		);

		const durationOutput = form.querySelector(
			'[data-booking-duration]'
		);

		const priceOutput = form.querySelector(
			'[data-booking-price]'
		);

		const colourOutput = form.querySelector(
			'[data-booking-colour]'
		);

		if (
            !bookingType
            || !startTime
            || !endTime
            || !durationOutput
            || !priceOutput
        ) {
            console.warn(
                'TutorHelper: Booking preview markup is incomplete.',
                {
                    bookingType: Boolean(bookingType),
                    startTime: Boolean(startTime),
                    endTime: Boolean(endTime),
                    durationOutput: Boolean(durationOutput),
                    priceOutput: Boolean(priceOutput)
                }
            );

            return;
        }

		let suggestedEndTime = '';
		let pricePreviewRequest = null;

        /*
        * Existing edit values are authoritative initial values. Booking Type
        * suggestions must not replace the saved location or details when the
        * form first loads.
        */
        let locationWasEdited = isEditMode;
        let detailsWereEdited = isEditMode;

		/**
		 * Return the selected Booking Type option.
		 *
		 * @returns {HTMLOptionElement|null}
		 */
		function getSelectedOption() {
			const option =
				bookingType.options[
					bookingType.selectedIndex
				];

			return option && option.value
				? option
				: null;
		}

		/**
		 * Suggest an end time when safe to do so.
		 */
		function suggestEndTime() {
			const option =
				getSelectedOption();

			const startMinutes =
				parseTime(startTime.value);

			if (
				!option
				|| startMinutes === null
			) {
				return;
			}

			const standardDuration = parseInt(
				option.dataset.duration,
				10
			);

			if (
				!Number.isInteger(standardDuration)
				|| standardDuration < 1
			) {
				return;
			}

			if (
				endTime.value !== ''
				&& endTime.value !== suggestedEndTime
			) {
				return;
			}

			suggestedEndTime = formatTime(
				startMinutes
					+ standardDuration
			);

			endTime.value =
				suggestedEndTime;
		}

		/**
		 * Apply safe Booking Type defaults.
		 */
		function applyBookingTypeDefaults() {
			const option =
				getSelectedOption();

			if (!option) {
				return;
			}

			suggestEndTime();

			if (
                location
                && !locationWasEdited
                && option.dataset.location
            ) {
                const allowedOption =
                    location.querySelector(
                        'option[value="'
                        + CSS.escape(
                            option.dataset.location
                        )
                        + '"]'
                    );

                if (allowedOption) {
                    location.value =
                        option.dataset.location;
                }
            }

			if (
				locationDetails
				&& !detailsWereEdited
			) {
				locationDetails.value =
					option.dataset.locationDetails
					|| '';
			}
		}

		/**
		 * Resolve the booking price using the server-side
		 * pricing engine.
		 *
		 * Core's locally calculated standard price is used as
		 * a safe fallback when the request is unavailable or fails.
		 *
		 * @param {number} studentId       Student WordPress user ID.
		 * @param {number} bookingTypeId   Booking Type ID.
		 * @param {number} duration        Booking duration in minutes.
		 * @param {number} fallbackPrice   Locally calculated Core price.
		 */
		function resolvePreviewPrice(
			studentId,
			bookingTypeId,
			duration,
			fallbackPrice
		) {
			const settings = getSettings();

			if (
				!settings
				|| !settings.pricePreview
				|| !settings.ajaxUrl
				|| typeof window.jQuery === 'undefined'
				|| !Number.isInteger(studentId)
				|| studentId < 1
				|| !Number.isInteger(bookingTypeId)
				|| bookingTypeId < 1
				|| !Number.isInteger(duration)
				|| duration < 1
			) {
				priceOutput.textContent =
					formatPrice(fallbackPrice);

				return;
			}

			if (pricePreviewRequest) {
				pricePreviewRequest.abort();
			}

			pricePreviewRequest = window.jQuery.ajax({
				url: settings.ajaxUrl,
				method: 'POST',

				data: {
					action:
						settings.pricePreview.action,

					nonce:
						settings.pricePreview.nonce,

					student_user_id:
						studentId,

					booking_type_id:
						bookingTypeId,

					duration_minutes:
						duration
				}
			})
				.done(function (response) {
					if (
						!response
						|| !response.success
						|| !response.data
					) {
						priceOutput.textContent =
							formatPrice(fallbackPrice);

						return;
					}

					const resolvedPrice = parseInt(
						response.data.priceMinorUnits,
						10
					);

					if (
						!Number.isInteger(resolvedPrice)
						|| resolvedPrice < 0
					) {
						priceOutput.textContent =
							formatPrice(fallbackPrice);

						return;
					}

					priceOutput.textContent =
						formatPrice(resolvedPrice);
				})
				.fail(function (request, statusText) {
					if (statusText === 'abort') {
						return;
					}

					priceOutput.textContent =
						formatPrice(fallbackPrice);
				})
				.always(function () {
					pricePreviewRequest = null;
				});
		}

		/**
		 * Update duration, price and colour.
		 */
		function updatePreview() {
			const option =
				getSelectedOption();

			const startMinutes =
				parseTime(startTime.value);

			const endMinutes =
				parseTime(endTime.value);

			if (colourOutput) {
				colourOutput.style.backgroundColor =
					option
					&& option.dataset.colour
						? option.dataset.colour
						: '#328BB8';
			}

			if (
				!option
				|| startMinutes === null
				|| endMinutes === null
				|| endMinutes <= startMinutes
			) {
				durationOutput.textContent = '—';
				priceOutput.textContent = '—';

				return;
			}

			const duration =
				endMinutes - startMinutes;

			const standardDuration = parseInt(
				option.dataset.duration,
				10
			);

			const standardPrice = parseInt(
				option.dataset.price,
				10
			);

			durationOutput.textContent =
				duration + ' minutes';

			if (
				!Number.isInteger(standardDuration)
				|| standardDuration < 1
				|| !Number.isInteger(standardPrice)
				|| standardPrice < 0
			) {
				priceOutput.textContent = '—';

				return;
			}

			/*
			 * Match the PHP integer half-up pricing calculation:
			 *
			 * intdiv(
			 *     price * duration
			 *         + intdiv(standardDuration, 2),
			 *     standardDuration
			 * )
			 */
			const calculatedPrice = Math.floor(
				(
					standardPrice * duration
					+ Math.floor(
						standardDuration / 2
					)
				)
				/ standardDuration
			);

			const studentId = studentInput
				? parseInt(
					studentInput.value,
					10
				)
				: 0;

			const bookingTypeId = parseInt(
				option.value,
				10
			);

			resolvePreviewPrice(
				studentId,
				bookingTypeId,
				duration,
				calculatedPrice
			);
		}

		bookingType.addEventListener(
			'change',
			function () {
				applyBookingTypeDefaults();
				updatePreview();
			}
		);

		startTime.addEventListener(
			'change',
			function () {
				suggestEndTime();
				updatePreview();
			}
		);

		startTime.addEventListener(
			'input',
			updatePreview
		);

		endTime.addEventListener(
			'input',
			function () {
				if (
					endTime.value
					!== suggestedEndTime
				) {
					suggestedEndTime = '';
				}

				updatePreview();
			}
		);

		if (studentInput) {
			studentInput.addEventListener(
				'change',
				updatePreview
			);
		}

		if (location) {
            location.addEventListener(
                'change',
                function () {
                    locationWasEdited = true;
                }
            );
        }

		if (locationDetails) {
			locationDetails.addEventListener(
				'input',
				function () {
					detailsWereEdited = true;
				}
			);
		}

		applyBookingTypeDefaults();
		updatePreview();
	}

	/**
	 * Initialise Add Booking enhancements.
	 */
	function initialise() {
		initialiseStudentSearch();
		initialiseBookingPreview();
	}

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