/**
 * Escape a value for insertion into HTML.
 * Needed wherever markup is built as a string instead of via .text(), e.g.
 * DataTables cells, which are rendered through innerHTML.
 *
 * @param {*} value Value to escape
 * @returns {*} Escaped string, or the value unchanged if null/undefined
 */
function escapeHtml(value) {
	if (value === null || value === undefined) return value;
	return String(value).replace(/[&<>"']/g, c => ({'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;'})[c]);
}

// ========================================
// PLATFORM DETECTION UTILITIES
// ========================================

/**
 * Platform detection utilities using modern userAgentData API with fallback
 */
var PlatformDetection = {
    /**
     * Check if the current platform is macOS
     * @returns {boolean} True if platform is macOS
     */
    isMac: function() {
        // Use modern userAgentData API if available, fallback to userAgent
        if (navigator.userAgentData && navigator.userAgentData.platform) {
            return navigator.userAgentData.platform.toUpperCase().indexOf('MAC') >= 0;
        }
        return navigator.userAgent.toUpperCase().indexOf('MAC') >= 0;
    },

    /**
     * Check if the current platform is Windows
     * @returns {boolean} True if platform is Windows
     */
    isWindows: function() {
        if (navigator.userAgentData && navigator.userAgentData.platform) {
            return navigator.userAgentData.platform.toUpperCase().indexOf('WIN') >= 0;
        }
        return navigator.userAgent.toUpperCase().indexOf('WIN') >= 0;
    },

    /**
     * Check if the current platform is Linux
     * @returns {boolean} True if platform is Linux
     */
    isLinux: function() {
        if (navigator.userAgentData && navigator.userAgentData.platform) {
            return navigator.userAgentData.platform.toUpperCase().indexOf('LINUX') >= 0;
        }
        return navigator.userAgent.toUpperCase().indexOf('LINUX') >= 0;
    },

    /**
     * Check if the modifier key is pressed (Cmd on Mac, Ctrl on Windows/Linux)
     * @param {Event} event - The keyboard or mouse event
     * @returns {boolean} True if the platform-specific modifier key is pressed
     */
    isModifierKey: function(event) {
        return this.isMac() ? event.metaKey : event.ctrlKey;
    }
};

// ========================================
// QSO FORM UTILITIES
// ========================================

function setRst(mode) {
	if(mode == 'JT65' || mode == 'JT65B' || mode == 'JT6C' || mode == 'JTMS' || mode == 'ISCAT' || mode == 'MSK144' || mode == 'JTMSK' || mode == 'QRA64' || mode == 'FT8' || mode == 'FT4' || mode == 'JS8' || mode == 'JT9' || mode == 'JT9-1' || mode == 'ROS'){
		$('#rst_sent').val('-05');
		$('#rst_rcvd').val('-05');
	} else if (mode == 'FSK441' || mode == 'JT6M') {
		$('#rst_sent').val('26');
		$('#rst_rcvd').val('26');
	} else if (mode == 'CW') {
		if ($('#selectPropagation :selected').val() == 'AUR') {
			$('#rst_sent').val('59A');
			$('#rst_rcvd').val('59A');
		} else {
			$('#rst_sent').val('599');
			$('#rst_rcvd').val('599');
		}
	} else if (mode == 'RTTY' || mode == 'PSK31' || mode == 'PSK63') {
		$('#rst_sent').val('599');
		$('#rst_rcvd').val('599');
	} else if (mode == 'SSTV' || mode == 'ATV') {
		$('#rst_sent').val('595');
		$('#rst_rcvd').val('595');
	} else {
		$('#rst_sent').val('59');
		$('#rst_rcvd').val('59');
	}
}

function qsl_rcvd(id, method) {
	$(".ld-ext-right-r-"+method).addClass('running');
	$(".ld-ext-right-r-"+method).prop('disabled', true);
	$.ajax({
		url: base_url + 'index.php/qso/qsl_rcvd_ajax',
		type: 'post',
		data: {'id': id,
			'method': method
		},
		success: function(data) {
			$(".ld-ext-right-r-"+method).removeClass('running');
			$(".ld-ext-right-r-"+method).prop('disabled', false);
			if (data.message == 'OK') {
				$("#qsl_" + id).find("span:eq(1)").attr('class', 'qsl-green'); // Paints arrow green
				if ($("#qrz_"+ id).find("span:eq(0)").hasClass("qrz-green")) {
					$("#qrz_" + id).find("span:eq(0)").attr('class', 'qrz-yellow'); // marks the QRZ Upload as modified
				}
				$(".qsl_rcvd_" + id).remove(); // removes choice from menu
			}
			else {
				$(".bootstrap-dialog-message").append('<div class="alert alert-danger"><a href="#" class="close" data-dismiss="alert" aria-label="close">&times;</a>You are not allowed to update QSL status!</div>');
			}
		}
	});
}

function qsl_sent(id, method) {
	$.ajax({
		url: base_url + 'index.php/qso/qsl_sent_ajax',
		type: 'post',
		data: {'id': id,
			'method': method
		},
		success: function(data) {
			if (data.message == 'OK') {
				$("#qsl_" + id).find("span:eq(0)").attr('class', 'qsl-green'); // Paints arrow green
				if ($("#qrz_"+ id).find("span:eq(0)").hasClass("qrz-green")) {
					$("#qrz_" + id).find("span:eq(0)").attr('class', 'qrz-yellow'); // marks the QRZ Upload as modified
				}
				$(".qsl_sent_" + id).remove(); // removes choice from menu
			}
			else {
				$(".bootstrap-dialog-message").append('<div class="alert alert-danger"><a href="#" class="close" data-dismiss="alert" aria-label="close">&times;</a>You are not allowed to update QSL status!</div>');
			}
		}
	});
}

// Function: qsl_requested
// Marks QSL card requested against the QSO.
function qsl_requested(id, method) {
	$(".ld-ext-right-t-"+method).addClass('running');
	$(".ld-ext-right-t-"+method).prop('disabled', true);
	$.ajax({
		url: base_url + 'index.php/qso/qsl_requested_ajax',
		type: 'post',
		data: {'id': id,
			'method': method
		},
		success: function(data) {
			$(".ld-ext-right-t-"+method).removeClass('running');
			$(".ld-ext-right-t-"+method).prop('disabled', false);
			if (data.message == 'OK') {
				$("#qsl_" + id).find("span:eq(0)").attr('class', 'qsl-yellow'); // Paints arrow yellow
				if ($("#qrz_"+ id).find("span:eq(0)").hasClass("qrz-green")) {
					$("#qrz_" + id).find("span:eq(0)").attr('class', 'qrz-yellow'); // marks the QRZ Upload as modified
				}
			}
			else {
				$(".bootstrap-dialog-message").append('<div class="alert alert-danger"><a href="#" class="close" data-dismiss="alert" aria-label="close">&times;</a>You are not allowed to update QSL status!</div>');
			}
		}
	});
}

// Function: qsl_ignore
// Marks QSL card ignore against the QSO.
function qsl_ignore(id, method) {
	$(".ld-ext-right-ignore").addClass('running');
	$(".ld-ext-right-ignore").prop('disabled', true);
	$.ajax({
		url: base_url + 'index.php/qso/qsl_ignore_ajax',
		type: 'post',
		data: {'id': id,
			'method': method
		},
		success: function(data) {
			$(".ld-ext-right-ignore").removeClass('running');
			$(".ld-ext-right-ignore").prop('disabled', false);
			if (data.message == 'OK') {
				$("#qsl_" + id).find("span:eq(0)").attr('class', 'qsl-grey'); // Paints arrow grey
				if ($("#qrz_"+ id).find("span:eq(0)").hasClass("qrz-green")) {
					$("#qrz_" + id).find("span:eq(0)").attr('class', 'qrz-yellow'); // marks the QRZ Upload as modified
				}
			}
			else {
				$(".bootstrap-dialog-message").append('<div class="alert alert-danger"><a href="#" class="close" data-dismiss="alert" aria-label="close">&times;</a>You are not allowed to update QSL status!</div>');
			}
		}
	});
}

function displayQso(id) {
    $.ajax({
        url: base_url + 'index.php/logbook/view/' + id,
        type: 'post',
        success: function(html) {
            BootstrapDialog.show({
                title: lang_general_word_qso_data,
                cssClass: 'qso-dialog',
                size: BootstrapDialog.SIZE_WIDE,
                nl2br: false,
                message: html,
                onshown: function(dialog) {
                    var qsoid = $("#qsoid").text();
                    $(".editButton").html('<a class="btn btn-primary" id="edit_qso" href="javascript:qso_edit('+qsoid+')"><i class="fas fa-edit"></i>'+lang_general_edit_qso+'</a>');
                    var lat = $("#lat").text();
                    var lng = $("#lng").text();
                    var dxcc = $("#dxcc").text();
                    var callsign = $("#callsign").text();
                    var zoom = 5;
                    if (dxcc == 0) {
                        zoom = 1;
                    }
                    var mymap = L.map('mapqso').setView([lat,lng], zoom);

                    var tiles = L.tileLayer(option_map_tile_server, {
                        maxZoom: 18,
                        attribution: option_map_tile_server_copyright,
                    }).addTo(mymap);


                    var printer = L.easyPrint({
                        tileLayer: tiles,
                        sizeModes: ['Current'],
                        filename: 'myMap',
                        exportOnly: true,
                        hideControlContainer: true
                    }).addTo(mymap);

                    if (dxcc != 0) {
                        var redIcon = L.icon({
                            iconUrl: icon_dot_url,
                            iconSize:     [18, 18], // size of the icon
                        });

                        L.marker([lat,lng], {icon: redIcon}).addTo(mymap)
                            .bindPopup(callsign);
                    }

                },
            });

        }
    });
}

// used in edit_ajax.php to update the currently editing QSO
function single_callbook_update() {

    var callsign = $('#edit_callsign').val();
    var band = $('#edit_band').val();
    var mode = $('#edit_mode').val();

    $('#update_from_callbook').prop("disabled", true).addClass("running");

    $.ajax({
        url: site_url + '/logbook/json/' + callsign + '/' + band + '/' + mode,
        dataType: 'json',
        success: function (data) {
            // console.log(data);
            fill_if_empty('#qth_edit', data.callsign_qth);
            fill_if_empty('#dxcc_id_edit', data.dxcc.adif);
            fill_if_empty('#continent_edit', data.dxcc.cont);
            fill_if_empty('#cqz_edit', data.dxcc.cqz);
            if (data.callsign_ituz != '') {
                fill_if_empty('#ituz_edit', data.callsign_ituz);
            } else {
                fill_if_empty('#ituz_edit', data.dxcc.ituz);
            }
            fill_if_empty('#locator_edit', data.callsign_qra);
            fill_if_empty('#email_edit', data.callsign_email);
            // fill_if_empty('#image', data.image);  Not in use yet, but may in future
            fill_if_empty('#iota_ref_edit', data.callsign_iota);
            fill_if_empty('#name_edit', data.callsign_name);
            fill_if_empty('#qsl-via', data.qsl_manager);
            fill_if_empty('select[name="input_state_edit"]', data.callsign_state);
            fill_if_empty('#stationCntyInputEdit', data.callsign_us_county);
            if (data.callsign_darc_dok && $('#darc_dok_edit').val() == '') {
                var dok_selectize = $('#darc_dok_edit')[0].selectize;
                dok_selectize.addOption({ name: data.callsign_darc_dok });
                dok_selectize.setValue(data.callsign_darc_dok, false);
            }

            $('#update_from_callbook').prop("disabled", false).removeClass("running");
        },
        error: function () {
            console.error("Sorry, something went wrong to get the callbook data.");

            $('#update_from_callbook').prop("disabled", false).removeClass("running");
        },
    });
}
// used with single_callbook_update() to only fill fields which are empty
async function fill_if_empty(field, data) {
    var border_color = '2px solid green';

    // catch special case for dxcc
    if (field == "#dxcc_id_edit" && $(field).val() == 0) {
        $(field).val(data).css('border', border_color);
        return;
    }

    if (field == 'select[name="input_state_edit"]') {
        await updateStateDropdown('#dxcc_id_edit', '#stateInputLabelEdit', '#location_us_county_edit', '#stationCntyInputEdit', '#stateDropdownEdit');
        $(field).val(data).css('border', border_color);
        return;
    }

    // catch special case for grid
    if (field == "#locator_edit" && $(field).val() == '') {
        $(field).val(data.toUpperCase()).css('border', border_color).trigger('change');
        return;
    }

    if ($(field).val() == '' && data != '') {
        $(field).val(data).css('border', border_color);
        return;
    }
}

function qso_delete(id, call) {
    BootstrapDialog.confirm({
        title: lang_general_word_danger,
        message: lang_qso_delete_warning + call + '?' ,
        type: BootstrapDialog.TYPE_DANGER,
        closable: true,
        draggable: true,
        btnOKClass: 'btn-danger',
        callback: function(result) {
            if(result) {
                $(".edit-dialog").modal('hide');
                $(".qso-dialog").modal('hide');
                $.ajax({
                    url: base_url + 'index.php/qso/delete_ajax',
                    type: 'post',
                    data: {'id': id
                    },
                    success: function(data) {
                        $(".alert").remove();
                        $(".bootstrap-dialog-message").prepend('<div class="alert alert-danger"><a href="#" class="close" data-dismiss="alert" aria-label="close">&times;</a>The contact with ' + call + ' has been deleted!</div>');
                        $("#qso_" + id).remove(); // removes qso from table in dialog
                    }
                });
            }
        }
    });
}

function qso_edit(id) {
    $.ajax({
        url: base_url + 'index.php/qso/edit_ajax',
        type: 'post',
        data: {'id': id
        },
        success: function(html) {
            // remove actions QSO menu //
            $('.menuOnResultTab').hide();
            $('.menuOnBody').remove();
            BootstrapDialog.show({
                title: lang_general_word_qso_data,
                cssClass: 'edit-dialog bg-black bg-opacity-50',
                size: BootstrapDialog.SIZE_WIDE,
                nl2br: false,
                message: html,
                onshown: function(dialog) {

                    $('[data-bs-toggle="tooltip"]').tooltip();

                    var dxcc = $('#dxcc_id_edit').val();
                    var state = $("#stateDropdownEdit option:selected").text();
                    switch (dxcc) {
                       case '6':
                       case '110':
                       case '291':
                          if (state != "") {
                             $("#stationCntyInputEdit").prop('disabled', false);
                             selectize_usa_county('#stateDropdownEdit', '#stationCntyInputEdit');
                          }
                          $('#location_us_county_edit').show();
                          break;
                       case '15':
                       case '54':
                       case '61':
                       case '126':
                       case '151':
                       case '288':
                       case '339':
                       case '170':
                       case '21':
                       case '29':
                       case '32':
                       case '281':
                          $("#stationCntyInputEdit").prop('disabled', false);
                          $('#location_us_county_edit').show();
                          break;
                       default:
                          $('#location_us_county_edit').show();
                    }

                    var unsupported_lotw_prop_modes = [];
                    $.ajax({
                       url: base_url + 'index.php/qso/unsupported_lotw_prop_modes',
                       type: 'get',
                       async: false,
                       success: function(data) {
                          unsupported_lotw_prop_modes = $.parseJSON(data);
                       },
                    });

                    $('#prop_mode_edit').change(function(){
                       if (unsupported_lotw_prop_modes.includes($('#prop_mode_edit').val())) {
                          $('#lotw_sent').prop('disabled', true);
                          $('#lotw_rcvd').prop('disabled', true);
                          $('*[id=lotw_propmode_hint]').each(function() {
                             $(this).html(lang_lotw_propmode_hint).fadeIn("slow");
                          });
                       } else {
                          $('#lotw_sent').prop('disabled', false);
                          $('#lotw_rcvd').prop('disabled', false);
                          $('*[id=lotw_propmode_hint]').each(function() {
                             $(this).html("&nbsp;").fadeIn("fast");
                          });
                       }
                    });

                    $('#stateDropdownEdit').change(function(){
                        var dxcc = $('#dxcc_id_edit').val();
                       console.log("TEST: "+dxcc);
                        var state = $("#stateDropdownEdit option:selected").text();
                        switch (dxcc) {
                            case '6':
                            case '110':
                            case '291':
                                 if (state != "") {
                                     $("#stationCntyInputEdit").prop('disabled', false);
                                     selectize_usa_county('#stateDropdownEdit', '#stationCntyInputEdit');
                                 }
                                 break;
                            case '15':
                            case '54':
                            case '61':
                            case '126':
                            case '151':
                            case '288':
                            case '339':
                            case '170':
                            case '21':
                            case '29':
                            case '32':
                            case '281':
								 if (state != "") {
									 $("#stationCntyInputEdit").prop('disabled', false);
								 } else {
									 $("#stationCntyInputEdit").prop('disabled', true);
								 }
                                 break;
                        }
                    });

                    $('#locator_edit, #ant_path_edit').on('change', function(){
                        if ($('#locator_edit').val().length >= 4) {
                            $.ajax({
                               url: base_url + 'index.php/logbook/searchbearing',
                               type: 'post',
                               dataType: 'json',
                               data: {
                                  grid: $('#locator_edit').val(),
                                  ant_path: $('#ant_path_edit').val(),
                                  stationProfile: $('#stationProfile').val()
                               },
                               success: function(data) {
                                  $('#locator_info_edit').html(data.text).fadeIn("slow");
                                  $("#distance").val(data.distance_km ?? '');
                               },
                               error: function() {
                                  $('#locator_info_edit').text("Error loading bearing!").fadeIn("slow");
                                  $('#distance').val('');
                               },
                            });
                        } else if ($('#locator_edit').val().length == 0) {
                           $('#locator_info_edit').fadeOut("slow");
                           $('#distance').val('');
                        }
                    }).trigger('change'); // we also run this when the dom is ready, Trick 17 ;-)

                    $('#vucc_grids').change(function(){
                        if ($(this).val().length >= 9) {
                            $.ajax({
                               url: base_url + 'index.php/logbook/searchbearing',
                               type: 'post',
                               dataType: 'json',
                               data: {
                                  grid: $(this).val(),
                                  stationProfile: $('#stationProfile').val()
                               },
                               success: function(data) {
                                  $('#locator_info_edit').html(data.text).fadeIn("slow");
                                  $("#distance").val(data.distance_km ?? '');
                               },
                               error: function() {
                                  $('#locator_info_edit').text("Error loading bearing!").fadeIn("slow");
                                  $("#distance").val('');
                               },
                            });
                        } else if ($(this).val().length == 0) {
                           $('#locator_info_edit').fadeOut("slow");
                           $("#distance").val('');
                        }
                    });

                    $('#sota_ref_edit').selectize({
                        maxItems: 1,
                        closeAfterSelect: true,
                        loadThrottle: 250,
                        valueField: 'name',
                        labelField: 'name',
                        searchField: 'name',
                        options: [],
                        create: true,
                        load: function(query, callback) {
                            if (!query || query.length < 3) return callback();  // Only trigger if 3 or more characters are entered
                            $.ajax({
                                url: base_url+'index.php/qso/get_sota',
                                type: 'GET',
                                dataType: 'json',
                                data: {
                                    query: query,
                                },
                                error: function() {
                                    callback();
                                },
                                success: function(res) {
                                    callback(res);
                                }
                            });
                        }
                    });

                    $('#wwff_ref_edit').selectize({
                        maxItems: 1,
                        closeAfterSelect: true,
                        loadThrottle: 250,
                        valueField: 'name',
                        labelField: 'name',
                        searchField: 'name',
                        options: [],
                        create: true,
                        load: function(query, callback) {
                            if (!query || query.length < 3) return callback();  // Only trigger if 3 or more characters are entered
                            $.ajax({
                                url: base_url+'index.php/qso/get_wwff',
                                type: 'GET',
                                dataType: 'json',
                                data: {
                                    query: query,
                                },
                                error: function() {
                                    callback();
                                },
                                success: function(res) {
                                    callback(res);
                                }
                            });
                        }
                    });

                    $('#pota_ref_edit').selectize({
                        maxItems: null,
                        closeAfterSelect: true,
                        loadThrottle: 250,
                        valueField: 'name',
                        labelField: 'name',
                        searchField: 'name',
                        options: [],
                        create: true,
                        load: function(query, callback) {
                            if (!query || query.length < 3) return callback();  // Only trigger if 3 or more characters are entered
                            $.ajax({
                                url: base_url+'index.php/qso/get_pota',
                                type: 'GET',
                                dataType: 'json',
                                data: {
                                    query: query,
                                },
                                error: function() {
                                    callback();
                                },
                                success: function(res) {
                                    callback(res);
                                }
                            });
                        }
                    });

                    $('#darc_dok_edit').selectize({
                        maxItems: 1,
                        closeAfterSelect: true,
                        loadThrottle: 250,
                        valueField: 'name',
                        labelField: 'name',
                        searchField: 'name',
                        options: [],
                        create: true,
                        load: function(query, callback) {
                            if (!query) return callback();  // Only trigger if 3 or more characters are entered
                            $.ajax({
                                url: base_url+'index.php/qso/get_dok',
                                type: 'GET',
                                dataType: 'json',
                                data: {
                                    query: query,
                                },
                                error: function() {
                                    callback();
                                },
                                success: function(res) {
                                    callback(res);
                                }
                            });
                        }
                    });
                    // [eQSL default msg] change value (for qso edit page) //
                    $('.modal-content #stationProfile').change(function() {
                        qso_set_eqsl_qslmsg($('.modal-content #stationProfile').val(),false,'.modal-content');
                    });
                    $('.modal-content .qso_eqsl_qslmsg_update').off('click').on('click',function() {
                        qso_set_eqsl_qslmsg($('.modal-content #stationProfile').val(),true,'.modal-content');
                        $('.modal-content #charsLeft').text(" ");
                    });
                    $('.modal-content #qslmsg').keyup(function(event) {
                        calcRemainingChars(event, '.modal-content');
                    });

                    $("#dxcc_id_edit").change(async function () {
                        await updateStateDropdown('#dxcc_id_edit', '#stateInputLabelEdit', '#location_us_county_edit', '#stationCntyInputEdit', '#stateDropdownEdit');
                    });
                },
            });
        }
    });
}

function qso_save() {
    let myform = $("#qsoform")[0];
    let fd = new FormData(myform);
    $.ajax({
        url: base_url + 'index.php/qso/qso_save_ajax',
        data: fd,
        cache: false,
        processData: false,
        contentType: false,
        type: 'POST',
        success: function (dataofconfirm) {
		if (dataofconfirm.success) {
			$(".edit-dialog").modal('hide');
			$(".qso-dialog").modal('hide');
			if (reload_after_qso_safe == true) {
				location.reload();
			}
			if (reload_qso_line == true) {
				let qsoId = document.querySelector('input[name="id"]').value;
				getQsos(qsoId);
			}
		} else {
			$("#error-messages-qso-edit").html('<div class="alert alert-danger alert-dismissible fade show" role="alert">'+dataofconfirm.detail+'<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>');
			$(".modal-body").animate({ scrollTop: 0 }, 'fast');
		}
        },
        error: function(xhr, status, error) {
            console.log(xhr.responseText);
        }
    });
}

function selectize_usa_county(state_field, county_field) {
    $(county_field).selectize()[0].selectize.destroy();
    $(county_field).selectize({
        delimiter: ';',
        maxItems: 1,
        closeAfterSelect: true,
        loadThrottle: 250,
        valueField: 'name',
        labelField: 'name',
        searchField: 'name',
        options: [],
        create: false,
        preload: true,
        load: function(query, callback) {
            var state = $(state_field + ' option:selected').text();

            $.ajax({
                url: base_url + 'index.php/lookup/get_county',
                type: 'GET',
                dataType: 'json',
                data: {
                    query: query,
                    state: state,
                },
                error: function() {
                    callback();
                },
                success: function(res) {
                    callback(res);
                }
            });
        }
    });
}

async function updateStateDropdown(dxcc_field, state_label, county_div, county_input, dropdown = '#stateDropdown', set_state = null) {
    var selectedDxcc = $(dxcc_field);
	var selectedState = $(dropdown);

    if (selectedDxcc.val() !== "") {
        await $.ajax({
            url: base_url + "index.php/lookup/get_state_list",
            type: "POST",
            data: { dxcc: selectedDxcc.val() },
            success: function (response) {
                if (response.status === "ok") {
                    statesDropdown(response, set_state, dropdown);
                    $(state_label).html(response.subdivision_name);
                } else {
                    statesDropdown(response);
                    $(state_label).html('State');
                }
            },
            error: function () {
                console.log('ERROR', response.status);
            },
        });
    }

	switch (selectedDxcc.val()) {
		case '6':
		case '110':
		case '291':
			$(county_div).find('.form-control').hide();
			$(county_div).find('.selectize-control').show();
			$(county_div).show();
			break;
		case '15':
		case '54':
		case '61':
		case '126':
		case '151':
		case '288':
		case '339':
		case '170':
		case '21':
		case '29':
		case '32':
		case '281':
			$(county_div).find('.form-control').show();
			var state = selectedState.val();
			if (state == '') {
				$(county_div).find('.form-control').prop('disabled', true);
			} else {
				$(county_div).find('.form-control').prop('disabled', false);
			}
			$(county_div).find('.selectize-control').hide();
			$(county_div).show();
			break;
		default:
			$(county_div).hide();
			$(county_input).val('');
	}
}

function spawnQrbCalculator(locator1, locator2) {
	$.ajax({
		url: base_url + 'index.php/qrbcalc',
		type: 'post',
		success: function (html) {
			BootstrapDialog.show({
				title: lang_qrbcalc_title,
				size: BootstrapDialog.SIZE_WIDE,
				cssClass: 'lookup-dialog bg-black bg-opacity-50',
				nl2br: false,
				message: html,
				onshown: function(dialog) {
					if (locator1 !== undefined) {
						$("#qrbcalc_locator1").val(locator1);
					}
					if (locator2 !== undefined) {
						$("#qrbcalc_locator2").val(locator2);
						calculateQrb();
					}
				},
				buttons: [{
					label: lang_admin_close,
					action: function (dialogItself) {
						dialogItself.close();
					}
				}]
			});
		}
	});
}

function spawnActivatorsMap(call, count, grids) {
	$.ajax({
		url: base_url + 'index.php/activatorsmap',
		type: 'post',
		success: function (html) {
			BootstrapDialog.show({
				title: lang_activators_map,
				size: BootstrapDialog.SIZE_WIDE,
				cssClass: 'lookup-dialog',
				nl2br: false,
				message: html,
				onshown: function(dialog) {
					showActivatorsMap(call, count, grids, grid_color);
				},
				buttons: [{
					label: lang_admin_close,
					action: function (dialogItself) {
						dialogItself.close();
					}
				}]
			});
		}
	});
}

function calculateQrb() {
    let locator1 = $("#qrbcalc_locator1").val();
    let locator2 = $("#qrbcalc_locator2").val();

    $(".qrbalert").remove();

    if (validateLocator(locator1) && validateLocator(locator2)) {
        $.ajax({
            url: base_url+'index.php/qrbcalc/calculate',
            type: 'post',
            data: {'locator1': locator1,
                    'locator2': locator2},
            success: function (html) {

                var result = "<h5>" + html['latlong_info_text'] + "<br><br>";
                result += html['text_latlng1'] + '<br>';
                result += html['text_latlng2'] + '<br><br>';
                result += html['distance'] + ' ';
                result += html['bearing'] + '</h5>';

                $(".qrbResult").html(result);
                newpath(html['latlng1'], html['latlng2'], locator1, locator2);
            }
        });
    } else if (locator1 == '' || locator2 == '') {
        $('.qrbResult').html('<div class="qrbalert alert alert-danger" role="alert">' + lang_qrbcalc_empty_loc + '</div>');
        $("#mapqrb").hide();
    } else {
        $("#mapqrb").hide();
        $('.qrbResult').html('<div class="qrbalert alert alert-danger" role="alert">' + lang_qrbcalc_errmsg + '</div>');
    }
}

function validateLocator(locator) {
    const regex = /^[A-R]{2}[0-9]{2}([A-X]{2}([0-9]{2}([A-X]{2})?)?)?$/i;
    const locators = locator.split(",");

    if (locators.length === 3 || locators.length > 4) {
        return false;
    }

    for (let i = 0; i < locators.length; i++) {
        let loc = locators[i].trim();

        if (!regex.test(loc)) {
            return false;
        }
    }

    return true;
}

// This displays the dialog with the form and it's where the resulttable is displayed
function spawnLookupModal(searchphrase, searchtype) {
	$.ajax({
		url: base_url + 'index.php/lookup',
		type: 'post',
		success: function (html) {
			BootstrapDialog.show({
				title: 'Quick lookup',
				size: BootstrapDialog.SIZE_WIDE,
				cssClass: 'lookup-dialog',
				nl2br: false,
				message: html,
				onshown: function(dialog) {
					$('#quicklookuptype').change(function(){
						var type = $('#quicklookuptype').val();
                        changeLookupType(type);
					});
                    if (searchtype !== undefined) {
                        $('#quicklookuptype').val(searchtype);
                        if (searchtype == 'dxcc') {
                            $("#quicklookupdxcc").val(searchphrase);
                        } else if (searchtype == 'continent') {
                            $("#quicklookupcontinent").val(searchphrase);
						} else if (searchtype == 'iota') {
                            $("#quicklookupiota").val(searchphrase);
                        } else if (searchtype == 'cq') {
                            $("#quicklookupcqz").val(searchphrase);
						} else if (searchtype == 'itu') {
                            $("#quicklookupituz").val(searchphrase);
                        } else {
                            $("#quicklookuptext").val(searchphrase);
                        }
                        changeLookupType(searchtype);
                        getLookupResult(this.form);
                    }
				},
				buttons: [{
					label: lang_admin_close,
					action: function (dialogItself) {
						dialogItself.close();
					}
				}]
			});
		}
	});
}

function changeLookupType(type) {
	$('#quicklookupdxcc').hide();
	$('#quicklookupiota').hide();
	$('#quicklookupcqz').hide();
	$('#quicklookupituz').hide();
	$('#quicklookupwas').hide();
	$('#quicklookuptext').hide();
	$('#quicklookupcontinent').hide();
    if (type == "dxcc") {
        $('#quicklookupdxcc').show();
    } else if (type == "iota") {
        $('#quicklookupiota').show();
    } else if (type == "vucc" || type == "sota" || type == "wwff" || type == "lotw" || type == "pota" || type == "dok") {
        $('#quicklookuptext').show();
    } else if (type == "cq") {
        $('#quicklookupcqz').show();
	} else if (type == "itu") {
        $('#quicklookupituz').show();
    } else if (type == "was") {
        $('#quicklookupwas').show();
    } else if (type == "continent") {
        $('#quicklookupcontinent').show();
	}
}

// This function executes the call to the backend for fetching queryresult and displays the table in the dialog
function getLookupResult() {
	$(".ld-ext-right").addClass('running');
	$(".ld-ext-right").prop('disabled', true);
	$.ajax({
		url: base_url + 'index.php/lookup/search',
		type: 'post',
		data: {
			type: $('#quicklookuptype').val(),
			dxcc: $('#quicklookupdxcc').val(),
			was:  $('#quicklookupwas').val(),
			grid: $('#quicklookuptext').val(),
			cqz:  $('#quicklookupcqz').val(),
			iota: $('#quicklookupiota').val(),
			sota: $('#quicklookuptext').val(),
			wwff: $('#quicklookuptext').val(),
			lotw: $('#quicklookuptext').val(),
			ituz: $('#quicklookupituz').val(),
			pota: $('#quicklookuptext').val(),
			continent: $('#quicklookupcontinent').val(),
			dok: $('#quicklookuptext').val(),
		},
		success: function (html) {
			$('#lookupresulttable').html(html);
			$(".ld-ext-right").removeClass('running');
			$(".ld-ext-right").prop('disabled', false);
		}
	});
}

// This function executes the call to the backend for fetching dxcc summary and inserted table below qso entry
function getDxccResult(dxcc, name) {
	let $targetPane = $('#dxcc-summary');

	if (!$targetPane.data("loaded")) {
		$targetPane.data("loaded", true); // Mark as loaded
			satOrBand = $('#band').val();
			if ($('#selectPropagation').val() == 'SAT') {
				satOrBand = 'SAT';
			}
		$.ajax({
			url: base_url + 'index.php/lookup/search',
			type: 'post',
			data: {
				type: 'dxcc',
				dxcc: dxcc,
				reduced_mode: true,
				current_band: satOrBand,
				current_mode: $('#mode').val(),
			},
			success: function (html) {
				$('#dxcc-summary').empty();
				$('#dxcc-summary').append(lang_summary_dxcc + ' ' + name + '.');
				$('#dxcc-summary').append(html);
			}
		});
	}
}

function displayQsl(id) {
    $.ajax({
		url: base_url + 'index.php/qsl/viewQsl',
		type: 'post',
        data: {
			id: id,
		},
		success: function (html) {
			BootstrapDialog.show({
				title: 'QSL Card',
				size: BootstrapDialog.SIZE_WIDE,
				cssClass: 'lookup-dialog',
				nl2br: false,
				message: html,
				onshown: function(dialog) {

				},
				buttons: [{
					label: lang_admin_close,
					action: function (dialogItself) {
						dialogItself.close();
					}
				}]
			});
		}
	});
}


// [eQSL default msg] function to load default qslmsg to qslmsg field on qso add/edit //
function qso_set_eqsl_qslmsg(station_id, force_diff_to_origin=false, object='') {
    $.ajax({
        url: base_url+'index.php/qso/get_eqsl_default_qslmsg',
        type: 'post', data: {'option_key':station_id },
        success: function(res) {
            if (typeof res.eqsl_default_qslmsg !== "undefined") {
                object = (object!='')?(object+' '):'';
                // .text() so the comparison sees the same decoded string as .val()
                if ((force_diff_to_origin) || ($(object+'#qslmsg').val()==$(object+'#qslmsg_hide').text())) {
                    $(object+'#qslmsg').val(res.eqsl_default_qslmsg);
                    $(object+'#qslmsg_hide').text(res.eqsl_default_qslmsg);
                }
            }
        },
        error: function() { },
    });
}

// [PWD] button show/hide //
function btn_pwd_showhide() {
	if ($(this).closest('div').find('input[type="password"]').length>0) {
        $(this).closest('div').find('input[type="password"]').attr('type','text');
        $(this).closest('div').find('.fa-eye-slash').removeClass('fa-eye-slash').addClass('fa-eye');
	} else {
        $(this).closest('div').find('input[type="text"]').attr('type','password');
        $(this).closest('div').find('.fa-eye').removeClass('fa-eye').addClass('fa-eye-slash');
	}
}
$('.user_edit .btn-pwd-showhide').off('click').on('click', btn_pwd_showhide );

// [QSO] show/hide actions menu on qso list  (_this = div.dropdown actived) //
function showQsoActionsMenu(_this) {
    $('.menuOnResultTab').hide();
    $('.menuOnBody').remove();
    var _id = _this.find('.menuOnResultTab').attr('data-qsoid');
    var _dropdownMenuClone = _this.find('.menuOnResultTab[data-qsoid="'+ _id +'"]').clone();
    _dropdownMenuClone.removeClass('menuOnResultTab').addClass('menuOnBody');
    $('body').append(_dropdownMenuClone);
    var _dropdownMenu = _this.find('.menuOnResultTab[data-qsoid="'+ _id +'"]');
    var eOffset = _this.offset();
    if ((eOffset.top - $(window).scrollTop() + _dropdownMenu.outerHeight()) >= ($(window).height()-50)) {
            _topMenu = eOffset.top - _dropdownMenu.outerHeight();
        } else {
            _topMenu = eOffset.top + _this.outerHeight();
        }
        _dropdownMenuClone.css({
        'top': _topMenu,
        'left': eOffset.left - _dropdownMenu.width() + _this.find('.dropdown-toggle').outerWidth(),
        'display': 'block',
    });
    _dropdownMenuClone.off('mouseenter').on('mouseenter', function () {
        _dropdownMenuClone.attr('data-mouseenteronmenu','1');
    });
    _this.off('mouseleave').on('mouseleave', function () {
        setTimeout(function(){ if (_dropdownMenuClone.attr('data-mouseenteronmenu')!='1') { _dropdownMenuClone.remove();}  }, 200);
    });
    _dropdownMenuClone.off('mouseleave').on('mouseleave', function () {
        $(this).remove();
    });
    _dropdownMenuClone.find('a').off('click').on('click', function () {
        if ($(this).is(':first-child') || $(this).is(':last-child')) { // Only for edit & delete action //
            $(this).closest('.menuOnResultTab').remove();
        }
    });
}

if ($('.table-responsive .dropdown-toggle').length>0) {
    $('.table-responsive .dropdown-toggle').off('mouseenter').on('mouseenter', function () {
        showQsoActionsMenu($(this).closest('.dropdown'));
    });
}

function statesDropdown(states, set_state = null, dropdown = '#stateDropdown') {
    var dropdown = $(dropdown);
    dropdown.empty();
    dropdown.append($('<option>', {
        value: ''
    }));
    if (states.status == 'ok') {
        dropdown.prop('disabled', false);
        $.each(states.data, function(index, state) {
            var d_text = '';
            if (state.deprecated == '1') {
                d_text = '\u26A0\uFE0F [' + lang_general_states_deprecated + '] - ';
            }
            if (state.state == set_state || state.deprecated != '1') {
                var option = $('<option>', {
                    value: state.state,
                    text: d_text + state.subdivision + ' (' + state.state + ')'
                });
            }
            dropdown.append(option);
        });
        $(dropdown).val(set_state);
    } else {
        dropdown.empty();
        var option = $('<option>', {
            value: '',
            text: lang_no_states_for_dxcc_available
        });
        dropdown.append(option);
        dropdown.prop('disabled', true);
    }
}

// Location Quickswitcher
function quickswitcher_show_activebadge(current_active) {
    $('#quickswitcher_active_badge_' + current_active).removeClass('d-none');
    $('#quickswitcher_list_button_' + current_active).addClass('disabled');
}

function current_active_ajax(callback) {
    $.ajax({
        url: base_url + 'index.php/stationsetup/getActiveStation',
        type: 'GET',
        dataType: 'json',
        success: function(response) {
            var current_active = response;
            callback(current_active);
        }
    });
}

function set_active_loc_quickswitcher(new_active) {
    current_active_ajax(function(current_active) {
        $.ajax({
            url: base_url + 'index.php/stationsetup/setActiveStation_json',
            type: 'POST',
            dataType: 'json',
            data: {
                id2setActive: new_active
            },
            success: function(response) {
                $('[id^="quickswitcher_list_button_"]').not('#quickswitcher_list_button_' + new_active).removeClass('disabled');
                $('[id^="quickswitcher_active_badge_"]').not('#quickswitcher_active_badge_' + new_active).addClass('d-none');

                $('#quickswitcher_list_button_' + new_active).addClass('disabled');
                $('#quickswitcher_active_badge_' + new_active).removeClass('d-none');


                // if we are on the stationsetup page the function reloadStations exists and we can run it
                if (typeof reloadStations === 'function') {
                    reloadStations();
                }

                // If the user is in the QSO or SimpleFLE view we change the station in the QSO input aswell
                if (window.location.pathname.indexOf("qso") !== -1 ||
                    window.location.pathname.indexOf("simplefle") !== -1) {

                    if ($('#stationProfile option[value="' + new_active + '"]').length > 0) {
                        $('#stationProfile').val(new_active);
                    }
                }
            },
            error: function(xhr, status, error) {
                console.error('Error while setting the new active location: ' + error);
            }
        });
    });
}

// Quick theme switcher — POST the chosen theme folder to the user controller,
// then reload the page so the new stylesheet and logos take effect.
function quick_switch_theme(foldername) {
    $.ajax({
        url: base_url + 'index.php/user/theme_switch',
        type: 'POST',
        dataType: 'json',
        data: {
            theme: foldername
        },
        success: function(response) {
            if (response && response.status === 'success') {
                window.location.reload();
            } else {
                console.error('Theme switch failed: ' + ((response && response.message) ? response.message : 'unknown error'));
            }
        },
        error: function(xhr, status, error) {
            console.error('Error while switching theme: ' + error);
        }
    });
}

$(document).ready(function() {
    if ($('#utc_header').length > 0) {
        function getCurrentUTCTime() {
            var now = new Date();
            var hours = now.getUTCHours().toString().padStart(2, '0');
            var minutes = now.getUTCMinutes().toString().padStart(2, '0');
            var seconds = now.getUTCSeconds().toString().padStart(2, '0');
            return hours + ':' + minutes + ':' + seconds;
        }

        function updateUTCTime() {
            $('#utc_header').text(getCurrentUTCTime() + 'z');
        }

        setInterval(updateUTCTime, 1000);
        updateUTCTime();
    }
});

// auto setting of gridmap height
function set_map_height(extra_height = 0) {
    //header menu
    var headerNavHeight = $('nav').outerHeight();
    // console.log('nav: ' + headerNavHeight);

    // line with coordinates
    var coordinatesHeight = $('.coordinates').outerHeight();
    // console.log('.coordinates: ' + coordinatesHeight);

    // form for gridsquare map
    var gridsquareFormHeight = $('.gridsquare_map_form').outerHeight();
    // console.log('.gridsquare_map_form: ' + gridsquareFormHeight);

    // calculate correct map height
    var gridsquareMapHeight = window.innerHeight - headerNavHeight - coordinatesHeight - gridsquareFormHeight - extra_height;

    // and set it
    $('#gridsquare_map').css('height', gridsquareMapHeight + 'px');
    // console.log('#gridsquare_map: ' + gridsquareMapHeight);
}

function newpath(latlng1, latlng2, locator1, locator2) {
    // If map is already initialized
    var container = L.DomUtil.get('mapqrbcontainer');

    if(container != null){
        container._leaflet_id = null;
        container.remove();
        $("#mapqrb").append('<div id="mapqrbcontainer" style="Height: 500px"></div>');
    }

    var map = new L.Map('mapqrbcontainer', {
        fullscreenControl: true,
        fullscreenControlOptions: {
          position: 'topleft'
        },
      }).setView([30, 0], 1.5);

    if (locator1.toUpperCase() != locator2.toUpperCase()) {

        // Need to fix so that marker is placed at same place as end of line, but this only needs to be done when longitude is < -170
        if (latlng2[1] < -170) {
            latlng2[1] =  parseFloat(latlng2[1])+360;
        }
        if (latlng1[1] < -170) {
            latlng1[1] =  parseFloat(latlng1[1])+360;
        }

        if ((latlng1[1] - latlng2[1]) < -180) {
            latlng2[1] = parseFloat(latlng2[1]) -360;
        } else if ((latlng1[1] - latlng2[1]) > 180) {
            latlng2[1] = parseFloat(latlng2[1]) +360;
        }

        map.fitBounds([
            [latlng1[0], latlng1[1]],
            [latlng2[0], latlng2[1]]
        ]);

        var maidenhead = L.maidenheadqrb().addTo(map);

        var osmUrl = option_map_tile_server;
        var osmAttrib= option_map_tile_server_copyright;
        var osm = new L.TileLayer(osmUrl, {minZoom: 1, maxZoom: 12, attribution: osmAttrib});

        var redIcon = L.icon({
            iconUrl: icon_dot_url,
            iconSize:     [10, 10], // size of the icon
        });

        map.addLayer(osm);

        var marker = L.marker([latlng1[0], latlng1[1]], {closeOnClick: false, autoClose: false}).addTo(map).bindPopup(locator1);

        var marker2 = L.marker([latlng2[0], latlng2[1]], {closeOnClick: false, autoClose: false}).addTo(map).bindPopup(locator2);

        const multiplelines = [];
            multiplelines.push(
                new L.LatLng(latlng1[0], latlng1[1]),
                new L.LatLng(latlng2[0], latlng2[1])
            )

        const geodesic = L.geodesic(multiplelines, {
            weight: 3,
            opacity: 1,
            color: 'red',
            wrap: false,
            steps: 100
        }).addTo(map);
    } else {
        // Need to fix so that marker is placed at same place as end of line, but this only needs to be done when longitude is < -170
        if (latlng1[1] < -170) {
            latlng1[1] =  parseFloat(latlng1[1])+360;
        }
        var maidenhead = L.maidenheadqrb().addTo(map);
        map.setView([latlng1[0], latlng1[1]], 13);

        var osmUrl = option_map_tile_server;
        var osmAttrib= option_map_tile_server_copyright;
        var osm = new L.TileLayer(osmUrl, {minZoom: 1, maxZoom: 12, attribution: osmAttrib});

        var redIcon = L.icon({
            iconUrl: icon_dot_url,
            iconSize:     [10, 10], // size of the icon
        });
        map.addLayer(osm);
        var marker = L.marker([latlng1[0], latlng1[1]], {closeOnClick: false, autoClose: false}).addTo(map).bindPopup(locator1);
    }
}

function disableMap() {
    // console.log('disable map');
    map.dragging.disable();
    map.scrollWheelZoom.disable();
    map.touchZoom.disable();
    map.doubleClickZoom.disable();
    map.boxZoom.disable();
    map.keyboard.disable();
}

function enableMap() {
    // console.log('enable map');
    map.dragging.enable();
    map.scrollWheelZoom.enable();
    map.touchZoom.enable();
    map.doubleClickZoom.enable();
    map.boxZoom.enable();
    map.keyboard.enable();
}

function shareModal(qso_data, title) {
    $.ajax({
        url: base_url + 'index.php/qso/getShareModal',
        type: 'post',
        data: {
            qso_data: qso_data
        },
        success: function (html) {
            BootstrapDialog.show({
                title: title || lang_general_share_qso,
                cssClass: 'bg-black bg-opacity-50',
                nl2br: false,
                message: html,
                buttons: [{
                    label: lang_admin_close,
                    action: function (dialogItself) {
                        dialogItself.close();
                    }
                }]
            });
        }
    });
}


// Show Bootstrap Toast
function showToast(title, text, type = 'bg-success text-white', delay = 3000, autohide = true) {
	/*
	Examples:
	showToast('Saved', 'Your data was saved!', 'bg-success text-white', 3000);
	showToast('Error', 'Failed to connect to server.', 'bg-danger text-white', 5000);
	showToast('Warning', 'Please check your input.', 'bg-warning text-dark', 4000);
	showToast('Info', 'System will restart soon.', 'bg-info text-dark', 4000);
	// Persistent toast (stays until manually dismissed):
	showToast('Error', 'Connection lost.', 'bg-danger text-white', 0, false);
	*/

	const container = document.getElementById('toast-container');

	// Create toast element
	const toastEl = document.createElement('div');
	toastEl.className = `toast align-items-center ${type}`;
	toastEl.setAttribute('role', 'alert');
	toastEl.setAttribute('aria-live', 'assertive');
	toastEl.setAttribute('aria-atomic', 'true');
	toastEl.setAttribute('data-bs-autohide', autohide ? 'true' : 'false');
	toastEl.setAttribute('data-bs-delay', delay);

	// Toast inner HTML
	toastEl.innerHTML = `
		<div class="d-flex">
		<div class="toast-body">
			<strong>${title}</strong><br>${text}
		</div>
		<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
		</div>
	`;

	// Append and show
	container.appendChild(toastEl);
	const bsToast = new bootstrap.Toast(toastEl);
	bsToast.show();

	// Remove from DOM when hidden
	toastEl.addEventListener('hidden.bs.toast', () => toastEl.remove());
}

function hexToRgba(hex, alpha = 1) {
	if (!hex) return null;
	// Remove the leading "#"
	hex = hex.replace(/^#/, '');

	// Expand short form (#f0a → #ff00aa)
	if (hex.length === 3) {
		hex = hex.split('').map(c => c + c).join('');
	}

	const num = parseInt(hex, 16);
	const r = (num >> 16) & 255;
	const g = (num >> 8) & 255;
	const b = num & 255;

	return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}

/**
 * Cookie Management Utilities
 */

/**
 * Set a cookie
 * @param {string} name - Cookie name
 * @param {string} value - Cookie value
 * @param {number} days - Days until expiration
 */
function setCookie(name, value, days) {
	var expires = "";
	if (days) {
		var date = new Date();
		date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
		expires = "; expires=" + date.toUTCString();
	}
	document.cookie = name + "=" + (value || "") + expires + "; path=/";
}

/**
 * Get a cookie value
 * @param {string} name - Cookie name
 * @returns {string|null} Cookie value or null if not found
 */
function getCookie(name) {
	var nameEQ = name + "=";
	var ca = document.cookie.split(';');
	for (var i = 0; i < ca.length; i++) {
		var c = ca[i];
		while (c.charAt(0) === ' ') c = c.substring(1, c.length);
		if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
	}
	return null;
}

/**
 * In some cases gettext-translated strings are still htmlencoded in the frontend. Use this funktion to decode them.
 * @param {string} text - The text to decode
 * @returns {string} Decoded text
 */
function decodeHtml(html) {
    const txt = document.createElement('textarea');
    txt.innerHTML = html;
    return txt.value;
}

/**
 * Convert latitude/longitude coordinates to Maidenhead grid square locator
 * @param {number} y - Latitude in decimal degrees (-90 to +90)
 * @param {number} x - Longitude in decimal degrees (-180 to +180)
 * @param {number} num - Precision level: 2=field, 4=square, 6=subsquare, 8=extended, 10=extended subsquare
 * @returns {string} Maidenhead locator string (e.g., 'JO01ab' for 6-character precision)
 *
 * @example
 * LatLng2Loc(51.5074, -0.1278, 6)  // → 'IO91wm' (London)
 * LatLng2Loc(40.7128, -74.0060, 4) // → 'FN20' (New York)
 */
function LatLng2Loc(y, x, num) {
	if (x<-180) {x=x+360;}
	if (x>180) {x=x-360;}
	var yi, yk, ydiv, yres, ylp, y;
	var ycalc = new Array(0,0,0);
	var yn    = new Array(0,0,0,0,0,0,0);

	var ydiv_arr=new Array(10, 1, 1/24, 1/240, 1/240/24);
	ycalc[0] = (x + 180)/2;
	ycalc[1] =  y +  90;

	for (yi = 0; yi < 2; yi++) {
		for (yk = 0; yk < 5; yk++) {
			ydiv = ydiv_arr[yk];
			yres = ycalc[yi] / ydiv;
			ycalc[yi] = yres;
			if (ycalc[yi] > 0) ylp = Math.floor(yres); else ylp = Math.ceil(yres);
			ycalc[yi] = (ycalc[yi] - ylp) * ydiv;
			yn[2*yk + yi] = ylp;
		}
	}

	var qthloc="";
	if (num >= 2) qthloc+=String.fromCharCode(yn[0] + 0x41) + String.fromCharCode(yn[1] + 0x41);
	if (num >= 4) qthloc+=String.fromCharCode(yn[2] + 0x30) + String.fromCharCode(yn[3] + 0x30);
	if (num >= 6) qthloc+=String.fromCharCode(yn[4] + 0x41) + String.fromCharCode(yn[5] + 0x41);
	if (num >= 8) qthloc+=' ' + String.fromCharCode(yn[6] + 0x30) + String.fromCharCode(yn[7] + 0x30);
	if (num >= 10) qthloc+=String.fromCharCode(yn[8] + 0x61) + String.fromCharCode(yn[9] + 0x61);
	return qthloc;
}

// Fetch an HTML fragment and swap it into a target element, then reinit tooltips.
// Replaces the former htmx hx-get / hx-target mechanism.
window.wlLoadInto = function (url, target) {
    const el = (typeof target === 'string') ? document.querySelector(target) : target;
    if (!el) return Promise.resolve();
    return fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' } })
        .then(r => r.text())
        .then(html => {
            el.innerHTML = html;
            // reinit Bootstrap tooltips on freshly swapped content (was htmx:afterSwap)
            $('[data-bs-toggle="tooltip"]', el).tooltip();
        });
};

// DO NOT DELETE: This message is intentional and serves as developer recruitment/engagement
console.log("Ready to unleash your coding prowess and join the fun?\n\n" +
    "Check out our GitHub Repository and dive into the coding adventure:\n\n" +
    "🚀 https://www.github.com/wavelog/wavelog");
