let buildInfo = {};
let customFileInfo = {}; 
let errors = {};
(async function() {
    const statusItems = [
        {
            id: "compatibility",
            label: "Version Compatibility",
            endPoint: "VersionCompatibility.jsp"
        },
        {
            id: "version",
            label: "Deployment or Build Structure",
            endPoint: "BuildStructureValidation.jsp",
        },
        {
            id: "Integrity",
            label: "File Integrity Check",
            endPoint: "FilesIntegrityCheck.jsp",
        },
        {
            id: "customFiles",
            label: "Custom Files Validation",
            endPoint: "CustomFilesValidation.jsp",
            callback: validateCustomProperties,
        },
        {
            id: "license",
            label: "License Verification",
            endPoint: "LicenseValidation.jsp"
        },
        {
            id: "mobConfig",
            label: "Mobile Config Check",
            endPoint: "Integrity",
            callback: validateMobileConfig,
        },
    ];

    const parser = new DOMParser();
    const loadingIconSrc = `<svg class="loading" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#9E9E9E"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M6 2v6h.01L6 8.01 10 12l-4 4 .01.01H6V22h12v-5.99h-.01L18 16l-4-4 4-3.99-.01-.01H18V2H6zm10 14.5V20H8v-3.5l4-4 4 4zm-4-5l-4-4V4h8v3.5l-4 4z"/></svg>`;
    const passIconSrc = `<svg class="pass" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#28a745"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>`;
    const warningIconSrc = `<svg class="warning" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#FB8C00"><path d="M0 0h24v24H0z" fill="none"/><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></svg>`;
    const failIconSrc = `<svg class="fail" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#D32F2F"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>`;
    const expandIconSrc = `<svg class="expand" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#24292e"><path d="M0 0h24v24H0z" fill="none"/><path d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z"/></svg>`;
    const resolutionIconSrc = `<svg class="resolution" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#FBC02D"><path d="M0 0h24v24H0z" fill="none"/><path d="M9 21h6a1 1 0 0 0 1-1v-1H8v1a1 1 0 0 0 1 1zm3-20a7 7 0 0 0-4 12.8V17a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1v-3.2A7 7 0 0 0 12 1zm2.93 10.36-.93.65V16h-4v-3.99l-.93-.65A5 5 0 1 1 14.93 11.36z"/></svg>`;

    const statusNodeSrc = `
        <li class="status-item">
            <span class="status-details">
                <b class="status-label"></b>
                <p class="status-flag">In Progress</p>
            </span>
            <span class="status-icon"></span>
        </li>
    `;

    const errorNodeSrc = `
        <details class="error-details">
            <summary></summary>
            <ul class="error-list"></ul>
        </details>
    `;

    const resolveNodeSrc = `
        <details class="resolve-details">
            <summary></summary>
            <ul class="resolve-list"></ul>
        </details>
    `
    const resolveDetailNodeSrc = `
        <li>
            <span class="resolve-icon"></span>
            <span class="resolve-message"></span>
        </li>
    `;

    const errorDetailNodeSrc = `
        <li>
            <span class="error-icon"></span>
            <span class="error-message"></span>
        </li>
    `;

    const loadingIcon = parser.parseFromString(loadingIconSrc, "image/svg+xml").documentElement;
    const passIcon = parser.parseFromString(passIconSrc, "image/svg+xml").documentElement;
    const warningIcon = parser.parseFromString(warningIconSrc, "image/svg+xml").documentElement;
    const failIcon = parser.parseFromString(failIconSrc, "image/svg+xml").documentElement;
    const expandIcon = parser.parseFromString(expandIconSrc, "image/svg+xml").documentElement;
    const resolveIcon = parser.parseFromString(resolutionIconSrc, "image/svg+xml").documentElement;

    async function fetchStatus(endPoint, callback) {
        try {
            const prefix = "CheckHealth/";
            const response = await fetch(prefix + endPoint);
            let data = await response.json();
            if (callback)
                data = callback(data);
            return data;
        } catch (error) {
            console.error("Error fetching data:", error);
        }
        return null;
    }

    function createStatusItem(status) {
        const statusNode = parser.parseFromString(statusNodeSrc, "text/html");
        const statusLabel = statusNode.querySelector(".status-label");
        const statusFlag = statusNode.querySelector(".status-flag");
        const statusIcon = statusNode.querySelector(".status-icon");
        statusNode.body.firstChild.id = status.id;
        statusIcon.appendChild(loadingIcon.cloneNode(true));
        statusLabel.textContent = status.label;
        document.querySelector(".status-table").appendChild(statusNode.body.firstChild);

        return {
            ...status,
            statusFlag,
            statusIcon
        };
    }

    function createErrorItem(type, details, wrapperNode) {
        
        const errorListNode = wrapperNode.querySelector(".error-list");
        const errorDetailNode = parser.parseFromString(errorDetailNodeSrc, "text/html");
        const errorIcon = errorDetailNode.querySelector(".error-icon");
        const errorMessage = errorDetailNode.querySelector(".error-message");
        
        if (type === "resolve") {
            errorIcon.appendChild(resolveIcon.cloneNode(true));
            errorMessage.append(parser.parseFromString(`<b>${details[1]}</b> `, "text/html").body.firstChild);
        }else {
            errors[details[0]] = details[1];
            errorIcon.appendChild(type === "warning" ? warningIcon.cloneNode(true) : failIcon.cloneNode(true));
            errorMessage.append(parser.parseFromString(`<b>${details[0]}: </b> `, "text/html").body.firstChild);
            errorMessage.append(parser.parseFromString(`<p>${details[1]}</p>`, "text/html").body.firstChild);
        }
        errorListNode.appendChild(errorDetailNode.body.firstChild);
    }

    function createResolutionItem(details, wrapperNode) {
        const resolveListNode = wrapperNode.querySelector(".resolve-list");
        const resolveDetailNode = parser.parseFromString(resolveDetailNodeSrc, "text/html");
        const resolveIcon = resolveDetailNode.querySelector(".resolve-icon");
        const resolveMessage = resolveDetailNode.querySelector(".resolve-message");

        resolveMessage.append(parser.parseFromString(`<b>${details[0]}: </b> `, "text/html").body.firstChild);
        resolveMessage.append(parser.parseFromString(`<p>${details[1]}</p>`, "text/html").body.firstChild);
        resolveListNode.appendChild(resolveDetailNode.body.firstChild);
    }

    const showBuildInfo = function (info) {
        const str = info.version.toString();
        document.querySelector(".platform_name").innerText = info.platform;
        document.querySelector(".plugin_name").innerText = info.appName;
        document.querySelector(".plugin_Version").innerText =  formatVersion(str);
    }
    const formatVersion = function(value) {
        const str = String(value);

        const build = str.slice(-3);          
        const rest = str.slice(0, -3);        

        const major = rest[0];               
        const minor = rest[1];                
        const patch = rest.slice(2);          // remaining (1 or 2 digits)

        return `v${major}.${minor}.${patch}.${build}`;
    }

    const updateStatusItems = async (items) => {
        let integrityResponse = null;
        let compatibility = null;
        let buildStructureValidation = null;
        for (const item of items) {
            if (compatibility?.status === 'fail') continue;
            if (!item?.endPoint) continue;
            let response;
            if (item.id === "mobConfig" && integrityResponse) {
                response = validateMobileConfig(integrityResponse); // Reuse the Integrity response
            } else {
                response = await fetchStatus(item.endPoint, item.callback);
                if (item.id === "Integrity") {
                    integrityResponse = response; // Store Integrity response
                }
                if (item.id === "version") {
                    buildStructureValidation = response;
                    showBuildInfo(response.extraInfo)
                }
            }

            if (!response) continue;
            if (buildStructureValidation) {
                const extraInfo = buildStructureValidation.extraInfo; 
                if (extraInfo.platform === "MicroStrategy" && item.id === "mobConfig") {
                    response.status = "pass";
                } else if (extraInfo.platform === "MicroStrategyMobile" && item.id === "license") {
                    response.status = "pass";
                }
            }

            // Update status icon and flag
            switch (response.status) {
                case "pass":
                    item.statusFlag.textContent = "Passed";
                    item.statusIcon.replaceChild(passIcon.cloneNode(true), item.statusIcon.firstChild);
                    break;
                case "passWithWarnings":
                    item.statusFlag.textContent = "Passed With Warnings";
                    item.statusIcon.replaceChild(warningIcon.cloneNode(true), item.statusIcon.firstChild);
                    break;
                case "fail":
                    item.statusFlag.textContent = "Failed";
                    item.statusIcon.replaceChild(failIcon.cloneNode(true), item.statusIcon.firstChild);
                    break;
            }

            if(item.id === "compatibility") {
               compatibility =  response;  
            }
            // Add error/warning details
            if (response.status !== "pass") {
                const wrapperNode = parser.parseFromString(errorNodeSrc, "text/html");
                const resolveNode = parser.parseFromString(resolveNodeSrc, "text/html");
                const summaryNode = wrapperNode.querySelector("summary");
                summaryNode.textContent = item.label;
                summaryNode.appendChild(expandIcon.cloneNode(true));

                response.resolutions &&
                    Object.entries(response.resolutions).forEach(resolve => createErrorItem("resolve", resolve, wrapperNode));
                response.errors &&
                    Object.entries(response.errors).forEach(error => createErrorItem("error", error, wrapperNode));
                response.warnings &&
                    Object.entries(response.warnings).forEach(warning => createErrorItem("warning", warning, wrapperNode));

                document.querySelector(".error-display-section").appendChild(wrapperNode.body.firstChild);
            }
        }
    };

    const itemRefs = statusItems.map(createStatusItem);
    await updateStatusItems(itemRefs);
})();

