_pageDriverVersion = '1.0.1';

// Handle color for icon svg with id="logo" based on the current theme
const logo = document.getElementById('logo');
if (logo) {
	logo.style.fill = getComputedStyle(document.documentElement).getPropertyValue('--bulma-text');
}

if (_wrapperVersion === undefined) {
	console.error('API Wrapper not found; Please include the API Wrapper before including the Page Driver');
	exit();
} else {
	console.log('API Wrapper found; Page Driver is ready to use');
}

// Find all tables on the page which have data-dataSource attribute
var tables = document.querySelectorAll('table[data-dataSource]');
//var tables = []

// Get all single values with data-dataSource, data-dataCol and data-dataAction
var singleValues = document.querySelectorAll('span[data-dataSource]');

// Find all search fields with data-searchTargetId
var searchFields = document.querySelectorAll('input[data-searchTargetId]');

// Find all modalForms
var modalForms = document.querySelectorAll('form[data-targetTable]');


// Create a floating container for notifications
const notificationContainer = document.createElement('div');
notificationContainer.classList.add('notification-container');
document.body.appendChild(notificationContainer);
let notifications = [];

console.info('Processing single values');
console.info(singleValues);

// Iterate over all single values
singleValues.forEach(async (singleValue) => {
	writeSingelton(singleValue);
});

// Iterate over all tables
tables.forEach(async (table) => {
	// Get THs and attach onClick event to sort
	const ths = table.querySelectorAll('th');
	ths.forEach((th) => {
		if(th.getAttribute('fnc') == "actions") {
			return;
		}
		th.style.cursor = 'pointer';
		th.style.userSelect = 'none';
		th.addEventListener('click', async function () {
			const table = th.closest('table');
			const order = th.getAttribute('data-order');
			// Clear all other order attributes
			ths.forEach((th) => {
				if (th != this) {
					th.removeAttribute('data-order');
					th.classList.remove("bi-caret-up-fill")
					th.classList.remove("bi-caret-down-fill")
				}
			});
			if (order == 'ASC') {
				th.setAttribute('data-order', 'DESC');
				th.classList.add("bi-caret-down-fill")
				th.classList.remove("bi-caret-up-fill")
			} else {
				th.setAttribute('data-order', 'ASC');
				th.classList.add("bi-caret-up-fill")
				th.classList.remove("bi-caret-down-fill")
			}
			refreshTable(table);
		});
	});
	refreshTable(table);

});

async function writeSingelton(element) {
	const table = element.getAttribute('data-dataSource');
	console.log('Table: ', table, ' Action: ', element.getAttribute('data-dataAction'), ' Element: ', element);
	switch (element.getAttribute('data-dataAction')) {
		case 'COUNT': {
			console.log('Count action found');
			element.innerHTML = await getCountByTable(table);
			break;
		}
		case 'SPECIAL': {
			if (table == 'version') {
				element.innerHTML = (await getServerVersion())['version'];
				break;
			}
		}
		default: {
			console.error('Unknown action found: ', element.getAttribute('data-dataAction'));
			break;
		}
	}

	element.classList.remove('is-skeleton');
}

// Attach listeners to search fields
searchFields.forEach((searchField) => {
	// Apply restrictions to search field (min, max, chars, etc)

	getApiDescriptionByTable(document.getElementById(searchField.getAttribute('data-searchTargetId')).getAttribute('data-dataSource')).then((desc) => {
		desc = desc['GET']['keys']['search'];
		var rules = desc['rules'];
		rules.forEach((rule) => {
			switch (rule['name']) {
				case 'min': {
					searchField.setAttribute('minlength', rule['args']['limit']);
					break;
				}
				case 'max': {
					searchField.setAttribute('maxlength', rule['args']['limit']);
					break;
				}
			}
		});
	});
	searchField.addEventListener('input', async function () {
		console.log('Search field changed: ', searchField);
		if (searchField.checkValidity() == false) {
			console.log('Invalid input');
			searchField.classList.add('is-danger');
			return;
		} else {
			searchField.classList.remove('is-danger');
			const targetId = searchField.getAttribute('data-searchTargetId');
			const target = document.getElementById(targetId);
			const table = target.getAttribute('data-dataSource');
			const column = target.getAttribute('data-dataCol');
			const value = searchField.value;
			console.log('Searching for ', value, ' in ', table, ' column ', column);
			refreshTableByName(table);
		}
	});
});

