import { isDefined } from '@takeshape/util';
import mapValues from 'lodash/mapValues.js';
import { getRef, getShapeByRef, normalizeRefExpression, parseRef, refItemToNamespacedShapeName, refItemToShape, shapeToRefItem } from "./refs.js";
import { isUnionSchema } from "./unions.js";
export function isInterfaceShape(shape) {
    return shape?.type === 'interface';
}
export function isInterfaceRef(context, propertySchema) {
    const ref = getRef(context, propertySchema);
    return Boolean(ref && isInterfaceShape(refItemToShape(context, ref)));
}
function add(record, key, value) {
    if (!record[key]) {
        record[key] = new Set([value]);
    }
    else {
        record[key].add(value);
    }
}
function getImplementationsHelper(shapeMap, mapFn) {
    const result = {};
    for (const shape of Object.values(shapeMap)) {
        for (const interfaceName of shape.interfaces ?? []) {
            add(result, interfaceName, mapFn(shape));
        }
    }
    return result;
}
export function getImplementationShapeMap(shapeMap) {
    return getImplementationsHelper(shapeMap, (x) => x);
}
export function getImplementationShapeNameMap(shapeMap) {
    return getImplementationsHelper(shapeMap, (shape) => shape.name);
}
export function pruneUnusedInterfaces(projectSchema, shapeMap) {
    return mapValues(shapeMap, (shape) => {
        const { interfaces } = shape;
        if (interfaces?.length) {
            const context = { ...projectSchema, shapes: shapeMap };
            const filteredInterfaces = interfaces.filter((refExpression) => getShapeByRef(context, refExpression));
            if (interfaces.length !== filteredInterfaces.length) {
                return { ...shape, interfaces: filteredInterfaces };
            }
        }
        return shape;
    });
}
function compareRefItems(refItem1, refItem2) {
    return refItem1.shapeName === refItem2.shapeName && refItem1.layerId === refItem2.layerId;
}
export function getImplementingShapes(context, interfaceRef) {
    const shapeMap = context.shapes;
    const implementingShapes = [];
    for (const shape of Object.values(shapeMap)) {
        if (shape.interfaces?.find((interfaceRefExpression) => compareRefItems(parseRef(context, interfaceRefExpression), interfaceRef))) {
            implementingShapes.push(shape);
        }
    }
    return implementingShapes;
}
export function interfaceToUnion(context, interfaceRef) {
    const shapes = getImplementingShapes(context, interfaceRef);
    return {
        oneOf: shapes.map((shape) => ({
            '@ref': normalizeRefExpression(context, shape.name)
        }))
    };
}
export function getPossibleShapeNames(projectSchema, shape) {
    let names;
    if (isInterfaceShape(shape)) {
        names = getImplementingShapes(projectSchema, shapeToRefItem(projectSchema, shape)).map((shape) => shape.name);
    }
    else if (isUnionSchema(shape.schema)) {
        names = shape.schema.oneOf
            .map((member) => {
            const ref = getRef(projectSchema, member);
            return ref && refItemToNamespacedShapeName(projectSchema, ref);
        })
            .filter(isDefined);
    }
    else {
        names = [shape.name];
    }
    return names;
}
