import { value } from '@takeshape/util';
import { getRef, refItemToShape } from "../refs.js";
function immutableSetAdd(set, str) {
    const result = new Set(set);
    result.add(str);
    return result;
}
/**
 * Use a basic depth-first search to determine whether a schema contains a cycle
 */
export function hasCycle(projectSchema, schema, predicate = value(true)) {
    const hasCycleHelper = (schema, shapesSeen = new Set(), shapeName, propName) => {
        if (!predicate(projectSchema, schema, shapeName, propName)) {
            return false;
        }
        const refItem = getRef(projectSchema, schema);
        if (refItem) {
            const refShape = refItemToShape(projectSchema, refItem);
            // If we have a refItem, but no shape, this is a dangling ref and has no cycles
            if (!refShape) {
                return false;
            }
            if (shapesSeen.has(refShape.name)) {
                return true;
            }
            return hasCycleHelper(refShape.schema, immutableSetAdd(shapesSeen, refShape.name), refShape.name);
        }
        const combo = schema.oneOf ?? schema.allOf ?? schema.extends;
        if (combo) {
            return combo.some((childSchema) => hasCycleHelper(childSchema, shapesSeen, shapeName, propName));
        }
        if (schema.items) {
            return hasCycleHelper(schema.items, shapesSeen, shapeName, propName);
        }
        const { properties } = schema;
        if (properties) {
            return Object.keys(properties).some((name) => {
                const propSchema = properties[name];
                return hasCycleHelper(propSchema, shapesSeen, shapeName, name);
            });
        }
        return false;
    };
    return hasCycleHelper(schema);
}
