/* -----------------------------------------------------------------------------
*  NetAlertX
*  Open Source Network Guard / WIFI & LAN intrusion detector
*
*  common.js - Front module. Common Javascript functions
*-------------------------------------------------------------------------------
#  Puche 2021 / 2022+ jokob             jokob@duck.com                GNU GPLv3
----------------------------------------------------------------------------- */

// -----------------------------------------------------------------------------
var timerRefreshData = ''

var   emptyArr      = ['undefined', "", undefined, null, 'null'];
var   UI_LANG       = "English (en_us)";
const allLanguages  = ["ar_ar","ca_ca","cs_cz","de_de","en_us","es_es","fa_fa","fr_fr","it_it","ja_jp","nb_no","pl_pl","pt_br","pt_pt","ru_ru","sv_sv","tr_tr","uk_ua","zh_cn"]; // needs to be same as in lang.php
var   settingsJSON  = {}


// -----------------------------------------------------------------------------
// Simple session cache withe expiration managed via cookies
// -----------------------------------------------------------------------------
function getCache(key, noCookie = false)
{
  // check cache
  cachedValue = localStorage.getItem(key)

  // console.log(cachedValue);

  if(cachedValue)
  {
    // // check if not expired
    // if(noCookie || getCookie(key + '_session_expiry') != "")
    // {
      return cachedValue;
    // }
  }

  return "";
}

// -----------------------------------------------------------------------------
function setCache(key, data, expirationMinutes='')
{
  localStorage.setItem(key, data);

  // // create cookie if expiration set to handle refresh of data
  // if (expirationMinutes != '')
  // {
  //   setCookie ('cache_session_expiry', 'OK', 1)
  // }
}


// -----------------------------------------------------------------------------
function setCookie (cookie, value, expirationMinutes='') {
  // Calc expiration date
  var expires = '';
  if (typeof expirationMinutes === 'number') {
    expires = ';expires=' + new Date(Date.now() + expirationMinutes *60*1000).toUTCString();
  }

  // Save Cookie
  document.cookie = cookie + "=" + value + expires;
}

// -----------------------------------------------------------------------------
function getCookie (cookie) {
  // Array of cookies
  var allCookies = document.cookie.split(';');

  // For each cookie
  for (var i = 0; i < allCookies.length; i++) {
    var currentCookie = allCookies[i].trim();

    // If the current cookie is the correct cookie
    if (currentCookie.indexOf (cookie +'=') == 0) {
      // Return value
      return currentCookie.substring (cookie.length+1);
    }
  }

  // Return empty (not found)
  return "";
}


// -----------------------------------------------------------------------------
function deleteCookie (cookie) {
  document.cookie = cookie + '=;expires=Thu, 01 Jan 1970 00:00:00 UTC';
}

// -----------------------------------------------------------------------------
function deleteAllCookies() {
  // Array of cookies
  var allCookies = document.cookie.split(";");

  // For each cookie
  for (var i = 0; i < allCookies.length; i++) {
    var cookie = allCookies[i].trim();
    var eqPos = cookie.indexOf("=");
    var name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie;
    document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 UTC";
    }
}




// -----------------------------------------------------------------------------
// Get settings from the .json file generated by the python backend
// and cache them, if available, with options
// -----------------------------------------------------------------------------
function cacheSettings()
{
  return new Promise((resolve, reject) => {
    if(!getCache('cacheSettings_completed') === true)
    {
      $.get('php/server/query_json.php', { file: 'table_settings.json', nocache: Date.now() }, function(resSet) {

        $.get('php/server/query_json.php', { file: 'plugins.json', nocache: Date.now() }, function(resPlug) {

          pluginsData = resPlug["data"];
          settingsData = resSet["data"];

          settingsData.forEach((set) => {

            resolvedOptions = createArray(set.setOptions)
            resolvedOptionsOld = resolvedOptions
            setPlugObj     = {};
            options_params = [];
            resolved = ""

            // proceed only if first option item contains something to resolve
            if( !set.setKey.includes("__metadata") &&
                resolvedOptions.length != 0 &&
                resolvedOptions[0].includes("{value}"))
            {
              // get setting definition from the plugin config if available
              setPlugObj = getPluginSettingObject(pluginsData, set.setKey)

              // check if options contains parameters and resolve
              if(setPlugObj != {} && setPlugObj["options_params"])
              {
                // get option_params for {value} resolution
                options_params = setPlugObj["options_params"]

                if(options_params != [])
                {
                  // handles only strings of length == 1

                  resolved = resolveParams(options_params, resolvedOptions[0])

                  if(resolved.includes('"')) // check if list of strings
                  {
                    resolvedOptions = `[${resolved}]`
                  } else // one value only
                  {
                    resolvedOptions = `["${resolved}"]`
                  }
                }
              }
            }

            setCache(`nax_set_${set.setKey}`, set.setValue)
            setCache(`nax_set_opt_${set.setKey}`, resolvedOptions)
          });
        }).then(() => handleSuccess('cacheSettings', resolve())).catch(() => handleFailure('cacheSettings', reject("cacheSettings already completed")));    // handle AJAX synchronization
      })
    }
  });
}

// -----------------------------------------------------------------------------
// Get a setting options value by key
function getSettingOptions (key) {

  // handle initial load to make sure everything is set-up and cached
  // handleFirstLoad()

  result = getCache(`nax_set_opt_${key}`, true);

  if (result == "")
  {
    console.log(`Setting options with key "${key}" not found`)
    result = []
  }

  return result;
}

// -----------------------------------------------------------------------------
// Get a setting value by key
function getSetting (key) {

  // handle initial load to make sure everything is set-up and cached
  // handleFirstLoad()

  result = getCache(`nax_set_${key}`, true);

  if (result == "")
  {
    console.log(`Setting with key "${key}" not found`)
  }

  return result;
}

