document.addEventListener('DOMContentLoaded', function () {
	// --- Browser locale detection ---
	const userLocale = navigator.language || navigator.languages[0] || 'en-US';
	const isRTL = ['ar', 'he', 'fa'].some(lang => userLocale.startsWith(lang));
	
	// Update HTML lang attribute
	document.documentElement.lang = userLocale.split('-')[0];
	
	// Get localized day abbreviations
	const localizedDayNames = [];
	const tempDate = new Date(2023, 0, 1); // Start with a Sunday
	for (let i = 0; i < 7; i++) {
		tempDate.setDate(1 + i);
		localizedDayNames.push(new Intl.DateTimeFormat(userLocale, { weekday: 'short' }).format(tempDate));
	}

	// Detect first day of week from locale using Intl API
	const getFirstDayOfWeek = () => {
		// Use Intl.Locale if available (modern browsers)
		if (Intl.Locale && typeof Intl.Locale.prototype.getWeekInfo === 'function') {
			try {
				const locale = new Intl.Locale(userLocale);
				return locale.getWeekInfo().firstDay % 7; // Convert to 0-6 range
			} catch (e) {
				// Fallback
			}
		}
		// Fallback: common locales that start week on Monday
		const mondayFirstLocales = ['de', 'fr', 'es', 'it', 'nl', 'pt', 'ru', 'pl', 'sv', 'no', 'da', 'fi', 'cs', 'hu', 'ro', 'bg', 'hr', 'sk', 'sl', 'uk', 'tr', 'el', 'he', 'ar', 'zh', 'ja', 'ko', 'vi', 'th', 'id', 'ms'];
		const lang = userLocale.split('-')[0];
		return mondayFirstLocales.includes(lang) ? 1 : 0;
	};

	// Get first day of week preference (user preference overrides browser default)
	let firstDayOfWeek;
	const storedFirstDay = localStorage.getItem('firstDayOfWeek');
	if (storedFirstDay && storedFirstDay !== 'auto') {
		firstDayOfWeek = parseInt(storedFirstDay);
	} else {
		firstDayOfWeek = getFirstDayOfWeek();
	}

	// Function to update day labels with localized names and reorder based on first day
	const updateDayLabels = () => {
		const container = document.querySelector('#repeatingEventInfo .btn-group');
		if (!container) return;

		// Get all checkbox/label pairs
		const pairs = [];
		for (let i = 0; i < 7; i++) {
			const checkbox = document.getElementById(String(i));
			const label = document.querySelector(`label[for="${i}"]`);
			if (checkbox && label) {
				pairs.push({ day: i, checkbox, label });
			}
		}

		// Reorder based on first day of week
		const reorderedPairs = [];
		for (let i = 0; i < 7; i++) {
			const dayIndex = (firstDayOfWeek + i) % 7;
			const pair = pairs.find(p => p.day === dayIndex);
			if (pair) {
				reorderedPairs.push(pair);
			}
		}

		// Clear container and re-append in new order with localized names
		container.innerHTML = '';
		reorderedPairs.forEach(pair => {
			pair.label.textContent = localizedDayNames[pair.day];
			container.appendChild(pair.checkbox);
			container.appendChild(pair.label);
		});
	};
	
	// Populate first day of week dropdown with localized day names
	const populateFirstDayOfWeekOptions = () => {
		const select = document.getElementById('firstDayOfWeek');
		if (!select) return;
		
		// Get full day names for the dropdown
		const localizedFullDayNames = [];
		const tempDate = new Date(2023, 0, 1); // Start with a Sunday
		for (let i = 0; i < 7; i++) {
			tempDate.setDate(1 + i);
			localizedFullDayNames.push(new Intl.DateTimeFormat(userLocale, { weekday: 'long' }).format(tempDate));
		}
		
		// Add options for each day
		for (let i = 0; i < 7; i++) {
			const option = document.createElement('option');
			option.value = i;
			option.textContent = localizedFullDayNames[i];
			select.appendChild(option);
		}
		
		// Refresh the selectpicker to apply new options
		$('#firstDayOfWeek').selectpicker('refresh');
	};
	
	
	// --- Localized date formatting functions ---
	const formatDate = (date, options = {}) => {
		return new Intl.DateTimeFormat(userLocale, {
			year: 'numeric',
			month: '2-digit',
			day: '2-digit',
			...options
		}).format(new Date(date));
	};
	
	const formatTime = (date, options = {}) => {
		return new Intl.DateTimeFormat(userLocale, {
			hour: '2-digit',
			minute: '2-digit',
			hour12: false,
			...options
		}).format(new Date(date));
	};
	
	const formatDateTime = (date, options = {}) => {
		return new Intl.DateTimeFormat(userLocale, {
			year: 'numeric',
			month: '2-digit',
			day: '2-digit',
			hour: '2-digit',
			minute: '2-digit',
			hour12: false,
			...options
		}).format(new Date(date));
	};
	
	// --- For daylight saving time ---
	const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
	// ---
	const today = new Date()
	const dateFormat = /(\d{4})([\/-])(\d{1,2})\2(\d{1,2})._/ // "YYYY-MM-DD" format for event titles
	let modalEventChange = false
	let copyInProgress = false

	// Switcher license and data
	let hasSwitcherLicense = false
	let switcherSources = []

	//MovieRecorder Connection Info
	address = window.location.hostname
	port = window.location.port

	//Settings - MovieRecorder Options
	let defaultDestinations = []

	if (localStorage.getItem('defaultDestinations')) {
		defaultDestinations = JSON.parse(localStorage.getItem('defaultDestinations'))
	}

	// Settings - Calendar Options
	let weekends = false
	let dayStart = '08:00'
	let userFirstDayOfWeek = 'auto'

	if (localStorage.getItem('weekends') != null) {
		weekends = localStorage.getItem('weekends') === 'true' ? true : false
	}
	if (localStorage.getItem('dayStart') != null) {
		dayStart = localStorage.getItem('dayStart')
	}
	if (localStorage.getItem('firstDayOfWeek') != null) {
		userFirstDayOfWeek = localStorage.getItem('firstDayOfWeek')
	}

	//Check if user is on a phone or other small screen
	mobileCheck = function () {
		if (/Android|webOS|iPhone|iPad/i.test(navigator.userAgent)) {
			return true
		} else {
			return false
		}
	}

	//Create Calendar
	var calendarEl = document.getElementById('calendar')
	var calendar = new FullCalendar.Calendar(calendarEl, {
		locale: userLocale,
		direction: isRTL ? 'rtl' : 'ltr',
		firstDay: firstDayOfWeek,
		aspectRatio: mobileCheck() ? 1 : 2,
		headerToolbar: {
			left: 'prev,next today',
			center: 'title',
			right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek',
		},
		allDaySlot: false,
		navLinks: true,
		dayMaxEvents: true,
		eventMaxStack: 3,
		weekends: weekends,
		initialView: mobileCheck() ? 'timeGridDay' : 'timeGridWeek',
		scrollTime: dayStart,
		slotMinTime: '00:00:00',
		slotMaxTime: '24:00:00',
		initialDate: today,
		nowIndicator: true,
		selectable: true,
		snapDuration: '00:05',
		editable: true,
		events: {},
		eventClick: function (info) {
			if (info.event.durationEditable) {
				newEditModal(info.event)
			}
		},
		select: function (event) {
			newRecModal(event)
		},
		eventChange: function (info) {
			if (modalEventChange == false) {
				eventMoved(info.event)
			}
		},
		eventOverlap: function (stillEvent, movingEvent) {
			return stillEvent.extendedProps.source_unique_id !== movingEvent.extendedProps.source_unique_id
		},
		eventAllow: function (dropInfo, draggedEvent) {
			//Prevent moving recurring events between days, only allow time changes
			if (draggedEvent.extendedProps.recurring === true) {
				return dayjs(dropInfo.start).format('YYYY[-]MM[-]DD') === dayjs(draggedEvent.start).format('YYYY[-]MM[-]DD')
			} else {
				return true
			}
		},
		datesSet: function (info) {
			if (!this.copyInProgress) {
				if (info.view.type == 'timeGridWeek' || info.view.type == 'listWeek') {
					$('.copyButton').text('Copy Week')
					$('.pasteButton').text('Paste Week')
				} else if (info.view.type == 'timeGridDay') {
					$('.copyButton').text('Copy Day')
					$('.pasteButton').text('Paste Day')
				} else if (info.view.type == 'dayGridMonth') {
					$('.copyButton').text('Copy Month')
					$('.pasteButton').text('Paste Month')
				}
			}
		},
	})

	calendar.render()
	
	// Update day labels with localized names
	updateDayLabels();
	
	// Populate first day of week dropdown with localized names
	populateFirstDayOfWeekOptions();
	

	var scheduledRecordings = []

	var colors = [
		'#5d439c',
		'#1d5799',
		'#d76700',
		'#298a71',
		'#8f466a',
		'#65758f',
		'#bc911e',
		'#277b1f',
		'#a491d9',
		'#86abe6',
		'#f4ad6c',
		'#85d1be',
	]
	const activeRecColor = '#ff0000'
	const pastRecColor = '#303030'
	const disabledColor = 'rgba(255, 255, 255, 0.1)'
	const warningColor = '#ffaa00'

	createCalendarEvent = function (scheduledRec) {
		if (scheduledRec.is_enabled) {
			
			// ----- Consider potential daylight saving time -----
			// console.log("-------")
			
			// Initialize variables
			let day = dayjs(scheduledRec.date).format('YYYY[-]MM[-]DD')
			var isDSTTransition = false
			var transitionTypeIsForward = false
			var transitionTime = "00:00"
			
			// Create moment timezone
			const dayMoment = moment.tz(day, timeZone);

			// Is there a daylight saving change ?
			const todayIsDST = dayMoment.isDST(); // Get the isDST of today
			const tomorrowIsDST = dayMoment.clone().add(1, 'day').isDST(); // Get the isDST of tomorrow
			isDSTTransition = todayIsDST !== tomorrowIsDST; // If one is inDST and not the other => There is a transition !

			// If there is a transition
			if (isDSTTransition) {
				// Forward ? Backward ?
				transitionTypeIsForward = tomorrowIsDST;
				// The time of transition (check hour by hour when the change occurs)
				let increment = { minutes: 60 };
				const initialOffset = dayMoment.utcOffset();
				let nextTransition = dayMoment.clone().add(increment);
				while (nextTransition.utcOffset() === initialOffset) {
					nextTransition = nextTransition.add(increment);
				}
				// Do -1 minute to know the "limit"
				transitionTime = nextTransition.subtract({ minutes: 1 }).format('HH:mm');
			}
			
			// Print the Daylight Saving variables
			// console.log("day: ", day)
			// console.log("isDSTTransition: ", isDSTTransition)
			// console.log("transitionTypeIsForward: ", transitionTypeIsForward)
			// console.log("transitionTime: just after ", transitionTime)

			// Time change correction
			let correction = 0
			if (isDSTTransition) {
				const dstTime = moment(transitionTime, 'HH:mm').add(1, 'minute').format('HH:mm');
				
				if (transitionTypeIsForward) {
					const eventTime = dayjs(scheduledRec.date).startOf('day').add(scheduledRec.start_time-Number(60), 'm').format('HH:mm')
					if (eventTime > dstTime && eventTime < "23:00" ) { correction = -60 }
				}
				else {
					const eventTime = dayjs(scheduledRec.date).startOf('day').add(scheduledRec.start_time+Number(60), 'm').format('HH:mm')
					if (eventTime >= dstTime && eventTime > "01:00") { correction = 60 }
				}
			}
			// -------------
			
			let startTime =
				dayjs(scheduledRec.date).format('YYYY[-]MM[-]DD') +
				dayjs(scheduledRec.date).startOf('day').add(scheduledRec.start_time + Number(correction), 'm').format('[T]HH:mm')
			let endTime = dayjs(startTime).add(scheduledRec.duration, 'm').format('YYYY[-]MM[-]DD[T]HH:mm') // + correction for the potential daylight saving time
			let isRecording = false
			let isCurrentDateInTimeFrame = false

			if (scheduledRec.weekly_repeated) {
				//we have to compute the current day is in recording days
				for (let i = 0; i < scheduledRec.recording_days.length; i++) {
					// we compare integers
					if (dayjs().day() == scheduledRec.recording_days[i]) {
						//same day
						const dayStartTime = dayjs()
							.day(scheduledRec.recording_days[i])
							.startOf('day')
							.add(scheduledRec.start_time, 'm')
						const dayEndTime = dayStartTime.add(scheduledRec.duration, 'm')
						//if this day has an event running on its time frame, mark it as recording
						if (
							dayjs(dayStartTime).isBefore(Date.now()) &&
							dayjs(dayEndTime).isAfter(Date.now()) &&
							!scheduledRec.stopped_by_user
						) {
							isCurrentDateInTimeFrame = true
							// we know that this event will be marked as currently recording, ignore other recurring days as they should be marked the same way
							break
						}
					}
				}
			} else {
				//Stylize Active Recordings
				if ((dayjs(scheduledRec.date).isSame(Date.now()), 'date')) {
					if (
						dayjs(startTime).isBefore(Date.now()) &&
						dayjs(endTime).isAfter(Date.now()) &&
						!scheduledRec.stopped_by_user
					) {
						isCurrentDateInTimeFrame = true
					}
				}
			}

			let bckColor = isSourceAvailable(scheduledRec.source_unique_id)
				? colorPicker(scheduledRec.source_unique_id)
				: disabledColor
			let editable = true
			let calendarTitle = removeDateFromTitle(scheduledRec.name)
			let warningMessage = undefined
			let borderColor = bckColor

			//is the linked source recording ?
			for (let i in this.sources) {
				const source = this.sources[i]
				if (source.unique_id == scheduledRec.source_unique_id) {
					isRecording = source.is_recording && isCurrentDateInTimeFrame
				}
			}

			//Past Recordings
			if (dayjs(startTime).isBefore(Date.now()) && !isRecording) {
				if (!scheduledRec.weekly_repeated) {
					bckColor = pastRecColor
					editable = false
				}
				//past repeated event should stay editable ...
			}

			//Current Recording
			if (isRecording) {
				borderColor = activeRecColor
				editable = false
			} else {
				borderColor = bckColor
			}

			//Event with no source set
			if (!isSourceAvailable(scheduledRec.source_unique_id)) {
				bckColor = disabledColor
				borderColor = warningColor
				warningMessage = 'This event has no source ! It will not be able to record ...'
			}

			//ScheduledEvent ID == calendar ID
			let event = {
				id: scheduledRec.unique_id,
				title: calendarTitle,
				start: startTime,
				end: endTime,
				editable: editable,
				backgroundColor: bckColor,
				borderColor: borderColor,
				//extendedProps
				name: scheduledRec.name,
				source_name: scheduledRec.source_name,
				source_unique_id: scheduledRec.source_unique_id,
				destinations: scheduledRec.destination_unique_ids,
				interrupt_manual_recordings: scheduledRec.interrupt_manual_recordings,
				switcher_source_id: scheduledRec.switcher_source_id,
			}
			//Displaying recurring recordings
			//colors & specific stuff will be applied on all repeating events
			if (scheduledRec.weekly_repeated) {
				delete event.start
				delete event.end
				event.startRecur = dayjs().format('YYYY[-]MM[-]DD')
				event.startTime = dayjs().startOf('day').add(scheduledRec.start_time, 'm').format('HH:mm')
				event.endTime = dayjs(startTime).add(scheduledRec.duration, 'm').format('HH:mm')
				event.daysOfWeek = scheduledRec.recording_days
				event.groupId = scheduledRec.unique_id
				//extendedProps
				event.recurring = true
				event.recordingDays = scheduledRec.recording_days
			}

			return event
		}

		return undefined
	}

	//------------------------------------------------------------
	//-------------------------Utilities--------------------------
	//------------------------------------------------------------

	//Calculate start time in format needed for MovieRecorder API
	minutesFromMidnight = function (date) {
		let hours = date.getHours()
		let minutes = date.getMinutes()
		return 60 * hours + minutes
	}
	//Assign colors to each source
	colorPicker = function (sourceID) {
		let color = '#b1b1b1'
		for (s in this.sources) {
			if (sourceID == this.sources[s].unique_id) {
				color = colors[s]
			}
		}
		return color
	}
	//Remove dates from calendar event names to increase readability
	removeDateFromTitle = function (name) {
		return name.replace(dateFormat, '')
	}
	//Check if recording name is unique, and append an integer to the name if it is not
	checkNameAvailability = function (name, int) {
		let regex = /_\d+$/i // "_int" at the end of the name
		if (!this.scheduledRecordings.find((o) => o.name == name)) {
			return name
		} else {
			name = name.replace(regex, '')
			if (int) {
				int++
				name = name + '_' + int
				return checkNameAvailability(name, int)
			} else {
				let int = 1
				name = name + '_' + int
				return checkNameAvailability(name, int)
			}
		}
	}

	isSourceAvailable = function (sourceID) {
		if (sourceID == undefined) {
			return false
		}

		for (let i in this.sources) {
			if (this.sources[i].unique_id == sourceID) {
				return true
			}
		}

		return false
	}

	//------------------------------------------------------------
	//---------------------------WS-------------------------------
	//------------------------------------------------------------

	handleWebSocketEvent = function (event) {
		const data = JSON.parse(event.data)
		const dataType = data.dataType
		const change = data.change
		const objectData = data.data

		switch (dataType) {
			case 'sources':
			case 'source':
				// console.log("sources changed");
				if (change == 'replacement') {
					//ignore total_time_recorded messages, it's likely source infos
					if (objectData.total_time_recorded === undefined) {
						setSourceWithID(objectData, objectData.unique_id)
						//source can no more record, reload
						updateScheduleEventsUI()
					}
				} else if (change == 'removal') {
					// we need to remove the given ids to get the updated array
					for (let i in objectData) {
						const uid = objectData[i]
						removeSourceWithID(uid)
					}
					//some sources might have disappear or reappear, we need to know if our events are still ok
					//we also need to know if our source is still recording
					updateScheduleEventsUI()
				} else if (change == 'insertion') {
					for (let i in objectData) {
						addSource(objectData[i])
					}
					//some sources might have disappear or reappear, we need to know if our events are still ok
					//we also need to know if our source is still recording
					updateScheduleEventsUI()
				}
				break
			case 'destinations':
			case 'destination':
				// console.log("destinations changed");
				if (change == 'replacement') {
					setDestinationWithID(objectData, objectData.unique_id)
				} else if (change == 'removal') {
					// we need to remove the given ids to get the updated array
					for (let i in objectData) {
						const uid = objectData[i]
						removeDestinationWithID(uid)
					}
				} else if (change == 'insertion') {
					for (let i in objectData) {
						addDestination(objectData[i])
					}
				}

				break
			case 'scheduled_recordings':
			case 'scheduled_recording':
				// console.log("scheduled_recordings changed");
				if (change == 'replacement') {
					setScheduledRecordingEventWithID(objectData, objectData.unique_id)
				} else if (change == 'removal') {
					// we need to remove the given ids to get the updated array
					for (let i in objectData) {
						const uid = objectData[i]
						removeScheduleEventWithID(uid)
					}
				} else if (change == 'insertion') {
					for (let i in objectData) {
						addScheduleEvent(objectData[i])
					}
				}
				break
			default:
				break
		}
	}

	// Protocol helper functions
	function getHttpProtocol() {
		return window.location.protocol === 'https:' ? 'https://' : 'http://';
	}

	function getWsProtocol() {
		return window.location.protocol === 'https:' ? 'wss://' : 'ws://';
	}

	loadApplication = async function () {
		const d = await this.asyncGETDestinations() //Get Destinations from MovieRecorder
		const s = await this.asyncGETSources() //Get Sources from MovieRecorder
		const e = await this.asyncGETScheduledRecordingEvents() //Get Scheduled Recordings from MovieRecorder
		await this.asyncCheckSwitcherLicense() //Check for switcher license

		this.setDestinations(d)
		this.setSources(s)
		this.setScheduledRecordingEvents(e) //Ensure sources are known before populating recordings

		let mainUpdateWebSocket = new WebSocket(`${getWsProtocol()}${address}:${port}/remote`)

		mainUpdateWebSocket.protocol = 'v1.1.main_update.movierecorder.softronmedia.com'

		mainUpdateWebSocket.addEventListener('open', function (event) {
			console.log('main update socket open!')
		})
		mainUpdateWebSocket.addEventListener(
			'message',
			function (event) {
				this.handleWebSocketEvent(event)
			}.bind(this)
		)
	}

	//------------------------------------------------------------
	//----------------------------UI------------------------------
	//------------------------------------------------------------

	updateScheduleEventsUI = function () {
		calendar.removeAllEvents()

		for (let i in this.scheduledRecordings) {
			const calendarEvent = createCalendarEvent(this.scheduledRecordings[i])

			if (calendarEvent) {
				calendar.addEvent(calendarEvent)
			}
		}

		calendar.render()
	}

	updateSourcesUI = function () {
		$('#selectSource').html('')
		for (let i in this.sources) {
			const source = this.sources[i]

			$('#selectSource').append(`<option value="${source.unique_id}">${source.display_name}</option>`)
			$('#selectSource').selectpicker('refresh')
		}
	}

	updateDestinationsUI = function () {
		$('#selectDestinations').html('')
		for (let i in this.destinations) {
			const destination = this.destinations[i]

			$('#selectDestinations').append(`<option value="${destination.unique_id}">${destination.name}</option>`)
			$('#selectDestinations').selectpicker('refresh')
			$('#defaultDestinations').append(`<option value="${destination.unique_id}">${destination.name}</option>`)
			$('#defaultDestinations').selectpicker('refresh')
		}
	}

	// Update switcher picker based on selected source
	updateSwitcherPickerForSource = function (sourceId) {
		if (!hasSwitcherLicense || !switcherSources || switcherSources.length === 0) {
			$('#switcherLabel').hide()
			$('#switcherPicker').selectpicker('hide')
			return
		}

		// Find the source to check if it has switcher capability
		let selectedSource = null
		for (let i in this.sources) {
			if (this.sources[i].unique_id === sourceId) {
				selectedSource = this.sources[i]
				break
			}
		}

		// Check if source has a switcher_id (indicating it's connected to a switcher)
		if (selectedSource && selectedSource.switcher_id) {
			// Find the switcher and its sources
			let switcher = null
			for (let i in switcherSources) {
				if (switcherSources[i].unique_id === selectedSource.switcher_id) {
					switcher = switcherSources[i]
					break
				}
			}

			if (switcher && switcher.sources && switcher.sources.length > 0) {
				$('#switcherPicker').html('<option value="">None</option>')
				for (let i in switcher.sources) {
					const switcherSource = switcher.sources[i]
					if (switcherSource.is_enabled) {
						const displayName = switcherSource.custom_name || switcherSource.mnemonic || `Input ${switcherSource.id}`
						$('#switcherPicker').append(`<option value="${switcherSource.id}">${displayName}</option>`)
					}
				}
				$('#switcherPicker').selectpicker('refresh')
				$('#switcherLabel').show()
				$('#switcherPicker').show()
				$('#switcherPicker').selectpicker('show')
				return
			}
		}

		// No switcher for this source
		$('#switcherLabel').hide()
		$('#switcherPicker').hide()
		$('#switcherPicker').selectpicker('hide')
	}

	// Initialize source change handler for switcher picker
	$('#selectSource').on('changed.bs.select', function (e, clickedIndex, isSelected, previousValue) {
		const selectedSourceId = $(this).val()
		updateSwitcherPickerForSource(selectedSourceId)
	})

	//------------------------------------------------------------
	//------------------------------------------------------------
	//------------------------------------------------------------

	setScheduledRecordingEventWithID = function (scheduledRecording, uid) {
		if (!scheduledRecording) {
			return
		}
		//keep track of new object
		for (let i in this.scheduledRecordings) {
			if (this.scheduledRecordings[i].unique_id == uid) {
				this.scheduledRecordings[i] = scheduledRecording
				break
			}
		}

		updateScheduleEventsUI()
	}

	setDestinationWithID = function (destination, uid) {
		let needToUpdateUI = false

		if (!destination) {
			return
		}
		//keep track of new object
		for (let i in this.destinations) {
			if (this.destinations[i].unique_id == uid) {
				if (this.destinations[i].name != destination.name) {
					needToUpdateUI = true
				}
				this.destinations[i] = destination
				break
			}
		}

		if (needToUpdateUI) {
			this.destinations.sort((a, b) => a.name.localeCompare(b.name))
			updateDestinationsUI()
		}
	}

	setSourceWithID = function (source, uid) {
		let needToUpdateUI = false

		if (!source) {
			return
		}
		//keep track of new object
		for (let i in this.sources) {
			if (this.sources[i].unique_id == uid) {
				if (this.sources[i].display_name != source.display_name) {
					needToUpdateUI = true
				}
				this.sources[i] = source
				break
			}
		}

		if (needToUpdateUI) {
			this.sources.sort((a, b) => a.display_name.localeCompare(b.display_name))
			updateSourcesUI()
		}
	}

	// add

	addSource = function (source) {
		this.sources.push(source)
		this.sources.sort((a, b) => a.display_name.localeCompare(b.display_name))

		updateSourcesUI()
	}

	addDestination = function (destination) {
		this.destinations.push(destination)
		this.destinations.sort((a, b) => a.name.localeCompare(b.name))

		updateDestinationsUI()
	}

	addScheduleEvent = function (scheduledEvent) {
		this.scheduledRecordings.push(scheduledEvent)
		updateScheduleEventsUI()
	}

	//remove
	removeSourceWithID = function (uid) {
		let indexToRemove = undefined
		for (let i in this.sources) {
			const source = this.sources[i]
			if (source.unique_id == uid) {
				indexToRemove = i
				break
			}
		}

		if (indexToRemove != undefined) {
			this.sources.splice(indexToRemove, 1)
		}
		updateSourcesUI()
	}

	removeDestinationWithID = function (uid) {
		let indexToRemove = undefined
		for (let i in this.destinations) {
			const destination = this.destinations[i]
			if (destination.unique_id == uid) {
				indexToRemove = i
				break
			}
		}

		if (indexToRemove != undefined) {
			this.destinations.splice(indexToRemove, 1)
		}

		updateDestinationsUI()
	}

	removeScheduleEventWithID = function (uid) {
		let indexToRemove = undefined
		for (let i in this.scheduledRecordings) {
			const sEvent = this.scheduledRecordings[i]
			if (sEvent.unique_id == uid) {
				indexToRemove = i
				break
			}
		}

		if (indexToRemove != undefined) {
			this.scheduledRecordings.splice(indexToRemove, 1)
		}
		updateScheduleEventsUI()
	}

	//------------------------------------------------------------

	setScheduledRecordingEvents = function (scheduledRecordingEvents) {
		if (!scheduledRecordingEvents || !Array.isArray(scheduledRecordingEvents)) {
			return
		}
		this.scheduledRecordings = scheduledRecordingEvents //Store scheduled recordings to reference later
		updateScheduleEventsUI()
	}

	setDestinations = function (destinations) {
		if (!destinations || !Array.isArray(destinations)) {
			return
		}

		//erase previous data
		this.destinations = destinations

		this.destinations.sort((a, b) => a.name.localeCompare(b.name))

		updateDestinationsUI()
	}

	setSources = function (sources) {
		if (!sources || !Array.isArray(sources)) {
			return
		}

		this.sources = sources

		this.sources.sort((a, b) => a.display_name.localeCompare(b.display_name))

		updateSourcesUI()
	}

	//------------------------------------------------------------
	//------------------------------------------------------------
	//------------------------------------------------------------

	asyncGETDestinations = async function () {
		let p = new Promise(function (resolve, reject) {
			fetch(`${getHttpProtocol()}${address}:${port}/destinations`)
				.then((res) => {
					if (res.status == 200) {
						return res.json()
					}
					if (res.status == 401) {
						$('.newRec').hide()
						$('#noConnection').delay(250).slideDown(400)
					}
				})
				.then((json) => {
					resolve(json)
				})
				.catch((err) => {
					$('.newRec').hide()
					$('#noConnection').delay(250).slideDown(400)
					reject(err)
				})
		})

		return p
	}

	asyncGETSources = async function () {
		let p = new Promise(function (resolve, reject) {
			fetch(`${getHttpProtocol()}${address}:${port}/sources`)
				.then((res) => {
					resolve(res.json())
				})
				.catch((err) => {
					reject(err)
				})
		})

		return p
	}

	asyncGETScheduledRecordingEvents = async function () {
		let p = new Promise(function (resolve, reject) {
			fetch(`${getHttpProtocol()}${address}:${port}/scheduled_recordings`)
				.then((res) => {
					resolve(res.json())
				})
				.catch((err) => {
					reject(err)
				})
		})

		return p
	}

	asyncGETScheduledRecordingEvent = function (schdeduledEvent, uid) {
		if (uid == undefined) {
			throw 'uid not defined'
		}
		let p = new Promise(function (resolve, reject) {
			fetch(`${getHttpProtocol()}${address}:${port}/scheduled_recordings/${uid}`)
				.then((res) => {
					if (res.status == 200) {
						return res.json()
					}
				})
				.then((json) => {
					const data = json

					resolve()
				})
				.catch((err) => {
					reject(err)
				})
		})

		return p
	}

	// Check for switcher license and fetch switcher sources
	asyncCheckSwitcherLicense = async function () {
		try {
			const res = await fetch(`${getHttpProtocol()}${address}:${port}/switchers`)
			if (res.status == 200) {
				hasSwitcherLicense = true
				switcherSources = await res.json()
				console.log('Switcher license detected, switcher sources loaded')
			} else {
				hasSwitcherLicense = false
				switcherSources = []
			}
		} catch (err) {
			hasSwitcherLicense = false
			switcherSources = []
			console.log('No switcher license or endpoint unavailable')
		}
	}

	//------------------------------------------------------------
	//------------------------------------------------------------
	//------------------------------------------------------------

	loadApplication()

	//------------------------------------------------------------
	//------------------------------------------------------------
	//------------------------------------------------------------

	// Add recording to MovieRecorder
	addRec = function () {
		let source = $('#selectSource').val()
		let destinations = []
		let selectedDestinations = []
		$('#selectDestinations :selected').each(function () {
			selectedDestinations.push({ unique_id: $(this).val(), destination_name: $(this).text() })
		})
		for (s in selectedDestinations) {
			destinations.push(selectedDestinations[s].unique_id)
		}

		let title = $('#newTitle').val()

		if (source && destinations.length > 0) {
			let body = {
				is_enabled: true,
				source_unique_id: source,
				destination_unique_ids: destinations,
			}

			//Avoid using default destination names if other names are present
			let destinationName = 'New Recording'

			for (s in selectedDestinations) {
				if (destinationName == 'New Recording') {
					if (!defaultDestinations.includes(selectedDestinations[s].unique_id)) {
						destinationName = selectedDestinations[s].destination_name
					}
				}
			}
			if (destinationName == 'New Recording') {
				destinationName = selectedDestinations[0].destination_name
			}

			let startTime = new Date($('#startDate').val() + 'T' + $('#startTime').val() + ':00')
			body.start_time = minutesFromMidnight(startTime)
			let endTime = new Date($('#startDate').val() + 'T' + $('#endTime').val() + ':00')
			body.duration = minutesFromMidnight(endTime) - minutesFromMidnight(startTime)

			if ($('#repeatingEvent:checked').val()) {
				body.weekly_repeated = true
				let days = []
				$('#repeatingEventInfo .btn-group input[type=checkbox]:checked').each(function () {
					days.push(parseInt(this.value))
				})
				body.recording_days = days
			} else {
				body.date = startTime
				body.weekly_repeated = false
			}

			if (title == '') {
				title = destinationName
			}
			let fileNameDate = dayjs(startTime).format('YYYY[-]MM[-]DD') + '_'
			let name = body.weekly_repeated == true ? title : fileNameDate + title
			body.name = checkNameAvailability(name)

			// Add interrupt manual recordings field
			body.interrupt_manual_recordings = $('#interruptManual').prop('checked')

			// Add switcher source if licensed and selected
			if (hasSwitcherLicense) {
				const switcherValue = $('#switcherPicker').val()
				if (switcherValue) {
					body.switcher_source_id = parseInt(switcherValue)
				}
			}

			publishRec = function (body) {
				fetch(`${getHttpProtocol()}${address}:${port}/scheduled_recordings/`, {
					method: 'POST',
					headers: {
						'Content-Type': 'application/json',
					},
					body: JSON.stringify(body),
				})
					.then((res) => {
						if (res.status == 200) {
							return res.json()
						}
					})
					.then((json) => {
						if (json.success) {
							$('#newRecModal').modal('hide')
							$('#addedName').text('Recording ' + removeDateFromTitle(body.name) + ' Scheduled')
							$('#alertScheduled').delay(250).slideDown(400).delay(2000).slideUp(400)
							//UI change will be triggered by websocket notification
						} else if (json.error && json.error == 'Expected an array of integers for recording days') {
							alert('Select at least one day of the week to record.')
						} else {
							alert(json.error)
						}
					})
			}
			if (body.name && body.duration >= 1) {
				publishRec(body)
			} else if (body.duration < 1) {
				alert('End time must be after start time.')
			}
		} else if (source && destinations.length == 0) {
			alert('Please select at least one destination')
		} else if (!source && destinations.length > 0) {
			alert('Please select a source')
		} else {
			alert('Please select a source & at least one destination')
		}
	}
	// Remove recording from MovieRecorder
	cancelRec = function (event) {
		fetch(`${getHttpProtocol()}${address}:${port}/scheduled_recordings/${event.id}`, {
			method: 'DELETE',
			headers: {
				'Content-Type': 'application/json',
			},
		})
			.then((res) => {
				if (res.status == 200) {
					return res.json()
				}
			})
			.then((json) => {
				if (json.success) {
					$('#newRecModal').modal('hide')
					$('#canceledName').text('Recording ' + event.title + ' Canceled')
					$('#alertCanceled').delay(250).slideDown(400).delay(2000).slideUp(400)
					// UI will be updated by websocket notification
				} else {
					alert(json.error)
				}
			})
	}
	// Edit recording in MovieRecorder
	modifyRec = function (event, data) {
		fetch(`${getHttpProtocol()}${address}:${port}/scheduled_recordings/${event.id}`, {
			method: 'PUT',
			headers: {
				'Content-Type': 'application/json',
			},
			body: JSON.stringify(data),
		})
			.then((res) => {
				if (res.status == 200) {
					return res.json()
				}
			})
			.then((json) => {
				if (json.success) {
					// UI will be updated by websocket notification
				} else if (json.error && json.error == 'Expected a valid integer duration') {
					alert('End time must be after start time.')
				} else if (json.error && json.error == 'Expected an array of integers for recording days') {
					alert('Select at least one day of the week to record.')
				} else {
					alert(json.error)
				}
			})
	}
	// Triggered when an event is dragged on calendar
	eventMoved = function (event) {
		let data = {
			unique_id: event.id,
			weekly_repeated: event.extendedProps.recurring == true ? true : false,
		}

		let startTime = new Date(event.start)
		data.start_time = minutesFromMidnight(startTime)

		data.duration = dayjs(event.end).diff(dayjs(event.start), 'minute')
		let fileNameDate = dayjs(startTime).format('YYYY[-]MM[-]DD') + '_'

		let title = removeDateFromTitle(event.extendedProps.name)
		let name = data.weekly_repeated == true ? title : fileNameDate + title

		//Prevent false positive name change if event is moved on the same day
		let matchedEventName = this.scheduledRecordings?.find((o) => o.name == name)
		if (matchedEventName?.unique_id === data.unique_id) {
			data.name = name
		} else {
			data.name = checkNameAvailability(name)
		}

		if (event.extendedProps.recurring) {
			data.recording_days = event.extendedProps.recordingDays
		} else {
			data.date = startTime
		}
		modifyRec(event, data)
	}

	//------------------------------------------------------------
	//-------------------------Modals-----------------------------
	//------------------------------------------------------------

	//New Recording Modal (Triggered by New Recording button, or by clicking empty cal area)
	newRecModal = function (event) {
		$('#recordingModal').text('New Recording')
		$('.cancel').hide()
		$('.edit').hide()
		$('.duplicate').hide()
		$('.create').show()

		$('#specificDateInfo').show()
		$('#repeatingEventInfo').hide()

		$('#specificDate').click(function () {
			$('#specificDateInfo').slideDown()
			$('#repeatingEventInfo').slideUp()
		})
		$('#repeatingEvent').click(function () {
			$('#specificDateInfo').slideUp()
			$('#repeatingEventInfo').slideDown()
		})
		$('label[for="specificDate"]').css('pointer-events', 'auto')
		$('label[for="repeatingEvent"]').css('pointer-events', 'auto')
		$('input:checkbox').prop('checked', false)
		$('#specificDate').prop('checked', true)
		$('#repeatingEvent').prop('checked', false)
		$('#selectSource').selectpicker('val', '')
		$('#selectDestinations').selectpicker('deselectAll')
		if (defaultDestinations.length > 0) {
			$('#selectDestinations').selectpicker('val', defaultDestinations)
		}
		$('#newTitle').val('')

		// Reset interrupt manual recordings checkbox
		$('#interruptManual').prop('checked', false)

		// Hide and reset switcher picker (only show if licensed and source has switchers)
		$('#switcherLabel').hide()
		$('#switcherPicker').selectpicker('val', '')
		$('#switcherPicker').selectpicker('hide')

		if (event) {
			$('#startDate').val(dayjs(event.start).format('YYYY[-]MM[-]DD'))
			$('#startTime').val(dayjs(event.start).format('HH[:]mm'))
			$('#endTime').val(dayjs(event.end).format('HH[:]mm'))
			$(`#${dayjs(event.start).day()}`).prop('checked', true)
		} else {
			now = dayjs()
			$('#startDate').val(now.format('YYYY[-]MM[-]DD'))
			$('#startTime').val(now.add(30, 'm').format('HH[:]mm'))
			$('#endTime').val(now.add(60, 'm').format('HH[:]mm'))
		}
		$('#newRecModal').modal('show')
		$('.create')
			.off('click')
			.on('click', function () {
				addRec()
			})
	}
	//Edit Recording Modal (Triggered by clicking an existing event)
	newEditModal = function (event) {
		$('#recordingModal').text('Edit Recording')
		$('.cancel').show()
		$('.edit').show()
		$('.duplicate').show()
		$('.create').hide()
		$('#selectSource').selectpicker('val', event.extendedProps.source_unique_id)
		$('#selectDestinations').selectpicker('val', event.extendedProps.destinations)
		$('#newTitle').val('')

		//Prevent changing type in edit modal
		$('label[for="specificDate"]').css('pointer-events', 'none')
		$('label[for="repeatingEvent"]').css('pointer-events', 'none')

		if (event.extendedProps.recurring) {
			$('#specificDate').prop('checked', false)
			$('#repeatingEvent').prop('checked', true)

			$('#specificDateInfo').hide()
			$('#repeatingEventInfo').show()

			$('input:checkbox').prop('checked', false)

			for (d in event.extendedProps.recordingDays) {
				$(`#${event.extendedProps.recordingDays[d]}`).prop('checked', true)
			}
		} else {
			$('#specificDate').prop('checked', true)
			$('#repeatingEvent').prop('checked', false)

			$('#specificDateInfo').show()
			$('#repeatingEventInfo').hide()
		}
		$('#startDate').val(dayjs(event.start).format('YYYY[-]MM[-]DD'))
		$('#startTime').val(dayjs(event.start).format('HH[:]mm'))
		$('#endTime').val(dayjs(event.end).format('HH[:]mm'))
		$('#newTitle').val(event.title)

		// Populate interrupt manual recordings checkbox
		$('#interruptManual').prop('checked', event.extendedProps.interrupt_manual_recordings || false)

		// Populate switcher picker if licensed
		updateSwitcherPickerForSource(event.extendedProps.source_unique_id)
		if (hasSwitcherLicense && event.extendedProps.switcher_source_id) {
			$('#switcherPicker').selectpicker('val', String(event.extendedProps.switcher_source_id))
		}

		$('#newRecModal').modal('show')
		$('.cancel')
			.off('click')
			.on('click', function () {
				cancelRec(event)
			})
		$('.edit')
			.off('click')
			.on('click', function () {
				let destinations = []
				let title = $('#newTitle').val()
				let selectedDestinations = $('#selectDestinations').val()
				for (s in selectedDestinations) {
					destinations.push(selectedDestinations[s])
				}
				let source = $('#selectSource').val()
				if (source && destinations.length > 0) {
					let data = {
						is_enabled: true,
						source_unique_id: source,
						destination_unique_ids: destinations,
					}

					let destinationSelected = document.getElementById('selectDestinations')
					let destinationName = destinationSelected.options[destinationSelected.selectedIndex].text

					let startTime = new Date($('#startDate').val() + 'T' + $('#startTime').val() + ':00')
					data.start_time = minutesFromMidnight(startTime)
					let endTime = new Date($('#startDate').val() + 'T' + $('#endTime').val() + ':00')
					data.duration = minutesFromMidnight(endTime) - minutesFromMidnight(startTime)

					if ($('#repeatingEvent:checked').val()) {
						data.weekly_repeated = true
						let days = []
						$('#repeatingEventInfo .btn-group input[type=checkbox]:checked').each(function () {
							days.push(parseInt(this.value))
						})
						data.recording_days = days
					} else {
						data.date = startTime
						data.weekly_repeated = false
					}

					if (title == '') {
						title = destinationName
					}
					let fileNameDate = dayjs(startTime).format('YYYY[-]MM[-]DD') + '_'
					let name = data.weekly_repeated == true ? title : fileNameDate + title
					//Prevent false positive name change if event is moved on the same day
					let matchedEventName = scheduledRecordings?.find((o) => o.name == name)
					if (matchedEventName?.unique_id === data.unique_id) {
						data.name = name
					} else {
						data.name = checkNameAvailability(name)
					}

					// Add interrupt manual recordings field
					data.interrupt_manual_recordings = $('#interruptManual').prop('checked')

					// Add switcher source if licensed and selected
					if (hasSwitcherLicense) {
						const switcherValue = $('#switcherPicker').val()
						if (switcherValue) {
							data.switcher_source_id = parseInt(switcherValue)
						} else {
							data.switcher_source_id = null
						}
					}

					modalEventChange = true
					if (data.weekly_repeated) {
						event.setStart(startTime)
						event.setEnd(endTime)
					} else {
						event.setDates(startTime, endTime)
					}
					let color = colorPicker(source)
					event.setProp('color', color)
					event.setExtendedProp('source_unique_id', source)
					event.setExtendedProp('destinations', destinations)
					event.setExtendedProp('recordingDays', data.recording_days)
					modalEventChange = false
					modifyRec(event, data)
					$('#newRecModal').modal('hide')
				} else if (source && destinations.length == 0) {
					alert('Please select at least one destination')
				} else if (!source && destinations.length > 0) {
					alert('Please select a source')
				} else {
					alert('Please select a source & at least one destination')
				}
			})
		$('.duplicate')
			.off('click')
			.on('click', function () {
				addRec()
			})
	}
	//Settings Modal (Triggered by clicking the Settings button)
	newSettingsModal = function () {
		if (defaultDestinations.length > 0) {
			$('#defaultDestinations').selectpicker('val', defaultDestinations)
		}
		$('#weekends').selectpicker('val', `${weekends}`)
		$('#dayStart').val(dayStart)
		$('#firstDayOfWeek').selectpicker('val', userFirstDayOfWeek)

		$('#settingsModal').modal('show')
		$('.adjustSettings')
			.off('click')
			.on('click', function () {
				defaultDestinations = $('#defaultDestinations').val()
				localStorage.setItem('defaultDestinations', JSON.stringify(defaultDestinations))

				weekends = $('#weekends').val()
				localStorage.setItem('weekends', weekends)

				dayStart = $('#dayStart').val()
				localStorage.setItem('dayStart', dayStart)

				userFirstDayOfWeek = $('#firstDayOfWeek').val()
				localStorage.setItem('firstDayOfWeek', userFirstDayOfWeek)

				$('#settingsModal').modal('hide')

				location.reload()
			})
	}
	//Triggered by Copy button
	//Gathers info for all events in current calendar view
	copyRecInfo = function () {
		this.copyDates = []
		this.copyInProgress = true

		let calEnd = new Date(calendar.view.activeEnd)
		calEnd.setDate(calEnd.getDate() - 1)
		var getDaysArray = function (start, end) {
			for (var arr = [], dt = new Date(start); dt <= end; dt.setDate(dt.getDate() + 1)) {
				arr.push(dayjs(dt).format('YYYY[-]MM[-]DD'))
			}
			return arr
		}
		this.copyDates = getDaysArray(new Date(calendar.view.activeStart), new Date(calEnd))

		$('.copyButton').hide()
		$('.cancelPaste').show()
		$('.pasteButton').show()
	}
	//Triggered by Paste button
	//Calculates difference between current view + copied view, then creates new events for all copied events
	pasteRec = function () {
		this.copyInProgress = false

		let calViewDate = calendar.view.activeStart
		let allEvents = calendar.getEvents()
		let dayDiff = dayjs(calViewDate).diff(dayjs(this.copyDates[0]), 'day')

		for (let s in allEvents) {
			let event = allEvents[s]
			if (this.copyDates.includes(dayjs(event.start).format('YYYY[-]MM[-]DD'))) {
				function addDays(date, days) {
					var result = new Date(date)
					result.setDate(date.getDate() + days)
					return result
				}

				let startTime = addDays(new Date(event.start), dayDiff)
				let endTime = addDays(new Date(event.end), dayDiff)
				let duration = dayjs(event.end).diff(dayjs(event.start), 'minute')

				let fileNameDate = dayjs(startTime).format('YYYY[-]MM[-]DD')
				let name = fileNameDate + '_' + event.title
				name = checkNameAvailability(name)

				let data = {
					date: startTime,
					start_time: minutesFromMidnight(startTime),
					duration: duration,
					name: name,
					weekly_repeated: false,
					is_enabled: true,
					source_unique_id: event.extendedProps.source_unique_id,
					destination_unique_ids: event.extendedProps.destinations,
					interrupt_manual_recordings: event.extendedProps.interrupt_manual_recordings || false,
					switcher_source_id: event.extendedProps.switcher_source_id || null,
				}

				fetch(`${getHttpProtocol()}${address}:${port}/scheduled_recordings/`, {
					method: 'POST',
					headers: {
						'Content-Type': 'application/json',
					},
					body: JSON.stringify(data),
				})
					.then((res) => {
						if (res.status == 200) {
							return res.json()
						}
					})
					.then((json) => {
						if (json.success) {
						} else {
							$('#canceledName').text('Unable to copy ' + data.name)
							$('#alertCanceled').delay(250).slideDown(400).delay(2000).slideUp(400)
						}
					})
			}
		}
		$('.copyButton').show()
		$('.pasteButton').hide()
		$('.cancelPaste').hide()
	}
	//Triggered by Cancel Paste button
	//Clears copied events and resets paste-related buttons
	cancelPaste = function () {
		this.copyDates = []
		this.copyInProgress = false
		if (calendar.view.type == 'timeGridWeek' || calendar.view.type == 'listWeek') {
			$('.copyButton').text('Copy Week')
			$('.pasteButton').text('Paste Week')
		} else if (calendar.view.type == 'timeGridDay') {
			$('.copyButton').text('Copy Day')
			$('.pasteButton').text('Paste Day')
		} else if (calendar.view.type == 'dayGridMonth') {
			$('.copyButton').text('Copy Month')
			$('.pasteButton').text('Paste Month')
		}
		$('.copyButton').show()
		$('.pasteButton').hide()
		$('.cancelPaste').hide()
	}
})
