// ==================== User inputs and container elements ==================== //

// Palette
const addColor = document.querySelector(".add-color");
const paletteName = document.querySelector(".palette-name");
const paletteClear = document.querySelector(".clear-palette");
const paletteSave = document.querySelector(".save-palette");
const colorPickerContainer = document.querySelector(".color-picker-container");
const createPaletteSection = document.querySelector(".create-palette-section");
const savedPalettesSection = document.querySelector(".saved-palettes-section");

// Font
const fontName = document.querySelector(".font-name");
// const fontFile = document.querySelector(".font-file");
// const fontUpload = document.querySelector(".font-upload");
const fontFileName = document.querySelector(".font-file-name");
const fontClear = document.querySelector(".clear-font");
const fontSave = document.querySelector(".save-font");
const createFontSection = document.querySelector(".upload-font-section");
const savedFontsSection = document.querySelector(".saved-fonts-section");

// Translation
const languageDropdown = document.querySelector(".language-dropdown");
const translationsContainer = document.querySelector(".chart-translations");

// Settings
const chartAnimations = document.querySelector(".chart-animations");
const enablePrivilege = document.querySelector("#enable-privilege");
const useDefaultPrivilege = document.querySelector("#default-privilege");
const useCustomPrivilege = document.querySelector("#custom-privilege");
const customPrivilegeValue = document.querySelector(".custom-privilege-value");
const linksAccess = document.querySelector(".links-access");
const contextMenuAccess = document.querySelector(".context-menu-access");
const rtlSupport = document.querySelector(".rtl-support");
const negativeValueFormat = document.querySelector(".negative-value-format");
const seriesColorsLimit = document.querySelector(".series-colors-limit");
const accessControlLevels = document.querySelectorAll(".access-control-level");
const metricSuffixes = document.querySelectorAll(".metric-suffix");

// Other
const chartStylingComponents = document.querySelectorAll(".styling-component");
const saveChanges = document.querySelectorAll(".save-changes");
const chartContainerClass = ".vitara-theme-custom.vitara-chart-container";
const parser = new DOMParser();
let unsavedChanges = false;


// ======================= Data collections ======================= //

const paletteList = [];
const fontList = [];
const settingsData = {};
const stylesData = {};
let translationsData = {};
const fontFaceDefinitions = [];
const editingPalette = {
    status: false,
    name: ""
};
const editingFont = {
    status: false,
    name: ""
};
const blockSave = {
    blockedPages: [],
    reason: ""
};
// let fontUploadData = [];
let fontDeleteData = [];
const genericFontFamilies = ["serif", "sans-serif", "monospace", "cursive", "fantasy", "system-ui"];
const accessControlLevelCodes = ["delete", "execute", "read", "use", "write"]; // should be in sorted order
const metricSuffixValues = ["k", "M", "B", "T", "P", "E"];
const stylingComponentKeys = [
    "vitara-tooltip",
    "vitara-datalabel",
    "vitara-axis",
    "vitara-axis-title",
    "vitara-center-label",
    "highcharts-title",
    "vitara-legend-text",
    "highcharts-legend-item"
];
const translationFiles = {
    "English": "MessageBundle_en.txt",
    "English-US": "MessageBundle_en_us.txt",
    "Spanish": "MessageBundle_es.txt",
    "German": "MessageBundle_de.txt",
    "French": "MessageBundle_fr.txt",
    "Korean": "MessageBundle_ko.txt"
};


// ==================== HTML elements source strings ==================== //

const translationSelectorSrc = `
    <div class="table-field">
        <label>Select Text:</label>
        <select class="select-translation"></select>
    </div>
`;

const translationEditorSrc = `
    <div class="table-field">
        <label class="original-text">Translation:</label>
        <input type="text" class="translated-text">
    </div>
`;

const colorPickerSrc = `
    <div class="color-picker-wrapper">
        <input type="color" class="color-picker">
        <button class="remove-color delete-button">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#d84a38"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
        </button>
    </div>
`;

const getPaletteNodeSrc = (uniqueId) => {
    return `
        <li class="saved-palette">
            <span class="palette-label"></span>
            <ul class="palette-colors"></ul>
            <div class="action-buttons">
                <label for="palette-active-${uniqueId}" class="custom-check">
                    <input type="checkbox" id="palette-active-${uniqueId}" class="palette-active">
                    <span class="custom-check-icon"></span>
                </label>
                <label for="palette-default-btn-${uniqueId}" class="custom-radio">
                    <input type="radio" id="palette-default-btn-${uniqueId}" name="set-default-palette" class="palette-default">
                    <span class="custom-radio-icon"></span>
                </label>
                <button class="palette-edit edit-button">Edit</button>
                <button class="palette-delete delete-button">Delete</button>
            </div>
        </li>
    `;
};

const getFontNodeSrc = (uniqueId) => {
    return `
        <li class="saved-font">
            <span class="custom-font-label"></span>
            <div class="action-buttons">
                <label for="font-default-btn-${uniqueId}" class="custom-radio">
                    <input type="radio" name="set-default-font" id="font-default-btn-${uniqueId}" class="font-default">
                    <span class="custom-radio-icon"></span>
                </label>
                <button class="font-edit edit-button">Edit</button>
                <button class="font-delete delete-button">Delete</button>
            </div>
        </li>
    `;
};

// ==================== Helper functions ==================== //

function extractLineFromRawText(rawText, identifier) {
    return rawText.trim().split(/\r?\n/).find(line => line.startsWith(identifier));
}

function generateUniqueId() {
    return `${Date.now()}-${Math.random().toString(36)}`;
}

function generateStyleSelector(element) {
    const prefix = ".vitara-theme-custom";
    const selectors = JSON.parse(element.getAttribute("data-css-selectors"));
    const chartName = element.getAttribute("data-chart-name");
    chartName ? selectors.unshift(chartName, prefix) : selectors.unshift(prefix);
    return selectors.join(" ");
}

