import { IGXE } from "@intelligentgraphics/igd.productdata";
import { Obj, isValue } from "@intelligentgraphics/ig.utilities";
/**
 * Creates a single group for each of the provided articles
 *
 * @param {*} sources
 * @returns
 */
export const byComId = sources => {
    const propertyGroups = [];
    for (const articles of sources) {
        for (const { properties, comId } of articles) {
            propertyGroups.push({
                groupId: comId,
                properties,
                references: comId,
                groupLabel: defaultCreateLabel(articles[0], properties),
                isMerged: false,
            });
        }
    }
    return propertyGroups;
};
/**
 * Creates a common item for each source id
 *
 * @param {Article[][]} sources
 * @returns
 */
export const bySource = sources => buildPropertyGroups({ sources });
// /**
//  * Creates a common item for each unique articleId
//  *
//  * @param {any} articles
//  * @returns
//  */
// export const byArticleId: PropertyGrouper = sources => {
// 	return [];
// };
export var GroupPropertyMergeBehaviour;
(function (GroupPropertyMergeBehaviour) {
    /**
     * Adds all non conflicting properties to a merge group
     */
    GroupPropertyMergeBehaviour["union"] = "union";
    /**
     * Only adds propeties which exist on all group articles to the merged group
     */
    GroupPropertyMergeBehaviour["intersection"] = "intersection";
})(GroupPropertyMergeBehaviour || (GroupPropertyMergeBehaviour = {}));
export var ChoiceListMergeBehaviour;
(function (ChoiceListMergeBehaviour) {
    /**
     * Allows merged property value lists to
     * contain an intersection of the value lists of multiple properties
     */
    ChoiceListMergeBehaviour["allowPartialValueList"] = "allowPartialValueList";
    /**
     * Only allows merged properties when a value list matches in length and content
     */
    ChoiceListMergeBehaviour["onlyCompleteValueList"] = "onlyCompleteValueList";
})(ChoiceListMergeBehaviour || (ChoiceListMergeBehaviour = {}));
export var GroupContent;
(function (GroupContent) {
    /**
     * Create merged groups and a group for each article with all of its properties
     */
    GroupContent["all"] = "all";
    /**
     * Only create groups for merged properties (will create a group for an article if the article count equals one)
     */
    GroupContent["onlyMerged"] = "onlyMerged";
    /**
     * Create merged groups and a group for each article containing the non merged properties
     */
    GroupContent["mergedAndRest"] = "mergedAndRest";
})(GroupContent || (GroupContent = {}));
export var PropertySortMode;
(function (PropertySortMode) {
    /**
     * Sorts properties alphabetically by their text property
     */
    PropertySortMode["alphabetical"] = "alphabetical";
    /**
     * Sorts properties by their position property
     */
    PropertySortMode["position"] = "position";
    /**
     * Sorts properties by their position property and if in ambigous cases alphabetical
     */
    PropertySortMode["positionThenAlphabetical"] = "positionThenAlphabetical";
})(PropertySortMode || (PropertySortMode = {}));
export const buildPropertyGroups = ({ sources, mergeBehaviour = GroupPropertyMergeBehaviour.union, displayMode = GroupContent.all, createLabel = defaultCreateLabel, mergeProperties = defaultMergeProperties, choiceListMergeBehaviour = ChoiceListMergeBehaviour.allowPartialValueList, minGroupPropertyReferenceCount = 1, groupPropertySorting = false, sortGroupProperties = defaultSortProperties, }) => {
    const groups = [];
    const mergePropertiesSettings = {
        choiceListMergeBehaviour,
    };
    for (const articles of sources) {
        if (!articles.length) {
            continue;
        }
        if (articles.length === 1) {
            groups.push({
                groupId: articles[0].comId,
                references: articles[0].comId,
                groupLabel: createLabel(articles[0], articles[0].properties),
                properties: articles[0].properties,
                isMerged: false,
            });
            continue;
        }
        const sourceGroups = [];
        const [templateArticle] = articles;
        const commonSourceItem = {
            groupId: templateArticle.source,
            groupLabel: undefined,
            references: [],
            properties: [],
            isMerged: true,
        };
        // list of all properties for this group. null means some merging failed and the property should not be used
        const groupProperties = new Map();
        const referenceCounter = new Map();
        for (const article of articles) {
            for (const prop of article.properties) {
                const groupProperty = groupProperties.get(prop.Key);
                // property key was already declared as non mergeable, so this property should be skipped
                if (groupProperty === null) {
                    continue;
                }
                // property key did not yet exist, add it
                if (groupProperty === undefined) {
                    // create a copy of the original property, so we can manipulate its values and subproperties, without manipulating the original property
                    groupProperties.set(prop.Key, {
                        ...prop,
                        SubProperties: Obj.deepClone(prop.SubProperties),
                    });
                    referenceCounter.set(prop.Key, 1);
                    continue;
                }
                const mergeResult = mergeProperties(groupProperty, prop, mergePropertiesSettings);
                // property could not be merged. remove it from the referenceCounter and set the groupproperty to null,
                // so it can be ignored later
                if (mergeResult === false) {
                    referenceCounter.delete(prop.Key);
                    groupProperties.set(prop.Key, null);
                    continue;
                }
                referenceCounter.set(prop.Key, referenceCounter.get(prop.Key) + 1);
                groupProperties.set(prop.Key, mergeResult);
            }
            commonSourceItem.references.push(article.comId);
            if (displayMode === GroupContent.all) {
                // add article itself as a group
                sourceGroups.push({
                    groupId: article.comId,
                    groupLabel: createLabel(article, article.properties),
                    properties: article.properties,
                    references: article.comId,
                    isMerged: false,
                });
            }
        }
        commonSourceItem.properties = Array.from(groupProperties.values()).filter(isValue);
        if (mergeBehaviour === GroupPropertyMergeBehaviour.intersection) {
            minGroupPropertyReferenceCount = articles.length;
        }
        if (minGroupPropertyReferenceCount > 1) {
            commonSourceItem.properties = commonSourceItem.properties.filter(prop => {
                if (referenceCounter.get(prop.Key) >=
                    minGroupPropertyReferenceCount) {
                    return true;
                }
                groupProperties.set(prop.Key, null);
                return false;
            });
        }
        if (displayMode === GroupContent.mergedAndRest) {
            // create new groups for each article with an unmerged group
            for (const article of articles) {
                const nonMergedProperties = article.properties.filter(property => groupProperties.get(property.Key) === null);
                if (nonMergedProperties.length > 0) {
                    sourceGroups.push({
                        groupId: article.comId,
                        properties: nonMergedProperties,
                        references: article.comId,
                        groupLabel: createLabel(article, nonMergedProperties),
                        isMerged: false,
                    });
                }
            }
        }
        commonSourceItem.groupLabel = createLabel(commonSourceItem.references.map(comId => articles.find(item => item.comId === comId)), commonSourceItem.properties);
        sourceGroups.unshift(commonSourceItem);
        for (const group of sourceGroups) {
            if (group.properties.length) {
                groups.push(group);
            }
        }
    }
    if (groupPropertySorting) {
        for (const group of groups) {
            group.properties = sortGroupProperties(group.properties, groupPropertySorting, group);
        }
    }
    return groups;
};
export const defaultCreateLabel = input => {
    if (Array.isArray(input)) {
        if (input.length === 0) {
            return "";
        }
        const [firstItem] = input;
        return [
            firstItem.info.manufacturerName,
            firstItem.info.programName,
        ].join(" ");
    }
    const { info, articleId } = input;
    if (info.name) {
        return info.name;
    }
    if (info.shortText.length) {
        return info.shortText.join(" ");
    }
    return articleId;
};
export const defaultMergeProperties = (a, b, { choiceListMergeBehaviour: choiceListchoiceListMergeBehaviour, }) => {
    if (a.ValueType !== b.ValueType || a.Type !== b.Type) {
        return false;
    }
    if (a.Type === "toggle") {
        a.Value = a.Value === b.Value ? a.Value : undefined;
        return a;
    }
    if (a.Type === "choiceList") {
        return mergeChoiceListProperties(a, b, choiceListchoiceListMergeBehaviour);
    }
    return mergeNormalProperties(a, b);
};
export const mergeNormalProperties = (a, b) => {
    if (a.Value !== b.Value) {
        a.Value = undefined;
        a.Values = [];
    }
    if (a.ValueType === IGXE.EPropertyType.Int ||
        a.ValueType === IGXE.EPropertyType.Float) {
        if (a.ValueRangeFrom !== undefined || b.ValueRangeFrom !== undefined) {
            a.ValueRangeFrom = Math.max(a.ValueRangeFrom || 0, b.ValueRangeFrom || 0);
            a.ValueRangeTo = Math.min(a.ValueRangeTo || Infinity, b.ValueRangeTo || Infinity);
            if (a.ValueRangeTo < a.ValueRangeFrom) {
                return false;
            }
        }
    }
    return a;
};
export const mergeChoiceListProperties = (a, b, mergeBehaviour) => {
    if (a.Value !== b.Value) {
        a.Value = undefined;
    }
    if (a.ValueType === IGXE.EPropertyType.Bool) {
        return a;
    }
    const mergedValues = a.Values.concat(b.Values);
    const referenceCounter = new Map();
    for (const value of mergedValues) {
        const stringifiedValue = value.Value.toString();
        referenceCounter.set(stringifiedValue, (referenceCounter.get(stringifiedValue) || 0) + 1);
    }
    const newValues = [];
    for (const [value, references] of referenceCounter) {
        if (references < 2) {
            if (mergeBehaviour ===
                ChoiceListMergeBehaviour.onlyCompleteValueList) {
                return false;
            }
            continue;
        }
        newValues.push(mergedValues.find(val => val.Value.toString() === value));
    }
    a.Values = newValues;
    const subPropertyCollection = new Map();
    const allProperties = [...a.SubProperties, ...b.SubProperties];
    for (const property of allProperties) {
        let collection = subPropertyCollection.get(property.Key);
        if (collection === undefined) {
            collection = [];
            subPropertyCollection.set(property.Key, collection);
        }
        collection.push(property);
    }
    const newSubProperties = [];
    for (const [, properties] of subPropertyCollection) {
        if (properties.length !== 2) {
            if (mergeBehaviour ===
                ChoiceListMergeBehaviour.onlyCompleteValueList) {
                return false;
            }
            continue;
        }
        const mergedProperty = mergeChoiceListProperties(properties[0], properties[1], mergeBehaviour);
        if (!mergedProperty &&
            mergeBehaviour === ChoiceListMergeBehaviour.onlyCompleteValueList) {
            return false;
        }
        if (mergedProperty) {
            newSubProperties.push(mergedProperty);
        }
    }
    a.SubProperties = newSubProperties;
    if (!a.Values.length && !a.SubProperties.length) {
        return false;
    }
    return a;
};
export const defaultSortProperties = (properties, sortMode) => {
    if (properties.length === 0) {
        return properties;
    }
    switch (sortMode) {
        case PropertySortMode.alphabetical:
            return properties.sort(comparePropertiesAlphabetically);
        case PropertySortMode.position:
            return properties.sort(comparePropertyPosition);
        case PropertySortMode.positionThenAlphabetical:
            return properties.sort((a, b) => {
                const byPosition = comparePropertyPosition(a, b);
                if (byPosition !== 0) {
                    return byPosition;
                }
                return comparePropertiesAlphabetically(a, b);
            });
    }
    return properties;
};
const comparePropertiesAlphabetically = (a, b) => {
    const aText = a.Text.join(" ");
    const bText = b.Text.join(" ");
    return aText.localeCompare(bText);
};
const comparePropertyPosition = (a, b) => {
    if (a.Position === undefined) {
        if (b.Position === undefined) {
            return 0;
        }
        return 1;
    }
    if (b.Position === undefined) {
        return -1;
    }
    return a.Position - b.Position;
};