// Attach listeners to modal forms
modalForms.forEach((modalForm) => {
	// Add validation to form by using API description (everything is assumed POST for now)
	modalForm.addEventListener('input', async function (event) {
		if (event.target.checkValidity() == false) {
			modalForm.querySelector("input[type='submit']").setAttribute('disabled', true);
			event.target.classList.add('is-danger');
			return;
		} else {
			modalForm.querySelector("input[type='submit']").removeAttribute('disabled');
			event.target.classList.remove('is-danger');
		}
	});


	getApiDescriptionByTable(modalForm.getAttribute('data-targetTable')).then((desc) => {
		console.log('Description: ', desc);
		const keys = desc['POST']['keys'];
		// Apply resitrictions and types to form fields
		for (key in keys) {
			const field = modalForm.querySelector("input[name='" + key + "']");
			if (field) {
				const rules = keys[key]['rules'];
				const flags = keys[key]['flags'];
				console.log('Field: ', field, ' Rules: ', rules, ' Flags: ', flags);
				rules.forEach((rule) => {
					switch (rule['name']) {
						case 'min': {
							field.setAttribute('minlength', rule['args']['limit']);
							break;
						}
						case 'max': {
							field.setAttribute('maxlength', rule['args']['limit']);
							break;
						}
						case 'pattern': {
							field.setAttribute('pattern', rule['args']['regex'].substring(1, rule['args']['regex'].length - 1));
							//field.setAttribute("pattern", "^[\\+]?[\\(]?[0-9]{3}[\\)]?[\\-\\s\\.]?[0-9]{3}[\\-\\s\\.]?[0-9]{4,9}$");
							break;
						}
						case 'type': {
							//field.setAttribute("type", rule["args"]["type"]);
							console.log('Type: ', rule['args']['type']);
							break;
						}
						case 'email': {
							field.setAttribute('type', 'email');
							break;
						}
					}
				});
				if (flags) {
					flags['presence'] == 'required' ? field.setAttribute('required', true) : field.removeAttribute('required');
				}
			}
		}
		console.log('Keys: ', keys);
	});

	modalForm.addEventListener('submit', async function (event) {
		event.preventDefault();
		// Check what button submitted the form and if it has data-actionBtn = save
		// If not, close modal
		const pressedBtn = event.submitter;
		if (pressedBtn.getAttribute('data-actionBtn') != 'save') {
			modalForm.closest('.modal').classList.remove('is-active');
			return;
		}

		// Find .entryPhase and hide it
		const entryPhase = modalForm.querySelector('.entryPhase');
		const loadPhase = modalForm.querySelector('.loadPhase');
		if (entryPhase) {
			entryPhase.classList.add('is-hidden');
		}
		if (loadPhase) {
			loadPhase.classList.remove('is-hidden');
		}
		console.log('Form submitted: ', modalForm);
		const table = modalForm.getAttribute('data-targetTable');
		const data = new FormData(modalForm);
		// Convert to JSON object
		let jsonData = {};
		data.forEach((value, key) => {
			jsonData[key] = value;
		});
		console.log('JSON Data: ', jsonData);
		let resp = {};
		if(modalForm.getAttribute('data-action') == 'edit') {
			Rid = modalForm.getAttribute('data-rid');
			resp = await updateRow(table, Rid,jsonData);
			modalForm.setAttribute('data-action', 'create');
		} else {
			resp = await createEntry(table, jsonData);
		}
		
		console.log('Response: ', resp);
		if (resp['status'] == 'CREATED' || resp['status'] == 'UPDATED') {
			console.log('Entry created successfully');
			modalForm.closest('.modal').classList.remove('is-active');
			modalForm.reset();
			// Hide loadPhase
			if (loadPhase) {
				loadPhase.classList.add('is-hidden');
			}
			// Show entryPhase
			if (entryPhase) {
				entryPhase.classList.remove('is-hidden');
			}
		} else {
			// Hide loadPhase
			if (loadPhase) {
				loadPhase.classList.add('is-hidden');
			}
			// Show entryPhase
			if (entryPhase) {
				entryPhase.classList.remove('is-hidden');
			}
			// TODO: Show error message
		}

		// Find all tables with data-searchTargetId set to table
		setTimeout(() => {
			refreshTableByName(table);
			updateSingeltonsByTableName(table);
		}, 500);
	});
});