function generateFontFamilyString(fonts, defaultFont) {
    fonts.sort((a, b) => {
        // Move default font to the start of list
        if (a === defaultFont) return -1;
        if (b === defaultFont) return 1;

        // Move generic fonts to the end of list
        const aIsGeneric = genericFontFamilies.includes(a);
        const bIsGeneric = genericFontFamilies.includes(b);
        if (aIsGeneric && !bIsGeneric) return 1;
        if (!aIsGeneric && bIsGeneric) return -1;
        
        // Keep other fonts in original order
        return 0;
    });
    return `${fonts.map(font => {
        if (genericFontFamilies.includes(font)) {
            return font; // If the font is a generic family, return it as is
        } else {
            return `'${font}'`; // If the font is a custom font, wrap it in quotes
        }
    }).join(", ")} !important`;
}

function rgbToHex(rgb) {
    const result = rgb.match(/\d+/g);
    return `#${result.map(x => {
        const hex = parseInt(x).toString(16);
        return hex.length === 1 ? '0' + hex : hex;
    }).join('').toUpperCase()}`;
}

function clearPaletteColors() {
    while (colorPickerContainer.childElementCount > 1) {
        colorPickerContainer.lastElementChild.remove();
    }
}

function clearPaletteEdit() {
    paletteName.value = "";
    editingPalette.status = false;
    editingPalette.name = "";
    switchPaletteSectionLabels(false);
    clearPaletteColors();
}

function clearFontEdit() {
    fontName.value = "";
    fontFileName.value = "";
    editingFont.status = false;
    editingFont.name = "";
    switchFontSectionLabels(false);
}

function stripQuotes(str) {
    if ((str.startsWith('"') && str.endsWith('"')) || (str.startsWith("'") && str.endsWith("'"))) {
        return str.slice(1, -1);
    }
    return str;
}

function decodeHtmlEntities(str) {
    const textArea = document.createElement("textarea");
    textArea.innerHTML = str;
    return textArea.value;
}

function difference(setA, setB) {
    const result = new Set();
    for (const item of setA) {
        if (!setB.has(item)) result.add(item);
    }
    return result;
}

async function checkFileExists(fileName) {
    try {
        const response = await fetch(`../custom/fonts/${fileName}`);
        return response.ok;
    } catch (error) {
        return false;
    }
}

function getRandomColor() {
    const randomColor = Math.floor(Math.random() * 0xffffff).toString(16);
    return `#${randomColor.padStart(6, '0')}`;
}

