"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.writeXmlFileOrRemoveFileUponNoResources = exports.xmlElementsEqual = exports.writeXmlFile = exports.readXmlFile = exports.mergeXmlElements = void 0;
const xml_js_1 = require("xml-js");
const file_utils_1 = require("../utils/file-utils");
function isElementType(el) {
    return el.name !== undefined;
}
function isElementsType(el) {
    return !el.name && Boolean(el.elements);
}
function isCommentType(el) {
    return el.comment !== undefined;
}
function isTextType(el) {
    return el.text !== undefined;
}
function isExplicitNewValue(el) {
    // @ts-ignore
    return typeof el === 'object' && el.hasOwnProperty('newValue');
}
function unboxExplicitNewValue(el) {
    return isExplicitNewValue(el) ? el.newValue : el;
}
function compareElements(element, expectedElement) {
    var _a;
    if (isTextType(expectedElement)) {
        return element.type === 'text';
    }
    if (isCommentType(expectedElement)) {
        return element.type === 'comment' && ((_a = element.comment) === null || _a === void 0 ? void 0 : _a.trim()) === expectedElement.comment.trim();
    }
    if (isElementType(expectedElement) && element.type === 'element') {
        if (expectedElement.name !== element.name) {
            return false;
        }
        if (!element.attributes) {
            return true;
        }
        for (const [key, value] of Object.entries(expectedElement.attributes || {})) {
            if (isExplicitNewValue(value)) {
                // this attribute has to be overridden
                continue;
            }
            if (element.attributes[key] !== value) {
                return false;
            }
        }
        return true;
    }
    return false;
}
function sortWithExplicitIndex(elements) {
    if (!elements) {
        return;
    }
    const result = new Array(elements.length);
    const elementsWithExplicitIndices = elements.filter(({ idx }) => idx !== undefined);
    const elementsWithoutExplicitIndices = elements.filter(({ idx }) => idx === undefined);
    elementsWithoutExplicitIndices.forEach((el, idx) => (result[idx] = el));
    elementsWithExplicitIndices.forEach(({ idx, ...el }, i) => {
        // @ts-ignore
        result.splice(idx !== null && idx !== void 0 ? idx : i, 0, el);
    });
    return result;
}
function mergeXmlElementsLists(current, expected) {
    if (isExplicitNewValue(expected) || !current) {
        const sortedExpected = sortWithExplicitIndex(unboxExplicitNewValue(expected));
        return sortedExpected === null || sortedExpected === void 0 ? void 0 : sortedExpected.map(convertToElement);
    }
    if (!expected) {
        return current;
    }
    const result = [];
    for (const currentElement of current) {
        const idxInExpected = expected.findIndex(el => compareElements(currentElement, el));
        if (idxInExpected !== -1) {
            const { idx, ...element } = expected.splice(idxInExpected, 1)[0];
            if (!element.deletionFlag) {
                result.push({ idx, ...mergeXmlElements(currentElement, element) });
            }
        }
        else {
            result.push(currentElement);
        }
    }
    result.push(...expected
        .filter(({ deletionFlag }) => !deletionFlag)
        .map(({ idx, ...el }) => ({ idx, ...convertToElement(el) })));
    return sortWithExplicitIndex(result);
}
function convertToElement({ idx, ...expectedElement }) {
    // @ts-ignore
    if (expectedElement.deletionFlag) {
        throw new Error('Cannot convert ExpectedElement to Element when deletionFlag is set');
    }
    if (isCommentType(expectedElement)) {
        return {
            ...expectedElement,
            type: 'comment',
        };
    }
    if (isTextType(expectedElement)) {
        return {
            ...expectedElement,
            type: 'text',
        };
    }
    if (isElementsType(expectedElement)) {
        return {
            elements: unboxExplicitNewValue(expectedElement.elements)
                .filter(({ deletionFlag }) => !deletionFlag)
                .map(convertToElement),
            type: 'element',
        };
    }
    const { elements, attributes, ...expectedRest } = expectedElement;
    const result = {
        ...expectedRest,
        type: 'element',
    };
    if (attributes) {
        result.attributes = convertExpectedAttributes(attributes);
    }
    if (elements) {
        result.elements = unboxExplicitNewValue(elements)
            .filter(({ deletionFlag }) => !deletionFlag)
            .map(convertToElement);
    }
    return result;
}
function convertExpectedAttributes(expectedAttributes) {
    if (expectedAttributes) {
        return Object.entries(expectedAttributes).reduce((acc, [key, value]) => ({
            ...acc,
            [key]: unboxExplicitNewValue(value),
        }), {});
    }
    return undefined;
}
function mergeAndConvertToElement({ attributes: currentAttributes, ...currentRest }, { attributes: expectedAttributes, ...expectedRest }) {
    const result = {
        ...currentRest,
        ...expectedRest,
    };
    const attributes = (currentAttributes || expectedAttributes) && {
        ...currentAttributes,
        ...convertExpectedAttributes(expectedAttributes),
    };
    if (attributes) {
        result.attributes = attributes;
    }
    return result;
}
function deepEqual(e1, e2) {
    if ((!e1 && e2) || (e1 && !e2)) {
        return false;
    }
    else if (!e1 && !e2) {
        return true;
    }
    if (Object.keys(e1).length !== Object.keys(e2).length) {
        return false;
    }
    for (const [key, val1] of Object.entries(e1)) {
        const val2 = e2[key];
        const areObjects = isObject(val1) && isObject(val2);
        if ((areObjects && !deepEqual(val1, val2)) || (!areObjects && val1 !== val2)) {
            return false;
        }
    }
    return true;
}
function isObject(element) {
    return element && typeof element === 'object';
}
/**
 * Assumption is that elements are `equal` semantically
 */