// Helper
async function refreshTable(table) {
	// Refresh a table while keeping (optionally set) search value
	const searchField = document.querySelector("input[data-searchTargetId='" + table.id + "']");
	// Get state of order and sort
	const ths = table.querySelectorAll('th');
	const columnIndices = [];
	ths.forEach((th, index) => {
		columnIndices[th.getAttribute('data-dataCol')] = index;
	});
	let order = '';
	let column = '';
	ths.forEach((th) => {
		if (th.hasAttribute('data-order')) {
			order = th.getAttribute('data-order');
			column = th.getAttribute('data-dataCol');
		}
	});
	console.log('Order: ', order, ' Column: ', column);

	const maxLinesPerPage = table.getAttribute('data-pageSize');
	let currentPage = table.getAttribute('data-currentPage');
	if(currentPage == null) {
		table.setAttribute('data-currentPage', 1);
		currentPage = 1;
	}

	const start = (currentPage - 1) * maxLinesPerPage;
	const end = start + maxLinesPerPage;

	let paginationPassOnPre = {
		'start': start,
		'end': end,
		'currentPage': currentPage,
		'maxLinesPerPage': maxLinesPerPage
	};

	if (searchField) {
		const value = searchField.value;
		const dbTable = table.getAttribute('data-dataSource');
		const result = await returnTableDataByTableName(dbTable, value, order, column, take=maxLinesPerPage, skip= start);
		const totalResultCount = await getCountByTable(dbTable, value);
		paginationPassOnPre['dataLength'] = totalResultCount;
		var magMiddl = managePaginationMiddleware(result, paginationPassOnPre);
		var data = magMiddl[0];
		var paginationPassOn = magMiddl[1];
		clearTable(table);
		writeDataToTable(table, data, paginationPassOn);
	} else {
		const result = await returnTableDataByTableName(table.getAttribute('data-dataSource'), undefined, order, column, take= maxLinesPerPage, skip= start);
		const resultCount = await getCountByTable(table.getAttribute('data-dataSource'));
		paginationPassOnPre['dataLength'] = resultCount;
		var magMiddl = managePaginationMiddleware(result, paginationPassOnPre);
		var data = magMiddl[0];
		var paginationPassOn = magMiddl[1];
		clearTable(table);
		writeDataToTable(table, data, paginationPassOn);
	}
}

function managePaginationMiddleware(data, paginationPassOnPre) {
	const maxLinesPerPage = paginationPassOnPre['maxLinesPerPage'];

	const dataLength = paginationPassOnPre['dataLength'];
	const maxPages = Math.ceil(dataLength / maxLinesPerPage);
	paginationPassOn = paginationPassOnPre;
	paginationPassOn['maxPages'] = maxPages;
	// paginationPassOn['dataLength'] = dataLength;
	return [data, paginationPassOn];
}

async function refreshTableByName(name) {
	const dirtyTables = document.querySelectorAll("table[data-dataSource='" + name + "']");
	for (dirty of dirtyTables) {
		refreshTable(dirty);
	}
}

async function updateSingeltonsByTableName(name) {
	const dirtySingles = document.querySelectorAll("span[data-dataSource='" + name + "']");
	for (dirty of dirtySingles) {
		writeSingelton(dirty);
	}
}

function clearTable(table) {
	const tbody = table.querySelector('tbody');
	tbody.innerHTML = '';
}