function cleanAndFlattenCSS(cssText) {
    const noComments = cssText.replace(/\/\*[\s\S]*?\*\//g, '');
    const blocks = [...noComments.matchAll(/([^{]+)\{([^}]+)\}/g)];
    const cleaned = blocks.map(match => {
        const selector = match[1].trim();
        const properties = match[2].replace(/\s+/g, ' ').trim();
        return `${selector} { ${properties} }`;
    });
    return cleaned.join('\n');
}

function parseCssBlock(cssString) {
    const properties = cssString.split(';').filter(Boolean);
    const styleObj = {};

    for (let prop of properties) {
        const [key, ...valParts] = prop.split(':');
        if (!key || !valParts.length) continue;

        const property = key.trim();
        const value = valParts.join(':').trim();
        styleObj[property] = value;
    }
    return styleObj;
}

function stringifyCssBlock(styleObj) {
    return Object.entries(styleObj)
        .map(([key, value]) => `${key}: ${value};`)
        .join(' ');
}

function checkNameValidity(name, existingNames, currentName) {
    const trimmed = name.trim();
    if (trimmed === "") {
        return { valid: false, reason: "Name cannot be empty" };
    }

    const validNameRegex = /^[a-zA-Z0-9 _-]+$/;
    if (!validNameRegex.test(trimmed)) {
        return { valid: false, reason: "Name contains invalid characters" };
    }

    const lowerName = trimmed.toLowerCase();
    const isDuplicate = (existingNames.some(existing => existing.toLowerCase() === lowerName) && trimmed !== currentName);
    if (isDuplicate) {
        return { valid: false, reason: "Name already exists" };
    }

    return { valid: true };
}

function switchPaletteSectionLabels(isUnderEdit) {
    const title = createPaletteSection.querySelector("h2");
    if (isUnderEdit) {
        title.textContent = "Update Palette";
        paletteClear.textContent = "Cancel";
        paletteSave.textContent = "Update";
    } else {
        title.textContent = "Create New Palette";
        paletteClear.textContent = "Clear";
        paletteSave.textContent = "Save Palette";
    }
}

function switchFontSectionLabels(isUnderEdit) {
    const title = createFontSection.querySelector("h2");
    if (isUnderEdit) {
        title.textContent = "Update Font";
        fontClear.textContent = "Cancel";
        fontSave.textContent = "Update";
    } else {
        title.textContent = "Add New Font";
        fontClear.textContent = "Clear";
        fontSave.textContent = "Add Font";
    }
}


function doPost(type, data, action) {
    if ([type, data, action].every(strVal => strVal.trim() !== "")) {
        let file = "";
        switch (type) {
            case "settings":
                file = "global.txt";
                break;
            case "styles":
                file = "customStyles.css";
                break;
            case "translations":
                file = translationFiles[languageDropdown.value];
                break;
            default:
                throw new Error("Invalid type provided for file update");
        }

        fetch(`CustomEditor/backend.jsp`, {
            method: "POST",
            headers: {
                "Content-Type": "application/x-www-form-urlencoded"
            },
            body: `file=custom/${file}&type=${type}&data=${encodeURIComponent(decodeHtmlEntities(data))}&action=${action}&ts=${new Date().getTime()}`
        }).catch(error => {
            throw new Error("Error updating file -", error);
        });
    }
}

function createColorPickerUI(color = null) {
    const colorPickerNode = parser.parseFromString(colorPickerSrc, "text/html").body.firstChild;
    const colorPicker = colorPickerNode.querySelector(".color-picker");
    const removeColorButton = colorPickerNode.querySelector(".remove-color");
    colorPickerContainer.children[0].insertAdjacentElement("afterend", colorPickerNode);

    if (color) {
        // Normalize color code
        colorPicker.style.color = color;
        colorPicker.value = rgbToHex(window.getComputedStyle(colorPicker).color);
        colorPicker.style.color = "";
    } else {
        colorPicker.value = getRandomColor();
    }

    removeColorButton.addEventListener("click", (e) => {
        e.target.closest(".color-picker-wrapper").remove();
    });
}

function createPaletteUI(name, colors, isActive, isDefault) {
    const paletteNode = parser.parseFromString(getPaletteNodeSrc(generateUniqueId()), "text/html");
    const editPalette = paletteNode.querySelector(".palette-edit");
    const deletePalette = paletteNode.querySelector(".palette-delete");
    const activeSwitch = paletteNode.querySelector(".saved-palette .palette-active");
    const defaultSwitch = paletteNode.querySelector(".saved-palette .palette-default");
    paletteNode.querySelector(".palette-label").textContent = name;
    paletteNode.body.firstChild.setAttribute("data-palette-name", name);

    colors.forEach((color) => {
        const colorTile = document.createElement("li");
        colorTile.className = "color-tile";
        colorTile.style.backgroundColor = color;
        colorTile.title = color;
        paletteNode.querySelector(".palette-colors").appendChild(colorTile);
    });

    if (isActive) {
        activeSwitch.checked = true;
    } else {
        defaultSwitch.disabled = true;
        defaultSwitch.parentElement.title = "Inactive palettes cannot be set as default";
    }

    if (isDefault) {
        defaultSwitch.checked = true;
        activeSwitch.disabled = true;
        activeSwitch.parentElement.title = "Default palette cannot be inactive";
    }

    activeSwitch.addEventListener("change", (e) => {
        unsavedChanges = true;
        let currentPalette = paletteList.find((palette) => palette.name === name);
        let activePalettes = paletteList.filter((palette) => palette.isActive).map(palette => palette.name);
        // Only allow activating/deactivating palettes that are not default
        if (!currentPalette.isDefault) {
            if (e.target.checked) {
                currentPalette.isActive = true;
                activePalettes.push(name);
                defaultSwitch.disabled = false;
                defaultSwitch.parentElement.title = "";
            } else {
                currentPalette.isActive = false;
                activePalettes = activePalettes.filter(paletteName => paletteName !== name);
                defaultSwitch.disabled = true;
                defaultSwitch.parentElement.title = "Inactive palettes cannot be set as default";
            }
            settingsData["palette.list"] = activePalettes;
        }
    });

    defaultSwitch.addEventListener("change", (e) => {
        unsavedChanges = true;
        let currentDefault = paletteList.find((palette) => palette.name === name);
        let previousDefault = paletteList.find((palette) => palette.isDefault);
        // Only allow setting a default palette if it is active
        if (e.target.checked && currentDefault.isActive) {
            currentDefault.isDefault = true;
            activeSwitch.disabled = true;
            activeSwitch.parentElement.title = "Default palette cannot be inactive";
            settingsData["palette.defaultPalette"] = name;

            if (previousDefault) {
                let previousDefaultNode = document.querySelector(`[data-palette-name="${previousDefault.name}"]`);
                let previousActiveSwitch = previousDefaultNode.querySelector(".palette-active");
                previousDefault.isDefault = false;
                previousActiveSwitch.disabled = false;
                previousActiveSwitch.parentElement.title = "";
            }
        }
    });

    editPalette.addEventListener("click", (e) => {
        editingPalette.status = true;
        editingPalette.name = name;
        paletteName.value = name;
        clearPaletteColors();
        switchPaletteSectionLabels(true);
        createPaletteSection.scrollIntoView({ behavior: "smooth", block: "center" });
        [...colors].reverse().forEach((color) => {
            createColorPickerUI(color);
        });
    });

    deletePalette.addEventListener("click", (e) => {
        unsavedChanges = true;
        clearPaletteEdit();
        let currentPalette = document.querySelector(`[data-palette-name="${name}"]`);
        currentPalette.remove();
        delete settingsData[`palette.${name}`];
        paletteList.splice(0, paletteList.length, ...paletteList.filter((palette) => {
            return palette.name !== name;
        }));
        if (settingsData["palette.defaultPalette"] === name) {
            delete settingsData["palette.defaultPalette"];
        }
        const activePalettes = paletteList.filter(palette => palette.isActive).map(palette => palette.name);
        if (!activePalettes.length) {
            delete settingsData["palette.list"];
        } else {
            settingsData["palette.list"] = activePalettes;
        }
        if (!paletteList.length) {
            savedPalettesSection.classList.remove("visible");
        }
    });

    document.querySelector(".palette-list").prepend(paletteNode.body.firstChild);
}

function createCustomFontsUI(name, isDefault) {
    const fontNode = parser.parseFromString(getFontNodeSrc(generateUniqueId()), "text/html");
    const fontLabel = fontNode.querySelector(".custom-font-label");
    const editFont = fontNode.querySelector(".font-edit");
    const deleteFont = fontNode.querySelector(".font-delete");
    const defaultSwitch = fontNode.querySelector(".font-default");
    fontLabel.textContent = name;

    if (isDefault) {
        defaultSwitch.checked = true;
    }

    defaultSwitch.addEventListener("change", (e) => {
        if (e.target.checked) {
            unsavedChanges = true;
            settingsData["font.defaultFont"] = name;
            settingsData["font.customFonts"] = fontList;
            stylesData[chartContainerClass]["font-family"] = generateFontFamilyString(fontList, name);
        }
    });

    editFont.addEventListener("click", (e) => {
        editingFont.status = true;
        editingFont.name = name;
        fontName.value = name;
        const fontFaceString = fontFaceDefinitions.find((definition) => definition.includes(name)) || "";
        const fontFileMatch = fontFaceString.match(/src\s*:\s*url\(['"]?([^'")]+)['"]?\)/);
        const fileName = fontFileMatch ? fontFileMatch[1].split("fonts/")[1] : null;
        fontFileName.value = fileName || "";
        switchFontSectionLabels(true);
        createFontSection.scrollIntoView({ behavior: "smooth", block: "center" });
    });

    deleteFont.addEventListener("click", (e) => {
        unsavedChanges = true;
        clearFontEdit();
        // const isUnsavedFont = fontUploadData.find(fontInfo => fontInfo.fontName === name);
        // if (isUnsavedFont) {
        //     fontUploadData.filter(fontInfo => fontInfo.fontName !== name);
        // } else {
        //     fontDeleteData.push(settingsData[`font.${name}`]);
        // }

        fontFaceDefinitions.splice(0, fontFaceDefinitions.length, ...fontFaceDefinitions.filter((definition) => {
            return !definition.includes(name);
        }));

        document.querySelector(`[data-custom-font="${name}"]`).remove();
        // delete settingsData[`font.${name}`];

        fontList.splice(0, fontList.length, ...fontList.filter((customFont) => {
            return customFont !== name;
        }));

        if (settingsData["font.defaultFont"] === name) {
            delete settingsData["font.defaultFont"];
        }

        if (!fontList.length) {
            delete settingsData["font.customFonts"];
            delete stylesData[chartContainerClass]["font-family"];
            if (Object.keys(stylesData[chartContainerClass]).length === 0) {
                stylesData[chartContainerClass] = null;
            }
            savedFontsSection.classList.remove("visible");
        } else {
            settingsData["font.customFonts"] = fontList;
            stylesData[chartContainerClass]["font-family"] = generateFontFamilyString(fontList, settingsData["font.defaultFont"]);
        }
    });

    fontNode.body.firstChild.setAttribute("data-custom-font", name);
    document.querySelector(".font-list").prepend(fontNode.body.firstChild);
}

function createTranslationsUI(language) {
    fetch(`CustomEditor/backend.jsp?file=custom/${translationFiles[language]}&type=translation&ts=${new Date().getTime()}`)
    .then((response) => response.json()).then(({data}) => {
        if (!data) {
            if (document.querySelector(".translation-fields")) {
                document.querySelector(".translation-fields").remove();
            }
            throw new Error("Translation file not found or cannot be read");
        }

        const translationLines = data.split("\n");
        const translations = {};

        translationLines.sort((a, b) => {
            return a.localeCompare(b);
        }).forEach(line => {
            const [key, value] = line.split('=');
            if (key && value) {
                translations[key.trim()] = value.trim();
            }
        });

        translationsData = {...translations};
        const wrapper = document.createElement("section");
        wrapper.className = "translation-fields";

        const translationSelectorNode = parser.parseFromString(translationSelectorSrc, "text/html").body.firstChild;
        const translationEditorNode = parser.parseFromString(translationEditorSrc, "text/html").body.firstChild;
        const translationSelect = translationSelectorNode.querySelector(".select-translation");
        const translationInput = translationEditorNode.querySelector(".translated-text");
        translationInput.value = Object.values(translationsData)[0];
        translationInput.setAttribute("data-translation-key", Object.keys(translationsData)[0]);

        translationInput.addEventListener("change", (e) => {
            if (e.target.value.trim() !== "") {
                unsavedChanges = true;
                translationsData[e.target.getAttribute("data-translation-key")] = e.target.value;
            }
        });

        translationSelect.addEventListener("change", (e) => {
            translationInput.value = translationsData[e.target.value];
            translationInput.setAttribute("data-translation-key", e.target.value);
            if (e.target.value.length > 40) {
                translationInput.classList.add("fullwidth");
            } else {
                translationInput.classList.remove("fullwidth");
            }
        });

        Object.entries(translationsData).forEach(([original, translated]) => {
            const translationSelectOption = document.createElement("option");
            translationSelectOption.value = original;
            translationSelectOption.title = original;

            if (original.length > 20) {
                translationSelectOption.textContent = original.slice(0, 19) + "...";
            } else {
                translationSelectOption.textContent = original;
            }

            translationSelect.appendChild(translationSelectOption);
        });

        wrapper.appendChild(translationSelectorNode);
        wrapper.appendChild(translationEditorNode);

        // When re-rendering during language change, clear previous fields
        if (document.querySelector(".translation-fields")) {
            document.querySelector(".translation-fields").remove();
        }

        translationsContainer.appendChild(wrapper);
        blockSave.blockedPages = blockSave.blockedPages.filter(page => page !== "translations");
    }).catch((error) => {
        blockSave.blockedPages.push("translations");
        blockSave.reason = `An error occurred while loading ${translationFiles[language]} file`;
        console.error("Error loading translations:", error);
        alert(`An error occurred while loading ${translationFiles[language]} file. Please check if the file exists and try again.`);
    });
}

// ==================== Events handling ==================== //

window.onload = (async (e) => {
    try {
        // Load and fill setting and style values (palette, font and others)
        const settingsResponse = await fetch(`CustomEditor/backend.jsp?file=custom/global.txt&type=setting&ts=${new Date().getTime()}`);
        const settingsJson = await settingsResponse.json();
        const stylesResponse = await fetch(`CustomEditor/backend.jsp?file=custom/customStyles.css&type=style&ts=${new Date().getTime()}`);
        const stylesJson = await stylesResponse.json();

        const rawSettingsText = settingsJson.data || "";
        const settingLines = rawSettingsText.split(/\r?\n/).map(line => line.trim()).filter(line => line && !line.startsWith('#'));
        const rawStylesText = stylesJson.data || "";
        const styleLines = cleanAndFlattenCSS(rawStylesText).split(/\r?\n/);

        settingLines.forEach(line => {
            const [key, value] = line.split('=');
            if (key && value) {
                const collectionKeys = ["font.customFonts", "palette.list", "shortFormatSymbols", "accessControlLevels"];
                if (collectionKeys.includes(key.trim())) {
                    settingsData[key.trim()] = value.split(",");
                } else {
                    settingsData[key.trim()] = value.trim();
                }
            }
        });

        // Handle custom palettes
        const defaultPaletteLine = extractLineFromRawText(rawSettingsText, "palette.defaultPalette");
        const activePalettesLine = extractLineFromRawText(rawSettingsText, "palette.list");
        const defaultPalette = defaultPaletteLine ? defaultPaletteLine.split("=")[1].trim() : null;
        const activePalettes = activePalettesLine ? activePalettesLine.split("=")[1].split(",").map(s => s.trim()) : [];
        const palettes = settingLines.filter(line => {
            return line.startsWith('palette.') &&
                !line.startsWith('palette.list') &&
                !line.startsWith('palette.defaultPalette')
        }).map(line => {
            const [key, value] = line.split('=').map(string => string.trim());
            settingsData[key] = value.split(",");
            const name = key.split('.')[1];
            const colors = value.split(',').map(color => color.trim());
            return {
                name: name,
                colors: colors,
                isActive: activePalettes.includes(name),
                isDefault: name === defaultPalette
            };
        });

        paletteList.push(...palettes);
        [...paletteList].reverse().forEach((palette) => {
            createPaletteUI(palette.name, palette.colors, palette.isActive, palette.isDefault);
        });

        if (paletteList.length) {
            savedPalettesSection.classList.add("visible");
        }

        // Fill other global.txt settings
        if (settingsData["chart.animation"]) {
            chartAnimations.checked = settingsData["chart.animation"] === "1" ? true : false;
        }

        if (settingsData["enablePrivilegeForEditor"] && settingsData["enablePrivilegeForEditor"] === "1") {
            enablePrivilege.checked = true;
        } else {
            useDefaultPrivilege.disabled = true;
            useCustomPrivilege.disabled = true;
            customPrivilegeValue.disabled = true;
        }

        if (settingsData["checkPrivilegeForPropertiesEditor"] && settingsData["checkPrivilegeForPropertiesEditor"] !== "125") {
            useCustomPrivilege.checked = true;
            customPrivilegeValue.value = settingsData["checkPrivilegeForPropertiesEditor"];
        } else {
            useDefaultPrivilege.checked = true;
            customPrivilegeValue.disabled = true;
        }

        if (settingsData["security.allowURLLinks"]) {
            linksAccess.checked = settingsData["security.allowURLLinks"] === "1" ? true : false;
        }

        if (settingsData["statePresentation.contextMenu"]) {
            contextMenuAccess.checked = settingsData["statePresentation.contextMenu"] === "1" ? true : false;
        }

        if (settingsData["rtlSupport"]) {
            rtlSupport.checked = settingsData["rtlSupport"] === "true";
        }

        if (settingsData["metricNegativeValueFormat"]) {
            negativeValueFormat.value = settingsData["metricNegativeValueFormat"];
        }

        if (settingsData["seriesLimit"]) {
            seriesColorsLimit.value = settingsData["seriesLimit"];
        }

        if (settingsData["shortFormatSymbols"]) {
            metricSuffixes.forEach((metricSuffix, index) => {
                metricSuffix.value = settingsData["shortFormatSymbols"][index];
            });
        }

        if (settingsData["accessControlLevels"]) {
            accessControlLevels.forEach((accessLevel) => {
                const accessLevelCode = accessLevel.getAttribute("data-access-level");
                accessLevel.checked = settingsData["accessControlLevels"].includes(accessLevelCode);
            });
        }

        // Handle custom fonts
        const customFontsLine = extractLineFromRawText(rawSettingsText, "font.customFonts");
        const defaultFontLine = extractLineFromRawText(rawSettingsText, "font.defaultFont");
        const customFonts = customFontsLine ? customFontsLine.split("=")[1].split(",").map(s => s.trim()) : [];
        const defaultFont = defaultFontLine ? defaultFontLine.split("=")[1].trim() : null;
        fontList.push(...customFonts);

        const userDefinedCss = styleLines.filter((line) => {
            if (line.includes("@font-face")) {
                fontFaceDefinitions.push(line);
                return false;
            }
            if (line.includes(chartContainerClass)) {
                const content = line.match(/\{([^}]*)\}/);
                stylesData[chartContainerClass] = parseCssBlock(content[1].trim());
                return false;
            }
            return true;
        });
        stylesData["userDefinedCss"] = userDefinedCss;

        if (stylesData?.[chartContainerClass]?.["font-family"]) {
            const cleanedFontlist = stylesData[chartContainerClass]["font-family"].replace(/\s*!important\s*$/i, '');
            const stylesheetFonts = cleanedFontlist ? cleanedFontlist.split(',').map(f => stripQuotes(f.trim())).filter(Boolean) : [];

            // Check if customStyles.css contains any fonts that are not present in global.txt
            const fontDifference = difference(new Set(stylesheetFonts), new Set(fontList));
            if (fontDifference.size > 0) {
                fontList.push(...Array.from(fontDifference));
            }
        }

        if (fontList.length > 0) {
            [...fontList].reverse().forEach((customFont) => {
                createCustomFontsUI(customFont, (defaultFont === customFont));
            });
            savedFontsSection.classList.add("visible");
            if (!stylesData[chartContainerClass]) {
                stylesData[chartContainerClass] = {};
            }
            stylesData[chartContainerClass]["font-family"] = generateFontFamilyString(fontList, defaultFont);
            settingsData["font.customFonts"] = fontList;
        }

        // stylingComponentKeys.forEach(key => {
        //     const regex = new RegExp(`[^{}]*${key}[^{}]*\\{([^}]*)\\}`, 'i');
        //     const match = rawStylesText.match(regex);
        //     document.querySelectorAll(".styling-component").forEach(element => {
        //         const selectorString = element.getAttribute("data-css-selectors");
        //         if (selectorString.includes(key)) {
        //             element.value = match[1].trim();
        //             stylesData[generateStyleSelector(element)] = match[1].trim();
        //         }
        //     });
        // });

        // Fill translation values
        const defaultLanguage = languageDropdown.value;
        createTranslationsUI(defaultLanguage);

        // Set tab navigation logic
        document.querySelectorAll(".tab-navigation").forEach((tabNav) => {
            tabNav.addEventListener("click", (e) => {
                let allowSwitch = true;
                if (unsavedChanges) {
                    allowSwitch = confirm("You have unsaved changes. Proceed?");
                }

                if (allowSwitch) {
                    unsavedChanges = false;
                    document.querySelectorAll(".tab-navigation").forEach(tabNavAlt => tabNavAlt.classList.remove("active"));
                    e.target.classList.add("active");
                    document.querySelectorAll(".tab").forEach(tab => tab.classList.remove("visible"));
                    document.querySelector(`.tab.${e.target.getAttribute("data-tab-key")}`).classList.add("visible");
                }
            });
        });
    } catch (error) {
        blockSave.blockedPages.push("settings", "translations");
        blockSave.reason = "An error occurred while loading initial data";
        console.error("Error loading initial data:", error);
        alert("An error occurred while loading the initial data. Please try again later.");
    }
});

