"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.hasDatum = exports.hasPinId = exports.shouldExposePin = exports.findUnitAtPath = exports.getUnitSpec = exports.findSpecAtPath = exports.findUnitPinPlug = exports.countMergePlugs = exports.findMergePlugs = exports.findMergePlugOfType = exports.hasPlug = exports.hasPin = exports.hasMergePin = exports.hasMerge = exports.hasUnit = exports.forEachGraphSpecPinOfType = exports.forEachGraphSpecPinType = exports.forEachGraphSpecPinPlug = exports.forEachGraphSpecPin = exports.forEachSpecPin = exports.forEachOutputOnMerge = exports.forEachInputOnMerge = exports.forEachPinOnMerge = exports.forEachPinOnMerges = exports.getMergeUnitTypePinCount = exports.getMergeUnitPinCount = exports.getMergeTypePinCount = exports.getMergePinTypeCount = exports.getMergePinCount = exports.getPlugCount = exports.findPinMergeId = exports.getSubPinSpec = exports.getPlugSpecs = exports.getSpecPinIcon = exports.getPinSpec = exports.getUnitExposedPins = exports.getExposePinSpec = exports.getExposedPinSpecs = exports.findFirstMergePin = exports.isSelfPin = exports.isUnitPinRef = exports.isMergeRef = exports.isPinRef = void 0;
exports.isValidSpecName = isValidSpecName;
exports.getPinNodeId = getPinNodeId;
exports.isUnitPinConstant = isUnitPinConstant;
exports.isUnitPinIgnored = isUnitPinIgnored;
exports.isUnitComponent = isUnitComponent;
exports.getInputNodeId = getInputNodeId;
exports.getOutputNodeId = getOutputNodeId;
exports.getMergePinNodeId = getMergePinNodeId;
exports.opposite = opposite;
exports.findUnitPlugs = findUnitPlugs;
exports.isSubPinSpecRef = isSubPinSpecRef;
exports.isPinSpecRef = isPinSpecRef;
exports.findUnitMerges = findUnitMerges;
exports.buildUnitPinToPlug = buildUnitPinToPlug;
exports.buildUnitPinToMerge = buildUnitPinToMerge;
exports.getUnit = getUnit;
exports.getMerge = getMerge;
exports.getUnitPinDatum = getUnitPinDatum;
exports.getUnitInputDatum = getUnitInputDatum;
exports.isEmptyMerge = isEmptyMerge;
const SELF_1 = require("../../constant/SELF");
const deepSet_1 = require("../../deepSet");
const f_1 = __importDefault(require("../../system/core/object/ForEachKeyValue/f"));
const f_2 = require("../../system/core/object/KeyCount/f");
const object_1 = require("../../util/object");
const util_1 = require("../util");
function isValidSpecName(name) {
    return !!/^[A-Za-z_ ][A-Za-z\d_ ]*$/g.exec(name);
}
function getPinNodeId(unitId, type, pinId) {
    return `${unitId}/${type}/${pinId}`;
}
function isUnitPinConstant(spec, unitId, type, pinId) {
    return (0, object_1.deepGetOrDefault)(spec, ['units', unitId, type, pinId, 'constant'], false);
}
function isUnitPinIgnored(spec, unitId, type, pinId) {
    return (0, object_1.deepGetOrDefault)(spec, ['units', unitId, type, pinId, 'ignored'], false);
}
function isUnitComponent(specs, spec, unitId) {
    const { units } = spec;
    const unit = units[unitId];
    return (0, util_1.isComponentId)(specs, unit.id);
}
const isPinRef = (specs, spec, type, pinId, visited = new Set()) => {
    if ((0, exports.isSelfPin)(type, pinId)) {
        return true;
    }
    const key = `${spec.id}/${type}/${pinId}`;
    if (visited.has(key)) {
        return false;
    }
    visited.add(key);
    const pinSpec = (0, object_1.deepGetOrDefault)(spec, [`${type}s`, pinId], {});
    let { ref } = pinSpec;
    if (ref === undefined) {
        const { plug = {} } = pinSpec;
        for (const subPinId in plug) {
            const subPin = plug[subPinId];
            if (isSubPinSpecRef(specs, spec, type, subPin, visited)) {
                ref = true;
                break;
            }
        }
    }
    return ref;
};
exports.isPinRef = isPinRef;
const isMergeRef = (specs, spec, mergeId, visited = new Set()) => {
    let ref = false;
    const merge = getMerge(spec, mergeId);
    (0, exports.forEachPinOnMerge)(merge, (unitId, type, pinId) => {
        ref = ref || (0, exports.isUnitPinRef)(specs, spec, unitId, type, pinId, visited);
    });
    return ref;
};
exports.isMergeRef = isMergeRef;
const isUnitPinRef = (specs, spec, unitId, type, pinId, visited = new Set()) => {
    const unit = getUnit(spec, unitId);
    const unitSpec = (0, util_1.getSpec)(specs, unit.id);
    return (0, exports.isPinRef)(specs, unitSpec, type, pinId, visited);
};
exports.isUnitPinRef = isUnitPinRef;
const isSelfPin = (type, pinId) => {
    return type === 'output' && pinId === SELF_1.SELF;
};
exports.isSelfPin = isSelfPin;
function getInputNodeId(unitId, pinId) {
    return getPinNodeId(unitId, 'input', pinId);
}
function getOutputNodeId(unitId, pinId) {
    return getPinNodeId(unitId, 'output', pinId);
}
function getMergePinNodeId(mergeId, type) {
    return `${mergeId}/${type}`;
}
function opposite(kind) {
    return kind === 'input' ? 'output' : 'input';
}
const findFirstMergePin = (merge, type) => {
    for (const unitId in merge) {
        if (merge[unitId][type]) {
            for (const pinId in merge[unitId][type]) {
                return {
                    unitId,
                    pinId,
                };
            }
        }
    }
};
exports.findFirstMergePin = findFirstMergePin;
const getExposedPinSpecs = (graph) => {
    return {
        input: graph.inputs,
        output: graph.outputs,
    };
};
exports.getExposedPinSpecs = getExposedPinSpecs;
const getExposePinSpec = (graph, type, pinId) => {
    return (0, object_1.deepGetOrDefault)(graph, [`${type}s`, pinId], undefined);
};
exports.getExposePinSpec = getExposePinSpec;
const getUnitExposedPins = (graph, unitId) => {
    const pins = {
        input: {},
        output: {},
    };
    (0, exports.forEachGraphSpecPin)(graph, (type, pinId, pinSpec) => {
        const { plug = {} } = pinSpec;
        for (const subPinId in plug) {
            const subPinSpec = plug[subPinId];
            const { unitId: unitId_, pinId: pinId_, mergeId, kind = type, } = subPinSpec;
            if (unitId_ === unitId) {
                (0, object_1.deepSet)(pins, [kind, pinId_], {
                    type,
                    kind,
                    pinId,
                    subPinId,
                });
            }
            else if (mergeId) {
                //
            }
        }
    });
    return pins;
};
exports.getUnitExposedPins = getUnitExposedPins;
const getPinSpec = (graph, type, pinId) => {
    return (0, object_1.deepGetOrDefault)(graph, [`${type}s`, pinId], null);
};
exports.getPinSpec = getPinSpec;
const getSpecPinIcon = (specs, graph, type, pinId) => {
    let pin_icon_name = null;
    if ((0, exports.isSelfPin)(type, pinId)) {
        return graph.metadata.icon;
    }
    const pin_spec = (0, exports.getPinSpec)(graph, type, pinId);
    const { plug, icon } = pin_spec;
    if (icon) {
        pin_icon_name = icon;
    }
    else if (plug) {
        const sub_pin_id = (0, object_1.getObjSingleKey)(plug);
        if (!sub_pin_id) {
            return null;
        }
        const sub_pin = plug[sub_pin_id];
        const { unitId, pinId, mergeId, kind = type } = sub_pin;
        const { units = {}, merges = {} } = graph;
        let plugUnitId;
        let plugPinId;
        if (unitId && pinId) {
            plugUnitId = unitId;
            plugPinId = pinId;
        }
        else {
            const merge = merges[mergeId];
            for (const unitId in merge) {
                const mergeUnit = merge[unitId];
                const { output = {} } = mergeUnit;
                for (const output_id in output) {
                    plugUnitId = unitId;
                    plugPinId = output_id;
                    break;
                }
            }
        }
        const unit = units[plugUnitId];
        if (unit) {
            const { id } = unit;
            const unit_spec = (0, util_1.getSpec)(specs, id);
            if (type === 'output' && pinId === SELF_1.SELF) {
                pin_icon_name = unit_spec.metadata?.icon ?? null;
            }
            else {
                pin_icon_name = unit_spec?.[`${type}s`][plugPinId]?.icon ?? null;
            }
            if (!pin_icon_name) {
                return (0, exports.getSpecPinIcon)(specs, unit_spec, kind, plugPinId);
            }
        }
    }
    else {
        const { icon } = pin_spec;
        pin_icon_name = icon || 'triangle';
    }
    return pin_icon_name;
};
exports.getSpecPinIcon = getSpecPinIcon;
const getPlugSpecs = (spec) => {
    const plugs = {
        input: {},
        output: {},
    };
    (0, exports.forEachGraphSpecPinPlug)(spec, (type, pinId, pinSpecs, subPinId, subPinSpec) => {
        const plug = (0, exports.getSubPinSpec)(spec, type, pinId, subPinId);
        (0, object_1.deepSet)(plugs, [type, pinId, subPinId], plug);
    });
    return plugs;
};
exports.getPlugSpecs = getPlugSpecs;
const getSubPinSpec = (graph, type, pinId, subPinId) => {
    return (0, object_1.deepGetOrDefault)(graph, [`${type}s`, pinId, 'plug', subPinId], null);
};
exports.getSubPinSpec = getSubPinSpec;
const findPinMergeId = (spec, unitId, type, pinId) => {
    const { merges = {} } = spec;
    for (const mergeId in merges) {
        const merge = merges[mergeId];
        if (merge?.[unitId]?.[type]?.[pinId]) {
            return mergeId;
        }
    }
};
exports.findPinMergeId = findPinMergeId;
const getPlugCount = (spec, type, pinId) => {
    return (0, f_2.keyCount)((0, object_1.deepGetOrDefault)(spec, [`${type}s`, pinId, 'plug'], {}));
};
exports.getPlugCount = getPlugCount;
const getMergePinCount = (merge) => {
    return (0, object_1.reduceObj)(merge, (count, mergeUnit) => {
        return count + (0, exports.getMergeUnitPinCount)(mergeUnit);
    }, 0);
};
exports.getMergePinCount = getMergePinCount;
const getMergePinTypeCount = (merge, type) => {
    return (0, object_1.reduceObj)(merge, (count, mergeUnit) => {
        return count + (0, exports.getMergeUnitTypePinCount)(mergeUnit, type);
    }, 0);
};
exports.getMergePinTypeCount = getMergePinTypeCount;
const getMergeTypePinCount = (merge, type) => {
    return (0, object_1.reduceObj)(merge, (count, mergeUnit) => {
        return count + (0, exports.getMergeUnitTypePinCount)(mergeUnit, type);
    }, 0);
};
exports.getMergeTypePinCount = getMergeTypePinCount;
const getMergeUnitPinCount = (mergeUnit) => {
    return ((0, exports.getMergeUnitTypePinCount)(mergeUnit, 'input') +
        (0, exports.getMergeUnitTypePinCount)(mergeUnit, 'output'));
};
exports.getMergeUnitPinCount = getMergeUnitPinCount;
const getMergeUnitTypePinCount = (mergeUnit, type) => {
    const { [type]: pins = {} } = mergeUnit;
    const pinCount = (0, object_1._keyCount)(pins);
    return pinCount;
};
exports.getMergeUnitTypePinCount = getMergeUnitTypePinCount;
const forEachPinOnMerges = (merges, callback) => {
    (0, f_1.default)(merges || {}, (merge, mergeId) => {
        (0, exports.forEachPinOnMerge)(merge, (unitId, type, pinId, data) => callback(mergeId, unitId, type, pinId, data));
    });
};
exports.forEachPinOnMerges = forEachPinOnMerges;
const forEachPinOnMerge = (merge, callback) => {
    (0, f_1.default)(merge, ({ input, output }, unitId) => {
        (0, f_1.default)(input || {}, (_, inputId) => {
            callback(unitId, 'input', inputId, _);
        });
        (0, f_1.default)(output || {}, (_, outputId) => {
            callback(unitId, 'output', outputId, _);
        });
    });
};
exports.forEachPinOnMerge = forEachPinOnMerge;
const forEachInputOnMerge = (merge, callback) => {
    (0, f_1.default)(merge, ({ input }, unitId) => {
        (0, f_1.default)(input || {}, (_, inputId) => {
            callback(unitId, inputId, _);
        });
    });
};
exports.forEachInputOnMerge = forEachInputOnMerge;
const forEachOutputOnMerge = (merge, callback) => {
    (0, f_1.default)(merge, ({ output }, unitId) => {
        (0, f_1.default)(output || {}, (_, outputId) => {
            callback(unitId, 'output', outputId, _);
        });
    });
};
exports.forEachOutputOnMerge = forEachOutputOnMerge;
const forEachSpecPin = (spec, callback) => {
    const { inputs = {}, outputs = {} } = spec;
    (0, f_1.default)(inputs, (inputSpec, inputId) => {
        callback('input', inputId, inputSpec);
    });
    (0, f_1.default)(outputs, (outputSpec, outputId) => {
        callback('output', outputId, outputSpec);
    });
};
exports.forEachSpecPin = forEachSpecPin;
const forEachGraphSpecPin = (spec, callback) => {
    (0, exports.forEachGraphSpecPinOfType)(spec, 'input', (inputId, inputSpec) => {
        callback('input', inputId, inputSpec);
    });
    (0, exports.forEachGraphSpecPinOfType)(spec, 'output', (outputSpec, outputId) => {
        callback('output', outputSpec, outputId);
    });
};
exports.forEachGraphSpecPin = forEachGraphSpecPin;
const forEachGraphSpecPinPlug = (spec, callback) => {
    (0, exports.forEachGraphSpecPin)(spec, (type, pinId, pinSpec) => {
        for (const subPinId in pinSpec.plug ?? {}) {
            const subPinSpec = pinSpec.plug[subPinId];
            callback(type, pinId, pinSpec, subPinId, subPinSpec);
        }
    });
};
exports.forEachGraphSpecPinPlug = forEachGraphSpecPinPlug;
const forEachGraphSpecPinType = (spec, type, callback) => {
    (0, exports.forEachGraphSpecPinOfType)(spec, type, (pinId, pinSpec) => {
        callback(type, pinId, pinSpec);
    });
};
exports.forEachGraphSpecPinType = forEachGraphSpecPinType;
const forEachGraphSpecPinOfType = (spec, type, callback) => {
    (0, f_1.default)(spec[`${type}s`] ?? {}, (pinSpec, pinId) => {
        callback(pinId, pinSpec);
    });
};
exports.forEachGraphSpecPinOfType = forEachGraphSpecPinOfType;
const hasUnit = (spec, unitId) => {
    return !!spec.units?.[unitId];
};
exports.hasUnit = hasUnit;
const hasMerge = (spec, mergeId) => {
    return !!spec.merges?.[mergeId];
};
exports.hasMerge = hasMerge;
const hasMergePin = (spec, mergeId, unitId, type, pinId) => {
    return !!(0, object_1.deepGetOrDefault)(spec, ['merges', mergeId, unitId, type, pinId], false);
};
exports.hasMergePin = hasMergePin;
const hasPin = (spec, type, pinId) => {
    return !!(0, object_1.deepGetOrDefault)(spec, [`${type}s`, pinId], false);
};
exports.hasPin = hasPin;
const hasPlug = (spec, type, pinId, subPinId) => {
    return !!(0, object_1.deepGetOrDefault)(spec, [`${type}s`, pinId, 'plug', subPinId], false);
};
exports.hasPlug = hasPlug;
const findMergePlugOfType = (spec, type, mergeId) => {
    const mergePlugs = (0, exports.findMergePlugs)(spec, mergeId);
    const mergePlugOfType = mergePlugs[type];
    return mergePlugOfType;
};
exports.findMergePlugOfType = findMergePlugOfType;
const findMergePlugs = (spec, mergeId) => {
    const mergePlugs = {
        input: [],
        output: [],
    };
    (0, exports.forEachGraphSpecPin)(spec, (type, pinId, pinSpec) => {
        const { plug = {} } = pinSpec;
        for (const subPinId in plug) {
            const subPinSpec = plug[subPinId];
            if (subPinSpec.mergeId === mergeId) {
                mergePlugs[type].push({
                    pinId,
                    type,
                    subPinId,
                    pinSpec,
                    subPinSpec,
                });
            }
        }
    });
    return mergePlugs;
};
exports.findMergePlugs = findMergePlugs;
const countMergePlugs = (spec, mergeId) => {
    const mergePlugs = (0, exports.findMergePlugs)(spec, mergeId);
    const count = mergePlugs.input.length + mergePlugs.output.length;
    return count;
};
exports.countMergePlugs = countMergePlugs;
const findUnitPinPlug = (spec, unitId, type, pinId) => {
    let pinPlug = null;
    (0, exports.forEachGraphSpecPinOfType)(spec, type, (_pinId, pinSpec) => {
        const { plug = {} } = pinSpec;
        for (const subPinId in plug) {
            const subPinSpec = plug[subPinId];
            if (subPinSpec.unitId === unitId && subPinSpec.pinId === pinId) {
                pinPlug = {
                    pinId: _pinId,
                    type,
                    subPinId,
                    pinSpec,
                    subPinSpec,
                };
            }
        }
    });
    return pinPlug;
};
exports.findUnitPinPlug = findUnitPinPlug;
function findUnitPlugs(spec, unitId) {
    const unitPlugs = {
        input: {},
        output: {},
    };
    (0, exports.forEachGraphSpecPinPlug)(spec, (type, pinId, pinSpec, subPinId, subPinSpec) => {
        if (subPinSpec.unitId === unitId) {
            (0, object_1.deepPush)(unitPlugs, [subPinSpec.kind ?? type, subPinSpec.pinId], {
                type,
                pinId,
                pinSpec,
                subPinId,
                subPinSpec,
            });
        }
    });
    return unitPlugs;
}
const findSpecAtPath = (specs, spec, path) => {
    if (path.length === 0) {
        return spec;
    }
    else {
        const [unitId, ...rest] = path;
        const unitSpec = (0, exports.getUnitSpec)(specs, spec, unitId);
        return (0, exports.findSpecAtPath)(specs, unitSpec, rest);
    }
};
exports.findSpecAtPath = findSpecAtPath;
const getUnitSpec = (specs, spec, unitId) => {
    const unit = spec.units[unitId];
    const unitSpec = (0, util_1.getSpec)(specs, unit.id);
    return unitSpec;
};
exports.getUnitSpec = getUnitSpec;
const findUnitAtPath = (specs, spec, path, unit) => {
    if (path.length === 0) {
        return unit;
    }
    else {
        const [unitId, ...rest] = path;
        const unit = spec.units[unitId];
        const unitSpec = (0, util_1.getSpec)(specs, unit.id);
        return (0, exports.findUnitAtPath)(specs, unitSpec, rest, unit);
    }
};
exports.findUnitAtPath = findUnitAtPath;
const shouldExposePin = (type, pinId, pinSpec, subPinId, subPinSpec, merged, outerPlug = null) => {
    if (merged || outerPlug) {
        return false;
    }
    if ((0, f_2.keyCount)(pinSpec.plug ?? {}) > 1) {
        return true;
    }
    if (subPinSpec.pinId === pinId) {
        return false;
    }
    return true;
};
exports.shouldExposePin = shouldExposePin;
function isSubPinSpecRef(specs, spec, type, subPinSpec, visited) {
    let ref = false;
    if (subPinSpec.unitId && subPinSpec.pinId) {
        const { kind = type } = subPinSpec;
        const unit = spec.units[subPinSpec.unitId];
        const unitSpec = (0, util_1.getSpec)(specs, unit.id);
        ref = (0, exports.isPinRef)(specs, unitSpec, kind, subPinSpec.pinId, visited);
    }
    else if (subPinSpec.mergeId) {
        const merge = spec.merges[subPinSpec.mergeId];
        (0, exports.forEachPinOnMerge)(merge, (unitId, type, pinId) => {
            const unit = spec.units[unitId];
            const unitSpec = (0, util_1.getSpec)(specs, unit.id);
            if (ref === true) {
                return;
            }
            ref = (0, exports.isPinRef)(specs, unitSpec, type, pinId, visited);
        });
    }
    return ref;
}
function isPinSpecRef(specs, spec, type, pinSpec, visited) {
    const { plug = {}, ref } = pinSpec;
    if (ref !== undefined) {
        return ref;
    }
    for (const subPinId in plug) {
        const subPinSpec = plug[subPinId];
        if (isSubPinSpecRef(specs, spec, type, subPinSpec, visited)) {
            return true;
        }
    }
    return false;
}
function findUnitMerges(spec, unitId) {
    const { merges } = spec;
    const unit_merges = {};
    for (const mergeId in merges) {
        const merge = merges[mergeId];
        if (merge[unitId]) {
            unit_merges[mergeId] = merge;
        }
    }
    return unit_merges;
}
function buildUnitPinToPlug(source, unitId) {
    const pinToPlug = {};
    (0, exports.forEachGraphSpecPin)(source, (type, pinId, pinSpec) => {
        const { plug = {} } = pinSpec;
        for (const subPinId in plug) {
            const subPinSpec = plug[subPinId];
            if (subPinSpec.unitId && subPinSpec.pinId) {
                if (subPinSpec.unitId === unitId) {
                    (0, deepSet_1.deepSet_)(pinToPlug, [subPinSpec.kind ?? type, subPinSpec.pinId], {
                        type,
                        pinId,
                        pinSpec,
                        subPinSpec,
                    });
                }
            }
            else if (subPinSpec.mergeId) {
                const merge = getMerge(source, subPinSpec.mergeId);
                (0, exports.forEachPinOnMerge)(merge, (unitId_, type_, pinId_) => {
                    if (unitId_ === unitId) {
                        (0, deepSet_1.deepSet_)(pinToPlug, [type_, pinId_], {
                            type,
                            pinId,
                            pinSpec,
                            subPinSpec,
                        });
                    }
                });
            }
        }
    });
    return pinToPlug;
}
function buildUnitPinToMerge(source, unitId) {
    const pinToMerge = {};
    const merges = findUnitMerges(source, unitId);
    (0, exports.forEachPinOnMerges)(merges, (mergeId, unitId_, type_, pinId_) => {
        if (unitId_ === unitId) {
            (0, deepSet_1.deepSet_)(pinToMerge, [type_, pinId_], mergeId);
        }
    });
    return pinToMerge;
}
function getUnit(spec, unitId) {
    const { units } = spec;
    const unit = units[unitId];
    return unit;
}
function getMerge(spec, mergeId) {
    const { merges = {} } = spec;
    const merge = merges[mergeId];
    return merge;
}
function getUnitPinDatum(spec, unitId, type, pinId) {
    return (0, object_1.deepGetOrDefault)(spec, ['units', unitId, type, pinId, 'data'], undefined);
}
function getUnitInputDatum(spec, unitId, pinId) {
    return getUnitPinDatum(spec, unitId, 'input', pinId);
}
function isEmptyMerge(mergeSpec) {
    return (0, exports.getMergePinCount)(mergeSpec) === 0;
}
const hasPinId = (spec, type, pinId) => {
    return !!(spec[`${type}s`] || {})[pinId];
};
exports.hasPinId = hasPinId;
const hasDatum = (spec, datumId) => {
    return !!spec.data?.[datumId];
};
exports.hasDatum = hasDatum;
//# sourceMappingURL=spec.js.map