dayjs.extend(window.dayjs_plugin_customParseFormat)
dayjs.extend(window.dayjs_plugin_minMax)
dayjs.extend(window.dayjs_plugin_isBetween)
dayjs.extend(window.dayjs_plugin_duration)
dayjs.isSameOrBefore = (e,t) => {return e.isSame(t) || e.isBefore(t)}
dayjs.isSameOrAfter  = (e,t) => {return e.isSame(t) || e.isAfter(t)}

const el = (id) => document.getElementById(id);

let selected_cal_type = Cookie.get('caltype') ?? "daily";
el("caltype").value = selected_cal_type;

const date = Cookie.get('date');
let selected_date = date ? dayjs(date) : dayjs().startOf('day');
el('selected_date').value = selected_date.format('YYYY-MM-DD');

let selected_event = null;
let update_timeout = null;
let lastPicker = null;
let week_starts = [];
let weeks = [];

let startOfWorkDay, endOfWorkDay, startOfDay, endOfDay, startOfMonth, endOfMonth, startOfCalMonth, endOfCalMonth, lastEvent;

const init_times = (today) => {
	startOfWorkDay = today.hour(8).minute(0).second(0);
	endOfWorkDay = today.hour(17).minute(0).second(0);
	lastEvent = today.hour(16).minute(30).second(0);
	startOfDay = today.startOf('day');
	endOfDay = today.endOf('day');
	startOfMonth = today.startOf('month');
	endOfMonth = today.endOf('month');
	startOfCalMonth = dayjs(startOfMonth).startOf('week');
	endOfCalMonth = dayjs(endOfMonth).endOf('week');
}

const create_daily_event = (event) => {
	let node = document.createElement("DIV");
	node.classList.add("event");
	if (event.timetbd && !event.datetbd) {
		node.classList.add("timetbd");
	}

	let border = document.createElement("DIV");
	border.classList.add("border-color");
	border.style.backgroundColor = event.colour;
	node.appendChild(border);

	let content = document.createElement("DIV");
	content.classList.add("p-1", "flex-grow-1");
	let html = `<span class='title'>${event.name}</span>`;
	if (event.message) {
		html += `<span> - ${event.message.replaceAll('\n','<br>')}</span>`;
	}
	content.innerHTML = html;
	node.appendChild(content);

	node.addEventListener("click", () => { eventedit(event); } );

	return node;
}

const create_monthly_event = (left, top, right, event, week) => {
	let node = document.createElement("DIV");
	node.className = "monthevent text-truncate";
	if (event.datetbd) {
		node.className += " datetbd";
	}
	node.title = event.message ? `${event.name} - ${event.message}` : event.name || '&nbsp;';
	node.innerHTML = node.title;

	node.style.left = left + "%";
	node.style.top = top + "rem";
	node.style.right = right + "%";
	node.style.backgroundColor = event.colour;

	node.addEventListener("click", (e) => {
		e.stopPropagation();
		eventedit(event);
	} );

	week.append(node);
}

const collide = (first, second, domain) => {
	return ( first[domain + "fromeffective"].isBetween(second[domain + "fromeffective"], second[domain + "toeffective"], null, '[]') || 
			second[domain + "fromeffective"].isBetween( first[domain + "fromeffective"],  first[domain + "toeffective"], null, '[]'));
}

const conflict = (events, domain) => {
	let conflict_list = [];
	for (let i = 0; i < events.length; i++) {
		conflict_list[i] = [];
		for (let j = 0; j < events.length; j++) {
			if ((i != j) && collide(events[i], events[j], domain)) {
				conflict_list[i].push(j);
			}
		}
	}
	return conflict_list;
}

const connected_components = (adj) => {
	let visited = new Array(adj.length)
	for(let i=0; i<adj.length; ++i) {
		visited[i] = false;
	}
	let components = [];
	for(let i=0; i<adj.length; ++i) {
		if(visited[i]) {
			continue;
		}
		let to_visit = [i];
		let cc = [i];
		visited[i] = true;
		while(to_visit.length > 0) {
			let v = to_visit.pop();
			for(let j=0; j<adj[v].length; ++j) {
				let u = adj[v][j]
				if(visited[u]) {
					continue;
				}
				visited[u] = true;
				to_visit.push(u);
				cc.push(u);
			}
		}
		components.push(cc);
	}
	return components;
}

const event_fits = (events, index, stream, domain) => {
	for (let i = 0; i < stream.length; i++) {
		if (collide(events[stream[i]], events[index], domain)) {
			return false;
		}
	}
	return true;
}

