import { ValidationError } from "../../core/index.js";
import { entityKey, sameEntity } from "../Entity.js";
import {} from "../System.js";
import {} from "../World.js";
import { HierarchyComponent } from "../components/HierarchyComponent.js";
export class HierarchySystem {
    name = "HierarchySystem";
    phase = "update";
    priority = -100;
    update(world, _context) {
        for (const entity of world.query({ include: [HierarchyComponent] }).toArray()) {
            const hierarchy = world.get(entity, HierarchyComponent);
            if (!hierarchy)
                continue;
            if (hierarchy.parent && !world.entities.isAlive(hierarchy.parent)) {
                hierarchy._setParent(null);
                hierarchy.depth = 0;
            }
        }
        const cycles = this.detectCycles(world);
        if (cycles.length > 0)
            throw new ValidationError("ECS_HIERARCHY_CYCLE", `Hierarchy cycle detected: ${cycles.map(entityKey).join(", ")}`);
    }
    setParent(world, child, parent) {
        world.assertAlive(child);
        if (parent)
            world.assertAlive(parent);
        if (parent && sameEntity(child, parent))
            throw new ValidationError("ECS_HIERARCHY_SELF_PARENT", "Entity cannot be parented to itself.");
        const childHierarchy = ensureHierarchy(world, child);
        if (parent)
            ensureHierarchy(world, parent);
        if (parent && this.isAncestorOf(world, child, parent)) {
            throw new ValidationError("ECS_HIERARCHY_CYCLE", `Parenting ${entityKey(child)} to ${entityKey(parent)} would create a cycle.`);
        }
        const previousParent = childHierarchy.parent;
        if (sameOptionalEntity(childHierarchy.parent, parent))
            return;
        if (previousParent) {
            world.get(previousParent, HierarchyComponent)?._removeChild(child);
            this.refreshChildSiblings(world, previousParent);
        }
        childHierarchy._setParent(parent ? { ...parent } : null);
        childHierarchy.previousSibling = null;
        childHierarchy.nextSibling = null;
        if (parent) {
            const parentHierarchy = ensureHierarchy(world, parent);
            parentHierarchy._addChild(child);
            this.refreshChildSiblings(world, parent);
            childHierarchy.depth = parentHierarchy.depth + 1;
        }
        else {
            childHierarchy.depth = 0;
        }
        this.updateDescendantDepths(world, child);
    }
    addChild(world, parent, child) {
        this.setParent(world, child, parent);
    }
    removeFromParent(world, entity) {
        this.setParent(world, entity, null);
    }
    getParent(world, entity) {
        return world.get(entity, HierarchyComponent)?.parent ?? null;
    }
    getChildren(world, entity) {
        return world.get(entity, HierarchyComponent)?.children ?? [];
    }
    getDepth(world, entity) {
        return world.get(entity, HierarchyComponent)?.depth ?? 0;
    }
    forEachDescendant(world, entity, callback) {
        const hierarchy = world.get(entity, HierarchyComponent);
        if (!hierarchy)
            return;
        for (const child of hierarchy.children) {
            const childHierarchy = world.get(child, HierarchyComponent);
            callback(child, childHierarchy?.depth ?? hierarchy.depth + 1);
            this.forEachDescendant(world, child, callback);
        }
    }
    isAncestorOf(world, ancestor, descendant) {
        let cursor = world.get(descendant, HierarchyComponent)?.parent ?? null;
        const guard = new Set();
        while (cursor) {
            if (sameEntity(cursor, ancestor))
                return true;
            const key = entityKey(cursor);
            if (guard.has(key))
                return false;
            guard.add(key);
            cursor = world.get(cursor, HierarchyComponent)?.parent ?? null;
        }
        return false;
    }
    isDescendantOf(world, descendant, ancestor) {
        return this.isAncestorOf(world, ancestor, descendant);
    }
    setSiblingIndex(world, entity, index) {
        const hierarchy = world.get(entity, HierarchyComponent);
        if (!hierarchy?.parent)
            return;
        world.get(hierarchy.parent, HierarchyComponent)?._moveChild(entity, index);
        this.refreshChildSiblings(world, hierarchy.parent);
    }
    detachChildren(world, entity) {
        const children = [...this.getChildren(world, entity)];
        for (const child of children)
            this.setParent(world, child, null);
        return children;
    }
    validateHierarchy(world) {
        return this.detectCycles(world).length === 0 && world.query({ include: [HierarchyComponent] }).toArray().every((entity) => {
            const hierarchy = world.get(entity, HierarchyComponent);
            if (!hierarchy?.parent)
                return true;
            const parent = world.get(hierarchy.parent, HierarchyComponent);
            return Boolean(parent?.includesChild(entity));
        });
    }
    detectCycles(world) {
        const cycles = [];
        const visiting = new Set();
        const visited = new Set();
        const visit = (entity) => {
            const key = entityKey(entity);
            if (visiting.has(key)) {
                cycles.push(entity);
                return;
            }
            if (visited.has(key))
                return;
            visiting.add(key);
            const parent = world.get(entity, HierarchyComponent)?.parent;
            if (parent)
                visit(parent);
            visiting.delete(key);
            visited.add(key);
        };
        for (const entity of world.query({ include: [HierarchyComponent] }).toArray())
            visit(entity);
        return cycles;
    }
    updateDescendantDepths(world, entity) {
        const hierarchy = world.get(entity, HierarchyComponent);
        if (!hierarchy)
            return;
        for (const child of hierarchy.children) {
            const childHierarchy = world.get(child, HierarchyComponent);
            if (!childHierarchy)
                continue;
            childHierarchy.depth = hierarchy.depth + 1;
            this.updateDescendantDepths(world, child);
        }
    }
    refreshChildSiblings(world, parent) {
        const parentHierarchy = world.get(parent, HierarchyComponent);
        if (!parentHierarchy)
            return;
        const children = parentHierarchy.children;
        for (let index = 0; index < children.length; index += 1) {
            const child = children[index];
            const hierarchy = world.get(child, HierarchyComponent);
            if (!hierarchy)
                continue;
            hierarchy.previousSibling = children[index - 1] ? { ...children[index - 1] } : null;
            hierarchy.nextSibling = children[index + 1] ? { ...children[index + 1] } : null;
        }
    }
}
function ensureHierarchy(world, entity) {
    if (!world.registry.get(HierarchyComponent))
        world.registerComponent(HierarchyComponent);
    const existing = world.get(entity, HierarchyComponent);
    if (existing)
        return existing;
    world.add(entity, HierarchyComponent, new HierarchyComponent());
    return world.get(entity, HierarchyComponent);
}
function sameOptionalEntity(left, right) {
    if (!left || !right)
        return left === right;
    return sameEntity(left, right);
}
//# sourceMappingURL=HierarchySystem.js.map