window.addEventListener('beforeunload', function (event) {
    if (unsavedChanges) {
        event.preventDefault();
        event.returnValue = ''; // This triggers the browser dialog
    }
});

document.querySelectorAll("form").forEach((form) => {
    form.addEventListener("submit", (e) => {
        e.preventDefault();
    });
});

addColor.addEventListener("click", (e) => {
    e.preventDefault();
    createColorPickerUI();
});

paletteClear.addEventListener("click", (e) => {
    clearPaletteEdit();
});

paletteSave.addEventListener("click", (e) => {
    const name = paletteName.value.trim();
    const isValidName = checkNameValidity(name, paletteList.map(palette => palette.name), editingPalette.name);
    const hasColors = colorPickerContainer.querySelectorAll(".color-picker").length > 1;
    if (!isValidName.valid) {
        alert(isValidName.reason);
        return;
    } else if (!hasColors) {
        alert("At least one color is required in the palette");
        return;
    } else {
        unsavedChanges = true;
        const colorPickersList = colorPickerContainer.querySelectorAll(".color-picker");
        const colors = Array.from(colorPickersList).map((colorPicker => colorPicker.value));

        // Check if editing existing palette or adding new palette
        if (editingPalette.status) {
            // Remove outdated palette
            delete settingsData[`palette.${editingPalette.name}`];
            document.querySelector(`[data-palette-name="${editingPalette.name}"]`).remove();

            // Update with latest palette data
            const paletteUnderEdit = paletteList.find((palette) => palette.name === editingPalette.name);
            paletteUnderEdit.name = name;
            paletteUnderEdit.colors = colors;
            createPaletteUI(name, colors, paletteUnderEdit.isActive, paletteUnderEdit.isDefault);
            if (settingsData["palette.defaultPalette"] === editingPalette.name) {
                settingsData["palette.defaultPalette"] = name;
            }

            editingPalette.status = false;
            editingPalette.name = "";
            switchPaletteSectionLabels(false);
        } else {
            paletteList.push({name: name, colors: colors, isActive: false, isDefault: false});
            createPaletteUI(name, colors, false, false);
            if (!savedPalettesSection.classList.contains("visible")) {
                savedPalettesSection.classList.add("visible");
            }
        }

        settingsData[`palette.${name}`] = colors;
        paletteName.value = "";
        clearPaletteColors();
        // Scroll the newly added/edited palette into the view
        savedPalettesSection.querySelector(".saved-palette").scrollIntoView({ behavior: "smooth", block: "center" });
    }
});

