import * as React from "react";
import { IGXE } from "@intelligentgraphics/igd.core";
import { PropertyUtil, RuleUtil, } from "@intelligentgraphics/igd.productdata";
import { isNullOrUndefined, memoizeOne, } from "@intelligentgraphics/ig.utilities";
import { withSessionContext } from "../SessionContext";
import * as Units from "../Units";
import { collectArticleData, groupArticlesBySourceId } from "./Articles";
import { byComId } from "./Groupers";
import { createComponentState, parsePropertyIdentifier, } from "./Util";
import { PropertyEditorContext } from "./Context";
export var PropertyEditorSubPropertyBehaviour;
(function (PropertyEditorSubPropertyBehaviour) {
    /**
     * Keep sub properties on their parent properties
     */
    PropertyEditorSubPropertyBehaviour["Depth"] = "depth";
    /**
     * Extract available properties and add them the to normal list of properties
     */
    PropertyEditorSubPropertyBehaviour["ShowInline"] = "showInline";
})(PropertyEditorSubPropertyBehaviour || (PropertyEditorSubPropertyBehaviour = {}));
export var PropertyEditorMode;
(function (PropertyEditorMode) {
    /**
     * Display articles of the session
     */
    PropertyEditorMode["AllArticles"] = "AllArticles";
    /**
     * Display articles of the session application state selection
     */
    PropertyEditorMode["Selection"] = "Selection";
    /**
     * Display articles provided via prop
     */
    PropertyEditorMode["ProvidedArticles"] = "ProvidedArticles";
    /**
     * Display provided articles or the session application state selection, if no articles are provided
     */
    PropertyEditorMode["Auto"] = "Auto";
})(PropertyEditorMode || (PropertyEditorMode = {}));
class PropertyEditor extends React.Component {
    constructor() {
        super(...arguments);
        this.state = {
            groups: [],
            groupReferences: {},
            properties: {},
        };
        this.createContextValue = memoizeOne((context, properties) => ({ ...context, properties }));
        this.createViewProps = memoizeOne((state) => ({
            groups: state.groups,
            properties: Object.getOwnPropertyNames(state.properties),
            getPropertyByIdentifier: identifier => state.properties[identifier],
        }));
        this.articleData = {};
        this.transferTargets = [];
        this.childContext = {
            properties: {},
            setPropertyValue: this.setPropertyValue.bind(this),
            applyToAll: this.applyToTransferTargets.bind(this),
            getComIdsByProperty: this.getComIdsByProperty.bind(this),
        };
        this.onItemCreated = (article) => {
            if (this.props.mode === PropertyEditorMode.AllArticles) {
                return this.queueUpdate(this.getComIds());
            }
        };
        this.onItemRemoved = (article) => {
            if (this.props.mode === PropertyEditorMode.AllArticles) {
                return this.queueUpdate(this.getComIds());
            }
        };
        this.onItemConfigured = (article) => {
            if (!this.articleData[article.comId]) {
                return;
            }
            this.queueUpdate(this.getComIds());
        };
        this.onApplicationStateValueChanged = (payload) => {
            if (payload.key !== "selection") {
                return;
            }
            this.queueUpdate(this.getComIds());
        };
        this.queueUpdate = (comIds = []) => {
            if (this.updateBatchTimer !== undefined) {
                window.clearTimeout(this.updateBatchTimer);
                this.updateBatchTimer = undefined;
            }
            this.updateBatchTimer = window.setTimeout(() => this.setupForArticles(comIds));
        };
        this.setupId = 0;
    }
    render() {
        const context = this.createContextValue(this.childContext, this.state.properties);
        const viewProps = this.createViewProps(this.state);
        return (React.createElement(PropertyEditorContext.Provider, { value: context }, this.props.children(viewProps)));
    }
    componentDidMount() {
        this.setupForArticles(this.getComIds());
        this.subscription = this.props.session.subscribe({
            ItemConfigured: this.onItemConfigured,
            ItemCreated: this.onItemCreated,
            ItemRemoved: this.onItemRemoved,
            ApplicationStateValueChanged: this.onApplicationStateValueChanged,
        });
    }
    componentWillUnmount() {
        if (this.subscription) {
            this.subscription.destroy();
        }
    }
    componentDidUpdate(prevProps) {
        if (this.props.articles !== prevProps.articles ||
            this.props.transferTargets !== prevProps.transferTargets ||
            this.props.units !== prevProps.units ||
            this.props.subPropertyBehaviour !==
                prevProps.subPropertyBehaviour ||
            this.props.mode !== prevProps.mode) {
            this.queueUpdate(this.getComIds());
        }
    }
    async setupForArticles(comIds) {
        this.setupId++;
        const id = this.setupId;
        let transferTargets = this.props.transferTargets;
        // replace article com ids with set com ids if the articles are not configurable
        comIds = RuleUtil.execute(this.props.session, comIds, "configurable");
        if (transferTargets !== undefined) {
            // do the same for transfer targets, when available
            transferTargets = RuleUtil.execute(this.props.session, transferTargets, "configurable");
        }
        // retrieve the full informations for all articles that might be of interest
        const articleData = collectArticleData(this.props.session, comIds.concat(transferTargets || []));
        // group articles by their source
        const articleGroups = groupArticlesBySourceId(comIds.map(comId => articleData[comId]));
        const sourceGroups = Object.values(articleGroups);
        const propertyList = await this.props.createPropertyGroups(sourceGroups);
        if (id !== this.setupId) {
            // if the setupId has changed while propertyGroups were created, skip remaining setup
            return;
        }
        this.transferTargets = transferTargets;
        this.articleData = articleData;
        const result = createComponentState({
            propertyList,
            units: this.props.units,
            transferTargets: transferTargets.map(comId => this.articleData[comId]),
            subPropertyBehaviour: this.props.subPropertyBehaviour,
        });
        this.setState(result);
    }
    setPropertyValue(name, newValue) {
        const { groupId, propertyKey } = parsePropertyIdentifier(name);
        const references = this.state.groupReferences[groupId];
        if (references === undefined) {
            throw new Error(`Could not find a group for id "${groupId}"`);
        }
        const normalizedReferences = Array.isArray(references)
            ? references
            : [references];
        const targetProperty = this.state.properties[name];
        if (name === undefined) {
            throw new Error(`Could not find a property for name "${name}"`);
        }
        if (!isNullOrUndefined(targetProperty.ValueUnit) &&
            (targetProperty.ValueType === IGXE.EPropertyType.Int ||
                targetProperty.ValueType === IGXE.EPropertyType.Float)) {
            newValue = Units.convert(Number(newValue), targetProperty.ValueUnit, IGXE.EPropertyValueUnit.LENGTH_M);
        }
        return this.props.setPropertyValue(this.props.session, normalizedReferences, propertyKey, newValue);
    }
    async applyToTransferTargets(name) {
        const { propertyKey } = parsePropertyIdentifier(name);
        const property = this.state.properties[name];
        if (property === undefined) {
            throw new Error(`Can not find a property for "${name}"`);
        }
        const newValueDefinition = IGXE.getFinalPropertyValue(property);
        if (newValueDefinition === undefined) {
            throw new Error(`Property "${name}" can not be applied to all, because no value has been configured`);
        }
        let newValue = newValueDefinition.Value;
        if (!isNullOrUndefined(property.ValueUnit) &&
            (property.ValueType === IGXE.EPropertyType.Int ||
                property.ValueType === IGXE.EPropertyType.Float)) {
            newValue = Units.convert(Number(newValue), property.ValueUnit, IGXE.EPropertyValueUnit.LENGTH_M);
        }
        const applyTargets = PropertyUtil.findApplyTargets(this.transferTargets.map(target => this.articleData[target]), propertyKey, newValue);
        return this.props.setPropertyValue(this.props.session, applyTargets.map(article => article.comId), propertyKey, newValue);
    }
    getComIdsByProperty(name) {
        const { groupId } = parsePropertyIdentifier(name);
        const references = this.state.groupReferences[groupId];
        return Array.isArray(references) ? references : [references];
    }
    getComIds() {
        switch (this.props.mode) {
            case PropertyEditorMode.AllArticles:
                return this.props.session.getAllComIds();
            case PropertyEditorMode.Selection:
                return this.props.session.getApplicationStateValue("selection");
            case PropertyEditorMode.ProvidedArticles:
                if (this.props.articles === undefined) {
                    throw new Error(`The property editor's mode has been set to ProvidedArticles, but no articles have been provided!`);
                }
                return this.props.articles;
        }
        if (this.props.articles !== undefined) {
            return this.props.articles;
        }
        return this.props.session.getApplicationStateValue("selection");
    }
}
PropertyEditor.defaultProps = {
    createPropertyGroups: byComId,
    transferTargets: [],
    setPropertyValue: (session, articles, propKey, propValue) => session.changeProperty(articles, propKey, propValue),
    subPropertyBehaviour: PropertyEditorSubPropertyBehaviour.ShowInline,
    mode: PropertyEditorMode.Auto,
};
export default withSessionContext(PropertyEditor);
