"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.removeUnitPinData = exports.setUnitInputIgnored = exports.setUnitOutputIgnored = exports.setUnitPinIgnored = exports.setUnitOutputConstant = exports.setUnitInputConstant = exports.setUnitPinData = exports.setUnitOutput = exports.setUnitInput = exports.setPinSetRef = exports.setPinSetFunctional = exports.setPinSetId = exports.setPinSetName = exports.setUnitErr = exports.coverOutput = exports.coverOutputSet = exports.exposeOutput = exports.exposeOutputSet = exports.coverInput = exports.coverInputSet = exports.unplugPin = exports.unplugOutput = exports.unplugInput = exports.plugPin = exports.exposeInput = exports.exposeInputSet = exports.exposePin = exports.exposePinSet = exports.coverPin = exports.coverPinSet = exports.isPinMerged = exports.removePinFromMerge = exports._removePinFromMerge = exports.mergeMerges = exports.removeMerge = exports.addMerges = exports.addPinToMerge = exports.addMerge = exports.removeUnit = exports.removeUnitMerges = exports.removeUnitExposedPins = exports.removeUnitExposedTypePins = exports.collapseUnits = exports.expandUnit = exports.mergeUnits = exports.removeUnits = exports.addUnits = exports.addUnit = exports.setName = exports.setSpec = void 0;
exports.renameUnitInMerges = exports.setUnitMetadata = exports.setMetadata = void 0;
const deepGet_1 = require("../../deepGet");
const deepSet_1 = require("../../deepSet");
const f_1 = require("../../system/core/object/DeletePath/f");
const f_2 = require("../../system/core/object/ForEachKeyValue/f");
const f_3 = require("../../system/f/object/DeepMerge/f");
const f_4 = require("../../system/f/object/Delete/f");
const f_5 = require("../../system/f/object/Merge/f");
const f_6 = require("../../system/f/object/Set/f");
const array_1 = require("../../util/array");
const object_1 = require("../../util/object");
const spec_1 = require("../util/spec");
const setSpec = ({ spec }) => spec;
exports.setSpec = setSpec;
const setName = ({ name }, state) => (0, f_6.default)(state, 'name', name);
exports.setName = setName;
const addUnit = ({ id, unit }, state) => (0, deepSet_1.default)(state, ['units', id], unit);
exports.addUnit = addUnit;
const addUnits = ({ units }, state) => (0, f_6.default)(state, 'units', (0, f_5.default)(state.units, units));
exports.addUnits = addUnits;
const removeUnits = ({ ids }, state) => {
    (0, array_1.forEach)(ids, (unitId) => {
        state = (0, exports.removeUnit)({ unitId }, state);
    });
    return state;
};
exports.removeUnits = removeUnits;
const mergeUnits = ({ units }, state) => (0, f_6.default)(state, 'units', (0, f_5.default)(state.units, units));
exports.mergeUnits = mergeUnits;
const expandUnit = ({ unitId, spec }, state) => {
    // unplug unit
    state = (0, exports.removeUnit)({ unitId }, state);
    // add new units and merges
    // TODO possible id conflict
    state = (0, exports.mergeUnits)({ units: spec.units || {} }, state);
    state = (0, exports.addMerges)({ merges: spec.merges || {} }, state);
    // add interface merges
    (0, f_2.default)(state.merges || {}, (merge, mergeId) => {
        if (merge[unitId]) {
            const { input = {}, output = {} } = merge[unitId];
            (0, f_2.default)(input, (_, inputId) => {
                const inputSpec = spec.inputs[inputId];
                const { plug } = inputSpec;
                (0, f_2.default)(plug, (subInputSpec, subInputId) => {
                    if (subInputSpec.mergeId) {
                        state = (0, exports.mergeMerges)({ a: mergeId, b: subInputSpec.mergeId }, state);
                    }
                    else {
                        state = (0, exports.addPinToMerge)({
                            mergeId,
                            type: 'input',
                            unitId: subInputSpec.unitId,
                            pinId: subInputSpec.pinId,
                        }, state);
                    }
                });
            });
            (0, f_2.default)(output, (_, outputId) => {
                const outputSpec = spec.outputs[outputId];
                const { plug } = outputSpec;
                (0, f_2.default)(plug, (subOutputSpec) => {
                    if (subOutputSpec.mergeId) {
                        state = (0, exports.mergeMerges)({ a: mergeId, b: subOutputSpec.mergeId }, state);
                    }
                    else {
                        state = (0, exports.addPinToMerge)({
                            mergeId: mergeId,
                            type: 'output',
                            unitId: subOutputSpec.unitId,
                            pinId: subOutputSpec.pinId,
                        }, state);
                    }
                });
            });
        }
    });
    return state;
};
exports.expandUnit = expandUnit;
// TODO
const collapseUnits = (unitIds, state) => {
    return state;
};
exports.collapseUnits = collapseUnits;
const removeUnitExposedTypePins = ({ id, type }, state) => {
    const nextState = (0, object_1.clone)(state);
    for (const pinId in state[`${type}s`]) {
        const pin = state[`${type}s`][pinId];
        const { plug } = pin;
        for (let subPinId in plug) {
            const subPin = plug[subPinId];
            const { unitId } = subPin;
            if (unitId === id) {
                nextState[`${type}s`][pinId]['plug'][subPinId] = {};
            }
        }
    }
    return nextState;
};
exports.removeUnitExposedTypePins = removeUnitExposedTypePins;
const removeUnitExposedPins = ({ id }, state) => {
    state = (0, exports.removeUnitExposedTypePins)({ id, type: 'input' }, state);
    state = (0, exports.removeUnitExposedTypePins)({ id, type: 'output' }, state);
    return state;
};
exports.removeUnitExposedPins = removeUnitExposedPins;
const removeUnitMerges = ({ id }, state) => {
    const { merges = {} } = state;
    let nextState = (0, object_1.clone)(state);
    const removedMergeIds = {};
    (0, f_2.default)(merges, (merge, mergeId) => {
        if (merge[id]) {
            delete nextState.merges[mergeId][id];
            if ((0, spec_1.getMergePinCount)(nextState.merges[mergeId]) < 2) {
                delete nextState.merges[mergeId];
                removedMergeIds[mergeId] = true;
            }
        }
    });
    const reasignExposedPins = (type) => {
        const pins = state[`${type}s`] || {};
        for (const exposedPinId in pins) {
            const pinSpec = pins[exposedPinId];
            const { plug } = pinSpec;
            for (let subPinId in plug) {
                const subPin = plug[subPinId];
                const { mergeId } = subPin;
                if (mergeId && removedMergeIds[mergeId]) {
                    for (const unitId in merges[mergeId]) {
                        if (unitId !== id) {
                            const pinId = (0, object_1.getObjSingleKey)((0, object_1.deepGetOrDefault)(merges, [mergeId, unitId, type], {}));
                            if (pinId) {
                                nextState[`${type}s`][exposedPinId]['plug'][subPinId] = {
                                    unitId,
                                    pinId,
                                };
                                break;
                            }
                            else {
                                nextState[`${type}s`][exposedPinId]['plug'][subPinId] = {};
                                break;
                            }
                        }
                    }
                }
            }
        }
    };
    reasignExposedPins('input');
    reasignExposedPins('output');
    return nextState;
};
exports.removeUnitMerges = removeUnitMerges;
const removeUnit = ({ unitId }, state) => {
    state = (0, exports.removeUnitMerges)({ id: unitId }, state);
    state = (0, exports.removeUnitExposedPins)({ id: unitId }, state);
    state = (0, f_1.default)(state, ['units', unitId]);
    return state;
};
exports.removeUnit = removeUnit;
const addMerge = ({ mergeId, merge }, state) => {
    const { inputs = {}, outputs = {} } = state;
    (0, f_2.default)(inputs, ({ plug }, inputId) => {
        (0, f_2.default)(plug, ({ unitId, pinId }, subPinId) => {
            var _a, _b;
            if (unitId && pinId && ((_b = (_a = merge[unitId]) === null || _a === void 0 ? void 0 : _a['input']) === null || _b === void 0 ? void 0 : _b[pinId])) {
                state = (0, exports.coverInput)({ pinId: inputId, subPinId }, state);
                state = (0, exports.exposeInput)({ pinId: inputId, subPinId, input: { mergeId } }, state);
            }
        });
    });
    // reassign exposed outputs that have just been merged
    (0, f_2.default)(outputs, ({ plug }, outputId) => {
        (0, f_2.default)(plug, ({ unitId, pinId }, subPinId) => {
            if (unitId &&
                pinId &&
                merge[unitId] &&
                merge[unitId]['output'] &&
                merge[unitId]['output'][pinId]) {
                state = (0, exports.coverOutput)({ pinId: outputId, subPinId }, state);
                state = (0, exports.exposeOutput)({ pinId: outputId, subPinId, output: { mergeId } }, state);
            }
        });
    });
    return (0, deepSet_1.default)(state, ['merges', mergeId], merge);
};
exports.addMerge = addMerge;
const addPinToMerge = ({ mergeId, unitId, type, pinId, }, state) => {
    // reassign exposed pin if it has just been merged
    (0, f_2.default)(state[`${type}s`], ({ name, plug = {} }, exposedPinId) => {
        for (const subPinId in plug) {
            const subPin = plug[subPinId];
            const { unitId: _unitId, pinId: _pinId } = subPin;
            if (_unitId === unitId && _pinId === pinId) {
                state = (0, exports.coverPinSet)({ pinId: exposedPinId, type }, state);
                state = (0, exports.exposePinSet)({
                    pinId: exposedPinId,
                    pin: { name, plug: { 0: { mergeId } } },
                    type,
                }, state);
            }
        }
    });
    return (0, deepSet_1.default)(state, ['merges', mergeId, unitId, type, pinId], true);
};
exports.addPinToMerge = addPinToMerge;
const addMerges = ({ merges }, state) => {
    // TODO update exposed inputs/outputs
    return (0, f_6.default)(state, 'merges', (0, f_3.default)(merges, state.merges));
};
exports.addMerges = addMerges;
const removeMerge = ({ id }, state) => {
    const nextState = (0, object_1.clone)(state);
    const nextMerges = (0, f_4.default)(state.merges, id);
    nextState.merges = nextMerges;
    nextState.inputs = (0, object_1.mapObjVK)(state.inputs, (input) => (Object.assign(Object.assign({}, input), { plug: (0, object_1.mapObjVK)(input.plug, (plug) => {
            const { mergeId } = plug;
            return !mergeId || mergeId !== id ? plug : {};
        }) })));
    nextState.outputs = (0, object_1.mapObjVK)(state.outputs, (output) => (Object.assign(Object.assign({}, output), { plug: (0, object_1.mapObjVK)(output.plug, (plug) => {
            const { mergeId } = plug;
            return !mergeId || mergeId !== id ? plug : {};
        }) })));
    if (nextState.metadata &&
        nextState.metadata.position &&
        nextState.metadata.position.merge) {
        delete nextState.metadata.position.merge[id];
    }
    return nextState;
};
exports.removeMerge = removeMerge;
const mergeMerges = ({ a, b }, state) => {
    const aMerge = state.merges[a];
    const bMerge = state.merges[b];
    state = (0, f_1.default)(state, ['merges', b]);
    state = (0, deepSet_1.default)(state, ['merges', a], (0, f_3.default)(aMerge, bMerge));
    return state;
};
exports.mergeMerges = mergeMerges;
const _removePinFromMerge = ({ id, unitId, type, pinId, }, state) => {
    state = (0, f_1.default)(state, ['merges', id, unitId, type, pinId]);
    // remove type from unit if there are no more pins involved
    if ((0, object_1.isEmptyObject)((0, deepGet_1.default)(state, ['merges', id, unitId, type]))) {
        state = (0, f_1.default)(state, ['merges', id, unitId, type]);
    }
    // remove unit from merge if there are no more pins involved
    if ((0, object_1.isEmptyObject)((0, deepGet_1.default)(state, ['merges', id, unitId]))) {
        state = (0, f_1.default)(state, ['merges', id, unitId]);
    }
    return state;
};
exports._removePinFromMerge = _removePinFromMerge;
const removePinFromMerge = ({ id, unitId, type, pinId, }, state) => {
    state = (0, exports._removePinFromMerge)({ id, unitId, type, pinId }, state);
    return state;
};
exports.removePinFromMerge = removePinFromMerge;
const isPinMerged = (state, id, unitId, type, pinId) => {
    return !!(state.merges[id] &&
        state.merges[id][unitId] &&
        state.merges[id][unitId][type] &&
        state.merges[id][unitId][type][pinId]);
};
exports.isPinMerged = isPinMerged;
const coverPinSet = ({ pinId, type }, state) => (0, f_1.default)(state, [`${type}s`, pinId]);
exports.coverPinSet = coverPinSet;
const coverPin = ({ pinId, type, subPinId, }, state) => {
    return (0, f_1.default)(state, [`${type}s`, pinId, 'plug', subPinId]);
};
exports.coverPin = coverPin;
const exposePinSet = ({ pinId, type, pin }, state) => (0, deepSet_1.default)(state, [`${type}s`, pinId], pin);
exports.exposePinSet = exposePinSet;
const exposePin = ({ pinId, type, subPinId, subPin, }, state) => {
    return (0, deepSet_1.default)(state, [`${type}s`, pinId, 'plug', subPinId], subPin);
};
exports.exposePin = exposePin;
const exposeInputSet = ({ pinId, input }, state) => (0, exports.exposePinSet)({ pinId, pin: input, type: 'input' }, state);
exports.exposeInputSet = exposeInputSet;
const exposeInput = ({ pinId, subPinId, input, }, state) => {
    return (0, exports.exposePin)({ pinId, subPinId, subPin: input, type: 'input' }, state);
};
exports.exposeInput = exposeInput;
const plugPin = ({ type, pinId, subPinId, subPinSpec, }, state) => {
    return (0, deepSet_1.default)(state, [`${type}s`, pinId, 'plug', subPinId], subPinSpec);
};
exports.plugPin = plugPin;
const unplugInput = ({ pinId, subPinId, }, state) => {
    return (0, exports.unplugPin)({ type: 'input', pinId, subPinId }, state);
};
exports.unplugInput = unplugInput;
const unplugOutput = ({ pinId, subPinId, }, state) => {
    return (0, exports.unplugPin)({ type: 'output', pinId, subPinId }, state);
};
exports.unplugOutput = unplugOutput;
const unplugPin = ({ type, pinId, subPinId, }, state) => {
    return (0, deepSet_1.default)(state, [`${type}s`, pinId, 'plug', subPinId], {});
};
exports.unplugPin = unplugPin;
const coverInputSet = ({ pinId }, state) => (0, exports.coverPinSet)({ pinId, type: 'input' }, state);
exports.coverInputSet = coverInputSet;
const coverInput = ({ pinId, subPinId, }, state) => {
    return (0, exports.coverPin)({ pinId, type: 'input', subPinId }, state);
};
exports.coverInput = coverInput;
const exposeOutputSet = ({ pinId, output }, state) => (0, exports.exposePinSet)({ pinId, pin: output, type: 'output' }, state);
exports.exposeOutputSet = exposeOutputSet;
const exposeOutput = ({ pinId, subPinId, output, }, state) => {
    return (0, exports.exposePin)({ pinId, subPinId, subPin: output, type: 'output' }, state);
};
exports.exposeOutput = exposeOutput;
const coverOutputSet = ({ pinId }, state) => (0, exports.coverPinSet)({ pinId, type: 'output' }, state);
exports.coverOutputSet = coverOutputSet;
const coverOutput = ({ pinId, subPinId, }, state) => {
    return (0, exports.coverPin)({ pinId, type: 'output', subPinId }, state);
};
exports.coverOutput = coverOutput;
const setUnitErr = ({ unitId, err }, state) => {
    if (err !== null) {
        return (0, deepSet_1.default)(state, ['units', unitId, 'err'], err);
    }
    else {
        return (0, f_1.default)(state, ['units', unitId, 'err']);
    }
};
exports.setUnitErr = setUnitErr;
const setPinSetName = ({ type, id, name }, state) => {
    return (0, deepSet_1.default)(state, [`${type}s`, id, 'name'], name);
};
exports.setPinSetName = setPinSetName;
const setPinSetId = ({ type, id, newId }, state) => {
    return (0, deepSet_1.default)((0, f_1.default)(state, [`${type}s`, id]), [`${type}s`, newId], (0, deepGet_1.default)(state, [`${type}s`, id]));
};
exports.setPinSetId = setPinSetId;
const setPinSetFunctional = ({ type, pinId, functional }, state) => {
    return (0, deepSet_1.default)(state, [`${type}s`, pinId, 'functional'], functional);
};
exports.setPinSetFunctional = setPinSetFunctional;
const setPinSetRef = ({ type, pinId, ref }, state) => {
    return (0, deepSet_1.default)(state, [`${type}s`, pinId, 'ref'], ref);
};
exports.setPinSetRef = setPinSetRef;
const setUnitInput = ({ id, name, data }, state) => {
    return (0, deepSet_1.default)(state, ['units', id, 'input', name], data);
};
exports.setUnitInput = setUnitInput;
const setUnitOutput = ({ id, name, data }, state) => {
    return (0, deepSet_1.default)(state, ['units', id, 'output', name], data);
};
exports.setUnitOutput = setUnitOutput;
const setUnitPinData = ({ unitId, type, pinId, data, }, state) => {
    return (0, deepSet_1.default)(state, ['units', unitId, type, pinId, 'data'], data);
};
exports.setUnitPinData = setUnitPinData;
const setUnitInputConstant = ({ unitId, pinId, constant, }, state) => {
    return (0, deepSet_1.default)(state, ['units', unitId, 'input', pinId, 'constant'], constant);
};
exports.setUnitInputConstant = setUnitInputConstant;
const setUnitOutputConstant = ({ unitId, pinId, constant, }, state) => {
    return (0, deepSet_1.default)(state, ['units', unitId, 'output', pinId, 'constant'], constant);
};
exports.setUnitOutputConstant = setUnitOutputConstant;
const setUnitPinIgnored = ({ unitId, type, pinId, ignored, }, state) => {
    if (ignored) {
        (0, spec_1.forEachPinOnMerges)(state.merges, (id, _unitId, _type, _pinId) => {
            if (_unitId === unitId && _type === type && _pinId === pinId) {
                state = (0, exports.removePinFromMerge)({ id, unitId, type, pinId }, state);
            }
        });
        if (type === 'input') {
            (0, f_2.default)(state.inputs, ({ plug }, id) => {
                (0, f_2.default)(plug, (input, subPinId) => {
                    if (input.unitId === unitId && input.pinId === pinId) {
                        state = (0, exports.unplugInput)({ pinId: id, subPinId }, state);
                    }
                });
            });
        }
        if (type === 'output') {
            (0, f_2.default)(state.outputs, ({ plug }, id) => {
                (0, f_2.default)(plug, (output, subPinId) => {
                    if (output.unitId === unitId && output.pinId === pinId) {
                        state = (0, exports.unplugOutput)({ pinId: id, subPinId }, state);
                    }
                });
            });
        }
    }
    state = (0, deepSet_1.default)(state, ['units', unitId, type, pinId, 'ignored'], ignored);
    return state;
};
exports.setUnitPinIgnored = setUnitPinIgnored;
const setUnitOutputIgnored = ({ unitId, pinId, ignored, }, state) => {
    return (0, exports.setUnitPinIgnored)({ unitId, type: 'output', pinId, ignored }, state);
};
exports.setUnitOutputIgnored = setUnitOutputIgnored;
const setUnitInputIgnored = ({ unitId, pinId, ignored, }, state) => {
    return (0, exports.setUnitPinIgnored)({ unitId, type: 'input', pinId, ignored }, state);
};
exports.setUnitInputIgnored = setUnitInputIgnored;
const removeUnitPinData = ({ unitId, type, pinId }, state) => {
    state = (0, f_1.default)(state, ['units', unitId, type, pinId, 'data']);
    return state;
};
exports.removeUnitPinData = removeUnitPinData;
const setMetadata = ({ path, value }, state) => {
    return (0, deepSet_1.default)(state, path, value);
};
exports.setMetadata = setMetadata;
const setUnitMetadata = ({ id, path, value }, state) => {
    return (0, deepSet_1.default)(state, ['units', id, 'metadata', ...path], value);
};
exports.setUnitMetadata = setUnitMetadata;
const renameUnitInMerges = (unitId, unitMerges, newUnitId) => {
    const newUnitMerges = {};
    (0, f_2.default)(unitMerges, (merge, mergeId) => {
        newUnitMerges[mergeId] = {};
        (0, f_2.default)(merge, (pins, _unitId) => {
            newUnitMerges[mergeId][unitId === _unitId ? newUnitId : _unitId] = pins;
        });
    });
    return newUnitMerges;
};
exports.renameUnitInMerges = renameUnitInMerges;
//# sourceMappingURL=spec.js.map