languageDropdown.addEventListener("change", (e) => {
    let allowSwitch = true;
    const currentLanguage = document.querySelector(".translation-language");
    if (unsavedChanges) {
        allowSwitch = confirm("You have unsaved changes. Proceed?");
    }
    if (allowSwitch) {
        unsavedChanges = false;
        currentLanguage.textContent = e.target.value;
        createTranslationsUI(e.target.value);
    } else {
        languageDropdown.value = currentLanguage.textContent; // Reset to previous language
    }
});

// fontUpload.addEventListener("click", (e) => {
//     const file = fontFile.files[0];
//     const name = fontName.value.trim();

//     if (!file) {
//         alert("No file uploaded");
//         return;
//     }

//     if (!name) {
//         alert("Font family name required");
//         return;
//     }

//     fontUploadData.push({
//         fontName: name,
//         fontFile: file,
//         fileName: file.name
//     });

//     // Clean-up inputs and create new custom font's UI
//     fontFile.value = "";
//     fontName.value = "";
//     fontList.push(name);
//     createCustomFontsUI(name, false);
//     unsavedChanges = true;

//     if (!savedFontsSection.classList.contains("visible")) {
//         savedFontsSection.classList.add("visible");
//     }

//     // Update data collections
    // fontFaceDefinitions.push(`@font-face { font-family: '${name}'; src: url('./fonts/${file.name}'); }`);