function copyErrorsToClipboard(e) {
    navigator.clipboard.writeText(JSON.stringify(errors, null, 2))
        .then(() => console.log('Copied!'))
        .catch(err => console.error('Failed to copy:', err));
}

function validateCustomProperties(response) {
    const { globalConfig, cssContent } = response.extraInfo;

    const fontResult = validateFontOptions(globalConfig, cssContent);
    const paletteResult = validatePaletteOptions(globalConfig);
    const otherPropsResult = validateOtherProperties(globalConfig);

    // Merge all errors
    response.errors = {
        ...response.errors,
        ...fontResult.errors,
        ...paletteResult.errors,
        ...otherPropsResult.errors
    };

    // Merge warnings (currently only from palette validation)
    response.warnings = {
        ...paletteResult.warnings,
        ...fontResult.warnings,
    };

    // Flatten all resolutions into a single array
    response.resolutions = [
        ...response.resolutions,
        ...paletteResult.resolutions,
        ...fontResult.resolutions,
        ...otherPropsResult.resolutions
    ];

    // Determine status based on validation results
    if (!fontResult.isValid || !paletteResult.isValid || !otherPropsResult.isValid) {
        response.status = "fail";
    } else if (Object.keys(response.warnings).length > 0) {
        response.status = "passWithWarnings";
    }
    return response;
}

