import { ASSERT, DRef, randomUUID } from '@zephyr3d/base';
import { getAABBClass } from './scene/misc.js';
import { getSceneNodeClass, getGraphNodeClass } from './scene/node.js';
import { getBatchGroupClass } from './scene/batch.js';
import { getCameraClass, getPerspectiveCameraClass, getOrthoCameraClass } from './scene/camera.js';
import { getPunctualLightClass, getDirectionalLightClass, getSpotLightClass, getRectLightClass, getPointLightClass } from './scene/light.js';
import { getMeshMaterialClass, getPBRBluePrintMaterialClass, getSpriteBlueprintMaterialClass, getUnlitMaterialClass, getLambertMaterialClass, getBlinnMaterialClass, getPBRMetallicRoughnessMaterialClass, getPBRSpecularGlossinessMaterialClass, getParticleMaterialClass, getSpriteMaterialClass, getStandardSpriteMaterialClass } from './scene/material.js';
import { getMeshClass } from './scene/mesh.js';
import { getParticleNodeClass } from './scene/particle.js';
import { getBoxShapeClass, getBoxFrameShapeClass, getSphereShapeClass, getTorusShapeClass, getCylinderShapeClass, getPlaneShapeClass, getTetrahedronShapeClass, getCapsuleShapeClass } from './scene/primitive.js';
import { getSceneClass } from './scene/scene.js';
import { getScriptAttachmentClass } from './scene/script.js';
import { getTerrainClass } from './scene/terrain.js';
import { getWaterClass, getFFTWaveGeneratorClass, getFBMWaveGeneratorClass } from './scene/water.js';
import { getInterpolatorClass, getSkeletonClass, getAnimationClass, getPropTrackClass, getNodeRotationTrackClass, getNodeScaleTrackClass, getNodeTranslationTrackClass, getNodeEulerRotationTrackClass, getFixedGeometryCacheTrackClass, getPCAGeometryCacheTrackClass, getMorphTrackClass } from './scene/animation.js';
import '../../scene/scene.js';
import '../../values.js';
import '../../render/render_queue.js';
import '../../render/primitive.js';
import '@zephyr3d/device';
import '../../material/shader/helper.js';
import '../../material/lambert.js';
import '../../material/blinn.js';
import '../../material/unlit.js';
import '../../material/particle.js';
import '../../material/meshmaterial.js';
import '../../material/grassmaterial.js';
import '../../material/terrain-cm.js';
import '../../material/pbrmr.js';
import '../../material/pbrsg.js';
import '../../material/pbrblueprint.js';
import '../../material/sprite.js';
import '../../material/sprite_std.js';
import { PBRBlockNode, SpriteBlockNode, VertexBlockNode } from '../blueprint/material/pbr.js';
import { ConstantScalarNode, ConstantVec2Node, ConstantVec3Node, ConstantVec4Node, ConstantBooleanNode, ConstantBVec2Node, ConstantBVec3Node, ConstantBVec4Node } from '../blueprint/common/constants.js';
import { ConstantTexture2DNode, ConstantTexture2DArrayNode, ConstantTextureCubeNode, PannerNode, TextureSampleNode } from '../blueprint/material/texture.js';
import { MakeVectorNode, SwizzleNode, EqualNode, NotEqualNode, LogicallyAndNode, LogicallyOrNode, CompComparisonNode, AnyConditionNode, AllConditionNode, SelectionNode, Degrees2RadiansNode, Radians2DegreesNode, SinNode, CosNode, TanNode, ArcSinNode, ArcCosNode, ArcTanNode, ArcTan2Node, SinHNode, CosHNode, TanHNode, ArcsineHNode, ArccosineHNode, ArctangentHNode, ExpNode, Exp2Node, LogNode, Log2Node, SqrtNode, InvSqrtNode, AbsNode, SignNode, FloorNode, CeilNode, FractNode, DDXNode, DDYNode, FWidthNode, CompAddNode, CompSubNode, CompMulNode, CompDivNode, ModNode, MinNode, MaxNode, PowNode, StepNode, SmoothStepNode, FmaNode, ClampNode, SaturateNode, MixNode, NormalizeNode, TransformNode, RotateAboutAxisNode, FaceForwardNode, ReflectNode, RefractNode, LengthNode, DistanceNode, DotProductNode, CrossProductNode, Hash1Node, Hash2Node, Hash3Node, SimplexNoise2DNode, PerlinNoise2DNode } from '../blueprint/common/math.js';
import { VertexColorNode, VertexUVNode, VertexPositionNode, PixelWorldPositionNode, VertexNormalNode, PixelNormalNode, VertexTangentNode, VertexBinormalNode, ViewMatrixNode, ProjectionMatrixNode, ViewProjMatrixNode, InvProjMatrixNode, InvViewProjMatrixNode, BillboardMatrixNode, CameraPositionNode, CameraVectorNode, CameraNearFarNode, SkyEnvTextureNode, ElapsedTimeNode, ResolveVertexPositionNode, ResolveVertexTangentNode, ResolveVertexNormalNode } from '../blueprint/material/inputs.js';
import { FunctionInputNode, FunctionOutputNode, FunctionCallNode } from '../blueprint/material/func.js';
import '../../render/rendergraph/forward_plus_builder.js';
import '../../render/sky.js';
import '../../render/clipmap.js';
import '../../shaders/atmosphere.js';
import { SceneNode } from '../../scene/scene_node.js';
import '../../shadow/shadowmapper.js';
import '../../scene/mesh.js';
import '../../scene/basesprite.js';
import '../../scene/water.js';
import '../../scene/particlesys.js';
import '../../scene/batchgroup.js';
import '../../scene/terrain-cm/terrain-cm.js';
import '../../scene/terrain-cm/grass.js';
import '../../scene/raycast_visitor.js';
import { AssetManager } from '../../asset/assetmanager.js';
import '../../animation/eulerrotationtrack.js';
import '../../animation/skeleton.js';
import '../../animation/joint_dynamics/collision.js';
import '../../animation/joint_dynamics/solver.js';
import '../../animation/joint_dynamics/joint_dynamics_system.js';
import { getJSONPropClass, getJSONNumberClass, getJSONStringClass, getJSONBoolClass, getJSONObjectClass, getJSONArrayClass } from './json.js';
import { getSpriteClass } from './scene/sprite.js';