//     stylesData[chartContainerClass]["font-family"] = generateFontFamilyString(fontList, settingsData["font.defaultFont"]);
//     settingsData[`font.${name}`] = file.name;
//     settingsData["font.customFonts"] = fontList;
// });

fontClear.addEventListener("click", (e) => {
    clearFontEdit();
});

fontSave.addEventListener("click", (e) => {
    const name = fontName.value;
    const file = fontFileName.value.trim();

    const isValidName = checkNameValidity(name, fontList, editingFont.name);
    if (!isValidName.valid) {
        alert(isValidName.reason);
        return;
    }

    function addAndUpdateFont() {
        fontName.value = "";
        fontFileName.value = "";
        unsavedChanges = true;

        // Check if editing existing font or adding new font
        if (editingFont.status) {
            // Remove outdated font data
            fontList.splice(0, fontList.length, ...fontList.filter(font => {
                return font !== editingFont.name;
            }));
            fontFaceDefinitions.splice(0, fontFaceDefinitions.length, ...fontFaceDefinitions.filter((definition) => {
                return !definition.includes(editingFont.name);
            }));
            document.querySelector(`[data-custom-font="${editingFont.name}"]`).remove();

            // Add latest font data
            fontList.push(name);
            createCustomFontsUI(name, settingsData["font.defaultFont"] === editingFont.name);
            if (settingsData["font.defaultFont"] === editingFont.name) {
                settingsData["font.defaultFont"] = name;
            }

            editingFont.status = false;
            editingFont.name = "";
            switchFontSectionLabels(false);
        } else {
            fontList.push(name);
            createCustomFontsUI(name, false);
            if (!savedFontsSection.classList.contains("visible")) {
                savedFontsSection.classList.add("visible");
            }
        }
        stylesData[chartContainerClass]["font-family"] = generateFontFamilyString(fontList, settingsData["font.defaultFont"]);
        // settingsData[`font.${name}`] = file;
        settingsData["font.customFonts"] = fontList;
        // Scroll the newly added/edited font into the view
        savedFontsSection.querySelector(".saved-font").scrollIntoView({ behavior: "smooth", block: "center" });
    }

    if (!file) {
        const saveGenericFont = confirm("No font file provided. Add this as a system-dependent font family instead?");
        if (saveGenericFont) {
            addAndUpdateFont();
        }
        return;
    }

    checkFileExists(file).then(exists => {
        if (exists) {
            addAndUpdateFont();
            fontFaceDefinitions.push(`@font-face { font-family: '${name}'; src: url('./fonts/${file}'); }`);
        } else {
            alert("File not found. Please check that the file name is correct (with extension included) and placed under 'custom/fonts' folder.");
            return;
        }
    });
});