function validateMobileConfig(response) {
    response.warnings = {};
    response.resolutions = [];
    response.errors={};
    if (response.extraInfo.isOldHashPresent && response.status === "fail") {
        response.status = "fail";
        response.errors = {"MobileCheck": "Link added on bad files"};
        response.resolutions.push(["Please redeploy and reconfigure"]);
    } else if (!response.extraInfo.isOldHashPresent && response.status === "pass") {
        response.status = "passWithWarnings";
        response.warnings = {"MobileCheck": "Link not added"};
        response.resolutions.push(["Please configure Web url"]);
    } else 
        response.resolutions.push(["Please redeploy and reconfigure"]);
    return response;
}

function validateFontOptions(globalConfig, cssContent) {
    // Extract custom fonts and default font from globalConfig
    const customFonts = propertyExtract(globalConfig, "font.customFonts");
    const defaultFont = propertyExtract(globalConfig, "font.defaultFont");
    const errors = {};
    const warnings = {};
    let resolutions = [];

    // If no fonts are defined, validation passes by default
    if (customFonts.length === 0 && defaultFont.length === 0) {
        return { isValid: true, errors, warnings, resolutions };
    }

    const allUsedFonts = new Set([...customFonts, ...defaultFont]);

    // List of standard system fonts
    const defaultFontList = new Set([
        'Arial', 'Arial Black', 'Arial Narrow', 'Arial Unicode MS', 'Batang', 'Book Antiqua', 'Bookman Old Style', 'Bookshelf Symbol 1',
        'Bookshelf Symbol 2', 'Bookshelf Symbol 3', 'Comic Sans MS', 'Courier New', 'Garamond', 'Haettenschweiler', 'Helvetica', 'Impact', 'Lucida Console',
        'Lucida Sans Unicode', 'Map Symbols', 'Marlett', 'Monotype Corsiva', 'Monotype Sorts', 'MS Gothic', 'MS Outlook', 'Microsoft Sans Serif', 'MT Extra',
        'Symbol', 'Tahoma', 'Times New Roman', 'Trebuchet MS', 'Verdana', 'Webdings', 'Wingdings', 'Wingdings 2', 'Wingdings 3'
    ]);

    // Extract fonts from CSS content
    const extractedFonts = new Set(extractFontFaces(cssContent));

    // Validate custom fonts
    const customFontValid = customFonts.every(font => extractedFonts.has(font));

    // Validate default font (must be in system fonts or custom fonts)
    let defaultFontValid = true;
    if (defaultFont.length === 1) {
        const df = defaultFont[0];
        defaultFontValid = defaultFontList.has(df) || (customFonts && customFonts.includes(df));
    }

    if (!customFontValid) {
        errors["Custom Fonts"] = "Invalid custom font definitions."
        resolutions.push("Custom Font Defination not found");
    }

    if (!defaultFontValid) {
        errors["Default Fonts"] = "Invalid default font definitions."
        resolutions.push("Default font Defination not found");
    }

    // Find unused fonts in CSS
    const unusedFonts = [...extractedFonts].filter(font => !allUsedFonts.has(font));
    if (unusedFonts.length) {
        warnings["Unused Fonts"] = `The following fonts are defined in CSS but not used: ${unusedFonts.join(", ")}`;
    }

    return { isValid: customFontValid && defaultFontValid, errors, warnings, resolutions };
}
function validatePaletteOptions(globalConfig) {
    // Extract the list of defined palettes
    const paletteList = propertyExtract(globalConfig, "palette.list");
    const errors = {};
    const warnings = {};
    const resolutions = [];
    const extraInfo = {
        missingPalettes: [],
        emptyPalettes: [],
        unusedPalettes: []
    };

    const definedPalettes = Object.keys(globalConfig)
        .filter(key => key.startsWith("palette.") && key !== "palette.list")
        .map(key => key.replace("palette.", ""));

    if (!paletteList && paletteList.length === 0) return { isValid: true, errors, extraInfo };

    paletteList.forEach(paletteName => {
        const paletteKey = `palette.${paletteName}`;
        if (!globalConfig.hasOwnProperty(paletteKey)) {
            extraInfo.missingPalettes.push(paletteName); // Palette is listed but not defined
        } else {
            const colors = propertyExtract(globalConfig, paletteKey);
            if (!colors || colors.length === 0) {
                extraInfo.emptyPalettes.push(paletteName); // Palette is defined but has no colors
            }
        }
    });

    // Check for palettes that are defined but not listed
    definedPalettes.forEach(paletteName => {
        if (!paletteList.includes(paletteName)) {
            extraInfo.unusedPalettes.push(paletteName); // Defined but not used
        }
    });

    if (extraInfo.missingPalettes.length) {
        errors["Missing Palettes"] = 
            `The following palette(s) are listed but not defined: ${extraInfo.missingPalettes.join(", ")}.`;
        resolutions.push("Please define listed palettes")
    }

    if (extraInfo.emptyPalettes.length) {
        errors["Empty Palettes"] = 
            `The following palette(s) are defined but contain no colors: ${extraInfo.emptyPalettes.join(", ")}.`;
        resolutions.push("Please define Colors to empty Palettes")
    }

    if (extraInfo.unusedPalettes.length) {
        warnings["Unused Palettes"] = 
            `The following palette(s) are defined but not listed in 'palette.list': ${extraInfo.unusedPalettes.join(", ")}.`;
    }

    return {
        isValid: extraInfo.missingPalettes.length === 0 && extraInfo.emptyPalettes.length === 0,
        errors,
        warnings,
        resolutions,
        extraInfo
    };
}
function validateOtherProperties(globalConfig) {
    let errors = {};
    let resolutions = [];

    // Validate metricNegativeValueFormat (should be "-1", "0", or "1")
    if (!["-1", "0", "1"].includes(globalConfig.metricNegativeValueFormat)) {
        errors.metricNegativeValueFormat = "Invalid value, expected '-1', '0', or '1'.";
    }

    // Validate statePresentation.contextMenu (should be "0" or "1")
    if (!["0", "1"].includes(globalConfig["statePresentation.contextMenu"])) {
        errors["statePresentation.contextMenu"] = "Invalid value, expected '0' or '1'.";
    }

    // Validate shortFormatSymbols (should be comma-separated values)
    if (!globalConfig.shortFormatSymbols || !globalConfig.shortFormatSymbols.includes(",")) {
        errors.shortFormatSymbols = "Invalid format, expected comma-separated symbols.";
    }

    // Validate seriesLimit (should be a number greater than 0)
    if (!globalConfig.seriesLimit || isNaN(Number(globalConfig.seriesLimit)) || Number(globalConfig.seriesLimit) <= 0) {
        errors.seriesLimit = "Invalid value, expected a number greater than 0.";
    }

    // Validate licenseServerURL (should be a valid URL)
    if (!isValidURL(globalConfig.licenseServerURL)) {
        errors.licenseServerURL = "Invalid URL format.";
    }

    // Validate security.allowURLLinks (should be "0" or "1")
    if (!["0", "1"].includes(globalConfig["security.allowURLLinks"])) {
        errors["security.allowURLLinks"] = "Invalid value, expected '0' or '1'.";
    }

    return {
        isValid: Object.keys(errors).length === 0,
        errors,
        resolutions
    };
}

