/******************************************************************************
 * Helper functions
 ******************************************************************************/

/**
 * Flash a message right below the header.
 */
var flash = function (message) {
    console.log("message:", message);

    let header = document.getElementsByTagName("header")[0];

    let div = document.createElement("div");
    div.textContent = message;
    div.className = "flash";

    header.after(div);
}

/**
 * Show an element using inline style
 */
var show = function (target) {
    target.style.display = 'block';
};

/**
 * Hide an element using inline style
 */
var hide = function (target) {
    target.style.display = 'none';
};

/**
 * Slugify a string. Uses underscores '_', not hyphens '-'.
 * Based on: https://lucidar.me/en/web-dev/how-to-slugify-a-string-in-javascript/
 */
var slugify = function(str) {
    str = str.replace(/^\s+|\s+$/g, '');

    // Make the string lowercase
    str = str.toLowerCase();

    // Remove accents, swap ñ for n, etc
    var from = "ÁÄÂÀÃÅČÇĆĎÉĚËÈÊẼĔȆÍÌÎÏŇÑÓÖÒÔÕØŘŔŠŤÚŮÜÙÛÝŸŽáäâàãåčçćďéěëèêẽĕȇíìîïňñóöòôõøðřŕšťúůüùûýÿžþÞĐđßÆa·/-,:;";
    var to   = "AAAAAACCCDEEEEEEEEIIIINNOOOOOORRSTUUUUUYYZaaaaaacccdeeeeeeeeiiiinnooooooorrstuuuuuyyzbBDdBAa______";
    for (var i=0, l=from.length ; i<l ; i++) {
	str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i));
    }

    // Remove invalid chars
    str = str.replace(/[^a-z0-9 -]/g, '')
    // Collapse whitespace and replace by _
	.replace(/\s+/g, '_')
    // Collapse underscores
	.replace(/_+/g, '_');

    return str;
}

/**
 * Quick and dirty validate crontab entries
 */