enablePrivilege.addEventListener("change", (e) => {
    unsavedChanges = true;
    if (e.target.checked) {
        useDefaultPrivilege.disabled = false;
        useCustomPrivilege.disabled = false;
        settingsData["enablePrivilegeForEditor"] = "1";

        if (useCustomPrivilege.checked) {
            customPrivilegeValue.disabled = false;
        }

        if (customPrivilegeValue.value.trim() !== "") {
            settingsData["checkPrivilegeForPropertiesEditor"] = customPrivilegeValue.value.trim();
        } else {
            settingsData["checkPrivilegeForPropertiesEditor"] = "125";
        }
    } else {
        useDefaultPrivilege.disabled = true;
        useCustomPrivilege.disabled = true;
        customPrivilegeValue.disabled = true;
        settingsData["enablePrivilegeForEditor"] = "0";
        delete settingsData["checkPrivilegeForPropertiesEditor"];
    }
});

useDefaultPrivilege.addEventListener("change", (e) => {
    if (e.target.checked) {
        customPrivilegeValue.disabled = true;
        settingsData["checkPrivilegeForPropertiesEditor"] = "125";
    }
});

useCustomPrivilege.addEventListener("change", (e) => {
    if (e.target.checked) {
        customPrivilegeValue.disabled = false;
    }
});

customPrivilegeValue.addEventListener("change", (e) => {
    if (e.target.value.trim() !== "") {
        settingsData["checkPrivilegeForPropertiesEditor"] = e.target.value.trim();
    }
});

chartAnimations.addEventListener("change", (e) => {
    unsavedChanges = true;
    const value = e.target.checked ? "1" : "0";
    settingsData["chart.animation"] = value;
});

linksAccess.addEventListener("change", (e) => {
    unsavedChanges = true;
    const value = e.target.checked ? "1" : "0";
    settingsData["security.allowURLLinks"] = value;
});

contextMenuAccess.addEventListener("change", (e) => {
    unsavedChanges = true;
    const value = e.target.checked ? "1" : "0";
    settingsData["statePresentation.contextMenu"] = value;
});

rtlSupport.addEventListener("change", (e) => {
    unsavedChanges = true;
    const value = e.target.checked ? true : false;
    settingsData["rtlSupport"] = value;
});

negativeValueFormat.addEventListener("change", (e) => {
    unsavedChanges = true;
    settingsData["metricNegativeValueFormat"] = e.target.value;
});

seriesColorsLimit.addEventListener("change", (e) => {
    unsavedChanges = true;
    let value = e.target.value;
    if (Number(value) < Number(e.target.min)) {
        value = 50;
    }
    settingsData["seriesLimit"] = value;
});