const fit_events = (components, events, domain) => {
	components.forEach((component) => {
		let events_per_stream = [ [] ];
		component.forEach((index) => {
			let i = 0;
			while (true) {
				if (event_fits(events, index, events_per_stream[i], domain)) {
					events_per_stream[i].push(index)
					events[index].unitoffset = i;
					break;
				}
				i++;
				if (i == events_per_stream.length) {
					events_per_stream.push([]);
				}
			}
		});
		component.forEach((index) => {
			events[index].unitwidth = events_per_stream.length;
		});
	});
}

//Helpers for daily calendar
const setup_ticker = () => {
	let ticker = ['<div id="now"></div>'];

	for (let i = dayjs(startOfWorkDay); dayjs.isSameOrBefore(i, endOfWorkDay); i = i.add(30, 'minutes')) {
		if (i.minute() == 0) {
			ticker.push(`<div><span>${i.format("h:mm")}</span> ${i.format("A")}</div>`);
		}
		else {
			ticker.push(`<div><span>&nbsp;</span>${i.format("h:mm")}</div>`);
		}
	}

	el('timings').innerHTML = ticker.join('');
}

const setup_daily = (dailyEvents, allDayEvents) => {
	el('main').replaceChildren(el('t-daily').content.cloneNode(true));
	el('main').classList.remove("flex-column");
	el('main').classList.add("flex-row");
	setup_ticker();
	el('events').addEventListener("dblclick", eventadd);

	let conflict_list = conflict(dailyEvents, "time");
	let components = connected_components(conflict_list);

	fit_events(components, dailyEvents, "time");

	dailyEvents.forEach(event => {
		let left = (99/event.unitwidth) * (event.unitoffset) + 1;
		let top = (event.timefromeffective - startOfWorkDay) / (endOfWorkDay - startOfWorkDay) * 100;
		let width = 99/event.unitwidth - 1;
		let height = (event.timetoeffective - event.timefromeffective) / (endOfWorkDay - startOfWorkDay) * 100 - 0.2;
		let node = create_daily_event(event);
		node.classList.add("position-absolute");
		node.style.width = width + "%";
		node.style.height = height + "%";
		node.style.top = top + "%";
		node.style.left = left + "%";
		el("events").append(node);
	});

	allDayEvents.forEach(event => {
		let node = create_daily_event(event);
		el("allday").append(node);
	});

}

//Helpers for monthly calendar
const setup_day_names = () => {
	let day_names = '';

	for (let word of ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]) {
		day_names += `<div class='col text-truncate text-center'>${word}</div>`;
	}

	el('daynames').innerHTML = day_names;
}

const setup_day_cells = () => {
	let day_cells = '';
	week_starts = [];
	weeks = [];
	let today = dayjs(startOfCalMonth);
	let node = null;

	for (; dayjs.isSameOrBefore(today, endOfCalMonth); today = today.add(1, 'days')) {
		if (today.day() == 0) {
			node = document.createElement("DIV");
			node.className = "row";
			node.eventcounter = 0;
			node.expanded = false;
			node.expandable = false;
			node.addEventListener("click", (e) => {
				let target = e.currentTarget;
				if (! target.expandable) { return; }
				target.expanded = !target.expanded;
				let newheight = target.expanded ? Math.max((target.eventcounter + 1) * 2.5 + 1, 11) : 11;
				target.style.height = newheight + "rem";
				if (target.expanded) {
					target.classList.remove("expandable");
					target.classList.add("contractable");
				}
				else {
					target.classList.add("expandable");
					target.classList.remove("contractable");
				}
			} );
			weeks.push(node);
			week_starts.push(dayjs(today));
		}
		let current = (today.isBefore(startOfMonth) || today.isAfter(endOfMonth)) ? "notcurrent" : "";
		let thisday = today.isSame(dayjs(), 'day') ? "today" : "";
		let weekend = (today.day() == 0 || today.day() == 6) ? "weekend" : "";
		day_cells += `<div class='col text-truncate cell ${current} ${thisday} ${weekend}'><h5>${today.date()}</h5></div>`;
		if (today.day() == 6) {
			node.innerHTML = day_cells;
			el('monthlycalendar').append(node);
			day_cells = "";
		}
	}
	week_starts.push(dayjs(today));
}