// -----------------------------------------------------------------------------
// Get language string
// -----------------------------------------------------------------------------
function cacheStrings() {
  return new Promise((resolve, reject) => {

      // Create a promise for each language (include en_us by default as fallback)
      languagesToLoad = ['en_us']

      additionalLanguage = getLangCode()

      if(additionalLanguage != 'en_us')
      {
        languagesToLoad.push(additionalLanguage)
      }

      console.log(languagesToLoad);

      const languagePromises = languagesToLoad.map((language_code) => {
        return new Promise((resolveLang, rejectLang) => {
          // Fetch core strings and translations

          $.get(`php/templates/language/${language_code}.json?nocache=${Date.now()}`)
            .done((res) => {
              // Iterate over each key-value pair and store the translations
              Object.entries(res).forEach(([key, value]) => {
                setCache(`pia_lang_${key}_${language_code}`, value);
              });

              // Fetch strings and translations from plugins
              $.get('php/server/query_json.php', { file: 'table_plugins_language_strings.json', nocache: Date.now() })
                .done((pluginRes) => {
                  const data = pluginRes["data"];

                  // Store plugin translations
                  data.forEach((langString) => {
                    setCache(`pia_lang_${langString.String_Key}_${langString.Language_Code}`, langString.String_Value);
                  });

                  // Handle successful completion of language processing
                  handleSuccess(`cacheStrings`, resolveLang);
                })
                .fail((pluginError) => {
                  // Handle failure in plugin strings fetching
                  rejectLang(pluginError);
                });
            })
            .fail((error) => {
              // Handle failure in core strings fetching
              rejectLang(error);
            });
        });
      });

      // Wait for all language promises to complete
      Promise.all(languagePromises)
        .then(() => {
          // All languages processed successfully
          resolve();
        })
        .catch((error) => {
          // Handle failure in any of the language processing
          handleFailure('cacheStrings', reject);
        });

  });
}

// -----------------------------------------------------------------------------
// Get translated language string
function getString(key) {

  function fetchString(key) {

    lang_code = getLangCode();

    let result = getCache(`pia_lang_${key}_${lang_code}`, true);

    if (isEmpty(result)) {
      result = getCache(`pia_lang_${key}_en_us`, true);
    }

    return result;
  }

  if (isAppInitialized()) {
    return fetchString(key);
  } else {
    callAfterAppInitialized(() => fetchString(key));
  }
}

// -----------------------------------------------------------------------------
// Get current language ISO code
// below has to match exactly the values in /front/php/templates/language/lang.php & /front/js/common.js
function getLangCode() {

    UI_LANG = getSetting("UI_LANG");

    let lang_code = 'en_us';

    switch (UI_LANG) {
      case 'English (en_us)':
        lang_code = 'en_us';
        break;
      case 'Spanish (es_es)':
        lang_code = 'es_es';
        break;
      case 'German (de_de)':
        lang_code = 'de_de';
        break;
      case 'Farsi (fa_fa)':
        lang_code = 'fa_fa';
        break;
      case 'French (fr_fr)':
        lang_code = 'fr_fr';
        break;
      case 'Norwegian (nb_no)':
        lang_code = 'nb_no';
        break;
      case 'Polish (pl_pl)':
        lang_code = 'pl_pl';
        break;
      case 'Portuguese (pt_br)':
        lang_code = 'pt_br';
        break;
      case 'Portuguese (pt_pt)':
        lang_code = 'pt_pt';
        break;
      case 'Turkish (tr_tr)':
        lang_code = 'tr_tr';
        break;
      case 'Swedish (sv_sv)':
        lang_code = 'sv_sv';
        break;
      case 'Italian (it_it)':
        lang_code = 'it_it';
        break;
      case 'Japanese (ja_jp)':
        lang_code = 'ja_jp';
        break;
      case 'Russian (ru_ru)':
        lang_code = 'ru_ru';
        break;
      case 'Chinese (zh_cn)':
        lang_code = 'zh_cn';
        break;
      case 'Czech (cs_cz)':
        lang_code = 'cs_cz';
        break;
      case 'Arabic (ar_ar)':
        lang_code = 'ar_ar';
        break;
      case 'Catalan (ca_ca)':
        lang_code = 'ca_ca';
        break;
      case 'Ukrainian (uk_uk)':
        lang_code = 'uk_ua';
        break;
    }

    return lang_code;
}

  const tz = getSetting("TIMEZONE") || 'Europe/Berlin';
  const LOCALE = getSetting('UI_LOCALE') || 'en-GB';

// -----------------------------------------------------------------------------
// DateTime utilities
// -----------------------------------------------------------------------------
function localizeTimestamp(input) {


  input = String(input || '').trim();

  // 1. Unix timestamps (10 or 13 digits)
  if (/^\d+$/.test(input)) {
    const ms = input.length === 10 ? parseInt(input, 10) * 1000 : parseInt(input, 10);
    return new Intl.DateTimeFormat('default', {
      timeZone: tz,
      year: 'numeric', month: '2-digit', day: '2-digit',
      hour: '2-digit', minute: '2-digit', second: '2-digit',
      hour12: false
    }).format(new Date(ms));
  }

 // 2. European DD/MM/YYYY
  let match = input.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:[ ,]+(\d{1,2}:\d{2}(?::\d{2})?))?$/);
  if (match) {
    let [, d, m, y, t = "00:00:00", tzPart = ""] = match;
    const dNum = parseInt(d, 10);
    const mNum = parseInt(m, 10);

    if (dNum <= 12 && mNum > 12) {
    } else {
      const iso = `${y}-${m.padStart(2,'0')}-${d.padStart(2,'0')}T${t.length===5 ? t + ":00" : t}${tzPart}`;
      return formatSafe(iso, tz);
    }
  }

  // 3. US MM/DD/YYYY
  match = input.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:[ ,]+(\d{1,2}:\d{2}(?::\d{2})?))?(.*)$/);
  if (match) {
    let [, m, d, y, t = "00:00:00", tzPart = ""] = match;
    const iso = `${y}-${m.padStart(2,'0')}-${d.padStart(2,'0')}T${t.length===5?t+":00":t}${tzPart}`;
    return formatSafe(iso, tz);
  }

  // 4. ISO YYYY-MM-DD with optional Z/+offset
  match = input.match(/^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])[ T](\d{1,2}:\d{2}(?::\d{2})?)(Z|[+-]\d{2}:?\d{2})?$/);
  if (match) {
    let [, y, m, d, time, offset = ""] = match;
    const iso = `${y}-${m}-${d}T${time.length===5?time+":00":time}${offset}`;
    return formatSafe(iso, tz);
  }

  // 5. RFC2822 / "25 Aug 2025 13:45:22 +0200"
  match = input.match(/^\d{1,2} [A-Za-z]{3,} \d{4}/);
  if (match) {
    return formatSafe(input, tz);
  }

  // 6. DD-MM-YYYY with optional time
  match = input.match(/^(\d{1,2})-(\d{1,2})-(\d{4})(?:[ T](\d{1,2}:\d{2}(?::\d{2})?))?$/);
  if (match) {
    let [, d, m, y, time = "00:00:00"] = match;
    const iso = `${y}-${m.padStart(2,'0')}-${d.padStart(2,'0')}T${time.length===5?time+":00":time}`;
    return formatSafe(iso, tz);
  }

  // 7. Strict YYYY-DD-MM with optional time
  match = input.match(/^(\d{4})-(0[1-9]|[12]\d|3[01])-(0[1-9]|1[0-2])(?:[ T](\d{1,2}:\d{2}(?::\d{2})?))?$/);
  if (match) {
    let [, y, d, m, time = "00:00:00"] = match;
    const iso = `${y}-${m}-${d}T${time.length === 5 ? time + ":00" : time}`;
    return formatSafe(iso, tz);
  }

  // 8. Fallback
  return formatSafe(input, tz);

  function formatSafe(str, tz) {
    const date = new Date(str);
    if (!isFinite(date)) {
      console.error(`ERROR: Couldn't parse date: '${str}' with TIMEZONE ${tz}`);
      return 'Failed conversion - Check browser console';
    }
    return new Intl.DateTimeFormat(LOCALE, {
      timeZone: tz,
      year: 'numeric', month: '2-digit', day: '2-digit',
      hour: '2-digit', minute: '2-digit', second: '2-digit',
      hour12: false
    }).format(date);
  }
}