accessControlLevels.forEach((accessControlLevel) => {
    accessControlLevel.addEventListener("change", (e) => {
        unsavedChanges = true;
        const accessLevel = e.target.getAttribute("data-access-level");
        const hasFullControl = function() {
            const directFullControl = (e.target.checked) && (accessLevel === "fullcontrol");
            const indirectFullControl = settingsData["accessControlLevels"].sort((a, b) => a.localeCompare(b)).join(",") === accessControlLevelCodes.join(",");
            return directFullControl || indirectFullControl;
        };

        if (!settingsData["accessControlLevels"]) {
            settingsData["accessControlLevels"] = [];
        }

        if (e.target.checked && accessLevel !== "fullcontrol") {
            settingsData["accessControlLevels"].push(accessLevel);
        }

        if (hasFullControl()) {
            accessControlLevels.forEach((level) => {
                level.checked = true;
            });
            settingsData["accessControlLevels"].splice(0, settingsData["accessControlLevels"].length, "fullcontrol");
        } else {
            // Empty accessControlLevels before filling it
            settingsData["accessControlLevels"].splice(0, settingsData["accessControlLevels"].length);

            if (accessLevel === "fullcontrol") {
                // Revoke all access levels if full control is unchecked
                accessControlLevels.forEach((level) => {
                    if (level.getAttribute("data-access-level") !== "fullcontrol") {
                        level.checked = false;
                    }
                });                
            } else {
                accessControlLevels.forEach((level) => {
                    if (level.getAttribute("data-access-level") === "fullcontrol") {
                        level.checked = false;
                    } else if (level.checked) {
                        settingsData["accessControlLevels"].push(level.getAttribute("data-access-level"));
                    }
                });
            }
        }

        if (!settingsData["accessControlLevels"].length) {
            delete settingsData["accessControlLevels"];
        }
    });
});

metricSuffixes.forEach((metricSuffix, index) => {
    metricSuffix.addEventListener("change", (e) => {
        const value = e.target.value;
        if (value.trim() !== "") {
            unsavedChanges = true;
            metricSuffixValues[index] = value;
            settingsData["shortFormatSymbols"] = metricSuffixValues;
        }
    });
});

chartStylingComponents.forEach((stylingComponent) => {
    stylingComponent.addEventListener("change", (e) => {
        const value = e.target.value;
        const selectorString = generateStyleSelector(e.target);

        if (value.trim() !== "") {
            const el = document.createElement("div");
            el.style.cssText = value.trim();
            if (el.style.length > 0) {
                unsavedChanges = true;
                stylesData[selectorString] = value.trim();
            } else {
                alert("Invalid CSS");
            }
        }
    });
});

saveChanges.forEach(saveChangesBtn => {
    saveChangesBtn.addEventListener("click", (e) => {
        if (e.detail === 0) {
            return;
        }
        const formType = e.target.getAttribute("data-form-type");
        const message = "Generated using Chart Customization UI";
        switch (formType) {
            case "settings":
                if (blockSave.blockedPages.includes("settings")) {
                    alert(`Cannot save changes: ${blockSave.reason}`);
                    return;
                }
                // Update settings
                const settings = Object.entries(settingsData).map(setting => setting.join("="));
                settings.unshift(`#${message}`);
                doPost("settings", settings.join("\n"), "update");

                // Upload fonts
                // if (fontUploadData.length) {
                //     fontUploadData.forEach((fontInfo) => {
                //         const urlString = `fontName=${encodeURIComponent(fontInfo.fontName)}&fileName=${fontInfo.fileName}`;
                //         const formData = new FormData();
                //         formData.append('fontFile', fontInfo.fontFile);
                //         fetch(`CustomEditor/backend.jsp?file=custom/global.txt&type=font&action=upload&${urlString}`, {
                //             method: 'POST',
                //             body: formData
                //         })
                //         .then((response) => response.json())
                //         .catch((error) => {
                //             throw new Error("Font upload failed - ", error);
                //         });
                //     });
                //     fontUploadData = [];
                // }

                // Delete fonts
                // if (fontDeleteData.length) {
                //     fontDeleteData.forEach((fileName) => {
                //         fetch(`CustomEditor/backend.jsp?file=custom/global.txt&type=font&action=delete&fileName=${fileName}`);
                //     });
                //     fontDeleteData = [];
                // }
                
                // "break" keyword deliberately omitted, need fallthrough to styles case
            case "styles":
                // Update styles
                // const styles = Object.entries(stylesData).map(style => {
                //     const [key, value] = style;
                //     return `${key} { ${value} }`;
                // });
                const styles = [];
                if (stylesData[chartContainerClass]) {
                    styles.push(`${chartContainerClass} { ${stringifyCssBlock(stylesData[chartContainerClass])} }`);
                }
                styles.push(...stylesData["userDefinedCss"]);
                styles.unshift(...fontFaceDefinitions);
                styles.unshift(`/* ${message} */`);
                doPost("styles", styles.join("\n"), "update");
                break;
            case "translations":
                if (blockSave.blockedPages.includes("translations")) {
                    alert(`Cannot save changes: ${blockSave.reason}`);
                    return;
                }
                // Update translations
                const translations = Object.entries(translationsData).map(translation => translation.join("=")).join("\n");
                doPost("translations", translations, "update");
                break;
            default:
                throw new Error("Unknown form type given");
        }

        unsavedChanges = false;
        alert("Successfully applied changes");
    });
});

// For setting styles to sticky header
const formHeaders = document.querySelectorAll(".form-header");
formHeaders.forEach((formHeader) => {
    const observer = new IntersectionObserver(([entry]) => {
        if (entry.isIntersecting) {
            entry.target.classList.add("is-sticky");
        } else {
            entry.target.classList.remove("is-sticky");
        }
    }, {
        root: formHeader.parentElement,
        rootMargin: '-1px 0px 0px 0px',
        threshold: [1],
      });
    
    observer.observe(formHeader);
});