const breakup = (events) => {
	let brokenupevents = [];
	for (let event of events) {
		for (let weekstart of week_starts) {
			if (weekstart.isBetween(event.datefromeffective, event.datetoeffective, null, '[]')) {
				if (!event.broken) {
					let mod = { ...event };
					mod.datetoeffective = dayjs(weekstart).subtract(1, 'days');
					if (dayjs.isSameOrAfter(mod.datetoeffective, mod.datefromeffective)) {
						brokenupevents.push(mod);
					}
					event.broken = true;
				}

				let mod = { ...event };
				mod.datefromeffective = dayjs(weekstart);
				mod.datetoeffective = dayjs.min(mod.datetoeffective, dayjs(weekstart).add(6, "days"));
				brokenupevents.push(mod);
			}
		}
		if (!event.broken) {
			brokenupevents.push(event);
		}
	}
	return brokenupevents;
}

const setup_monthly = (events) => {
	el('main').replaceChildren(el('t-monthly').content.cloneNode(true));
	el('main').classList.remove("flex-row");
	el('main').classList.add("flex-column");
	setup_day_names();
	setup_day_cells();

	let brokenupevents = breakup(events);
	let conflict_list = conflict(brokenupevents, "date");
	let components = connected_components(conflict_list);

	fit_events(components, brokenupevents, "date");

	for (let event of brokenupevents) {
		for (let i = 0; i < week_starts.length - 1; i++) {
			if (event.datefromeffective.isBetween(week_starts[i], week_starts[i+1], null, '[)')) {
				let leftdaysdiff = dayjs.duration(event.datefromeffective.diff(week_starts[i])).asDays();
				let left = leftdaysdiff / 7 * 100 + 0.5;
				let top = 2.5*(event.unitoffset+1);
				let rightdaysdiff = dayjs.duration(week_starts[i+1].diff(event.datetoeffective)).asDays() - 1;
				let right = rightdaysdiff / 7 * 100 + 0.5;
				let numevents = Math.max(weeks[i].eventcounter, event.unitoffset+1);
				if (numevents > 3) {
					weeks[i].eventcounter = numevents;
					weeks[i].expandable = true;
					weeks[i].classList.add("expandable");
				}
				create_monthly_event(left, top, right, event, weeks[i]);
				break;
			}
		}
	}
}

const update_indicator = () => {
	let curtime = (dayjs() - startOfWorkDay) / (endOfWorkDay - startOfWorkDay) * 100;
	el('now').style.top = curtime + "%";
	el('now').style.display = curtime >= 0 && curtime < 100 ? 'block' : 'none';
}

const day_onload = (request) => {
	let data = null;
	if (request.status >= 200 && request.status < 400) {
		data = JSON.parse(request.responseText);
	}
	//else { }
	let dailyEvents = [];
	let allDayEvents = [];
	for (let event of data) {
		const tf = dayjs(event.timefrom, "H:mm:ss");
		event.datefrom = dayjs(event.datefrom);
		event.timefrom = dayjs(event.datefrom).hour(tf.hour()).minute(tf.minute());
		const tt = dayjs(event.timeto, "H:mm:ss");
		event.dateto = dayjs(event.dateto);
		event.timeto = dayjs(event.dateto).hour(tt.hour()).minute(tt.minute());
		event.colour ??= event.deleted ? "#ff0000" : "#32acea";

		const startsBefore = event.datefrom.isBefore(startOfDay) || dayjs.isSameOrBefore(event.timefrom, startOfWorkDay);
		const endsAfter = event.dateto.isAfter(endOfDay) || dayjs.isSameOrAfter(event.timeto, endOfWorkDay) || event.datetbd;

		event.timefromeffective = startsBefore ? startOfWorkDay : dayjs.min(lastEvent, event.timefrom);

		if (endsAfter || dayjs(event.timefromeffective).add(30, 'minutes').isAfter(endOfWorkDay)) {
			event.timetoeffective = endOfWorkDay;
		}
		else if (event.timetbd) {
			if (event.dateto.isBefore(dayjs(), 'day')) {
				event.timetoeffective = endOfWorkDay;
			}
			else {
				event.timetoeffective = dayjs.min(dayjs().add(1, 'hour'), endOfWorkDay);
			}
		}
		else {
			event.timetoeffective = dayjs.max(dayjs(event.timefromeffective).add(30, 'minutes'), event.timeto);
		}

		if (startsBefore && endsAfter) {
			allDayEvents.push(event);
		}
		else {
			dailyEvents.push(event);
		}
	}
	setup_daily(dailyEvents, allDayEvents);
	update_indicator();
}

