"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getSubgraphs = exports._getGraphTypeMap = exports.getGraphTypeMap = exports._getGraphTypeMapById = exports.getGraphTypeMapById = exports._leastSpecific = exports.leastSpecific = exports._mostSpecific = exports.mostSpecific = exports._isMoreSpecific = exports.isMoreSpecific = exports.createGenericTypeInterface = exports._getGraphTypeInterface = exports.getGraphTypeInterface = exports._getGraphTypeInterfaceById = exports._getBaseTypeInterface = exports._getBaseTypeInterfaceById = exports._getSpecTypeInterface = exports.typeTreeMapToTypeMap = exports.typeTreeToType = exports._getSpecTypeInterfaceById = exports.getSpecTypeInterfaceById = void 0;
const f_1 = require("../system/core/object/ForEachKeyValue/f");
const f_2 = require("../system/f/object/DeepMerge/f");
const f_3 = require("../system/f/object/Keys/f");
const IOOf_1 = require("../types/IOOf");
const object_1 = require("../util/object");
const emptyIO_1 = require("./emptyIO");
const parser_1 = require("./parser");
const spec_1 = require("./util/spec");
const getSpecTypeInterfaceById = (id, specs, cache = {}, visited = {}) => {
    const typeInterface = (0, exports._getSpecTypeInterfaceById)(id, specs, cache, visited);
    return (0, exports.typeTreeToType)(typeInterface);
};
exports.getSpecTypeInterfaceById = getSpecTypeInterfaceById;
const _getSpecTypeInterfaceById = (id, specs, cache = {}, visited = {}) => {
    let typeInterface;
    if (cache[id]) {
        typeInterface = cache[id];
    }
    else {
        const spec = specs[id];
        if (!spec) {
            throw new Error(`spec not found ${id}`);
        }
        typeInterface = (0, exports._getSpecTypeInterface)(spec, specs, cache, Object.assign(Object.assign({}, visited), { [id]: true }));
        cache[id] = typeInterface;
    }
    return typeInterface;
};
exports._getSpecTypeInterfaceById = _getSpecTypeInterfaceById;
const typeTreeToType = (typeTreeInterface) => {
    const { input, output } = typeTreeInterface;
    const typeInterface = (0, emptyIO_1.emptyIO)({}, {});
    for (const inputId in input) {
        const inputTree = input[inputId];
        typeInterface.input[inputId] = inputTree.value;
    }
    for (const outputId in output) {
        const outputTree = output[outputId];
        typeInterface.output[outputId] = outputTree.value;
    }
    return typeInterface;
};
exports.typeTreeToType = typeTreeToType;
const typeTreeMapToTypeMap = (graphTypeTree) => {
    return (0, object_1.mapObjVK)(graphTypeTree, (typeTree) => {
        return (0, exports.typeTreeToType)(typeTree);
    });
};
exports.typeTreeMapToTypeMap = typeTreeMapToTypeMap;
const _getSpecTypeInterface = (spec, specs, cache = {}, visited = {}) => {
    let typeInterface;
    const base = !!spec.base;
    if (base) {
        typeInterface = (0, exports._getBaseTypeInterface)(spec);
    }
    else {
        typeInterface = (0, exports._getGraphTypeInterface)(spec, specs, cache, visited);
    }
    return typeInterface;
};
exports._getSpecTypeInterface = _getSpecTypeInterface;
const _getBaseTypeInterfaceById = (id, specs) => {
    const spec = specs[id];
    return (0, exports._getBaseTypeInterface)(spec);
};
exports._getBaseTypeInterfaceById = _getBaseTypeInterfaceById;
const _getBaseTypeInterface = (spec) => {
    const typeInterface = (0, emptyIO_1.emptyIO)({}, {});
    (0, f_1.default)(spec.inputs, ({ type }, inputId) => {
        typeInterface.input[inputId] = (0, parser_1.getTree)(type);
    });
    (0, f_1.default)(spec.outputs, ({ type }, outputId) => {
        typeInterface.output[outputId] = (0, parser_1.getTree)(type);
    });
    return typeInterface;
};
exports._getBaseTypeInterface = _getBaseTypeInterface;
const _getGraphTypeInterfaceById = (id, specs, cache = {}, visited = {}) => {
    // console.log('_getGraphTypeInterfaceById', id)
    const spec = specs[id];
    return (0, exports._getGraphTypeInterface)(spec, specs, cache, visited);
};
exports._getGraphTypeInterfaceById = _getGraphTypeInterfaceById;
const getGraphTypeInterface = (spec, specs, cache = {}, visited = {}) => {
    const graphTypeTree = (0, exports._getGraphTypeInterface)(spec, specs, cache, visited);
    return (0, exports.typeTreeToType)(graphTypeTree);
};
exports.getGraphTypeInterface = getGraphTypeInterface;
const _getGraphTypeInterface = (spec, specs, cache = {}, visited = {}) => {
    // console.log('_getGraphTypeInterface')
    const typeInterface = (0, emptyIO_1.emptyIO)({}, {});
    const unitTypeMap = (0, exports._getGraphTypeMap)(spec, specs, cache, visited);
    const { inputs = {}, outputs = {} } = spec;
    (0, f_1.default)(inputs, ({ plug, type }, inputId) => {
        let inputType = (0, parser_1.getTree)('any');
        if (type) {
            inputType = (0, parser_1.getTree)(type);
        }
        else {
            (0, f_1.default)(plug, ({ mergeId, unitId, pinId, kind = 'input' }) => {
                var _a;
                let subPinType;
                if (mergeId) {
                    const merge = (_a = spec.merges[mergeId]) !== null && _a !== void 0 ? _a : {};
                    // AD HOC
                    if ((0, object_1.isEmptyObject)(merge)) {
                        return;
                    }
                    const mergeInputPin = (0, spec_1.findFirstMergePin)(merge, 'input');
                    subPinType = (0, object_1.deepGetOrDefault)(unitTypeMap, [mergeInputPin.unitId, 'input', mergeInputPin.pinId], undefined);
                }
                else if (unitId && pinId) {
                    subPinType = (0, object_1.deepGetOrDefault)(unitTypeMap, [unitId, kind, pinId], undefined);
                }
                if (subPinType) {
                    inputType = (0, exports._mostSpecific)(inputType, subPinType);
                }
            });
        }
        typeInterface.input[inputId] = inputType;
    });
    (0, f_1.default)(outputs, ({ plug, type }, outputId) => {
        let outputType = (0, parser_1.getTree)('any');
        if (type) {
            outputType = (0, parser_1.getTree)(type);
        }
        else {
            (0, f_1.default)(plug, ({ mergeId, unitId, pinId, kind = 'output' }) => {
                let subPinType;
                if (mergeId) {
                    const merge = spec.merges[mergeId];
                    let type;
                    (0, spec_1.forEachPinOnMerge)(merge, (unitId, kind, pinId) => {
                        if (kind === 'output') {
                            if (!type) {
                                type = (0, object_1.deepGetOrDefault)(unitTypeMap, [unitId, 'output', pinId], parser_1.ANY_TREE);
                            }
                            else {
                                type = (0, exports._leastSpecific)(type, (0, object_1.deepGetOrDefault)(unitTypeMap, [unitId, 'output', pinId], parser_1.ANY_TREE));
                            }
                        }
                    });
                    subPinType = type;
                }
                else if (unitId && pinId) {
                    subPinType = (0, object_1.deepGetOrDefault)(unitTypeMap, [unitId, kind, pinId], undefined);
                }
                if (subPinType) {
                    outputType = (0, exports._mostSpecific)(outputType, subPinType);
                }
            });
        }
        typeInterface.output[outputId] = outputType;
    });
    let i = 65;
    const replacement = {};
    const { input, output } = typeInterface;
    const inputPinIds = (0, f_3.keys)(input).sort();
    const outputPinIds = (0, f_3.keys)(output).sort();
    for (const inputPinId of inputPinIds) {
        const type = input[inputPinId];
        const generics = (0, parser_1._findGenerics)(type);
        for (const generic of generics) {
            if (!replacement[generic]) {
                replacement[generic] = `<${String.fromCharCode(i++)}>`;
            }
        }
        input[inputPinId] = (0, parser_1._applyGenerics)(type, replacement);
    }
    for (const outputPinId of outputPinIds) {
        const type = output[outputPinId];
        const generics = (0, parser_1._findGenerics)(type);
        for (const generic of generics) {
            if (!replacement[generic]) {
                replacement[generic] = `<${String.fromCharCode(i++)}>`;
            }
        }
        output[outputPinId] = (0, parser_1._applyGenerics)(type, replacement);
    }
    return typeInterface;
};
exports._getGraphTypeInterface = _getGraphTypeInterface;
const createGenericTypeInterface = (id, specs) => {
    // console.log('createGenericTypeInterface', id)
    const spec = specs[id];
    const typeInterface = (0, emptyIO_1.emptyIO)({}, {});
    let i = 0;
    const inputIds = (0, f_3.keys)(spec.inputs);
    const outputIds = (0, f_3.keys)(spec.outputs);
    function register(kind, pinId) {
        typeInterface[kind][pinId] = (0, parser_1.getTree)(`<${i}>`);
        i++;
    }
    inputIds.forEach((inputId) => register('input', inputId));
    outputIds.forEach((outputId) => register('output', outputId));
    return typeInterface;
};
exports.createGenericTypeInterface = createGenericTypeInterface;
const isMoreSpecific = (a, b) => {
    const aTree = (0, parser_1.getTree)(a);
    const bTree = (0, parser_1.getTree)(b);
    return (0, exports._isMoreSpecific)(aTree, bTree);
};
exports.isMoreSpecific = isMoreSpecific;
const _isMoreSpecific = (a, b) => {
    if (a.value === 'any') {
        return false;
    }
    else if (b.value === 'any') {
        return true;
    }
    const aGenericCount = (0, parser_1._findGenerics)(a).size;
    const bGenericCount = (0, parser_1._findGenerics)(b).size;
    if (aGenericCount > bGenericCount) {
        return false;
    }
    else if (aGenericCount < bGenericCount) {
        return true;
    }
    else {
        if (a.type === parser_1.TreeNodeType.Class && b.type === parser_1.TreeNodeType.Class) {
            return !(0, parser_1.checkClassInheritance)(a.value, b.value);
        }
        if (a.children.length > b.children.length) {
            return true;
        }
        else if (a.children.length < b.children.length) {
            return false;
        }
        else if (a.value < b.value) {
            return true;
        }
        else {
            return false;
        }
    }
};
exports._isMoreSpecific = _isMoreSpecific;
const mostSpecific = (a, b) => {
    if ((0, exports.isMoreSpecific)(a, b)) {
        return a;
    }
    else {
        return b;
    }
};
exports.mostSpecific = mostSpecific;
const _mostSpecific = (a, b) => {
    if ((0, exports._isMoreSpecific)(a, b)) {
        return a;
    }
    else {
        return b;
    }
};
exports._mostSpecific = _mostSpecific;
const leastSpecific = (a, b) => {
    if ((0, exports.isMoreSpecific)(a, b)) {
        return b;
    }
    else {
        return a;
    }
};
exports.leastSpecific = leastSpecific;
const _leastSpecific = (a, b) => {
    if ((0, exports._isMoreSpecific)(a, b)) {
        return b;
    }
    else {
        return a;
    }
};
exports._leastSpecific = _leastSpecific;
const getGraphTypeMapById = (id, specs, cache = {}, visited = {}) => {
    const typeTreeMap = (0, exports._getGraphTypeMapById)(id, specs, cache, visited);
    return (0, exports.typeTreeMapToTypeMap)(typeTreeMap);
};
exports.getGraphTypeMapById = getGraphTypeMapById;
const _getGraphTypeMapById = (id, specs, cache = {}, visited = {}) => {
    const spec = specs[id];
    // console.log('_getGraphTypeMapById', id)
    return (0, exports._getGraphTypeMap)(spec, specs, cache, Object.assign(Object.assign({}, visited), { [id]: true }));
};
exports._getGraphTypeMapById = _getGraphTypeMapById;
const getGraphTypeMap = (spec, specs, cache = {}, visited = {}) => {
    const typeTreeMap = (0, exports._getGraphTypeMap)(spec, specs, cache, visited);
    return (0, exports.typeTreeMapToTypeMap)(typeTreeMap);
};
exports.getGraphTypeMap = getGraphTypeMap;
const _getGraphTypeMap = (spec, specs, cache = {}, visited = {}) => {
    const typeMap = {};
    const subgraphs = (0, exports.getSubgraphs)(spec);
    const { units = {}, merges = {}, outputs = {}, inputs = {} } = (0, object_1.clone)(spec);
    subgraphs.forEach((subgraph) => {
        const { unit, merge } = subgraph;
        let charCode = 65;
        const replacement = {};
        (0, f_1.default)(unit, (_, unitId) => {
            const unitSpec = units[unitId];
            // AD HOC
            if (!unitSpec) {
                return;
            }
            const { id, input = {} } = unitSpec;
            let unitTypeInterface;
            if (visited[id]) {
                unitTypeInterface = (0, exports.createGenericTypeInterface)(id, specs);
            }
            else {
                unitTypeInterface = (0, object_1.clone)((0, exports._getSpecTypeInterfaceById)(id, specs, cache, visited));
            }
            replacement[unitId] = {};
            function register(type, kind, pinId) {
                if ((0, parser_1._hasGeneric)(type)) {
                    const generics = (0, parser_1._findGenerics)(type);
                    for (const generic of generics) {
                        if (!replacement[unitId][generic]) {
                            replacement[unitId][generic] = `<${String.fromCharCode(charCode++)}>`;
                        }
                    }
                    unitTypeInterface[kind][pinId] = (0, parser_1._applyGenerics)(type, replacement[unitId]);
                }
            }
            (0, f_1.default)(unitTypeInterface.input, (type, inputId) => {
                if (input[inputId] &&
                    input[inputId].constant &&
                    input[inputId].data !== undefined) {
                    if ((0, parser_1._hasGeneric)(type)) {
                        const dataStr = unitSpec.input[inputId].data;
                        const dataTree = (0, parser_1.getTree)(dataStr);
                        const dataTypeTree = (0, parser_1._getValueType)(specs, dataTree);
                        if (!(0, parser_1._hasGeneric)(dataTypeTree)) {
                            unitTypeInterface['input'][inputId] = dataTypeTree;
                            const extracted_generic = (0, parser_1._extractGenerics)(specs, dataTypeTree, type);
                            (0, f_1.default)(extracted_generic, (value, generic) => {
                                replacement[unitId][generic] = value;
                            });
                        }
                    }
                }
            });
            (0, f_1.default)(unitTypeInterface.input, (type, inputId) => {
                register(type, 'input', inputId);
            });
            (0, f_1.default)(unitTypeInterface.output, (type, outputId) => register(type, 'output', outputId));
            typeMap[unitId] = unitTypeInterface;
        });
        const equivalence = [];
        const equivalence_index = {};
        let i = 0;
        const create_set_equivalent = () => {
            let equivalence_set = new Set();
            let merged = false;
            function set_equivalent(unitId, kind, pinId) {
                const type = (0, object_1.deepGetOrDefault)(typeMap, [unitId, kind, pinId], undefined);
                // AD HOC
                if (!type) {
                    return;
                }
                // do not set equivalence to any
                if (type.type === parser_1.TreeNodeType.Any) {
                    return;
                }
                if (equivalence_index[type.value] === undefined) {
                    equivalence_set.add(type.value);
                    if ((0, parser_1._isGeneric)(type)) {
                        equivalence_index[type.value] = i;
                    }
                }
                else {
                    merged = true;
                    i = equivalence_index[type.value];
                    equivalence[i] = new Set([
                        ...(equivalence[i] || []),
                        ...equivalence_set,
                    ]);
                    equivalence_set.forEach((j) => {
                        equivalence_index[j] = i;
                    });
                    equivalence_set = equivalence[i];
                }
                if (!merged) {
                    equivalence.push(equivalence_set);
                }
                i = equivalence.length;
            }
            return set_equivalent;
        };
        const set_merge_equivalence = (mergeId, set_equivalent) => {
            const mergeSpec = merges[mergeId];
            (0, spec_1.forEachPinOnMerge)(mergeSpec, (unitId, kind, pinId) => {
                set_equivalent(unitId, kind, pinId);
            });
        };
        const set_exposed_equivalence = (kind, pinId, set_equivalent) => {
            const { plug } = spec[`${kind}s`][pinId];
            (0, f_1.default)(plug, ({ unitId, pinId, mergeId }) => {
                if (mergeId) {
                    set_merge_equivalence(mergeId, set_equivalent);
                }
                else if (unitId && pinId) {
                    set_equivalent(unitId, kind, pinId);
                }
            });
        };
        (0, f_1.default)(merge, (_, mergeId) => {
            const set_equivalent = create_set_equivalent();
            set_merge_equivalence(mergeId, set_equivalent);
        });
        (0, f_1.default)(inputs, (_, inputId) => {
            const set_equivalent = create_set_equivalent();
            set_exposed_equivalence('input', inputId, set_equivalent);
        });
        (0, f_1.default)(outputs, (_, outputId) => {
            const set_equivalent = create_set_equivalent();
            set_exposed_equivalence('output', outputId, set_equivalent);
        });
        const specific = equivalence.map((equivalence_set) => {
            let theMostSpecific = undefined;
            for (const t of equivalence_set) {
                if (theMostSpecific === undefined) {
                    theMostSpecific = t;
                }
                else {
                    theMostSpecific = (0, exports.mostSpecific)(theMostSpecific, t);
                }
            }
            return theMostSpecific;
        });
        const generic_to_substitute = {};
        equivalence.forEach((equivalence_set, index) => {
            for (const t of equivalence_set) {
                const theMostSpecific = specific[index];
                const extracted = (0, parser_1.extractGenerics)(specs, theMostSpecific, t);
                for (const generic in extracted) {
                    const extract = extracted[generic];
                    let substitution;
                    if (generic_to_substitute[generic] === undefined) {
                        substitution = extract;
                    }
                    else {
                        const prev_substitution = generic_to_substitute[generic];
                        substitution = (0, exports.mostSpecific)(extract, prev_substitution);
                        if (!(0, parser_1.isGeneric)(substitution)) {
                            if (substitution !== extract) {
                                for (const g in generic_to_substitute) {
                                    generic_to_substitute[g] = (0, parser_1.applyGenerics)(generic_to_substitute[g], {
                                        [extract]: substitution,
                                    });
                                }
                            }
                            if (prev_substitution !== substitution) {
                                for (const gen in generic_to_substitute) {
                                    generic_to_substitute[gen] = (0, parser_1.applyGenerics)(generic_to_substitute[gen], {
                                        [prev_substitution]: substitution,
                                    });
                                }
                            }
                        }
                    }
                    generic_to_substitute[generic] = substitution;
                }
            }
        });
        const substitute_replacement = {};
        const generic_to_substitute_ = {};
        charCode = 65;
        (0, f_1.default)(generic_to_substitute, (value, key) => {
            generic_to_substitute_[key] = value;
            const generics = (0, parser_1.findGenerics)(value);
            for (const generic of generics) {
                if (!substitute_replacement[generic]) {
                    substitute_replacement[generic] = `<${String.fromCharCode(charCode++)}>`;
                }
                generic_to_substitute_[key] = (0, parser_1.applyGenerics)(value, substitute_replacement);
            }
        });
        (0, f_1.default)(unit, (_, unitId) => {
            const unitTypeMap = typeMap[unitId];
            if (!unitTypeMap) {
                return;
                // throw new Error('type map not found')
            }
            (0, IOOf_1.io)((kind) => {
                const pinTypeMap = unitTypeMap[kind];
                (0, f_1.default)(pinTypeMap, (type, pinId) => {
                    const nextType = (0, parser_1._applyGenerics)(type, generic_to_substitute_);
                    unitTypeMap[kind][pinId] = nextType;
                });
            });
        });
    });
    return typeMap;
};
exports._getGraphTypeMap = _getGraphTypeMap;
const getSubgraphs = (spec) => {
    const index_to_subgraph = [];
    const id_to_index = { unit: {}, merge: {} };
    const { units = {}, merges = {} } = spec;
    let i = 0;
    (0, f_1.default)(merges, (merge, mergeId) => {
        let subgraph = { unit: {}, merge: {} };
        subgraph.merge[mergeId] = true;
        let merged = false;
        let index = i;
        (0, f_1.default)(merge, (mergeUnit, unitId) => {
            // empty merge
            if ((0, object_1.isEmptyObject)(mergeUnit)) {
                return;
            }
            subgraph.unit[unitId] = true;
            const unit_index = id_to_index.unit[unitId];
            if (unit_index !== undefined) {
                merged = true;
                if (unit_index < index) {
                    (0, f_1.default)(subgraph.unit, (_, unitId) => {
                        id_to_index.unit[unitId] = unit_index;
                    });
                    (0, f_1.default)(subgraph.merge, (_, mergeId) => {
                        id_to_index.merge[mergeId] = unit_index;
                    });
                    subgraph = (0, f_2.default)(index_to_subgraph[unit_index], subgraph);
                    index_to_subgraph.splice(index, 1);
                    index = unit_index;
                }
            }
            id_to_index.unit[unitId] = index;
        });
        if (!merged) {
            i++;
        }
        id_to_index.merge[mergeId] = index;
        index_to_subgraph[index] = subgraph;
    });
    (0, f_1.default)(units, (_, unitId) => {
        if (id_to_index.unit[unitId] === undefined) {
            const subgraph = { unit: { [unitId]: true }, merge: {} };
            index_to_subgraph.push(subgraph);
            id_to_index.unit[unitId] = i;
            i++;
        }
    });
    return index_to_subgraph;
};
exports.getSubgraphs = getSubgraphs;
//# sourceMappingURL=type.js.map