"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.differ = exports.relationDiff = exports.schemaDiff = exports.nodeDiff = exports.objectDiff = void 0;
const lodash_1 = __importDefault(require("lodash"));
const utils_1 = require("../utils");
const hasOwnProperty = Object.prototype.hasOwnProperty;
/**
 * object diff
 * @param base
 * @param current
 * @returns
 */
const objectDiff = (base, current) => {
    const result = {};
    for (const key in current) {
        if (hasOwnProperty.call(current, key)) {
            const baseValue = base[key];
            const curValue = current[key];
            if (hasOwnProperty.call(base, key) && lodash_1.default.isObject(baseValue) && lodash_1.default.isObject(curValue)) {
                if (!lodash_1.default.isArray(baseValue) && !lodash_1.default.isArray(curValue)) {
                    const diffed = (0, exports.objectDiff)(baseValue, curValue);
                    if (diffed !== undefined) {
                        result[key] = result[key];
                    }
                }
                else {
                    result[key] = curValue;
                }
            }
            else {
                // only the "null" or "" is valid empty value
                if ((curValue || curValue === null || curValue === '') && baseValue !== curValue) {
                    result[key] = curValue;
                }
            }
        }
    }
    if (lodash_1.default.isEmpty(result)) {
        return undefined;
    }
    return result;
};
exports.objectDiff = objectDiff;
/**
 * structure node diff
 * @param base
 * @param current
 * @returns
 */
const nodeDiff = (base, current) => {
    const { label: baseLabel, name: baseName, props: baseProps = {}, directive: baseDirective = {}, extension: baseExtension = {}, } = base;
    const { label: currentLabel, name: currentName, props: currentProps = {}, directive: currentDirective = {}, extension: currentExtension = {}, } = current;
    // if diff is empty, will set default {}
    const props = (0, exports.objectDiff)(baseProps, currentProps);
    const directive = (0, exports.objectDiff)(baseDirective, currentDirective);
    let extension = (0, exports.objectDiff)(baseExtension, currentExtension);
    if (props || directive || baseLabel !== currentLabel || baseName !== currentName) {
        // bind extend info
        if (!extension) {
            extension = {};
        }
        if (!extension.extendId) {
            extension.extendId = current.id;
        }
        return {
            id: (0, utils_1.generateStructureId)(),
            name: currentName,
            label: currentLabel,
            type: current.type,
            props: props || {},
            directive: directive || {},
            extension: extension || {},
        };
    }
    return null;
};
exports.nodeDiff = nodeDiff;
const treeToRecord = (base) => {
    const record = {};
    function dfs(tree) {
        tree.forEach((item) => {
            var _a, _b;
            record[item.id] = Object.assign(Object.assign({}, item), { children: [], childIds: (_a = item.children) === null || _a === void 0 ? void 0 : _a.map((item) => item.id) });
            if ((_b = item.children) === null || _b === void 0 ? void 0 : _b.length) {
                dfs(item.children);
            }
        });
    }
    dfs(base);
    return record;
};
const recordToTree = (record) => {
    function dfs(list) {
        list.forEach((item) => {
            const childIds = item.childIds || [];
            if (childIds.length > 0) {
                const children = childIds === null || childIds === void 0 ? void 0 : childIds.map((id) => record[id]).filter((it) => !!it);
                item.children = children.length > 0 ? dfs(children) : [];
            }
            // avoid the extension data
            // @ts-ignore
            delete item.childIds;
        });
        return list;
    }
    const tree = dfs(Object.values(record).filter((item) => { var _a; return !record[((_a = item.extension) === null || _a === void 0 ? void 0 : _a.parentId) || '']; }));
    return tree;
};
/**
 * schema diff
 * @param base
 * @param current
 * @returns
 */
const schemaDiff = (base, current) => {
    const baseRecord = treeToRecord(base);
    const currentRecord = treeToRecord(current);
    const result = {};
    Object.values(currentRecord).forEach((item) => {
        var _a;
        const baseItem = baseRecord[item.id] || baseRecord[((_a = item.extension) === null || _a === void 0 ? void 0 : _a.extendId) || ''];
        if (baseItem) {
            const diffed = (0, exports.nodeDiff)(baseItem, item);
            if (diffed) {
                result[item.id] = diffed;
            }
        }
        else {
            result[item.id] = item;
        }
    });
    const schemas = recordToTree(result);
    return schemas;
};
exports.schemaDiff = schemaDiff;
/**
 * relation diff
 * @param base
 * @param current
 * @returns
 */
const relationDiff = (base, current) => {
    return (0, exports.objectDiff)(base, current) || {};
};
exports.relationDiff = relationDiff;
/**
 * dsl diff
 * Because inserting the inheritance function midway is very cumbersome and prone to problems, the inheritance function is implemented through dsl diff.
 * Even if the inheritance function is added, the original operation remains unchanged. In the end, only the content of the subclass can be generated, so it is realized by dsl diff.
 * @param baseContent base(parent) content
 * @param current current content
 * @returns differed
 */
const differ = (extension, current) => {
    const { baseContent } = extension;
    if (baseContent) {
        const { content } = baseContent;
        const diffed = Object.assign(Object.assign({}, current), { schemas: (0, exports.schemaDiff)(content.schemas || [], current.schemas), relation: (0, exports.relationDiff)(content.relation || {}, current.relation) });
        return diffed;
    }
    return current;
};
exports.differ = differ;