function writeDataToTable(table, data, paginationPassOn) {
	if(data == undefined || data == null || data.length == 0) {
		return;
	}
	data = data.result
	console.log('Writing data to table: ', table, data);
	// Get THEAD and TBODY elements
	const thead = table.querySelector('thead');
	const tbody = table.querySelector('tbody');

	// get index per column
	const columns = thead.querySelectorAll('th');
	const columnIndices = [];
	columns.forEach((column, index) => {
		columnIndices[column.getAttribute('data-dataCol')] = index;
	});

	// All required cols
	let requiredCols = [];
	let actionFields = [];
	columns.forEach((column) => {
		// console.log('Column: ', column, ' FNC: ', column.getAttribute('data-fnc'), column.attributes);
		if(column.getAttribute('data-fnc') == "actions") {
			console.log('!!! Found actions column !!!');
			actionFields.push(column);
			return;
		}
		if(column.getAttribute('data-type') == 'hidden') {
			return;
		}
		requiredCols.push(column.getAttribute('data-dataCol'));
	});


	// Get paginationPassOn
	const start = paginationPassOn['start'];
	const end = paginationPassOn['end'];
	const currentPage = paginationPassOn['currentPage'];
	const maxLinesPerPage = paginationPassOn['maxLinesPerPage'];
	const maxPages = paginationPassOn['maxPages'];
	const dataLength = paginationPassOn['dataLength'];


	// Find nav with class pagination and data-targetTable="table.id"
	const paginationElement = document.querySelector("nav.pagination[data-targetTable='" + table.id + "']");
	const paginationList = paginationElement.querySelector('ul.pagination-list');
	console.log('Data length: ', dataLength, ' Max pages: ', maxPages);

	if(maxPages > 1) {
		// Clear pagination list
		paginationList.innerHTML = '';

		for (let i = 1; i <= maxPages; i++) {
			const li = document.createElement('li');
			li.innerHTML = '<a class="pagination-link" aria-label="Goto page ' + i + '" data-page="' + i + '">' + i + '</a>';
			if(i == currentPage) {
				li.querySelector('a').classList.add('is-current');
			}
			paginationList.appendChild(li);
		}


		// Remove unused pages, only leave first, last, current and 2 neighbors
		let pages = paginationList.querySelectorAll('li');
		let friends = []
		// Always add first and last
		friends.push(0);
		friends.push(pages.length - 1);
		friends.push(currentPage-1);

		// Add direct neighbors
		// friends.push(currentPage - 2);
		friends.push(currentPage);
		friends.push(currentPage - 2);


		// Deduplicate friends
		friends = [...new Set(friends)];
		// Sort friends
		friends.sort((a, b) => a - b);
		// Parse friends (string to int)
		friends = friends.map((x) => parseInt(x));

		console.log('Friends: ', friends, ' Pages: ', pages.length, ' Current: ', currentPage);


		// Remove everyone who is not a friend
		for(let i = 0; i < pages.length; i++) {
			if(friends.includes(i)) {
				continue;
			}
			pages[i].remove();
		}

		// Find all gaps (step size bigger then 1) and add an ellipsis in between the two numbers
		let last = 0;
		for(let i = 0; i < friends.length; i++) {
			if(friends[i] - last > 1) {
				const li = document.createElement('li');
				li.innerHTML = '<span class="pagination-ellipsis">&hellip;</span>';
				paginationList.insertBefore(li, pages[friends[i]]);
			}
			last = friends[i];
		}


		// Append on click event to all pagination links
		paginationList.querySelectorAll('a').forEach((link) => {
			link.addEventListener('click', async function() {
				const page = link.getAttribute('data-page');
				table.setAttribute('data-currentPage', page);
				refreshTable(table);
			});
		});

		paginationElement.classList.remove('is-hidden');
	} else {
		paginationElement.classList.add('is-hidden');
	}


	for (resultIndex in data) {
		const row = data[resultIndex];
		const tr = document.createElement('tr');
		requiredCols.forEach((column) => {
			// console.log('Column: ', column, ' Index: ', columnIndices[column]);
			const td = document.createElement('td');
			// Grab attribute from header
			const header = columns[columnIndices[column]];
			if(header.getAttribute('data-dataCol') == "FUNC:INLINE") {
				try {
					// Call data-ColHandler as a function
					const handler = window[header.getAttribute('data-ColHandler')];
					const result = handler(row);
					row[column] = result;

				} catch (e) {
					console.error('Error in ColHandler: ', e);
				}
			}

			if(header.getAttribute('data-type') == "bool") {
				td.innerHTML = row[column] ? '<i class="bi bi-check"></i>' : '<i class="bi bi-x"></i>';
				
			} else {
				td.innerHTML = row[column];
			}
			tr.appendChild(td);
			
		});

		// Add action fields
		actionFields.forEach((actionField) => {
			const td = document.createElement('td');
			const actions = actionField.getAttribute('data-actions').split(',');
			actions.forEach((action) => {
				const button = document.createElement('button');
				let icon = '';
				let color = 'is-primary';
				switch(action) {
					case 'edit': {
						icon = '<i class="bi bi-pencil"></i>';
						break;
					}
					case 'delete': {
						icon = '<i class="bi bi-trash"></i>';
						color = 'is-danger';
						break;
					}
				}
				// Add classes
				button.classList.add('button');
				button.classList.add('is-small');
				button.classList.add(color);
				button.classList.add('is-outlined');
				button.innerHTML = ` <span class="icon is-small">${icon}</span> `;
				button.style.marginRight = '5px';

				// Add data-action and data-id
				button.setAttribute('data-action', action);
				button.setAttribute("data-id", row["id"]);

				// Add event listener
				button.addEventListener('click', async function() {
					const table = actionField.closest('table');
					const row = button.closest('tr');
					const columns = table.querySelectorAll('th');
					const columnIndices = [];
					columns.forEach((column, index) => {
						columnIndices[column.getAttribute('data-dataCol')] = index;
					});
					const data = [];
					columns.forEach((column) => {
						data[column.getAttribute('data-dataCol')] = row.children[columnIndices[column.getAttribute('data-dataCol')]].innerText;
					});
					console.log('Data: ', data);
					switch(action) {
						case 'edit': {
							// Open modal with form
							const form = document.querySelector("form[data-targetTable='" + table.getAttribute('data-dataSource') + "']");
							const formTitle = form.querySelector('.title');
							const entryPhase = form.querySelector('.entryPhase');
							const loadPhase = form.querySelector('.loadPhase');
							const fields = form.querySelectorAll('input');
							// Set modal to edit mode
							form.setAttribute('data-action', 'edit');
							form.setAttribute('data-rid', button.getAttribute('data-id'));
							formTitle.innerText = 'Edit entry';
							fields.forEach((field) => {
								// Skip for submit button
								if(field.getAttribute('type') == 'submit') {
									return;
								}
								if(field.getAttribute('data-edit-transfer') == 'disable') {
									return;	
								}
								field.value = data[field.getAttribute('name')];
							});
							form.closest('.modal').classList.add('is-active');
							// TBD
							break;
						}
						case 'delete': {
							// confirm
							const confirm = window.confirm('Do you really want to delete this entry?');
							// Delete entry
							if(confirm) {
							const table = actionField.closest('table');
							const id = button.getAttribute('data-id');
							const resp = await deleteRow(table.getAttribute('data-dataSource'), id);
							if(resp['status'] == 'DELETED') {
								refreshTable(table);
								updateSingeltonsByTableName(table.getAttribute('data-dataSource'));
								createTemporaryNotification('Entry deleted successfully', 'is-success');
							} else {
								// Show error message
								// TODO: Show error message
								createTemporaryNotification('Error while deleting entry', 'is-danger');
							}
							}
							break;
						}
					}
				}
				);
				td.appendChild(button);
			});
			tr.appendChild(td);
		});
		tbody.appendChild(tr);
	}
}