// Helper function to validate URL format
function isValidURL(url) {
    try {
        new URL(url);
        return true;
    } catch (_) {
        return false;
    }
}
function propertyExtract(prop, key) {
    return prop.hasOwnProperty(key) ? prop[key].split(',').map(k => k.trim()).filter(k => k!=="") : [];
}

function extractFontFaces(cssContent, options = {}) {
  const {
    maxInputLength = 200_000,
    maxBlockLength = 20_000,
    maxFontNameLength = 200,
    maxFonts = 50
  } = options;

  if (typeof cssContent !== 'string') return [];

  if (cssContent.length > maxInputLength) {
    throw new Error('CSS content too large');
  }

  const lower = cssContent;
  let pos = 0;
  const fonts = [];
  const seen = new Set();

  // build safe regex ONCE, not inside loop
  const safeRegex = new RegExp(
    `font-family\\s*:\\s*(?:'([^']{1,${maxFontNameLength}})'|"([^"]{1,${maxFontNameLength}})"|([^;'"]{1,${maxFontNameLength}}))`,
    'i'
  );

  while (true) {
    const idx = lower.indexOf('@font-face', pos);
    if (idx === -1) break;

    const openIdx = lower.indexOf('{', idx);
    if (openIdx === -1) break;

    let depth = 1;
    let i = openIdx + 1;
    const blockLimit = Math.min(cssContent.length, openIdx + 1 + maxBlockLength);

    for (; i < blockLimit; i++) {
      const ch = cssContent[i];
      if (ch === '{') depth++;
      else if (ch === '}') {
        depth--;
        if (depth === 0) break;
      }
    }

    if (depth !== 0) {
      pos = openIdx + 1;
      continue;
    }

    const block = cssContent.slice(openIdx + 1, i);
    const m = safeRegex.exec(block);

    if (m) {
      const name = (m[1] || m[2] || m[3] || '').trim();
      if (name && !seen.has(name)) {
        fonts.push(name);
        seen.add(name);

        if (fonts.length >= maxFonts) break;
      }
    }

    pos = i + 1;
  }

  return fonts;
}