/**
 * Returns start and end date for a given period.
 * @param {string} period - "1 day", "7 days", "1 month", "1 year", "100 years"
 * @returns {{start: string, end: string}} - Dates in "YYYY-MM-DD HH:MM:SS" format
 */
function getPeriodStartEnd(period) {
  const now = new Date();
  let start = new Date(now); // default start = now
  let end = new Date(now);   // default end = now

  switch (period) {
    case "1 day":
      start.setDate(now.getDate() - 1);
      break;
    case "7 days":
      start.setDate(now.getDate() - 7);
      break;
    case "1 month":
      start.setMonth(now.getMonth() - 1);
      break;
    case "1 year":
      start.setFullYear(now.getFullYear() - 1);
      break;
    case "100 years":
      start = new Date(0); // very old date for "all"
      break;
    default:
      console.warn("Unknown period, using 1 month as default");
      start.setMonth(now.getMonth() - 1);
  }

  // Helper function to format date as "YYYY-MM-DD HH:MM:SS"
  const formatDate = (date) => {
    const pad = (n) => String(n).padStart(2, "0");
    return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` +
           `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
  };

  return {
    start: formatDate(start),
    end: formatDate(end)
  };
}

// -----------------------------------------------------------------------------
// String utilities
// -----------------------------------------------------------------------------


// ----------------------------------------------------
/**
 * Replaces double quotes within single-quoted strings, then converts all single quotes to double quotes,
 * while preserving the intended structure.
 *
 * @param {string} inputString - The input string to process.
 * @returns {string} - The processed string with transformations applied.
 */
function processQuotes(inputString) {
  // Step 1: Replace double quotes within single-quoted strings
  let tempString = inputString.replace(/'([^']*?)'/g, (match, p1) => {
    const escapedContent = p1.replace(/"/g, '_escaped_double_quote_'); // Temporarily replace double quotes
    return `'${escapedContent}'`;
  });

  // Step 2: Replace all single quotes with double quotes
  tempString = tempString.replace(/'/g, '"');

  // Step 3: Restore escaped double quotes
  const processedString = tempString.replace(/_escaped_double_quote_/g, "'");

  return processedString;
}

// ----------------------------------------------------
function jsonSyntaxHighlight(json) {
  if (typeof json != 'string') {
       json = JSON.stringify(json, undefined, 2);
  }
  json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
  return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
      var cls = 'number';
      if (/^"/.test(match)) {
          if (/:$/.test(match)) {
              cls = 'key';
          } else {
              cls = 'string';
          }
      } else if (/true|false/.test(match)) {
          cls = 'boolean';
      } else if (/null/.test(match)) {
          cls = 'null';
      }
      return '<span class="' + cls + '">' + match + '</span>';
  });
}

// ----------------------------------------------------
function isValidBase64(str) {
  // Base64 characters set
  var base64CharacterSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
  // Remove all valid characters from the string
  var invalidCharacters = str.replace(new RegExp('[' + base64CharacterSet + ']', 'g'), '');
  // If there are any characters left, the string is invalid
  return invalidCharacters === '';
}

// -------------------------------------------------------------------
// Utility function to check if the value is already Base64
function isBase64(value) {
  if (typeof value !== "string" || value.trim() === "") return false;

  // Must have valid length
  if (value.length % 4 !== 0) return false;

  // Valid Base64 characters
  const base64Regex = /^[A-Za-z0-9+/]+={0,2}$/;
  if (!base64Regex.test(value)) return false;


  try {
    const decoded = atob(value);

    // Re-encode
    const reencoded = btoa(decoded);

    if (reencoded !== value) return false;

    // Extra verification:
    // Ensure decoding didn't silently drop bytes (atob bug)
    // Encode raw bytes: check if large char codes exist (invalid UTF-16)
    for (let i = 0; i < decoded.length; i++) {
      const code = decoded.charCodeAt(i);
      if (code > 255) return false; // invalid binary byte
    }

    return true;
  } catch (e) {
    return false;
  }
}


// ----------------------------------------------------
function isValidJSON(jsonString) {
  try {
      JSON.parse(jsonString);
      return true;
  } catch (e) {
      return false;
  }
}