function mergeXmlElements(current, expected) {
    if (isCommentType(expected)) {
        return {
            ...current,
            ...expected,
            type: 'comment',
        };
    }
    if (isTextType(expected)) {
        return {
            ...current,
            ...expected,
            type: 'text',
        };
    }
    if (isElementsType(expected)) {
        const result = {
            ...current,
            type: 'element',
        };
        const elements = mergeXmlElementsLists(current.elements, expected.elements);
        if (elements) {
            result.elements = elements;
        }
        return result;
    }
    const { elements: currentElements, ...currentRest } = current;
    const { elements: expectedElements, ...expectedRest } = expected;
    const elements = mergeXmlElementsLists(current.elements, expected.elements);
    const result = {
        ...mergeAndConvertToElement(currentRest, expectedRest),
        type: 'element',
    };
    if (elements) {
        result.elements = elements;
    }
    return result;
}
exports.mergeXmlElements = mergeXmlElements;
/**
 * @param filePath
 * @param fallbackContent
 */
async function readXmlFile(filePath, fallbackContent = `<?xml version="1.0" encoding="utf-8"?>`) {
    const fileContent = await file_utils_1.readFileWithFallback(filePath, typeof fallbackContent === 'string' ? fallbackContent : 'fallbackToElement');
    if (fileContent === 'fallbackToElement' && typeof fallbackContent === 'object') {
        return fallbackContent;
    }
    const fileXml = xml_js_1.xml2js(fileContent);
    return fileXml;
}
exports.readXmlFile = readXmlFile;
async function writeXmlFile(filePath, xml) {
    const fileXml = xml_js_1.js2xml(xml, { indentAttributes: true, spaces: 2 });
    const correctedFile = fileXml.replace(/(?<openTag><[^\s]+)\n *(?<firstAttribute> [^\s]+=".+?")\n *((?<secondAttribute> [^\s]+=".+?")\n *)?(?<closeTag>[/?]?>)/g, '$1$2$4$5');
    await file_utils_1.createDirAndWriteFile(filePath, `${correctedFile}\n`);
}
exports.writeXmlFile = writeXmlFile;
/**
 * Checks whether two xmlElements are equal in terms of their structure
 */
function xmlElementsEqual(a, b, { disregardComments = true } = {}) {
    const filteredA = !disregardComments ? a : removeComments(a);
    const filteredB = !disregardComments ? b : removeComments(b);
    return deepEqual(filteredA, filteredB);
}
exports.xmlElementsEqual = xmlElementsEqual;
function removeComments(e) {
    if (e.type === 'comment') {
        return;
    }
    const result = Object.entries(e)
        .map(([key, value]) => {
        if (key === 'elements' && Array.isArray(value)) {
            const filteredValue = value
                .map(removeComments)
                .filter((el) => el !== undefined);
            return [key, filteredValue.length > 0 ? filteredValue : undefined];
        }
        return [key, value];
    })
        .filter(([_, value]) => value !== undefined)
        .reduce((acc, [key, value]) => {
        // @ts-ignore
        acc[key] = value;
        return acc;
    }, {});
    return result;
}
/**
 * Check if given `element` has some meaningful data:
 * - if so: write it to the file
 * - if no: remove file completely
 * Function assumes that the structure of the input `element` is correct (`element.elements[name = resources]`).
 */
async function writeXmlFileOrRemoveFileUponNoResources(filePath, element, { disregardComments } = {}) {
    var _a, _b;
    if (((_a = element.elements) === null || _a === void 0 ? void 0 : _a[0].name) === 'resources' &&
        ((_b = element.elements[0].elements) === null || _b === void 0 ? void 0 : _b.filter(({ type }) => disregardComments ? type !== 'comment' : true).length) === 0) {
        await file_utils_1.removeFileIfExists(filePath);
    }
    else {
        await writeXmlFile(filePath, element);
    }
}
exports.writeXmlFileOrRemoveFileUponNoResources = writeXmlFileOrRemoveFileUponNoResources;
//# sourceMappingURL=index.js.map