var validateCrontab = function(cron) {
    let regex = new RegExp(/(@(annually|yearly|monthly|weekly|daily|hourly|reboot))|(@every (\d+(ns|us|µs|ms|s|m|h))+)|((((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*) ?){5,7})/);
    return regex.test(cron);
}

/**
 * Handle enter key press inside input field
 */
var handleEnterKey = ({ key, target }) => {
    if (!target.classList.contains('enterable')) return;

    let droneId;
    if (target.dataset.droneId !== undefined) {
	droneId = (!target.dataset.droneId) ? undefined : target.dataset.droneId;
    }
    if (key === "Enter") {
	edit(target.parentNode, droneId);
    } else if (!target.classList.contains('edited')) {
	target.classList.add('edited');
    }
};

/**
 * Show enterable input field
 */
var showEnterableInput = function(target, droneId) {
    let text = target.querySelector('div');
    if (text.offsetParent === null) return;

    let input = target.querySelector('input');
    let value = text.innerText.trim();
    input.value = value;

    if (droneId !== undefined) {
	input.dataset.droneId = droneId
    }

    hide(text);
    show(input);

    input.addEventListener('keyup', handleEnterKey);
};

/******************************************************************************
 * AJAX-related functions
 ******************************************************************************/

/**
 * The POST function
 */
var post = function (uri, data) {
    console.log("uri:", uri, "\ndata:", data);

    let xhttp = new XMLHttpRequest();
    xhttp.open("POST", uri);
    xhttp.setRequestHeader("Content-Type", "application/json");
    xhttp.onreadystatechange = function() {
	if (this.readyState == 4 && this.status == 200) {
	    location.reload(true);
	} else if (this.readyState == 4 && this.responseText) {

	    // TODO
	    console.log("responseText:", this.responseText);

	    let error = JSON.parse(this.responseText);
	    flash(error.error);
	} else if (this.readyState == 4) {
	    flash("An unknown error has occurred")
	}
    };

    xhttp.send(JSON.stringify(data));
};

/**
 * The control function
 */
var control = function (target, droneId) {
    let uri = null;
    let action = null;

    switch (target.id) {
    case 'control-reload':
	uri = '/control/reload';
	action = 'reload Wurker';
	break;
    case 'control-pause':
	uri = '/control/pause';
	action = 'pause Wurker';
	break;
    case 'control-kill':
	uri = '/kill/hive';
	action = 'kill the Hive';
	break;
    }

    if (droneId !== undefined) {
	uri = '/drone/'+droneId+uri;
    }
    if ( ! uri) return;

    uri = '/api'+uri;

    // Get confirmation 1st
    let ok = confirm("Are you sure you want to "+action+"?");
    if (!ok) {
	console.log("Cancelled control action:", action);
	return;
    }

    console.log("Performing control action:", action);
    post(uri);
};

/**
 * The add function
 */
var add = function (target, droneId) {
    let option = target.dataset.option;
    let data = null;
    let uri = '/add/'+option;
    if (droneId !== undefined) {
	uri = '/drone/'+droneId+uri;
    }
    uri = '/api'+uri;

    if (option == "ondemand") {
	let key = target.dataset.key;
	data = [key];

	let ok = confirm("Are you sure you want to add '"+key+"' to ondemand Bees?")
	if (!ok) {
	    console.log("Cancelled action:", uri);
	    return;
	}
    }

    console.log("Performing action:", uri);
    post(uri, data);
}

/**
 * The edit function
 */
var edit = function (target, droneId) {
    let column = target.dataset.column;
    console.log("edit column:", column);

    let slug = target.parentNode.dataset.slug;
    let uri = null;
    let data = {};

    switch (column) {
    case 'cron':
	let cron = target.children[0].value;
	let old_cron = target.parentNode.dataset.cron;

	uri = '/edit/bee/'+slug;

	// Edge-cases: 'orphaned' is not a real Cron...  remove or add, instead
	if (cron == 'orphaned') {
	    data['cron_id'] = null;
	} else {
	    data["cron_slug"] = cron;
	}

	break;

    case 'command':
	let select = target.children[0];
	let cmd_id = select.value;
	let old_cmd_id = target.parentNode.dataset.command_id;

	// Block for confirmation
	let ok = confirm("Are you sure you want to change the Command?\nCheck your Args if you do!");
	if (!ok) {
	    console.log("Cancelled Command change");
	    select.value = old_cmd_id;
	    return;
	}

	data["command_id"] = cmd_id;
	uri = '/edit/bee/'+slug;
	break;

    case 'enabled':
	let enabled = target.matches(".enabled");
	data["enabled"] = !enabled;
	uri = '/edit/bee/'+slug;
	break;

    case 'name':
    case 'description':
	let input = target.querySelector('input');
	let text = target.querySelector('div');

	if (input.value.trim() == text.innerText.trim()) {
	    console.log('no change');
	    input.classList.remove('edited');
	    hide(input);
	    show(text);
	    return;
	}

	input.classList.add('edited');
	data[column] = input.value;
	uri = '/edit/bee/'+slug;
	break;

    case 'args':
	initJsonEditorModal(target, slug, droneId, editArgsCallback);
	return;
    }

    if ( ! uri) {
	console.log("Undefined URI");
	return false;
    }

    // TODO
    console.log("droneId:", droneId);

    if (droneId !== undefined) {
	uri = '/drone/'+droneId+uri;
    }
    uri = '/api'+uri;

    post(uri, data);
};

/**
 * The remove function
 */
var remove = function (target, droneId) {
    let option = target.dataset.option;
    let key = target.dataset.key;
    let uri = '/remove/'+option+'/'+key;
    if (droneId !== undefined) {
	uri = '/drone/'+droneId+uri;
    }
    uri = '/api'+uri;

    // Get confirmation 1st
    let ok = confirm("Are you sure you want to remove the "+option+" '"+key+"'?")
    if ( ! ok) {
	console.log("Cancelled action:", uri);
	return;
    }

    console.log("Performing action:", uri);
    post(uri, null);
}

/**
 * Handle add and remove actions (edit is special)
 */
var action = function (target, droneId) {
    let action = target.dataset.action;
    if (action == "add") {
	add(target, droneId);
    } else if (action == "remove") {
	remove(target, droneId);
    }
};

/******************************************************************************
 * JsonEditor and Args handlers
 ******************************************************************************/

/**
 * The edit args callback
 */
var editArgsCallback = function (target, slug, droneId, args, cbargs) {
    let uri = '/edit/bee/'+slug;
    if (droneId !== undefined) {
	uri = '/drone/'+droneId+uri;
    }
    uri = '/api'+uri;

    if (typeof args === 'string') {
	args = JSON.parse(args);
    }

    let data = {"args": args};
    post(uri, data);
}

/**
 * The input args callback
 */
var inputArgsCallback = function (target, slug, droneId, args, modal) {
    target.value = args;
    modal.style.display = "block";
}

/**
 * The cancel JsonEditor callback. Returns to the last modal
 */
var cancelJsonEditorCallback  = function (modal) {
    modal.style.display = "block";
}

/**
 * Init the JSON editor modal
 */
var initJsonEditorModal = function (target, slug, droneId, callback, cbargs, cancelCallback) {
    // Init the modal
    let modal = document.getElementById('jsoneditor-modal');
    modal.style.display = "block";

    // Init the JSON editor
    let editorContainer = document.getElementById('jsoneditor');
    var isModule = (target.parentNode.dataset.ismoqqdule == "true");

    let options = {
	mode: 'code',
	mainMenuBar: false,
	navigationBar: false,
	statusBar: true,
	onValidate: function (json) {
	    var errors = [];

	    if (json) {
		if (isModule && Array.isArray(json)) {
		    errors.push({
			path: [],
			message: 'PyBee (module) args must be JSON Objects: {}'
		    });
		} else if (!isModule && !Array.isArray(json)) {
		    errors.push({
			path: [],
			message: 'Bee (command) args must be JSON Arrays: []'
		    });
		}
	    }

	    if (errors.length) console.log("errors:", errors);
	    return errors;
	}
    };

    // Create the JSONEditor itself
    let json = (target.tagName.toLowerCase() === 'input') ? target.value : target.innerText;
    if (!json) {
	json = (isModule) ? json = '{}' : json = '[]';
    }

    let initJson = JSON.parse(json);
    let editor = new JSONEditor(editorContainer, options, initJson);

    // Handle the modal buttons
    let cancel = modal.getElementsByClassName("cancel")[0];
    let submit = modal.getElementsByClassName("submit")[0];

    // Cancel button
    cancel.onclick = function() {
	modal.style.display = "none";
	editor.destroy();

	if (cancelCallback !== undefined) {
	    cancelCallback(cbargs);
	}
    };

    // Save button
    submit.onclick = function() {
	modal.style.display = "none";

	let args = editor.get();
	let updated = JSON.stringify(args);

	if (target.tagName.toLowerCase() !== 'input') {
	    target.innerHTML = updated;
	}

	target.classList.add("changed");
	editor.destroy();

	// OK run the callback
	if (callback !== undefined) {
	    callback(target, slug, droneId, updated, cbargs);
	}
    }
};

/******************************************************************************
 * Init the various modals and form modals
 ******************************************************************************/

/**
 * Init the modal with the given ID
 */
var initModal = function (triggerId, modalId) {
    let btn = document.getElementById(triggerId);
    let modal = document.getElementById(modalId);
    let span = document.getElementsByClassName("close")[0];

    // When the user clicks on the button, open the modal
    btn.onclick = function() {
	modal.style.display = "block";
    }

    // When the user clicks on <span> (x), close the modal
    span.onclick = function() {
	modal.style.display = "none";
    }

    // When the user clicks anywhere outside of the modal, close it
    window.onclick = function(event) {
	if (event.target == modal) {
	    modal.style.display = "none";
	}
    }
};

/**
 * Init the form modal with the given ID
 */
var initFormModal = function (triggerId, droneId) {
    let triggerBtn = document.getElementById(triggerId);
    let modal = document.getElementById(triggerId+'-modal');

    let formId = triggerId+'-form';
    let form = document.getElementById(formId);
    form.reset();
    form.dataset.droneid = droneId;

    let cancelBtn = modal.querySelector('button.cancel');
    let submitBtn = modal.querySelector('button.submit');

    // When the user clicks on the button, open the modal
    triggerBtn.onclick = function() {
	modal.style.display = "block";
    }

    // When the user clicks on 'cancel' button, clear the form and close the modal
    cancelBtn.onclick = function() {
	modal.style.display = "none";
	form.reset();
    }

    // When the user clicks on 'submit' button, save the form and close the modal
    submitBtn.onclick = function(event) {
	if (saveForm(form, droneId)) {
	    modal.style.display = "none";
	    form.reset();
	}
    }

    // Init special form behaviors
    switch (formId) {
    case 'add-bee-form':
	initAddBeeForm(modal, form);
	break;

    case 'edit-cron-form':
	initEditCronForm(modal, form);
	break;

    case 'edit-command-form':
	initEditCommandForm(modal, form);
	break;
    }

    // TODO
    console.log("form droneId:", form.dataset.droneid);
};

var initAddBeeForm = function (modal, form) {
    // Add Bee allows args, so we need to init the JSONEditor
    let args = document.getElementById('add-bee-args');
    args.onfocus = function() {
	modal.style.display = "none";
	// TODO droneId
	initJsonEditorModal(args, null, '', inputArgsCallback, modal, cancelJsonEditorCallback);
    }

    // Both command and name must be set before we enable other fields
    let beeCommand = document.getElementById('add-bee-command');
    let beeName = document.getElementById('add-bee-name');
    let isModule = false;

    beeCommand.addEventListener('change', function(e) {
	if (!!beeName.value) {
	    [...form.elements].forEach(item => {
		item.disabled = false;
	    });

	    isModule = (beeCommand.options[beeCommand.selectedIndex].dataset.ismodule == 'true');
	    if (isModule) {
		args.value = '{}';
		args.parentNode.dataset.ismodule = 'true';
	    } else {
		args.value = '[]';
		args.parentNode.dataset.ismodule = 'false';
	    }

	    form.querySelector('#add-bee-args').classList.remove('disabled');
	}
    });

    beeName.addEventListener('keyup', function(e) {
	if (!!beeCommand.value) {
	    [...form.elements].forEach(item => {
		item.disabled = false;
	    });

	    if (this.value.length == 1) {
		isModule = (beeCommand.options[beeCommand.selectedIndex].dataset.ismodule == 'true');
		if (isModule) {
		    args.value = '{}';
		    args.parentNode.dataset.ismodule = 'true';
		} else {
		    args.value = '[]';
		    args.parentNode.dataset.ismodule = 'false';
		}

		form.querySelector('#add-bee-args').classList.remove('disabled');
	    }
	}
    });
};

var initEditCronForm = function (modal, form) {
    // Populate the form fields when user selects a Cron to edit
    let cronSelect = document.getElementById('edit-cron-selector');
    let cronName = document.getElementById('edit-cron-name');
    let cronCron = document.getElementById('edit-cron-cron');
    let cronOverrun = document.getElementById('edit-cron-overrun');

    // TODO
    console.log('cronJson:', cronJson);

    cronSelect.addEventListener('change', function(e) {
	let cronSlug = this.value;
	let oldCron = cronJson[cronSlug];

	if (!oldCron) return;

	cronName.value = oldCron.name;
	cronCron.value = oldCron.cron;
	cronOverrun.value = oldCron.overrun;
    });
};

var initEditCommandForm = function (modal, form) {
    // Populate the form fields when user selects a Command to edit
    let commandSelect = document.getElementById('edit-command-selector');
    let commandName = document.getElementById('edit-command-name');
    let commandCommand = document.getElementById('edit-command-command');
    let commandDescription = document.getElementById('edit-command-description');
    let commandIsModule = document.getElementById('edit-command-ismodule');

    commandSelect.addEventListener('change', function(e) {
	let commandId = this.value;
	let oldCommand = commandJson[commandId];

	if (!oldCommand) return;

	commandName.value = oldCommand.name;
	commandCommand.value = oldCommand.command;
	commandDescription.value = oldCommand.description;
	commandIsModule.checked = (oldCommand.is_module == true);
    });
};

/******************************************************************************
 * Save the form data, depending on which form it came from
 ******************************************************************************/

/**
 * Save the given form
 */
var saveForm = function (form, droneId) {

    // TODO
    console.log('Submitting form:', form);
    console.log('Submitting form to droneId:', droneId);

    switch (form.id) {
    case 'add-bee-form':
	return saveAddBeeForm(form, droneId);

    case 'add-cron-form':
	return saveAddCronForm(form, droneId);

    case 'edit-cron-form':
	return saveEditCronForm(form, droneId);

    case 'remove-cron-form':
	return saveRemoveCronForm(form, droneId);

    case 'add-command-form':
	return saveAddCommandForm(form, droneId);

    case 'edit-command-form':
	return saveEditCommandForm(form, droneId);

    case 'remove-command-form':
	return saveRemoveCommandForm(form, droneId);
    }

    // Finally, we can reset the form
    return false;
};

var saveAddBeeForm = function (form, droneId) {
    let uri = null;
    let data = {};
    let valid = true;
    let errorMsg = '';

    let beeCommand = form.querySelector('#add-bee-command');
    if (!beeCommand.value) {
	beeCommand.classList.add('invalid');
	valid = false;
	errorMsg += 'Bee requires a Command. ';
    } else {
	beeCommand.classList.remove('invalid');
    }

    let beeName = form.querySelector('#add-bee-name');
    if (!beeName.value) {
	beeName.classList.add('invalid');
	valid = false;
	errorMsg += "Bee requires a Name. ";
    } else {
	beeCommand.classList.remove('invalid');
    }

    if (!valid) {
	flash(errorMsg);
	return false;
    }

    // ...now we can assemble the data from the form and add the bee...
    let args = form.querySelector('#add-bee-args');
    let isModule = (beeCommand.dataset.ismodule == 'true');
    let beeSlug = slugify(beeName.value);

    data['name'] = beeName.value;
    data['slug'] = beeSlug;
    data['description'] = form.querySelector('#add-bee-description').value;
    data['args'] = JSON.parse(form.querySelector('#add-bee-args').value);
    data['enabled'] = form.querySelector('#add-bee-enabled').checked;
    data['command_id'] = beeCommand.value;

    // A special UI feature not in CLI
    let cronSlug = form.querySelector('#add-bee-cron').value;
    data['cron_slug'] = cronSlug;

    uri = '/add/bee';
    if (droneId !== undefined) {
	uri = '/drone/'+droneId+uri;
    }
    uri = '/api'+uri;

    // TODO add drone!
    console.log('adding bee:', data);

    post(uri, data);
    return true;
};

var saveAddCronForm = function (form, droneId) {
    let uri = null;
    let data = {};
    let valid = true;
    let errorMsg = '';

    let cronName = form.querySelector('#add-cron-name');
    if (!cronName.value) {
	cronName.classList.add('invalid');
	valid = false;
	errorMsg += 'Cron requires a Name. ';
    } else {
	cronName.classList.remove('invalid');
    }

    let cron = null;
    let cronCron = form.querySelector('#add-cron-cron');
    if (!cronCron.value) {
	cron = null;
    } else if (!validateCrontab(cronCron.value)) {
	cronCron.classList.add('invalid');
	valid = false;
	errorMsg += 'Crontab is invalid: "'+cronCron.value+'"';
    } else {
	cronCron.classList.remove('invalid');
	cron = cronCron.value;
    }

    let cronOverrun = form.querySelector('#add-cron-overrun');
    if (!cronOverrun.value) {
	cronOverrun.value = "0";
    }

    if (!valid) {
	flash(errorMsg);
	return false;
    }

    data['name'] = cronName.value;
    data['slug'] = slugify(cronName.value);
    data['cron'] = cron;
    data['overrun'] = parseInt(cronOverrun.value);

    uri = '/add/cron';
    if (droneId !== undefined) {
	uri = '/drone/'+droneId+uri;
    }
    uri = '/api'+uri;

    // TODO add drone!
    console.log('adding cron:', data);

    post(uri, data);
    return true;
};

var saveEditCronForm = function (form, droneId) {
    let uri = null;
    let data = {};
    let valid = true;
    let errorMsg = '';

    let cronSlug = form.querySelector('#edit-cron-selector').value;

    let cronName = form.querySelector('#edit-cron-name');
    if (!cronName.value) {
	cronName.classList.add('invalid');
	valid = false;
	errorMsg += 'Cron requires a Name. ';
    } else {
	cronName.classList.remove('invalid');
    }

    let cron = null;
    let cronCron = form.querySelector('#edit-cron-cron');
    if (!cronCron.value) {
	cron = null;
    } else if (!validateCrontab(cronCron.value)) {
	cronCron.classList.add('invalid');
	valid = false;
	errorMsg += 'Crontab is invalid: "'+cronCron.value+'"';
    } else {
	cronCron.classList.remove('invalid');
	cron = cronCron.value;
    }

    let cronOverrun = form.querySelector('#edit-cron-overrun');
    if (!cronOverrun.value) {
	cronOverrun.value = "0";
    }

    if (!valid) {
	flash(errorMsg);
	return false;
    }

    data['name'] = cronName.value;
    data['cron'] = cron;
    data['overrun'] = parseInt(cronOverrun.value);

    uri = '/edit/cron/'+cronSlug;
    if (droneId !== undefined) {
	uri = '/drone/'+droneId+uri;
    }
    uri = '/api'+uri;

    // TODO
    console.log('editing cron:', data);

    post(uri, data);
};

var saveRemoveCronForm = function (form, droneId) {
    let cronSlug = form.querySelector('#remove-cron-selector').value;
    let uri = '/remove/cron/'+cronSlug;
    if (droneId !== undefined) {
	uri = '/drone/'+droneId+uri;
    }
    uri = '/api'+uri;

    // TODO
    console.log('deleting cron:', cronSlug);

    post(uri);
    return true;
};

var saveAddCommandForm = function (form, droneId) {
    let uri = null;
    let data = {};
    let valid = true;
    let errorMsg = '';

    let commandName = form.querySelector('#add-command-name');
    if (!commandName.value) {
	commandName.classList.add('invalid');
	valid = false;
	errorMsg += 'Command requires a Name. ';
    } else {
	commandName.classList.remove('invalid');
    }

    let commandCommand = form.querySelector('#add-command-command');
    if (!commandCommand.value) {
	commandCommand.classList.add('invalid');
	valid = false;
	errorMsg += 'Command requires a Command (path to script), or Module (python). ';
    } else {
	commandCommand.classList.remove('invalid');
    }

    if (!valid) {
	flash(errorMsg);
	return false;
    }

    data["name"] = commandName.value;
    data["slug"] = slugify(commandName.value);
    data["command"] = commandCommand.value;
    data['description'] = form.querySelector('#add-command-description').value;
    data['is_module'] = form.querySelector('#add-command-ismodule').checked;

    uri = '/add/command';
    if (droneId !== undefined) {
	uri = '/drone/'+droneId+uri;
    }
    uri = '/api'+uri;

    // TODO
    console.log('adding command:', data);

    post(uri, data);
    return true;
};

var saveEditCommandForm = function (form, droneId) {
    let uri = null;
    let data = {};
    let valid = true;
    let errorMsg = '';

    let commandSlug = form.querySelector('#edit-command-selector').value;

    let commandName = form.querySelector('#edit-command-name');
    if (!commandName.value) {
	commandName.classList.add('invalid');
	valid = false;
	errorMsg += 'Command requires a Name. ';
    } else {
	commandName.classList.remove('invalid');
    }

    let commandCommand = form.querySelector('#edit-command-command');
    if (!commandCommand.value) {
	commandCommand.classList.add('invalid');
	valid = false;
	errorMsg += 'Command requires a Command (path to script), or Module (python). ';
    } else {
	commandCommand.classList.remove('invalid');
    }

    if (!valid) {
	flash(errorMsg);
	return false;
    }

    data['name'] = commandName.value;
    data['command'] = commandCommand.value;
    data['description'] = form.querySelector('#edit-command-description').value;
    data['is_module'] = form.querySelector('#edit-command-ismodule').checked;

    uri = '/edit/command/'+commandSlug;
    if (droneId !== undefined) {
	uri = '/drone/'+droneId+uri;
    }
    uri = '/api'+uri;

    // TODO
    console.log('editing command:', data);

    post(uri, data);
    return true;
};

var saveRemoveCommandForm = function (form, droneId) {
    let commandSlug = form.querySelector('#remove-command-selector').value;
    let uri = '/remove/command/'+commandSlug;
    if (droneId !== undefined) {
	uri = '/drone/'+droneId+uri;
    }
    uri = '/api'+uri;

    // TODO
    console.log('deleting command:', commandSlug);

    post(uri);
    return true;
};

/******************************************************************************
 * What to do when the DOM loads...
 ******************************************************************************/

document.addEventListener("DOMContentLoaded", function() {

    let droneId;
    if (!!document.getElementById('view-node')) {
	droneId = document.getElementById('view-node').value;
	if (!droneId) {
	    droneId = undefined;
	}
    }
    console.log('droneId:', droneId);

    // Init modal behavior
    initModal("view-info", "info-modal");

    let hiveModals = [
	'add-bee', 'add-cron', 'edit-cron', 'remove-cron', 'add-command', 'edit-command', 'remove-command'
    ];

    for (const modal of hiveModals) {
	initFormModal(modal, droneId);
    }

    // What to do when clicking on an editable element?
    document.addEventListener('click', function(e) {
	if (!e.target.matches('.editable') && !e.target.parentNode.matches('.editable')) return;
	e.preventDefault();
	let target = (e.target.matches('.editable')) ? e.target : e.target.parentNode;

	// Toggle hidden input?
	if (target.matches('.editable-input')) {
	    showEnterableInput(target, droneId);
	    return;
	}

	edit(target, droneId);
    });

    // What to do when changing a selector element?
    document.addEventListener('change', function(e) {
	if (!e.target.parentNode.matches('.selector')) return;
	e.preventDefault();
	edit(e.target.parentNode, droneId);
    });

    // What to do when one of the control buttons is pushed?
    document.addEventListener('click', function(e) {
	if (e.target.id !== 'control-reload' && e.target.id !== 'control-pause' && e.target.id !== 'control-kill') {
	    return;
	}
	e.preventDefault();
	control(e.target, droneId);
    });

    // What to do when a hive kill button is pushed?
    document.addEventListener('click', function(e) {
	if (!e.target.matches('.hive-action') && !e.target.matches('.bee-action')) return;
	e.preventDefault();
	action(e.target, droneId);
    });

    // What to do when the view-section selector changes?
    document.addEventListener('input', function(e) {
	if (e.target.id !== 'view-section') return;
	e.preventDefault();

	let option = e.target.value;
	let sections = document.querySelectorAll('section');

	sections.forEach(function (section, index) {
	    if ( ! option || section.classList.contains(option)) {
		show(section);
	    } else {
		hide(section);
	    }
	});
    });

    // What to do when the view-node selector changes?
    document.addEventListener('input', function(e) {
	if (e.target.id !== 'view-node') return;
	e.preventDefault();

	let app_id = e.target.value;
	let uri = '/drone/'+app_id;
	if (!app_id) uri = '/';

	console.log('Navigating to:', uri);
	window.location = uri;
    });

});