// ----------------------------------------------------
// method to sanitize input so that HTML and other things don't break
function encodeSpecialChars(str) {
  return str
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#039;');
}
// ----------------------------------------------------
function decodeSpecialChars(str) {
  return str
      .replace(/&amp;/g, '&')
      .replace(/&lt;/g, '<')
      .replace(/&gt;/g, '>')
      .replace(/&quot;/g, '"')
      .replace(/&#039;/g, '\'');
}

// ----------------------------------------------------
// base64 conversion of UTF8 chars
function utf8ToBase64(str) {
  // Convert the string to a Uint8Array using TextEncoder
  const utf8Bytes = new TextEncoder().encode(str);

  // Convert the Uint8Array to a base64-encoded string
  return btoa(String.fromCharCode(...utf8Bytes));
}


// -----------------------------------------------------------------------------
// General utilities
// -----------------------------------------------------------------------------

// check if JSON object
function isJsonObject(value) {
  return typeof value === 'object' && value !== null && !Array.isArray(value);
}


// remove unnecessary lines from the result
function sanitize(data)
{
  return data.replace(/(\r\n|\n|\r)/gm,"").replace(/[^\x00-\x7F]/g, "")
}


// -----------------------------------------------------------------------------
// Check and handle locked database
function handle_locked_DB(data)
{
  if(data.includes('database is locked'))
  {
    // console.log(data)
    showSpinner()

    setTimeout(function() {
      console.warn("Database locked - reload")
      location.reload();
    }, 5000);
  }
}

// -----------------------------------------------------------------------------
function numberArrayFromString(data)
{
  data = JSON.parse(sanitize(data));
  return data.replace(/\[|\]/g, '').split(',').map(Number);
}

// -----------------------------------------------------------------------------
function saveData(functionName, id, value) {
  $.ajax({
    method: "GET",
    url: "php/server/devices.php",
    data: { action: functionName, id: id, value:value  },
    success: function(data) {

        if(sanitize(data) == 'OK')
        {
          showMessage("Saved")
          // Remove navigation prompt "Are you sure you want to leave..."
          window.onbeforeunload = null;
        } else
        {
          showMessage("ERROR")
        }

      }
  });

}


// -----------------------------------------------------------------------------
// create a link to the device
function createDeviceLink(input)
{
  if(checkMacOrInternet(input))
  {
    return `<span class="anonymizeMac"><a href="/deviceDetails.php?mac=${input}" target="_blank">${getDevDataByMac(input, "devName")}</a><span>`
  }

  return input;
}


// -----------------------------------------------------------------------------
// remove an item from an array
function removeItemFromArray(arr, value) {
  var index = arr.indexOf(value);
  if (index > -1) {
    arr.splice(index, 1);
  }
  return arr;
}

// -----------------------------------------------------------------------------
function sleep(milliseconds) {
  const date = Date.now();
  let currentDate = null;
  do {
    currentDate = Date.now();
  } while (currentDate - date < milliseconds);
}

// ---------------------------------------------------------
somethingChanged = false;
function settingsChanged()
{
  somethingChanged = true;
  // Enable navigation prompt ... "Are you sure you want to leave..."
  window.onbeforeunload = function() {
    return true;
  };
}

// -----------------------------------------------------------------------------
// Get Anchor from URL
function getUrlAnchor(defaultValue){

  target = defaultValue

  var url = window.location.href;
  if (url.includes("#")) {

    // default selection
    selectedTab = defaultValue

    // the #target from the url
    target = window.location.hash.substr(1)

    // get only the part between #...?
    if(target.includes('?'))
    {
      target = target.split('?')[0]
    }

    return target

  }

}

// -----------------------------------------------------------------------------
// get query string from URL
function getQueryString(key){
  params = new Proxy(new URLSearchParams(window.location.search), {
    get: (searchParams, prop) => searchParams.get(prop),
  });

  tmp = params[key]

  if(emptyArr.includes(tmp))
  {
    var queryParams = {};
    fullUrl = window.location.toString();

    // console.log(fullUrl);

    if (fullUrl.includes('?')) {
      var queryString = fullUrl.split('?')[1];

      // Split the query string into individual parameters
      var paramsArray = queryString.split('&');

      // Loop through the parameters array
      paramsArray.forEach(function(param) {
          // Split each parameter into key and value
          var keyValue = param.split('=');
          var keyTmp = decodeURIComponent(keyValue[0]);
          var value = decodeURIComponent(keyValue[1] || '');

          // Store key-value pair in the queryParams object
          queryParams[keyTmp] = value;
      });
    }

    // console.log(queryParams);

    tmp = queryParams[key]
  }

  result = emptyArr.includes(tmp) ? "" : tmp;

  return result
}
// -----------------------------------------------------------------------------
function translateHTMLcodes (text) {
  if (text == null || emptyArr.includes(text)) {
    return null;
  } else if (typeof text === 'string' || text instanceof String)
  {
    var text2 = text.replace(new RegExp(' ', 'g'), "&nbsp");
    text2 = text2.replace(new RegExp('<', 'g'), "&lt");
    return text2;
  }

  return "";
}


// -----------------------------------------------------------------------------
function stopTimerRefreshData () {
  try {
    clearTimeout (timerRefreshData);
  } catch (e) {}
}


// -----------------------------------------------------------------------------
function newTimerRefreshData (refeshFunction, timeToRefresh) {

  if(timeToRefresh && (timeToRefresh != 0 || timeToRefresh != ""))
  {
    time = parseInt(timeToRefresh)
  } else
  {
    time = 60000
  }

  timerRefreshData = setTimeout (function() {
    refeshFunction();
  }, time);
}


// -----------------------------------------------------------------------------
function debugTimer () {
  $('#pageTitle').html (new Date().getSeconds());
}

// -----------------------------------------------------------------------------
function secondsSincePageLoad() {
  // Get the current time since the page was loaded
  var timeSincePageLoad = performance.now();

  // Convert milliseconds to seconds
  var secondsAgo = Math.floor(timeSincePageLoad / 1000);

  return secondsAgo;
}

// -----------------------------------------------------------------------------
// Open url in new tab
function openInNewTab (url) {
  window.open(url, "_blank");
}

// -----------------------------------------------------------------------------
// Navigate to URL if the current URL is not in the provided list of URLs
function openUrl(urls) {
  var currentUrl = window.location.href;
  var mainUrl = currentUrl.match(/^.*?(?=#|\?|$)/)[0]; // Extract main URL

  var isMatch = false;

  $.each(urls,function(index, obj){

    // remove . for comaprison if in the string, e.g.: ./devices.php
    arrayUrl = obj.replace('.','')

    // check if we are on a url contained in the array
    if(mainUrl.includes(arrayUrl))
    {
      isMatch = true;
    }
  });

  // if we are not, redirect
  if (isMatch == false) {
    window.location.href = urls[0]; // Redirect to the first URL in the list if not found
  }
}

// -----------------------------------------------------------------------------
// force load URL in current window with specific anchor
function forceLoadUrl(relativeUrl) {

  window.location.replace(relativeUrl);
  window.location.reload()

}

// -----------------------------------------------------------------------------
function navigateToDeviceWithIp (ip) {

  $.get('php/server/query_json.php', { file: 'table_devices.json', nocache: Date.now() }, function(res) {

    devices = res["data"];

    mac = ""

    $.each(devices, function(index, obj) {

      if(obj.devLastIP.trim() == ip.trim())
      {
        mac = obj.devMac;

        window.open('./deviceDetails.php?mac=' + mac , "_blank");
      }
    });

  });
}

// -----------------------------------------------------------------------------
// Check if MAC or Internet
function checkMacOrInternet(inputStr) {
  // Regular expression pattern for matching a MAC address
  const macPattern = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;

  if (inputStr.toLowerCase() === 'internet') {
      return true;
  } else if (macPattern.test(inputStr)) {
      return true;
  } else {
      return false;
  }
}

// Alias
function isValidMac(value) {
  return checkMacOrInternet(value);
}

// -----------------------------------------------------------------------------
// Gte MAC from query string
function getMac(){
  params = new Proxy(new URLSearchParams(window.location.search), {
    get: (searchParams, prop) => searchParams.get(prop),
  });

  mac = params.mac;

  if (mac == "") {
    console.error("Couldn't retrieve mac");
  }

  return mac;
}

// -----------------------------------------------------------------------------
// A function used to make the IP address orderable
function isValidIPv6(ipAddress) {
  // Regular expression for IPv6 validation
  const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}$|^([0-9a-fA-F]{1,4}:){1,7}:|^([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}$|^([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}$|^([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}$|^([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}$|^([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}$|^[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})$/;

  return ipv6Regex.test(ipAddress);
}

function isValidIPv4(ip) {
  const ipv4Regex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
  return ipv4Regex.test(ip);
}


function formatIPlong(ipAddress) {
  if (ipAddress.includes(':') && isValidIPv6(ipAddress)) {
    const parts = ipAddress.split(':');

    return parts.reduce((acc, part, index) => {
      if (part === '') {
        const remainingGroups = 8 - parts.length + 1;
        return acc << (16 * remainingGroups);
      }

      const hexValue = parseInt(part, 16);
      return acc | (hexValue << (112 - index * 16));
    }, 0);
  } else {
    // Handle IPv4 address
    const parts = ipAddress.split('.');

    if (parts.length !== 4) {
      console.log("⚠ Invalid IPv4 address: " + ipAddress);
      return -1; // or any other default value indicating an error
    }

    return (parseInt(parts[0]) << 24) |
           (parseInt(parts[1]) << 16) |
           (parseInt(parts[2]) << 8) |
           parseInt(parts[3]);
  }
}

// -----------------------------------------------------------------------------
// Check if MAC is a random one
function isRandomMAC(mac)
{
  isRandom = false;

  isRandom = ["2", "6", "A", "E", "a", "e"].includes(mac[1]);

  // if detected as random, make sure it doesn't start with a prefix which teh suer doesn't want to mark as random
  if(isRandom)
  {
    $.each(createArray(getSetting("UI_NOT_RANDOM_MAC")), function(index, prefix) {

      if(mac.startsWith(prefix))
      {
        isRandom = false;
      }

    });

  }

  return isRandom;
}

  // ---------------------------------------------------------
  // Generate an array object from a string representation of an array
  function createArray(input) {
    // Is already array, return
    if (Array.isArray(input)) {
      return input;
    }
    // Empty array
    // if (input === '[]' || input === '') {
    if (input === '[]') {
      return [];
    }
    // handle integer
    if (typeof input === 'number') {
      input = input.toString();
    }

    // Regex pattern for brackets
    const patternBrackets = /(^\s*\[)|(\]\s*$)/g;
    const replacement = '';

    // Remove brackets
    const noBrackets = input.replace(patternBrackets, replacement);

    const options = [];

    // Detect the type of quote used after the opening bracket
    const firstChar = noBrackets.trim()[0];
    const isDoubleQuoted = firstChar === '"';
    const isSingleQuoted = firstChar === "'";

    // Create array while handling commas within quoted segments
    let currentSegment = '';
    let withinQuotes = false;
    for (let i = 0; i < noBrackets.length; i++) {
      const char = noBrackets[i];
      if ((char === '"' && !isSingleQuoted) || (char === "'" && !isDoubleQuoted)) {
        withinQuotes = !withinQuotes;
      }
      if (char === ',' && !withinQuotes) {
        options.push(currentSegment.trim());
        currentSegment = '';
      } else {
        currentSegment += char;
      }
    }
    // Push the last segment
    options.push(currentSegment.trim());

    // Remove quotes based on detected type
    options.forEach((item, index) => {
      let trimmedItem = item.trim();
      // Check if the string starts and ends with the same type of quote
      if ((isDoubleQuoted && trimmedItem.startsWith('"') && trimmedItem.endsWith('"')) ||
          (isSingleQuoted && trimmedItem.startsWith("'") && trimmedItem.endsWith("'"))) {
        // Remove the quotes
        trimmedItem = trimmedItem.substring(1, trimmedItem.length - 1);
      }
      options[index] = trimmedItem;
    });

    return options;
  }

// -----------------------------------------------------------------------------
// A function to get a device property using the mac address as key and DB column nakme as parameter
//  for the value to be returned
function getDevDataByMac(macAddress, dbColumn) {

  const sessionDataKey = 'devicesListAll_JSON';
  const devicesCache = getCache(sessionDataKey);

  if (!devicesCache || devicesCache == "") {
      console.error(`Session variable "${sessionDataKey}" not found.`);
      return null;
  }

  const devices = JSON.parse(devicesCache);

  for (const device of devices) {
      if (device["devMac"].toLowerCase() === macAddress.toLowerCase()) {

        if(dbColumn)
        {
          return device[dbColumn];
        }
        else
        {
          return device
        }
      }
  }

  console.error("⚠ Device with MAC not found:" + macAddress)
  return null; // Return a default value if MAC address is not found
}

// -----------------------------------------------------------------------------
// Cache the devices as one JSON
function cacheDevices()
{
  return new Promise((resolve, reject) => {

    $.get('php/server/query_json.php', { file: 'table_devices.json', nocache: Date.now() }, function(data) {

        // console.log(data)

        devicesListAll_JSON = data["data"]

        devicesListAll_JSON_str = JSON.stringify(devicesListAll_JSON)

        if(devicesListAll_JSON_str == "")
        {
          showSpinner()

          setTimeout(() => {
            cacheDevices()
          }, 1000);
        }
        // console.log(devicesListAll_JSON_str);

        setCache('devicesListAll_JSON', devicesListAll_JSON_str)

        // console.log(getCache('devicesListAll_JSON'))
      }).then(() => handleSuccess('cacheDevices', resolve())).catch(() => handleFailure('cacheDevices', reject("cacheDevices already completed"))); // handle AJAX synchronization
    }
  );
}

var devicesListAll_JSON      = [];   // this will contain a list off all devices

// -----------------------------------------------------------------------------
function isEmpty(value)
{
  return emptyArr.includes(value)
}

// -----------------------------------------------------------------------------
function mergeUniqueArrays(arr1, arr2) {
  let mergedArray = [...arr1]; // Make a copy of arr1

  arr2.forEach(element => {
      if (!mergedArray.includes(element)) {
          mergedArray.push(element);
      }
  });

  return mergedArray;
}

// -----------------------------------------------------------------------------
// Generate a GUID
function getGuid() {
  return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
    (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
  );
}

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


// -----------------------------------------------------------------------------
//  Loading Spinner overlay
// -----------------------------------------------------------------------------

let spinnerTimeout = null;
let animationTime = 300

function showSpinner(stringKey = 'Loading') {
  let text = isEmpty(stringKey) ? "Loading..." : getString(stringKey || "Loading");

  if (text == ""){
    text = "Loading"
  }

  const spinner = $("#loadingSpinner");
  const target = $(".spinnerTarget").first(); // Only use the first one if multiple exist

  $("#loadingSpinnerText").text(text);

  if (target.length) {
    // Position relative to target
    const offset = target.offset();
    const width = target.outerWidth();
    const height = target.outerHeight();

    spinner.css({
      position: "absolute",
      top: offset.top,
      left: offset.left,
      width: width,
      height: height,
      zIndex: 800
    });
  } else {
    // Fullscreen fallback
    spinner.css({
      position: "fixed",
      top: 0,
      left: 0,
      width: "100%",
      height: "100%",
      zIndex: 800
    });
  }

  requestAnimationFrame(() => {
    spinner.addClass("visible");
    spinner.fadeIn(animationTime);
  });
}

function hideSpinner() {
  clearTimeout(spinnerTimeout);
  const spinner = $("#loadingSpinner");

  if (!spinner.length) return;

  const target = $(".spinnerTarget").first();

  if (target.length) {
    // Lock position to target
    const offset = target.offset();
    const width = target.outerWidth();
    const height = target.outerHeight();

    spinner.css({
      position: "absolute",
      top: offset.top,
      left: offset.left,
      width: width,
      height: height,
      zIndex: 800
    });
  } else {
    // Fullscreen fallback
    spinner.css({
      position: "fixed",
      top: 0,
      left: 0,
      width: "100%",
      height: "100%",
      zIndex: 800
    });
  }

  // Trigger fade-out and only remove styles AFTER fade completes AND display is none
  spinner.removeClass("visible").fadeOut(animationTime, () => {
    // Ensure it's really hidden before resetting styles
    spinner.css({
      display: "none"
    });

    spinner.css({
      position: "",
      top: "",
      left: "",
      width: "",
      height: "",
      zIndex: ""
    });
  });
}


// --------------------------------------------------------
// Calls a backend function to add a front-end event to an execution queue
function updateApi(apiEndpoints)
{

  // value has to be in format event|param. e.g. run|ARPSCAN
  action = `${getGuid()}|update_api|${apiEndpoints}`


  $.ajax({
    method: "POST",
    url: "php/server/util.php",
    data: { function: "addToExecutionQueue", action: action  },
    success: function(data, textStatus) {
        console.log(data)
    }
  })
}

// -----------------------------------------------------------------------------
// handling smooth scrolling
// -----------------------------------------------------------------------------
function setupSmoothScrolling() {
  // Function to scroll to the element
  function scrollToElement(id) {
      $('html, body').animate({
          scrollTop: $("#" + id).offset().top - 50
      }, 1000);
  }

  // Scroll to the element when clicking on anchor links
  $('a[href*="#"]').on('click', function(event) {
      var href = $(this).attr('href');
      if (href !=='#' && href && href.includes('#') && !$(this).is('[data-toggle="collapse"]')) {
          var id = href.substring(href.indexOf("#") + 1); // Get the ID from the href attribute
          if ($("#" + id).length > 0) {
              event.preventDefault(); // Prevent default anchor behavior
              scrollToElement(id); // Scroll to the element
          }
      }
  });

  // Check if there's an ID in the URL and scroll to it
  var url = window.location.href;
  if (url.includes("#")) {
      var idFromURL = url.substring(url.indexOf("#") + 1);
      if (idFromURL != "" && $("#" + idFromURL).length > 0) {
          scrollToElement(idFromURL);
      }
  }
}

// -------------------------------------------------------------------
// Function to check if options_params contains a parameter with type "sql"
function hasSqlType(params) {
  for (let param of params) {
      if (param.type === "sql") {
          return true; // Found a parameter with type "sql"
      }
  }
  return false; // No parameter with type "sql" found
}

// -------------------------------------------------------------------
// Function to check if string is SQL query
function isSQLQuery(query) {
  // Regular expression to match common SQL keywords and syntax with word boundaries
  var sqlRegex = /\b(SELECT|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER|FROM|JOIN|WHERE|SET|VALUES|GROUP BY|ORDER BY|LIMIT)\b/i;

  return sqlRegex.test(query);
}


// -------------------------------------------------------------------
// Get corresponding plugin setting object
function getPluginSettingObject(pluginsData, setting_key, unique_prefix ) {

  result = {}
  unique_prefix == undefined ? unique_prefix = setting_key.split("_")[0] : unique_prefix = unique_prefix;

  $.each(pluginsData, function (i, plgnObj){
    // go thru plugins
    if(plgnObj.unique_prefix == unique_prefix)
    {
      // go thru plugin settings
      $.each(plgnObj["settings"], function (j, setObj){

        if(`${unique_prefix}_${setObj.function}` == setting_key)
        {
          result = setObj
        }

      });
    }
  });

  return result
}

// -------------------------------------------------------------------
// Resolve all option parameters
function resolveParams(params, template) {
  params.forEach(param => {
      // Check if the template includes the parameter name
      if (template.includes("{" + param.name + "}")) {
          // If the parameter type is 'setting', retrieve setting value
          if (param.type == "setting") {
              var value = getSetting(param.value);

              // Remove brackets and single quotes, replace them with double quotes
              value = value.replace('[','').replace(']','').replace(/'/g, '"');

              // Split the string into an array, remove empty elements
              const arr = value.split(',').filter(Boolean);

              // Join the array elements with commas
              const result = arr.join(', ');

              // Replace placeholder with setting value
              template = template.replace("{" + param.name + "}", result);
          } else {
              // If the parameter type is not 'setting', use the provided value
              template = template.replace("{" + param.name + "}", param.value);
          }
      }
  });

  // Log the resolved template
  // console.log(template);

  // Return the resolved template
  return template;
}

// -----------------------------------------------------------------------------
// check if two arrays contain same values even if out of order
function arraysContainSameValues(arr1, arr2) {
  // Check if both parameters are arrays
  if (!Array.isArray(arr1) || !Array.isArray(arr2)) {
    return false;
  } else
  {
    // Sort and stringify arrays, then compare
    return JSON.stringify(arr1.slice().sort()) === JSON.stringify(arr2.slice().sort());
  }
}

// -----------------------------------------------------------------------------
// Hide elements on the page based on the supplied setting
function hideUIelements(setKey) {

  hiddenSectionsSetting = getSetting(setKey)

  if(hiddenSectionsSetting != "") // handle if settings not yet initialized
  {

    sectionsArray = createArray(hiddenSectionsSetting)

    // remove spaces to get IDs
    var newArray = $.map(sectionsArray, function(value) {
        return value.replace(/\s/g, '');
    });

    $.each(newArray, function(index, hiddenSection) {

      if($('#' + hiddenSection))
      {
        $('#' + hiddenSection).hide()
      }

    });
  }

}

// ------------------------------------------------------------
function getDevicesList()
{
  // Read cache (skip cookie expiry check)
  devicesList = getCache('devicesListAll_JSON', true);

  if (devicesList != '') {
      devicesList = JSON.parse (devicesList);
  } else {
      devicesList = [];
  }

  // only loop thru the filtered down list
  visibleDevices = getCache("ntx_visible_macs")

  if(visibleDevices != "") {
    visibleDevicesMACs = visibleDevices.split(',');

    devicesList_tmp = [];

    // Iterate through the data and filter only visible devices
    $.each(devicesList, function(index, item) {
      // Check if the current item's MAC exists in visibleDevicesMACs
      if (visibleDevicesMACs.includes(item.devMac)) {
        devicesList_tmp.push(item);
      }
    });

    // Update devicesList with the filtered items
    devicesList = devicesList_tmp;
  }

  return devicesList;
}


// -----------------------------------------------------------------------------
// apply theme

$(document).ready(function() {
  let theme = getSetting("UI_theme");
  if (theme) {
    theme = theme.replace("['","").replace("']","");
    // Add the theme stylesheet
    setCookie("UI_theme", theme);
    switch(theme) {
      case "Dark":
	$('head').append('<link rel="stylesheet" href="css/dark-patch.css">');
	break;
      case "System":
	$('head').append('<link rel="stylesheet" href="css/system-dark-patch.css">');
	break
    }
  } else {
    setCookie("UI_theme", "Light");
  }
});

// -----------------------------------------------------------
// Restart Backend Python Server

function askRestartBackend() {
  // Ask
  showModalWarning(getString('Maint_RestartServer'), getString('Maint_Restart_Server_noti_text'),
  getString('Gen_Cancel'), getString('Maint_RestartServer'), 'restartBackend');
}

// -----------------------------------------------------------
function restartBackend() {

  modalEventStatusId = 'modal-message-front-event'

  // Execute
  $.ajax({
      method: "POST",
      url: "php/server/util.php",
      data: { function: "addToExecutionQueue", action: `${getGuid()}|cron_restart_backend`  },
      success: function(data, textStatus) {
          // showModalOk ('Result', data );

          // show message
          showModalOk(getString("general_event_title"), `${getString("general_event_description")}  <br/> <br/> <code id='${modalEventStatusId}'></code>`);

          updateModalState()

          write_notification('[Maintenance] App manually restarted', 'info')
      }
    })
}

// -----------------------------------------------------------------------------
// initialize
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------

// Define a unique key for storing the flag in sessionStorage
const sessionStorageKey = "myScriptExecuted_common_js";
var completedCalls = []
var completedCalls_final = ['cacheSettings', 'cacheStrings', 'cacheDevices'];
var lang_completedCalls = 0;


// -----------------------------------------------------------------------------
// Clearing all the caches
function clearCache() {
  showSpinner();
  sessionStorage.clear();
  localStorage.clear();
  setTimeout(() => {
    console.warn("clearChache called");
    window.location.reload();
  }, 500);
}

// -----------------------------------------------------------------------------
// Function to check if cache needs to be refreshed because of setting changes
function checkSettingChanges() {
  $.get('php/server/query_json.php', { file: 'app_state.json', nocache: Date.now() }, function(appState) {
    const importedMilliseconds = parseInt(appState["settingsImported"] * 1000);
    const lastReloaded = parseInt(sessionStorage.getItem(sessionStorageKey + '_time'));

    if (importedMilliseconds > lastReloaded) {
      console.log("Cache needs to be refreshed because of setting changes");
      setTimeout(() => {
        clearCache();
      }, 500);
    }
  });
}

// -----------------------------------------------------------------------------
// Display spinner and reload page if not yet initialized
async function handleFirstLoad(callback) {
  if (!isAppInitialized()) {
    await new Promise(resolve => setTimeout(resolve, 1000));
    callback();
  }
}

// -----------------------------------------------------------------------------
// Execute callback once the app is initialized and GraphQL server is running
async function callAfterAppInitialized(callback) {
  if (!isAppInitialized() || !(await isGraphQLServerRunning())) {
    setTimeout(() => {
      callAfterAppInitialized(callback);
    }, 500);
  } else {
    callback();
  }
}

// -----------------------------------------------------------------------------
// Polling function to repeatedly check if the server is running
async function waitForGraphQLServer() {
  const pollInterval = 2000; // 2 seconds between each check
  let serverRunning = false;

  while (!serverRunning) {
    serverRunning = await isGraphQLServerRunning();
    if (!serverRunning) {
      console.log("GraphQL server not running, retrying in 2 seconds...");
      await new Promise(resolve => setTimeout(resolve, pollInterval));
    }
  }

  console.log("GraphQL server is now running.");
}

// -----------------------------------------------------------------------------
// Returns 1 if running, 0 otherwise
async function isGraphQLServerRunning() {
  try {
    const response = await $.get('php/server/query_json.php', { file: 'app_state.json', nocache: Date.now()});
    console.log("graphQLServerStarted: " + response["graphQLServerStarted"]);
    setCache("graphQLServerStarted", response["graphQLServerStarted"]);
    return response["graphQLServerStarted"];
  } catch (error) {
    console.error("Failed to check GraphQL server status:", error);
    return false;
  }
}

// -----------------------------------------------------------------------------
// Check if the code has been executed before by checking sessionStorage
function isAppInitialized() {

  lang_shouldBeCompletedCalls = getLangCode() == 'en_us' ? 1 : 2;

  // check if each ajax call completed succesfully
  $.each(completedCalls_final, function(index, call_name){

    if(getCache(call_name + "_completed") != "true")
    {
      console.log(`[isAppInitialized] AJAX call ${call_name} unsuccesful: ${getCache(call_name + "_completed")}`)
      return false;
    }

  });

  // check if all required languages chached
  if(parseInt(getCache("cacheStringsCountCompleted")) != lang_shouldBeCompletedCalls)
  {
    console.log(`[isAppInitialized] AJAX call cacheStrings unsuccesful: ${getCache("cacheStringsCountCompleted")} out of ${lang_shouldBeCompletedCalls}`)
    return false;
  }

  return true;
}

// -----------------------------------------------------------------------------
// Main execution logic
async function executeOnce() {
  showSpinner();

  if (!isAppInitialized()) {
    try {

      await waitForGraphQLServer(); // Wait for the server to start

      await cacheDevices();
      await cacheSettings();
      await cacheStrings();

      console.log("All AJAX callbacks have completed");
      onAllCallsComplete();
    } catch (error) {
      console.error("Error:", error);
    }
  }
}


// -----------------------------------------------------------------------------
// Function to handle successful completion of an AJAX call
const handleSuccess = (callName) => {
  console.log(`AJAX call successful: ${callName}`);

  if(callName.includes("cacheStrings"))
  {
    completed_tmp = getCache("cacheStringsCountCompleted");
    completed_tmp == "" ? completed_tmp = 0 : completed_tmp = completed_tmp;
    completed_tmp++;
    setCache("cacheStringsCountCompleted", completed_tmp);
  }

  setCache(callName + "_completed", true)
};

// -----------------------------------------------------------------------------
// Function to handle failure of an AJAX call
const handleFailure = (callName, callback) => {
  msg = `AJAX call ${callName} failed`
  console.error(msg);
  // Implement retry logic here if needed
  // write_notification(msg, 'interrupt')
};

// -----------------------------------------------------------------------------
// Function to execute when all AJAX calls have completed
const onAllCallsComplete = () => {
  completedCalls = mergeUniqueArrays(getCache('completedCalls').split(','), completedCalls);
  setCache('completedCalls', completedCalls);

  // Check if all necessary strings are initialized
  if (areAllStringsInitialized()) {
    sessionStorage.setItem(sessionStorageKey, "true");
    const millisecondsNow = Date.now();
    sessionStorage.setItem(sessionStorageKey + '_time', millisecondsNow);

    console.log('✔ Cache initialized');
    // setTimeout(() => {
    //   location.reload()
    // }, 10);

  } else {
    // If not all strings are initialized, retry initialization
    console.log('❌ Not all strings are initialized. Retrying...');
    executeOnce();
    return;
  }

  // Call any other initialization functions here if needed

};

// Function to check if all necessary strings are initialized
const areAllStringsInitialized = () => {
  // Implement logic to check if all necessary strings are initialized
  // Return true if all strings are initialized, false otherwise
  return getString('UI_LANG_name') != ""
};

// Call the function to execute the code
executeOnce();

// Set timer for regular UI refresh if enabled
setTimeout(() => {

  // page refresh if configured
  const refreshTime = getSetting("UI_REFRESH");
  if (refreshTime && refreshTime !== "0" && refreshTime !== "") {
    console.log("Refreshing page becasue UI_REFRESH setting enabled.");
    newTimerRefreshData(clearCache, parseInt(refreshTime)*1000);
  }

  // Check if page needs to refresh due to setting changes
  checkSettingChanges()

}, 10000);


console.log("init common.js");