const defaultValues = {
    bool: false,
    float: 0,
    int: 0,
    int2: [
        0,
        0
    ],
    int3: [
        0,
        0,
        0
    ],
    int4: [
        0,
        0,
        0,
        0
    ],
    object: null,
    object_array: [],
    embedded: '',
    rgb: [
        0,
        0,
        0
    ],
    rgba: [
        0,
        0,
        0,
        0
    ],
    string: '',
    vec2: [
        0,
        0
    ],
    vec3: [
        0,
        0,
        0
    ],
    vec4: [
        0,
        0,
        0,
        0
    ],
    command: null
};
/**
 * Manages serialization and deserialization of engine objects to/from JSON,
 * including asset resolution via a virtual file system (VFS) and asset manager.
 *
 * @remarks
 * - Keeps a registry of serializable classes and their properties.
 * - Converts object graphs to JSON using per-class metadata and property accessors.
 * - Supports async asset embedding/export and lazy deserialization via phases.
 * - Maps loaded assets/objects back to their source IDs for caching and deduplication.
 *
 * Typical workflow:
 * 1. Construct with a `VFS`.
 * 2. Optionally `registerClass` for custom types.
 * 3. Use `serializeObject` and `deserializeObject` to convert between runtime objects and JSON.
 * 4. Use `saveScene` / `loadScene` for scene I/O.
 *
 * Caching:
 * - `getAssetId` returns the known source ID for an allocated asset when available.
 *
 * Threading:
 * - Methods returning Promises perform async I/O using the provided `VFS`/AssetManager.
 *
 * @public
 */ class ResourceManager {
    _classMap;
    _vfs;
    _propMap;
    _propNameMap;
    _clsPropMap;
    _assetManager;
    _editorMode;
    _allocated;
    /**
   * Create a ResourceManager bound to a virtual file system.
   *
   * @param vfs - Virtual file system used for reading/writing assets and scenes.
   */ constructor(vfs, editorMode = false){
        this._vfs = vfs;
        this._editorMode = editorMode;
        this._allocated = new WeakMap();
        this._assetManager = new AssetManager(this);
        this._propMap = {};
        this._propNameMap = new Map();
        this._clsPropMap = new Map();
        this._classMap = new Map([
            getJSONPropClass(),
            getJSONNumberClass(),
            getJSONStringClass(),
            getJSONBoolClass(),
            getJSONObjectClass(),
            getJSONArrayClass(),
            getScriptAttachmentClass(),
            getAABBClass(),
            getInterpolatorClass(),
            getSkeletonClass(),
            getAnimationClass(this),
            getPropTrackClass(this),
            getNodeRotationTrackClass(),
            getNodeScaleTrackClass(),
            getNodeTranslationTrackClass(),
            getNodeEulerRotationTrackClass(),
            getFixedGeometryCacheTrackClass(),
            getPCAGeometryCacheTrackClass(),
            getMorphTrackClass(),
            getSceneNodeClass(this),
            getGraphNodeClass(),
            getMeshClass(),
            getSpriteClass(),
            getWaterClass(this),
            getTerrainClass(this),
            getFFTWaveGeneratorClass(),
            getFBMWaveGeneratorClass(),
            getParticleNodeClass(),
            getPunctualLightClass(),
            getDirectionalLightClass(),
            getSpotLightClass(),
            getRectLightClass(),
            getPointLightClass(),
            getCameraClass(),
            getPerspectiveCameraClass(),
            getOrthoCameraClass(),
            getBatchGroupClass(),
            getSceneClass(this),
            ...getMeshMaterialClass(),
            ...getPBRBluePrintMaterialClass(),
            ...getSpriteBlueprintMaterialClass(),
            ...getUnlitMaterialClass(this),
            ...getLambertMaterialClass(this),
            ...getBlinnMaterialClass(this),
            ...getPBRMetallicRoughnessMaterialClass(this),
            ...getPBRSpecularGlossinessMaterialClass(this),
            ...getParticleMaterialClass(this),
            ...getSpriteMaterialClass(),
            ...getStandardSpriteMaterialClass(this),
            getBoxShapeClass(),
            getBoxFrameShapeClass(),
            getSphereShapeClass(),
            getTorusShapeClass(),
            getCylinderShapeClass(),
            getPlaneShapeClass(),
            getTetrahedronShapeClass(),
            getCapsuleShapeClass(),
            ConstantScalarNode.getSerializationCls(),
            ConstantVec2Node.getSerializationCls(),
            ConstantVec3Node.getSerializationCls(),
            ConstantVec4Node.getSerializationCls(),
            ConstantBooleanNode.getSerializationCls(),
            ConstantBVec2Node.getSerializationCls(),
            ConstantBVec3Node.getSerializationCls(),
            ConstantBVec4Node.getSerializationCls(),
            ConstantTexture2DNode.getSerializationCls(),
            ConstantTexture2DArrayNode.getSerializationCls(),
            ConstantTextureCubeNode.getSerializationCls(),
            MakeVectorNode.getSerializationCls(),
            SwizzleNode.getSerializationCls(),
            EqualNode.getSerializationCls(),
            NotEqualNode.getSerializationCls(),
            LogicallyAndNode.getSerializationCls(),
            LogicallyOrNode.getSerializationCls(),
            CompComparisonNode.getSerializationCls(),
            AnyConditionNode.getSerializationCls(),
            AllConditionNode.getSerializationCls(),
            SelectionNode.getSerializationCls(),
            Degrees2RadiansNode.getSerializationCls(),
            Radians2DegreesNode.getSerializationCls(),
            SinNode.getSerializationCls(),
            CosNode.getSerializationCls(),
            TanNode.getSerializationCls(),
            ArcSinNode.getSerializationCls(),
            ArcCosNode.getSerializationCls(),
            ArcTanNode.getSerializationCls(),
            ArcTan2Node.getSerializationCls(),
            SinHNode.getSerializationCls(),
            CosHNode.getSerializationCls(),
            TanHNode.getSerializationCls(),
            ArcsineHNode.getSerializationCls(),
            ArccosineHNode.getSerializationCls(),
            ArctangentHNode.getSerializationCls(),
            ExpNode.getSerializationCls(),
            Exp2Node.getSerializationCls(),
            LogNode.getSerializationCls(),
            Log2Node.getSerializationCls(),
            SqrtNode.getSerializationCls(),
            InvSqrtNode.getSerializationCls(),
            AbsNode.getSerializationCls(),
            SignNode.getSerializationCls(),
            FloorNode.getSerializationCls(),
            CeilNode.getSerializationCls(),
            FractNode.getSerializationCls(),
            DDXNode.getSerializationCls(),
            DDYNode.getSerializationCls(),
            FWidthNode.getSerializationCls(),
            CompAddNode.getSerializationCls(),
            CompSubNode.getSerializationCls(),
            CompMulNode.getSerializationCls(),
            CompDivNode.getSerializationCls(),
            ModNode.getSerializationCls(),
            MinNode.getSerializationCls(),
            MaxNode.getSerializationCls(),
            PowNode.getSerializationCls(),
            StepNode.getSerializationCls(),
            SmoothStepNode.getSerializationCls(),
            FmaNode.getSerializationCls(),
            ClampNode.getSerializationCls(),
            SaturateNode.getSerializationCls(),
            MixNode.getSerializationCls(),
            NormalizeNode.getSerializationCls(),
            TransformNode.getSerializationCls(),
            RotateAboutAxisNode.getSerializationCls(),
            FaceForwardNode.getSerializationCls(),
            ReflectNode.getSerializationCls(),
            RefractNode.getSerializationCls(),
            LengthNode.getSerializationCls(),
            DistanceNode.getSerializationCls(),
            DotProductNode.getSerializationCls(),
            CrossProductNode.getSerializationCls(),
            Hash1Node.getSerializationCls(),
            Hash2Node.getSerializationCls(),
            Hash3Node.getSerializationCls(),
            SimplexNoise2DNode.getSerializationCls(),
            PerlinNoise2DNode.getSerializationCls(),
            VertexColorNode.getSerializationCls(),
            VertexUVNode.getSerializationCls(),
            VertexPositionNode.getSerializationCls(),
            PixelWorldPositionNode.getSerializationCls(),
            VertexNormalNode.getSerializationCls(),
            PixelNormalNode.getSerializationCls(),
            VertexTangentNode.getSerializationCls(),
            VertexBinormalNode.getSerializationCls(),
            ViewMatrixNode.getSerializationCls(),
            ProjectionMatrixNode.getSerializationCls(),
            ViewProjMatrixNode.getSerializationCls(),
            InvProjMatrixNode.getSerializationCls(),
            InvViewProjMatrixNode.getSerializationCls(),
            BillboardMatrixNode.getSerializationCls(),
            CameraPositionNode.getSerializationCls(),
            CameraVectorNode.getSerializationCls(),
            CameraNearFarNode.getSerializationCls(),
            SkyEnvTextureNode.getSerializationCls(),
            ElapsedTimeNode.getSerializationCls(),
            PBRBlockNode.getSerializationCls(),
            SpriteBlockNode.getSerializationCls(),
            VertexBlockNode.getSerializationCls(),
            ResolveVertexPositionNode.getSerializationCls(),
            ResolveVertexTangentNode.getSerializationCls(),
            ResolveVertexNormalNode.getSerializationCls(),
            FunctionInputNode.getSerializationCls(),
            FunctionOutputNode.getSerializationCls(),
            FunctionCallNode.getSerializationCls(this),
            PannerNode.getSerializationCls(),
            TextureSampleNode.getSerializationCls()
        ].map((val)=>[
                val.ctor,
                val
            ]));
        for (const k of this._classMap){
            this.registerProps(k[1]);
        }
    }
    /**
   * The virtual file system used by this manager.
   *
   * @remarks
   * Used by asset fetchers and scene save/load operations.
   */ get VFS() {
        return this._vfs;
    }
    set VFS(vfs) {
        this._vfs = vfs;
    }
    /**
   * Wethether editor mode is enabled
   *
   * @remarks
   * In editor mode, some properties will be disabled
   */ get editorMode() {
        return this._editorMode;
    }
    /**
   * AssetManager
   */ get assetManager() {
        return this._assetManager;
    }
    /**
   * Get the list of all registered serializable classes.
   *
   * @remarks
   * Includes built-in classes registered during construction and any custom classes
   * registered via `registerClass`.
   *
   * @returns An array of `SerializableClass` metadata.
   */ getClasses() {
        return [
            ...this._classMap.values()
        ];
    }
    /**
   * Get serialization metadata by a constructor function.
   *
   * @param ctor - The class constructor to look up.
   *
   * @returns The `SerializableClass` metadata, or `null` if not found.
   */ getClassByConstructor(ctor) {
        let current = ctor;
        while(typeof current === 'function' && current !== Function.prototype){
            const cls = this._classMap.get(current);
            if (cls) {
                return cls;
            }
            current = Object.getPrototypeOf(current);
        }
        return null;
    }
    /**
   * Get serialization metadata by an object instance.
   *
   * @param obj - The object whose constructor will be used for the lookup.
   *
   * @returns The `SerializableClass` metadata, or `null` if not found.
   */ getClassByObject(obj) {
        return this.getClassByConstructor(obj.constructor);
    }
    /**
   * Get serialization metadata by class name.
   *
   * @param className - Fully qualified class name as stored in JSON.
   *
   * @returns The `SerializableClass` metadata, or `null` if not found.
   */ getClassByName(className) {
        for (const val of this._classMap){
            if (val[0].name === className) {
                return val[1];
            }
        }
        return null;
    }
    /**
   * Find the class that owns a given property accessor.
   *
   * @param prop - Property accessor to search for.
   *
   * @returns The `SerializableClass` that declares the property, or `null` if unknown.
   */ getClassByProperty(prop) {
        for (const k of this._clsPropMap){
            if (k[1].indexOf(prop) >= 0) {
                return k[0];
            }
        }
        return null;
    }
    /**
   * Get the properties declared on a given class.
   *
   * @param cls - Serializable class metadata.
   *
   * @returns An array of `PropertyAccessor` entries, or `null` if none.
   */ getPropertiesByClass(cls) {
        return this._clsPropMap.get(cls) ?? null;
    }
    /**
   * Get all properties declared on a given class and its ancestors.
   *
   * @param cls - Serializable class metadata.
   *
   * @returns An array of `PropertyAccessor` entries, or `null` if none.
   */ getAllPropertiesByClass(cls) {
        const props = new Map();
        while(cls){
            const classProps = this.getPropertiesByClass(cls);
            if (classProps) {
                for (const prop of classProps){
                    if (!props.has(prop.name)) {
                        props.set(prop.name, prop);
                    }
                }
            }
            cls = this.getClassByConstructor(cls.parent);
        }
        return [
            ...props.values()
        ];
    }
    /**
   * Get a property accessor by class and property name.
   *
   * @param cls - Serializable class metadata.
   * @param name - Property name to search for.
   *
   * @returns The `PropertyAccessor`, or `null` if not found.
   */ getPropertyByClass(cls, name) {
        return this.getPropertiesByClass(cls)?.find((value)=>value.name === name) ?? null;
    }
    /**
   * Get a property accessor by its canonical path.
   *
   * @remarks
   * The canonical path format is `/ClassName/propName`.
   *
   * @param name - Canonical property path.
   *
   * @returns The `PropertyAccessor`, or `null` if not found.
   */ getPropertyByName(name) {
        return this._propMap[name] ?? null;
    }
    /**
   * Get the canonical path for a property accessor.
   *
   * @remarks
   * Returns a string like `/ClassName/propName` if the property is registered.
   *
   * @param prop - Property accessor.
   *
   * @returns The canonical path, or `null` if unknown.
   */ getPropertyName(prop) {
        return this._propNameMap.get(prop) ?? null;
    }
    /**
   * Register a serializable class and its properties.
   *
   * @remarks
   * - No effect if the class is already registered.
   * - Also registers the class's properties with canonical paths.
   *
   * @param cls - Serializable class metadata to register.
   */ registerClass(cls) {
        if (!this._classMap.has(cls.ctor)) {
            this._classMap.set(cls.ctor, cls);
            this.registerProps(cls);
        }
    }
    /**
   * Get the known asset ID previously associated with a loaded/allocated asset.
   *
   * @remarks
   * Returns `null` if the asset was not loaded or tracked by this manager.
   *
   * @param asset - Asset instance (e.g., texture, model group) to look up.
   *
   * @returns The asset ID string, or `null` if unknown.
   */ getAssetId(asset) {
        return this._allocated.get(asset) ?? null;
    }
    /**
   * Associate an asset ID to a loaded/allocated asset.
   *
   * @param asset - Asset instance (e.g., texture, model group) to set Id.
   * @param id - Asset ID to associated to this asset.
   *
   */ setAssetId(asset, id) {
        if (asset) {
            if (id) {
                this._allocated.set(asset, id);
            } else {
                this._allocated.delete(asset);
            }
        }
    }
    /**
   * Fetch a binary asset by ID via the asset manager.
   *
   * @remarks
   * - Associates the returned data with the given ID for future reverse lookup.
   * - The ID is typically a VFS path or locator.
   *
   * @param id - Asset identifier or path.
   *
   * @returns A Promise that resolves to the binary content, or `null` if not found.
   */ async fetchBinary(id) {
        const data = await this._assetManager.fetchBinaryData(id);
        if (data) {
            this._allocated.set(data, id);
        }
        return data;
    }
    /**
   * Serialize an object to a JSON structure using registered class metadata.
   *
   * @remarks
   * - Throws if the object's class is not registered.
   * - Populates `asyncTasks` with pending I/O tasks for embedded resources.
   *
   * @param obj - The object to serialize.
   * @param json - Optional existing JSON object to fill.
   * @param asyncTasks - Optional list to collect async tasks for embedded asset export.
   *
   * @returns The serialized JSON structure.
   */ async serializeObject(obj, json, asyncTasks) {
        if (obj === null || obj === undefined) {
            return obj;
        }
        const cls = this.getClasses();
        const index = cls.findIndex((val)=>val.ctor === obj.constructor);
        if (index < 0) {
            throw new Error('Serialize object failed: Cannot found serialization meta data');
        }
        let info = cls[index];
        const flags = {
            saveProps: true
        };
        const initParams = await info?.getInitParams?.(obj, flags);
        json = json ?? {};
        json.ClassName = info.name;
        json.Object = {};
        if (initParams !== undefined && initParams !== null) {
            json.Init = initParams;
        }
        if (flags.saveProps) {
            await this.serializeObjectProps(obj, json.Object, asyncTasks, info);
        }
        return json;
    }
    /**
   * Serialize object properties into a JSON structure.
   *
   * @param obj - The target object to serialize.
   * @param json - The JSON structure to populate.
   * @param asyncTasks - Optional list to collect async tasks for embedded asset export.
   * @param info - Optional serialization metadata for the object's class.
   * @returns A Promise that resolves when serialization is complete.
   */ async serializeObjectProps(obj, json, asyncTasks, info) {
        if (!info) {
            const cls = this.getClasses();
            const index = cls.findIndex((val)=>val.ctor === obj.constructor);
            if (index < 0) {
                throw new Error('Serialize object properties failed: Cannot found serialization meta data');
            }
            info = cls[index];
        }
        json = json ?? {};
        while(info){
            await this.serializeObjectPropsForCls(obj, info, json, asyncTasks);
            info = this.getClassByConstructor(info.parent);
        }
        return json;
    }
    /**
   * Deserialize a JSON structure into an object instance.
   *
   * @remarks
   * - Uses the `ClassName` field to locate the registered class.
   * - Supports custom `createFunc` and phased property loading.
   *
   * @param ctx - Context object passed to custom constructors/resolvers.
   * @param json - The serialized JSON structure.
   *
   * @returns A Promise resolving to the reconstructed object instance, or `null` on failure.
   */ async deserializeObject(ctx, json) {
        const cls = this.getClasses();
        const className = json['ClassName'];
        const index = cls.findIndex((val)=>val.name === className);
        if (index < 0) {
            throw new Error(`Deserialize object failed: Cannot found serialization meta data for class "${String(className)}"`);
        }
        let info = cls[index];
        const initParams = json['Init'];
        json = json['Object'];
        let p;
        let loadProps = true;
        if (info.createFunc) {
            let result = info.createFunc(ctx, initParams);
            if (result instanceof Promise) {
                result = await result;
            }
            p = result.obj;
            loadProps = result.loadProps ?? true;
        } else {
            p = new info.ctor();
        }
        //const p: T | Promise<T> = info.createFunc ? info.createFunc(ctx, initParams) : new info.ctor();
        if (!p) {
            return null;
        }
        const obj = p instanceof Promise ? await p : p;
        if (loadProps) {
            await this.deserializeObjectProps(obj, json, info);
        }
        return obj;
    }
    /**
   * Deserialize object properties from JSON into an existing object instance.
   *
   * @param obj - The target object to populate.
   * @param json - The JSON structure containing serialized properties.
   * @param info - Optional serialization metadata for the object's class.
   */ async deserializeObjectProps(obj, json, info) {
        if (!info) {
            const cls = this.getClasses();
            const index = cls.findIndex((val)=>val.ctor === obj.constructor);
            if (index < 0) {
                throw new Error('Deserialize object properties failed: Cannot found serialization meta data');
            }
            info = cls[index];
        }
        while(info){
            await this.deserializeObjectPropsForCls(obj, info, json);
            info = this.getClassByConstructor(info.parent);
        }
    }
    /**
   * Load a model by ID and track the allocation for reverse lookup.
   *
   * @param id - Model identifier or path.
   * @param scene - Scene into which the model is loaded.
   * @param options - Optional model fetch options.
   *
   * @returns A Promise resolving to the loaded model object, or `null` if failed.
   */ async fetchModel(id, scene, options) {
        const model = await this._assetManager.fetchModel(scene, id, options);
        if (model) {
            this._allocated.set(model.group, id);
        }
        return model;
    }
    /**
   * Load a texture directly from an ArrayBuffer or typed array.
   *
   * - Chooses an appropriate loader based on the provided MIME type.
   * - Can upload into an existing texture if `texture` is specified.
   *
   * @typeParam T - Expected concrete texture type.
   * @param arrayBuffer - Raw texture data buffer.
   * @param mimeType - MIME type of the texture (must be supported by a registered loader).
   * @param srgb - If true, treat image as sRGB; otherwise linear.
   * @param samplerOptions - Optional sampler options passed to the loader path.
   * @param texture - Optional destination texture to populate.
   * @returns A promise that resolves to the created or populated texture.
   */ async loadTextureFromBuffer(arrayBuffer, mimeType, srgb, samplerOptions, texture) {
        return this._assetManager.loadTextureFromBuffer(arrayBuffer, mimeType, srgb, samplerOptions, texture);
    }
    /**
   * Load a texture by ID and track the allocation for reverse lookup.
   *
   * @param id - Texture identifier or path.
   * @param options - Optional texture fetch options.
   *
   * @returns A Promise resolving to the loaded texture, or `null` if failed.
   */ async fetchTexture(id, options) {
        const texture = await this._assetManager.fetchTexture(id, options);
        if (texture) {
            this._allocated.set(texture, id);
        }
        return texture;
    }
    /**
   * Load a material by ID and track the allocation for reverse lookup.
   *
   * @param id - Material identifier or path.
   *
   * @returns A Promise resolving to the loaded material, or `null` if failed.
   */ async fetchMaterial(id) {
        const material = await this._assetManager.fetchMaterial(id);
        if (material) {
            this._allocated.set(material, id);
        }
        return material;
    }
    /**
   * Reload specific blue print materials
   *
   * @param filter - Determine whether a blue print material should reload.
   *
   */ async reloadBluePrintMaterials(filter) {
        await this._assetManager.reloadBluePrintMaterials(filter);
    }
    /**
   * Mark specific blue print as changed
   * @param path - BluePrint file path
   */ invalidateBluePrint(path) {
        this._assetManager.invalidateBluePrint(path);
    }
    /**
   * Load a primitive by ID and track the allocation for reverse lookup.
   *
   * @param id - Primitive identifier or path.
   *
   * @returns A Promise resolving to the loaded primitive, or `null` if failed.
   */ async fetchPrimitive(id) {
        const primitive = await this._assetManager.fetchPrimitive(id);
        if (primitive) {
            this._allocated.set(primitive, id);
        }
        return primitive;
    }
    /**
   * Load a prefab content.
   * @param path - Path to the prefab JSON file in VFS.
   * @returns A Promise resolving to the prefab json object, or `null` on failure.
   */ async loadPrefabContent(path) {
        try {
            const content = await this._vfs.readFile(path, {
                encoding: 'utf8'
            });
            const json = JSON.parse(content);
            return json;
        } catch (err) {
            console.error(`Failed to load prefab from ${path}:`, err);
            return null;
        }
    }
    /**
   * Instantiate a prefab from a JSON file via VFS.
   * @param parent - Parent node to attach the instantiated prefab to.
   * @param path - Path to the prefab JSON file in VFS.
   * @returns A Promise resolving to the instantiated `SceneNode`, or `null` on failure.
   */ async instantiatePrefab(parent, path) {
        try {
            const json = await this.loadPrefabContent(path);
            ASSERT(json?.type === 'SceneNode', 'Invalid prefab format');
            const tmpNode = new DRef(new SceneNode(parent.scene));
            tmpNode.get().remove();
            tmpNode.get().prefabId = this._vfs.normalizePath(path);
            const node = await this.deserializeObject(tmpNode.get(), json['data']);
            node.prefabId = tmpNode.get().prefabId;
            node.parent = parent;
            node.persistentId = randomUUID();
            tmpNode.dispose();
            return node;
        } catch (err) {
            console.error(`Failed to instantiate prefab from ${path}:`, err);
            return null;
        }
    }
    /**
   * Load a scene from a JSON file via VFS.
   *
   * @remarks
   * - Deserializes the scene graph.
   * - Attaches scripts referenced by nodes after load (asynchronous).
   *
   * @param filename - Path to the scene JSON file in VFS.
   *
   * @returns A Promise resolving to the loaded `Scene`.
   */ async loadScene(filename) {
        const content = await this._vfs.readFile(filename, {
            encoding: 'utf8'
        });
        const json = JSON.parse(content);
        return await this.deserializeObject(null, json);
    }
    /**
   * Save a scene to a JSON file via VFS.
   *
   * @remarks
   * - Collects async export tasks for embedded resources and awaits them.
   * - Writes a UTF-8 JSON representation to VFS.
   *
   * @param scene - Scene to serialize and save.
   * @param filename - Destination path in VFS.
   */ async saveScene(scene, filename) {
        const asyncTasks = [];
        const content = await this.serializeObject(scene, null, asyncTasks);
        await Promise.all(asyncTasks);
        await this._vfs.writeFile(filename, JSON.stringify(content, null, 2), {
            encoding: 'utf8',
            create: true
        });
    }
    rebuildGraphStructure(nodes, links) {
        const gs = {
            outgoing: {},
            incoming: {}
        };
        // Initialize adjacency lists
        for(const nodeId in nodes){
            gs.outgoing[nodeId] = [];
            gs.incoming[nodeId] = [];
        }
        // Fill with links
        for (const link of links){
            const outConnection = {
                targetNodeId: link.endNodeId,
                startSlotId: link.startSlotId,
                endSlotId: link.endSlotId
            };
            const inConnection = {
                targetNodeId: link.startNodeId,
                startSlotId: link.startSlotId,
                endSlotId: link.endSlotId
            };
            gs.outgoing[link.startNodeId]?.push(outConnection);
            gs.incoming[link.endNodeId]?.push(inConnection);
        }
        return gs;
    }
    collectReachableBackward(gs, nodes, roots) {
        const reachable = new Set();
        const q = [];
        for (const r of roots){
            if (nodes[r]) {
                reachable.add(r);
                q.push(r);
            }
        }
        while(q.length > 0){
            const u = q.shift();
            const ins = gs.incoming[u] || [];
            for (const conn of ins){
                const v = conn.targetNodeId; // predecessor
                if (!reachable.has(v)) {
                    reachable.add(v);
                    q.push(v);
                }
            }
        }
        return reachable;
    }
    getReverseTopologicalOrderFromRoots(gs, nodes, roots) {
        if (!roots || roots.length === 0) {
            return {
                order: [],
                levels: []
            };
        }
        const sub = this.collectReachableBackward(gs, nodes, roots);
        if (sub.size === 0) {
            return {
                order: [],
                levels: []
            };
        }
        const outDegree = new Map();
        for (const id of sub){
            const outs = (gs.outgoing[id] || []).filter((c)=>sub.has(c.targetNodeId));
            outDegree.set(id, outs.length);
        }
        let currentLevel = Array.from(outDegree.entries()).filter(([, deg])=>deg === 0).map(([id])=>id);
        const result = [];
        const levels = [];
        while(currentLevel.length > 0){
            levels.push([
                ...currentLevel
            ]);
            result.push(...currentLevel);
            const nextLevel = [];
            for (const u of currentLevel){
                const ins = gs.incoming[u] || [];
                for (const conn of ins){
                    const v = conn.targetNodeId; // predecessor
                    if (!sub.has(v)) {
                        continue;
                    }
                    const deg = outDegree.get(v) - 1;
                    outDegree.set(v, deg);
                    if (deg === 0) {
                        nextLevel.push(v);
                    }
                }
            }
            currentLevel = nextLevel;
        }
        if (result.length !== sub.size) {
            console.warn('Subgraph contains cycles (from given roots).');
            return null;
        }
        return {
            order: result,
            levels
        };
    }
    createBluePrintDAG(nodeMap, roots, links) {
        const gs = this.rebuildGraphStructure(nodeMap, links);
        for(const k in gs.incoming){
            const node = nodeMap[k];
            for (const conn of gs.incoming[k]){
                const input = node.inputs.find((input)=>input.id === conn.endSlotId);
                input.inputNode = nodeMap[conn.targetNodeId];
                input.inputId = conn.startSlotId;
            }
        }
        return {
            graph: gs,
            nodeMap,
            roots,
            order: this.getReverseTopologicalOrderFromRoots(gs, nodeMap, roots).order.reverse()
        };
    }
    async loadBluePrint(path) {
        return this._assetManager.loadBluePrint(path);
    }
    /**
   * Clear cached allocations and asset-manager caches.
   *
   * @remarks
   * Useful when reloading content or changing VFS mounts.
   */ clearCache() {
        this._allocated = new WeakMap();
        this._assetManager.clearCache();
    }
    static _pathPattern = /^([^\][]+)(?:\[(\d+)\])?$/;
    static parsePropertyPath(str) {
        const match = str.match(this._pathPattern);
        if (match) {
            return {
                original: match[0],
                prefix: match[1],
                index: match[2] || null,
                indexValue: match[2] ? parseInt(match[2], 10) : null,
                hasIndex: !!match[2]
            };
        }
        return null;
    }
    /**
   * Find the object targeted by an animation track starting from a node.
   *
   * @remarks
   * - Parses a target path like `prop/subprop[0]/child` where indexed entries
   *   require `object_array` type and non-indexed entries require `object` type.
   * - Returns `null` if any segment cannot be resolved through registered metadata.
   *
   * @param node - Root node used as the starting point.
   * @param track - Property track containing a target path.
   *
   * @returns The resolved target object, or `null` if not found.
   */ findAnimationTarget(node, track) {
        const target = track.target ?? '';
        const value = {
            num: [],
            bool: [],
            str: [],
            object: []
        };
        const parts = target.split('/').filter((val)=>!!val);
        let targetObj = node;
        while(parts.length > 0){
            const propName = parts.shift();
            const info = ResourceManager.parsePropertyPath(propName);
            if (!info) {
                return null;
            }
            const cls = this.getClassByConstructor(targetObj.constructor);
            if (!cls) {
                return null;
            }
            const prop = this.getPropertyByClass(cls, info.prefix);
            if (!prop) {
                return null;
            }
            if (info.hasIndex) {
                if (prop.type !== 'object_array') {
                    return null;
                }
                prop.get.call(targetObj, value);
                targetObj = value.object?.[info.indexValue] ?? null;
            } else {
                if (prop.type !== 'object') {
                    return null;
                }
                prop.get.call(targetObj, value);
                targetObj = value.object?.[0] ?? null;
            }
            if (!targetObj) {
                return null;
            }
        }
        return targetObj;
    }
    /** @internal */ registerProps(cls) {
        if (!this._clsPropMap.get(cls)) {
            const props = cls.getProps() ?? [];
            this._clsPropMap.set(cls, props);
            for (const prop of props){
                const path = `/${cls.name}/${prop.name}`;
                if (this._propMap[path]) {
                    throw new Error(`Cannot register property ${path}: property name already exists`);
                }
                this._propMap[path] = prop;
                this._propNameMap.set(prop, path);
            }
        }
    }
    getDefaultValue(obj, prop) {
        let v = prop.getDefaultValue?.call(obj) ?? prop.default;
        if (v === undefined) {
            v = defaultValues[prop.type];
        }
        return v;
    }
    isSerializedObjectEnvelope(v) {
        return !!v && typeof v === 'object' && !Array.isArray(v) && typeof v.ClassName === 'string';
    }
    async deserializeObjectPropsForCls(obj, cls, json) {
        const props = (this.getPropertiesByClass(cls) ?? []).sort((a, b)=>(a.phase ?? 0) - (b.phase ?? 0));
        let currentPhase = undefined;
        const promises = [];
        for (const prop of props){
            const phase = prop.phase ?? 0;
            if (phase !== currentPhase) {
                currentPhase = phase;
                if (promises.length > 0) {
                    await Promise.all(promises);
                }
            }
            if (prop.type === 'command') {
                continue;
            }
            if (!prop.set) {
                continue;
            }
            if (prop.isValid && !prop.isValid.call(obj)) {
                continue;
            }
            const persistent = prop.isPersistent?.call(obj) ?? true;
            if (!persistent) {
                continue;
            }
            const k = prop.name;
            const v = json[k] ?? this.getDefaultValue(obj, prop);
            const tmpVal = {
                num: [
                    0,
                    0,
                    0,
                    0
                ],
                str: [
                    ''
                ],
                bool: [
                    false
                ],
                object: [
                    null
                ]
            };
            switch(prop.type){
                case 'object':
                    if (typeof v === 'string' && v) {
                        tmpVal.str[0] = v;
                    } else {
                        tmpVal.object[0] = v ? Array.isArray(v) ? v : this.isSerializedObjectEnvelope(v) ? await this.deserializeObject(obj, v) ?? null : v : null;
                    }
                    break;
                case 'object_array':
                    tmpVal.object = [];
                    if (Array.isArray(v)) {
                        for (const p of v){
                            if (typeof p === 'string' && p) {
                                tmpVal.str[0] = p;
                            } else {
                                tmpVal.object.push(p ? this.isSerializedObjectEnvelope(p) ? await this.deserializeObject(obj, p) ?? null : p : null);
                            }
                        }
                    }
                    break;
                case 'embedded':
                    if (typeof v === 'string' && v) {
                        tmpVal.str[0] = v;
                    }
                    break;
                case 'float':
                case 'int':
                    tmpVal.num[0] = v;
                    break;
                case 'string':
                    tmpVal.str[0] = v;
                    break;
                case 'bool':
                    tmpVal.bool[0] = v;
                    break;
                case 'vec2':
                case 'int2':
                    tmpVal.num[0] = v[0];
                    tmpVal.num[1] = v[1];
                    break;
                case 'vec3':
                case 'int3':
                case 'rgb':
                    tmpVal.num[0] = v[0];
                    tmpVal.num[1] = v[1];
                    tmpVal.num[2] = v[2];
                    break;
                case 'vec4':
                case 'int4':
                case 'rgba':
                    tmpVal.num[0] = v[0];
                    tmpVal.num[1] = v[1];
                    tmpVal.num[2] = v[2];
                    tmpVal.num[3] = v[3];
                    break;
            }
            promises.push(Promise.resolve(prop.set.call(obj, tmpVal, -1)));
        }
        if (promises.length > 0) {
            await Promise.all(promises);
        }
    }
    async serializeObjectPropsForCls(obj, cls, json, asyncTasks) {
        const props = this.getPropertiesByClass(cls) ?? [];
        for (const prop of props){
            if (prop.isValid && !prop.isValid.call(obj)) {
                continue;
            }
            if (prop.type === 'command') {
                continue;
            }
            const persistent = prop.isPersistent?.call(obj) ?? true;
            if (!persistent) {
                continue;
            }
            const tmpVal = {
                num: [
                    0,
                    0,
                    0,
                    0
                ],
                str: [
                    ''
                ],
                bool: [
                    false
                ],
                object: [
                    null
                ]
            };
            const k = prop.name;
            prop.get.call(obj, tmpVal);
            switch(prop.type){
                case 'object':
                    {
                        const value = typeof tmpVal.str[0] === 'string' && tmpVal.str[0] ? tmpVal.str[0] : tmpVal.object[0] ? Array.isArray(tmpVal.object[0]) ? tmpVal.object[0] : await this.serializeObject(tmpVal.object[0], {}, asyncTasks) : null;
                        if (value) {
                            json[k] = value;
                        }
                        break;
                    }
                case 'object_array':
                    if (tmpVal.object.length > 0) {
                        const arr = [];
                        for (const p of tmpVal.object){
                            arr.push(await this.serializeObject(p, {}, asyncTasks));
                        }
                        json[k] = arr;
                    }
                    break;
                case 'embedded':
                    {
                        const relativePath = tmpVal.str[0].startsWith('/') ? tmpVal.str[0].slice(1) : tmpVal.str[0];
                        json[k] = relativePath;
                        if (asyncTasks) {
                            const resource = tmpVal.object[0];
                            asyncTasks.push((async ()=>{
                                const buffer = await resource;
                                await this.VFS.writeFile(relativePath, buffer, {
                                    encoding: 'binary',
                                    create: true
                                });
                            })());
                        }
                        break;
                    }
                case 'float':
                case 'int':
                    if (prop.default === undefined && !prop.getDefaultValue || this.getDefaultValue(obj, prop) !== tmpVal.num[0]) {
                        json[k] = tmpVal.num[0];
                    }
                    break;
                case 'string':
                    if (prop.default === undefined && !prop.getDefaultValue || this.getDefaultValue(obj, prop) !== tmpVal.str[0]) {
                        json[k] = tmpVal.str[0];
                    }
                    break;
                case 'bool':
                    if (prop.default === undefined && !prop.getDefaultValue || this.getDefaultValue(obj, prop) !== tmpVal.bool[0]) {
                        json[k] = tmpVal.bool[0];
                    }
                    break;
                case 'vec2':
                case 'int2':
                    if (prop.default !== undefined || !!prop.getDefaultValue) {
                        const v = this.getDefaultValue(obj, prop);
                        if (v[0] === tmpVal.num[0] && v[1] === tmpVal.num[1]) {
                            break;
                        }
                    }
                    json[k] = [
                        tmpVal.num[0],
                        tmpVal.num[1]
                    ];
                    break;
                case 'vec3':
                case 'int3':
                case 'rgb':
                    if (prop.default !== undefined || !!prop.getDefaultValue) {
                        const v = this.getDefaultValue(obj, prop);
                        if (v[0] === tmpVal.num[0] && v[1] === tmpVal.num[1] && v[2] === tmpVal.num[2]) {
                            break;
                        }
                    }
                    json[k] = [
                        tmpVal.num[0],
                        tmpVal.num[1],
                        tmpVal.num[2]
                    ];
                    break;
                case 'vec4':
                case 'int4':
                case 'rgba':
                    if (prop.default !== undefined || !!prop.getDefaultValue) {
                        const v = this.getDefaultValue(obj, prop);
                        if (v[0] === tmpVal.num[0] && v[1] === tmpVal.num[1] && v[2] === tmpVal.num[2] && v[3] === tmpVal.num[3]) {
                            break;
                        }
                    }
                    json[k] = [
                        tmpVal.num[0],
                        tmpVal.num[1],
                        tmpVal.num[2],
                        tmpVal.num[3]
                    ];
                    break;
            }
        }
    }
}

export { ResourceManager };
//# sourceMappingURL=manager.js.map