// Handle modal
document.addEventListener('DOMContentLoaded', () => {
	// Functions to open and close a modal
	function openModal($el) {
		$el.classList.add('is-active');
	}

	function closeModal($el) {
		$el.classList.remove('is-active');
	}

	function closeAllModals() {
		(document.querySelectorAll('.modal') || []).forEach(($modal) => {
			closeModal($modal);
		});
	}

	// Add a click event on buttons to open a specific modal
	(document.querySelectorAll('.js-modal-trigger') || []).forEach(($trigger) => {
		const modal = $trigger.dataset.target;
		const $target = document.getElementById(modal);

		$trigger.addEventListener('click', () => {
			openModal($target);
		});
	});

	// Add a click event on various child elements to close the parent modal
	(document.querySelectorAll('.modal-close, .modal-card-head .delete, .modal-card-foot .button') || []).forEach(($close) => {
		const $target = $close.closest('.modal');
		if($target.data && $target.data.dissmiss == "false") {
			return;
		}

		$close.addEventListener('click', () => {
			closeModal($target);
		});
	});

	// Add a keyboard event to close all modals
	document.addEventListener('keydown', (event) => {
		if (event.key === 'Escape') {
			closeAllModals();
		}
	});
});


function createTemporaryNotification(message, type = 'is-success', timeout = 5000) {
	const notification = document.createElement('div');
	notification.classList.add('notification');
	notification.classList.add(type);
	notification.innerHTML = message;
	notificationContainer.appendChild(notification);
	setTimeout(() => {
		$(notification).fadeOut(500);
	}, timeout);
}

document.addEventListener('DOMContentLoaded', () => {
	(document.querySelectorAll('.notification .delete') || []).forEach(($delete) => {
	  const $notification = $delete.parentNode;
	  $delete.addEventListener('click', () => {
	    $notification.parentNode.removeChild($notification);
	  });
	});
      });