const month_onload = (request) => {
	let data = null;
	if (request.status >= 200 && request.status < 400) {
		data = JSON.parse(request.responseText);
	}
	//else { }
	for (let event of data) {
		let tf = dayjs(event.timefrom, "H:mm:ss");
		event.datefrom = dayjs(event.datefrom);
		event.timefrom = dayjs(event.datefrom).hour(tf.hour()).minute(tf.minute());
		let tt = dayjs(event.timeto, "H:mm:ss");
		event.dateto = dayjs(event.dateto);
		event.timeto = dayjs(event.dateto).hour(tt.hour()).minute(tt.minute());
		event.datefromeffective = event.datefrom;
		event.datetoeffective = event.dateto;
		if (event.datefrom.isBefore(startOfCalMonth)) {
			event.datefromeffective = startOfCalMonth;
		}
		if (event.dateto.isAfter(endOfCalMonth)) {
			event.datetoeffective = endOfCalMonth;
		}
		if (event.datetbd) {
			event.datetoeffective = endOfDay;
		}
		event.colour = event.deleted ? "#ff0000" : "#32acea";
	}
	setup_monthly(data);
}

const adjustField = (field) => {
	el(`${field}to`).prop( "readOnly", el(`${field}tbd`).prop("checked") );
}

const eventadd = () => {
	selectedEvent = null;
	let timenow = dayjs().format("HH:mm");
	el('name').value = "";
	el('message').value = "";
	el('timefrom').value = timenow;
	el('datefrom').value = selected_date.format('YYYY-MM-DD');
	el('timeto').value = timenow;
	el('dateto').value = selected_date.format('YYYY-MM-DD');
	el('timetbd').checked = false;
	el('datetbd').checked = false;
	//adjustField('time');
	//adjustField('date');
	if (el('eventdelete')) {
		el('eventdelete').style.display = 'none';
	}
	const myModal = new bootstrap.Modal(el('eventModal'))
	myModal.show();
}

const eventedit = (event) => {
	selectedEvent = event.id;
	el('name').value = event.name;
	el('message').value = event.message;
	el('timefrom').value = event.timefrom.format("HH:mm");
	el('datefrom').value = event.datefrom.format('YYYY-MM-DD');
	el('timeto').value = event.timeto.format("HH:mm");
	el('dateto').value = event.dateto.format('YYYY-MM-DD');
	el('timetbd').checked = event.timetbd;
	el('datetbd').checked = event.datetbd;

	if (el('eventdelete')) {
		el('eventdelete').style.display = 'block';
	}
	const myModal = new bootstrap.Modal(el('eventModal'))
	myModal.show();
}

const init_event_listeners = () => {
	el('selected_date').addEventListener('input', (e) => {
		selected_date = dayjs(e.target.value);
		init_times(selected_date);
		Cookie.set('date', selected_date.format('YYYY-MM-DD'), selected_date.endOf('day').toDate());
		lastPicker = null;
		clearTimeout(update_timeout);
		update();
	});

	const showPicker = (e) => {
		if (e.currentTarget == lastPicker) {
			lastPicker = null;
			return;
		}
		lastPicker = e.currentTarget;
		e.currentTarget.showPicker();
	}
	let pickers = ['selected_date', 'timefrom', 'datefrom', 'timeto', 'dateto'];
	for (let picker of pickers) {
		el(picker).addEventListener('click', showPicker);
		el(picker).addEventListener('blur', (e) => {lastPicker = null;})
	}

	el('caltype').addEventListener("change", (e) => {
		selected_cal_type = el('caltype').value;
		Cookie.set('caltype', selected_cal_type);
		clearTimeout(update_timeout);
		update();
	});
	
	el('eventModal').addEventListener('shown.bs.modal', () => {
		el('name').focus();
	})
}

const init_user_listeners = () => {
	el('checkout').addEventListener("click", eventadd);

	const eventForm = el('eventform');

	eventForm.addEventListener("submit", (e) => {
		e.preventDefault();
		eventForm.action = selectedEvent ? `/calendar/event/${selectedEvent}/edit` : '/calendar/event/add';
		eventForm.submit();
	});

	el('eventdelete').addEventListener("click", (e) => {
		eventForm.action = `/calendar/event/${selectedEvent}/delete`;
		eventForm.submit();
	});
	el('eventsubmit').addEventListener("click", (e) => {
		eventForm.action = selectedEvent ? `/calendar/event/${selectedEvent}/edit` : '/calendar/event/add';
		eventForm.submit();
	});
}