import { ValidationError } from "../core/index.js";
import { Bounds3 } from "./Bounds.js";
import { cloneMat4, invertMat4, multiplyMat4 } from "./MathTypes.js";
import { TransformNode } from "./TransformNode.js";
let nextSceneNodeId = 1;
export class SceneNode {
    id;
    name;
    visible = true;
    layerMask = 1;
    renderOrder = 0;
    disposed = false;
    userData = {};
    renderable;
    transform = new TransformNode();
    localBounds = null;
    worldBounds = new Bounds3();
    children = [];
    parent = null;
    constructor(options = {}) {
        this.id = options.id ?? `node-${nextSceneNodeId++}`;
        this.name = options.name ?? this.id;
        this.transform.onDirty(() => this.markWorldDirty());
    }
    addChild(child, options = {}) {
        this.assertAlive();
        child.assertAlive();
        assertCanParent(this, child);
        if (child.parent === this)
            return this;
        const preservedWorld = options.preserveWorldTransform ? cloneMat4(child.transform.worldMatrix) : undefined;
        if (preservedWorld)
            this.updateWorldTransform();
        child.parent?.removeChild(child);
        this.children.push(child);
        child.parent = this;
        if (preservedWorld)
            child.transform.setFromLocalMatrix(multiplyMat4(invertMat4(this.transform.worldMatrix), preservedWorld));
        child.markWorldDirty();
        return this;
    }
    removeChild(child) {
        this.assertAlive();
        const index = this.children.indexOf(child);
        if (index < 0)
            return false;
        this.children.splice(index, 1);
        child.parent = null;
        child.markWorldDirty();
        return true;
    }
    removeFromParent() {
        return this.parent?.removeChild(this) ?? false;
    }
    isAncestorOf(candidate) {
        let current = candidate.parent;
        while (current) {
            if (current === this)
                return true;
            current = current.parent;
        }
        return false;
    }
    traverse(visitor) {
        visitor(this);
        for (const child of [...this.children])
            child.traverse(visitor);
    }
    updateWorldTransform(force = false) {
        const changed = this.transform.updateWorld(this.parent?.transform.worldMatrix, force);
        for (const child of this.children)
            child.updateWorldTransform(force || changed);
    }
    updateWorldBounds() {
        let bounds = this.localBounds ? this.localBounds.transform(this.transform.worldMatrix) : new Bounds3();
        for (const child of this.children)
            bounds = bounds.union(child.updateWorldBounds());
        this.worldBounds = bounds;
        return this.worldBounds;
    }
    setLocalBounds(bounds) {
        this.assertAlive();
        this.localBounds = bounds?.clone() ?? null;
        this.worldBounds = this.localBounds ? this.localBounds.transform(this.transform.worldMatrix) : new Bounds3();
        return this;
    }
    dispose(options = {}) {
        if (this.disposed)
            return;
        const recursive = options.recursive ?? true;
        const children = [...this.children];
        if (recursive) {
            for (const child of children)
                child.dispose(options);
        }
        else {
            for (const child of children)
                child.parent = null;
        }
        this.children.length = 0;
        this.parent?.removeChild(this);
        this.parent = null;
        this.renderable = undefined;
        this.localBounds = null;
        this.userData = {};
        this.visible = false;
        this.disposed = true;
    }
    markWorldDirty() {
        for (const child of this.children) {
            child.transform.markDirty();
            child.markWorldDirty();
        }
    }
    assertAlive() {
        if (this.disposed)
            throw new ValidationError("DISPOSED_SCENE_NODE", `Scene node ${this.name} is disposed.`);
    }
}
export function assertCanParent(parent, child) {
    if (parent === child)
        throw new ValidationError("SCENE_SELF_PARENT", "A scene node cannot be parented to itself.");
    if (child.isAncestorOf(parent))
        throw new ValidationError("SCENE_HIERARCHY_CYCLE", "Scene hierarchy cycle rejected.");
}
//# sourceMappingURL=SceneNode.js.map