import { Logger } from "../../Misc/logger.js";
import { Camera } from "../../Cameras/camera.js";
import { Vector3 } from "../../Maths/math.vector.js";
import { Color3, Color4 } from "../../Maths/math.color.js";
import { Mesh } from "../../Meshes/mesh.js";
import { Geometry } from "../../Meshes/geometry.js";
import { TransformNode } from "../../Meshes/transformNode.js";
import { Material } from "../../Materials/material.js";
import { MultiMaterial } from "../../Materials/multiMaterial.js";
import { CubeTexture } from "../../Materials/Textures/cubeTexture.js";
import { HDRCubeTexture } from "../../Materials/Textures/hdrCubeTexture.js";
import { AnimationGroup } from "../../Animations/animationGroup.js";
import { Light } from "../../Lights/light.js";
import { SceneComponentConstants } from "../../sceneComponent.js";
import { RegisterSceneLoaderPlugin } from "../../Loading/sceneLoader.js";
import { SceneLoaderFlags } from "../sceneLoaderFlags.js";

import { AssetContainer } from "../../assetContainer.js";
import { ActionManager } from "../../Actions/actionManager.js";
import { Skeleton } from "../../Bones/skeleton.js";
import { MorphTargetManager } from "../../Morph/morphTargetManager.js";
import { CannonJSPlugin } from "../../Physics/v1/Plugins/cannonJSPlugin.js";
import { OimoJSPlugin } from "../../Physics/v1/Plugins/oimoJSPlugin.js";
import { AmmoJSPlugin } from "../../Physics/v1/Plugins/ammoJSPlugin.js";
import { ReflectionProbe } from "../../Probes/reflectionProbe.js";
import { GetClass } from "../../Misc/typeStore.js";
import { Tools } from "../../Misc/tools.js";
import { PostProcess } from "../../PostProcesses/postProcess.js";
import { SpriteManager } from "../../Sprites/spriteManager.js";
import { GetIndividualParser, Parse } from "./babylonFileParser.function.js";
import { Texture } from "../../Materials/Textures/texture.js";
import { Observable } from "../../Misc/observable.js";
/** @internal */
// eslint-disable-next-line @typescript-eslint/naming-convention, no-var
export var _BabylonLoaderRegistered = true;
/**
 * Helps setting up some configuration for the babylon file loader.
 */
export class BabylonFileLoaderConfiguration {
}
/**
 * The loader does not allow injecting custom physics engine into the plugins.
 * Unfortunately in ES6, we need to manually inject them into the plugin.
 * So you could set this variable to your engine import to make it work.
 */
BabylonFileLoaderConfiguration.LoaderInjectedPhysicsEngine = undefined;
let TempIndexContainer = {};
let TempMaterialIndexContainer = {};
let TempMorphTargetManagerIndexContainer = {};
const ParseMaterialByPredicate = (predicate, parsedData, scene, rootUrl) => {
    if (!parsedData.materials) {
        return null;
    }
    for (let index = 0, cache = parsedData.materials.length; index < cache; index++) {
        const parsedMaterial = parsedData.materials[index];
        if (predicate(parsedMaterial)) {
            return { parsedMaterial, material: Material.Parse(parsedMaterial, scene, rootUrl) };
        }
    }
    return null;
};
const IsDescendantOf = (mesh, names, hierarchyIds) => {
    for (const i in names) {
        if (mesh.name === names[i]) {
            hierarchyIds.push(mesh.id);
            return true;
        }
    }
    if (mesh.parentId !== undefined && hierarchyIds.indexOf(mesh.parentId) !== -1) {
        hierarchyIds.push(mesh.id);
        return true;
    }
    return false;
};
// eslint-disable-next-line @typescript-eslint/naming-convention
const logOperation = (operation, producer) => {
    return (operation +
        " of " +
        (producer ? producer.file + " from " + producer.name + " version: " + producer.version + ", exporter version: " + producer.exporter_version : "unknown"));
};
const LoadDetailLevels = (scene, mesh) => {
    const mastermesh = mesh;
    // Every value specified in the ids array of the lod data points to another mesh which should be used as the lower LOD level.
    // The distances (or coverages) array values specified are used along with the lod mesh ids as a hint to determine the switching threshold for the various LODs.
    if (mesh._waitingData.lods) {
        if (mesh._waitingData.lods.ids && mesh._waitingData.lods.ids.length > 0) {
            const lodmeshes = mesh._waitingData.lods.ids;
            const wasenabled = mastermesh.isEnabled(false);
            if (mesh._waitingData.lods.distances) {
                const distances = mesh._waitingData.lods.distances;
                if (distances.length >= lodmeshes.length) {
                    const culling = distances.length > lodmeshes.length ? distances[distances.length - 1] : 0;
                    mastermesh.setEnabled(false);
                    for (let index = 0; index < lodmeshes.length; index++) {
                        const lodid = lodmeshes[index];
                        const lodmesh = scene.getMeshById(lodid);
                        if (lodmesh != null) {
                            mastermesh.addLODLevel(distances[index], lodmesh);
                        }
                    }
                    if (culling > 0) {
                        mastermesh.addLODLevel(culling, null);
                    }
                    if (wasenabled === true) {
                        mastermesh.setEnabled(true);
                    }
                }
                else {
                    Tools.Warn("Invalid level of detail distances for " + mesh.name);
                }
            }
        }
        mesh._waitingData.lods = null;
    }
};
const FindParent = (parentId, parentInstanceIndex, scene) => {
    if (typeof parentId !== "number") {
        const parentEntry = scene.getLastEntryById(parentId);
        if (parentEntry && parentInstanceIndex !== undefined && parentInstanceIndex !== null) {
            const instance = parentEntry.instances[parseInt(parentInstanceIndex)];
            return instance;
        }
        return parentEntry;
    }
    const parent = TempIndexContainer[parentId];
    if (parent && parentInstanceIndex !== undefined && parentInstanceIndex !== null) {
        const instance = parent.instances[parseInt(parentInstanceIndex)];
        return instance;
    }
    return parent;
};
const FindMaterial = (materialId, scene) => {
    if (typeof materialId !== "number") {
        return scene.getLastMaterialById(materialId, true);
    }
    return TempMaterialIndexContainer[materialId];
};
/**
 * @experimental
 * Loads an AssetContainer from a serialized Babylon scene.
 * @param scene The scene to load the asset container into.
 * @param serializedScene The serialized scene data. This can be either a JSON string, or an object (e.g. from a call to JSON.parse).
 * @param rootUrl The root URL for loading assets.
 * @returns The loaded AssetContainer.
 */
export function LoadAssetContainerFromSerializedScene(scene, serializedScene, rootUrl) {
    return LoadAssetContainer(scene, serializedScene, rootUrl);
}
const LoadAssetContainer = (scene, data, rootUrl, onError, addToScene = false) => {
    const container = new AssetContainer(scene);
    if (!addToScene) {
        scene._blockEntityCollection = true;
    }
    // Entire method running in try block, so ALWAYS logs as far as it got, only actually writes details
    // when SceneLoader.debugLogging = true (default), or exception encountered.
    // Everything stored in var log instead of writing separate lines to support only writing in exception,
    // and avoid problems with multiple concurrent .babylon loads.
    let log = "importScene has failed JSON parse";
    try {
        // eslint-disable-next-line no-var
        var parsedData = typeof data === "object" ? data : JSON.parse(data);
        log = "";
        const fullDetails = SceneLoaderFlags.loggingLevel === 3;
        let index;
        let cache;
        // Environment texture
        if (parsedData.environmentTexture !== undefined && parsedData.environmentTexture !== null) {
            // PBR needed for both HDR texture (gamma space) & a sky box
            const isPBR = parsedData.isPBR !== undefined ? parsedData.isPBR : true;
            if (parsedData.environmentTextureType && parsedData.environmentTextureType === "BABYLON.HDRCubeTexture") {
                const hdrSize = parsedData.environmentTextureSize ? parsedData.environmentTextureSize : 128;
                const hdrTexture = new HDRCubeTexture((parsedData.environmentTexture.match(/https?:\/\//g) ? "" : rootUrl) + parsedData.environmentTexture, scene, hdrSize, true, !isPBR, undefined, parsedData.environmentTexturePrefilterOnLoad);
                if (parsedData.environmentTextureRotationY) {
                    hdrTexture.rotationY = parsedData.environmentTextureRotationY;
                }
                scene.environmentTexture = hdrTexture;
            }
            else {
                if (typeof parsedData.environmentTexture === "object") {
                    const environmentTexture = CubeTexture.Parse(parsedData.environmentTexture, scene, rootUrl);
                    scene.environmentTexture = environmentTexture;
                }
                else if (parsedData.environmentTexture.endsWith(".env")) {
                    const compressedTexture = new CubeTexture((parsedData.environmentTexture.match(/https?:\/\//g) ? "" : rootUrl) + parsedData.environmentTexture, scene, parsedData.environmentTextureForcedExtension);
                    if (parsedData.environmentTextureRotationY) {
                        compressedTexture.rotationY = parsedData.environmentTextureRotationY;
                    }
                    scene.environmentTexture = compressedTexture;
                }
                else {
                    const cubeTexture = CubeTexture.CreateFromPrefilteredData((parsedData.environmentTexture.match(/https?:\/\//g) ? "" : rootUrl) + parsedData.environmentTexture, scene, parsedData.environmentTextureForcedExtension);
                    if (parsedData.environmentTextureRotationY) {
                        cubeTexture.rotationY = parsedData.environmentTextureRotationY;
                    }
                    scene.environmentTexture = cubeTexture;
                }
            }
            if (parsedData.createDefaultSkybox === true) {
                const skyboxScale = scene.activeCamera !== undefined && scene.activeCamera !== null ? (scene.activeCamera.maxZ - scene.activeCamera.minZ) / 2 : 1000;
                const skyboxBlurLevel = parsedData.skyboxBlurLevel || 0;
                scene.createDefaultSkybox(scene.environmentTexture, isPBR, skyboxScale, skyboxBlurLevel);
            }
            container.environmentTexture = scene.environmentTexture;
        }
        // Environment Intensity
        if (parsedData.environmentIntensity !== undefined && parsedData.environmentIntensity !== null) {
            scene.environmentIntensity = parsedData.environmentIntensity;
        }
        // IBL Intensity
        if (parsedData.iblIntensity !== undefined && parsedData.iblIntensity !== null) {
            scene.iblIntensity = parsedData.iblIntensity;
        }
        // Lights
        if (parsedData.lights !== undefined && parsedData.lights !== null) {
            for (index = 0, cache = parsedData.lights.length; index < cache; index++) {
                const parsedLight = parsedData.lights[index];
                const light = Light.Parse(parsedLight, scene);
                if (light) {
                    TempIndexContainer[parsedLight.uniqueId] = light;
                    container.lights.push(light);
                    light._parentContainer = container;
                    log += index === 0 ? "\n\tLights:" : "";
                    log += "\n\t\t" + light.toString(fullDetails);
                }
            }
        }
        // Reflection probes
        if (parsedData.reflectionProbes !== undefined && parsedData.reflectionProbes !== null) {
            for (index = 0, cache = parsedData.reflectionProbes.length; index < cache; index++) {
                const parsedReflectionProbe = parsedData.reflectionProbes[index];
                const reflectionProbe = ReflectionProbe.Parse(parsedReflectionProbe, scene, rootUrl);
                if (reflectionProbe) {
                    container.reflectionProbes.push(reflectionProbe);
                    reflectionProbe._parentContainer = container;
                    log += index === 0 ? "\n\tReflection Probes:" : "";
                    log += "\n\t\t" + reflectionProbe.toString(fullDetails);
                }
            }
        }
        // Animations
        if (parsedData.animations !== undefined && parsedData.animations !== null) {
            for (index = 0, cache = parsedData.animations.length; index < cache; index++) {
                const parsedAnimation = parsedData.animations[index];
                const internalClass = GetClass("BABYLON.Animation");
                if (internalClass) {
                    const animation = internalClass.Parse(parsedAnimation);
                    scene.animations.push(animation);
                    container.animations.push(animation);
                    log += index === 0 ? "\n\tAnimations:" : "";
                    log += "\n\t\t" + animation.toString(fullDetails);
                }
            }
        }
        // Materials
        if (parsedData.materials !== undefined && parsedData.materials !== null) {
            for (index = 0, cache = parsedData.materials.length; index < cache; index++) {
                const parsedMaterial = parsedData.materials[index];
                const mat = Material.Parse(parsedMaterial, scene, rootUrl);
                if (mat) {
                    TempMaterialIndexContainer[parsedMaterial.uniqueId || parsedMaterial.id] = mat;
                    container.materials.push(mat);
                    mat._parentContainer = container;
                    log += index === 0 ? "\n\tMaterials:" : "";
                    log += "\n\t\t" + mat.toString(fullDetails);
                    // Textures
                    const textures = mat.getActiveTextures();
                    for (const t of textures) {
                        if (container.textures.indexOf(t) == -1) {
                            container.textures.push(t);
                            t._parentContainer = container;
                        }
                    }
                }
            }
        }
        if (parsedData.textures !== undefined && parsedData.textures !== null) {
            for (index = 0, cache = parsedData.textures.length; index < cache; index++) {
                const parsedTexture = parsedData.textures[index];
                const texture = Texture.Parse(parsedTexture, scene, rootUrl);
                if (texture) {
                    if (container.textures.indexOf(texture) == -1) {
                        container.textures.push(texture);
                        texture._parentContainer = container;
                    }
                }
            }
        }
        if (parsedData.multiMaterials !== undefined && parsedData.multiMaterials !== null) {
            for (index = 0, cache = parsedData.multiMaterials.length; index < cache; index++) {
                const parsedMultiMaterial = parsedData.multiMaterials[index];
                const mmat = MultiMaterial.ParseMultiMaterial(parsedMultiMaterial, scene);
                TempMaterialIndexContainer[parsedMultiMaterial.uniqueId || parsedMultiMaterial.id] = mmat;
                container.multiMaterials.push(mmat);
                mmat._parentContainer = container;
                log += index === 0 ? "\n\tMultiMaterials:" : "";
                log += "\n\t\t" + mmat.toString(fullDetails);
                // Textures
                const textures = mmat.getActiveTextures();
                for (const t of textures) {
                    if (container.textures.indexOf(t) == -1) {
                        container.textures.push(t);
                        t._parentContainer = container;
                    }
                }
            }
        }
        // Morph targets
        if (parsedData.morphTargetManagers !== undefined && parsedData.morphTargetManagers !== null) {
            for (const parsedManager of parsedData.morphTargetManagers) {
                const manager = MorphTargetManager.Parse(parsedManager, scene);
                TempMorphTargetManagerIndexContainer[parsedManager.id] = manager;
                container.morphTargetManagers.push(manager);
                manager._parentContainer = container;
            }
        }
        // Skeletons
        if (parsedData.skeletons !== undefined && parsedData.skeletons !== null) {
            for (index = 0, cache = parsedData.skeletons.length; index < cache; index++) {
                const parsedSkeleton = parsedData.skeletons[index];
                const skeleton = Skeleton.Parse(parsedSkeleton, scene);
                container.skeletons.push(skeleton);
                skeleton._parentContainer = container;
                log += index === 0 ? "\n\tSkeletons:" : "";
                log += "\n\t\t" + skeleton.toString(fullDetails);
            }
        }
        // Geometries
        const geometries = parsedData.geometries;
        if (geometries !== undefined && geometries !== null) {
            const addedGeometry = new Array();
            // VertexData
            const vertexData = geometries.vertexData;
            if (vertexData !== undefined && vertexData !== null) {
                for (index = 0, cache = vertexData.length; index < cache; index++) {
                    const parsedVertexData = vertexData[index];
                    // Geometies are found by loadedUniqueId when imported
                    // So we need to temporarily unblock the entity collection to add them to the scene
                    scene._blockEntityCollection = false;
                    // Temporarily replace the onNewGeometryAddedObservable to avoid multiple notifications
                    const onNewGeometryAddedObservable = scene.onNewGeometryAddedObservable;
                    scene.onNewGeometryAddedObservable = new Observable();
                    addedGeometry.push(Geometry.Parse(parsedVertexData, scene, rootUrl));
                    // Restore the onNewGeometryAddedObservable
                    scene.onNewGeometryAddedObservable = onNewGeometryAddedObservable;
                    // Restore the previous state of entity collection blocking
                    scene._blockEntityCollection = !addToScene;
                }
            }
            for (const g of addedGeometry) {
                if (g) {
                    container.geometries.push(g);
                    g._parentContainer = container;
                }
            }
        }
        // Transform nodes
        if (parsedData.transformNodes !== undefined && parsedData.transformNodes !== null) {
            for (index = 0, cache = parsedData.transformNodes.length; index < cache; index++) {
                const parsedTransformNode = parsedData.transformNodes[index];
                const node = TransformNode.Parse(parsedTransformNode, scene, rootUrl);
                TempIndexContainer[parsedTransformNode.uniqueId] = node;
                container.transformNodes.push(node);
                node._parentContainer = container;
            }
        }
        // Meshes
        if (parsedData.meshes !== undefined && parsedData.meshes !== null) {
            for (index = 0, cache = parsedData.meshes.length; index < cache; index++) {
                const parsedMesh = parsedData.meshes[index];
                const mesh = Mesh.Parse(parsedMesh, scene, rootUrl);
                TempIndexContainer[parsedMesh.uniqueId] = mesh;
                container.meshes.push(mesh);
                mesh._parentContainer = container;
                if (mesh.hasInstances) {
                    for (const instance of mesh.instances) {
                        container.meshes.push(instance);
                        instance._parentContainer = container;
                    }
                }
                log += index === 0 ? "\n\tMeshes:" : "";
                log += "\n\t\t" + mesh.toString(fullDetails);
            }
        }
        // Cameras
        if (parsedData.cameras !== undefined && parsedData.cameras !== null) {
            for (index = 0, cache = parsedData.cameras.length; index < cache; index++) {
                const parsedCamera = parsedData.cameras[index];
                const camera = Camera.Parse(parsedCamera, scene);
                TempIndexContainer[parsedCamera.uniqueId] = camera;
                container.cameras.push(camera);
                camera._parentContainer = container;
                log += index === 0 ? "\n\tCameras:" : "";
                log += "\n\t\t" + camera.toString(fullDetails);
            }
        }
        // Postprocesses
        if (parsedData.postProcesses !== undefined && parsedData.postProcesses !== null) {
            for (index = 0, cache = parsedData.postProcesses.length; index < cache; index++) {
                const parsedPostProcess = parsedData.postProcesses[index];
                const postProcess = PostProcess.Parse(parsedPostProcess, scene, rootUrl);
                if (postProcess) {
                    container.postProcesses.push(postProcess);
                    postProcess._parentContainer = container;
                    log += index === 0 ? "\nPostprocesses:" : "";
                    log += "\n\t\t" + postProcess.toString();
                }
            }
        }
        // Animation Groups
        if (parsedData.animationGroups !== undefined && parsedData.animationGroups !== null && parsedData.animationGroups.length) {
            // Build the nodeMap only for scenes with animationGroups
            const nodeMap = new Map();
            // Nodes in scene does not change when parsing animationGroups, so it's safe to build a map.
            // This follows the order of scene.getNodeById: mesh, transformNode, light, camera, bone
            for (let index = 0; index < scene.meshes.length; index++) {
                // This follows the behavior of scene.getXXXById, which picks the first match
                if (!nodeMap.has(scene.meshes[index].id)) {
                    nodeMap.set(scene.meshes[index].id, scene.meshes[index]);
                }
            }
            for (let index = 0; index < scene.transformNodes.length; index++) {
                if (!nodeMap.has(scene.transformNodes[index].id)) {
                    nodeMap.set(scene.transformNodes[index].id, scene.transformNodes[index]);
                }
            }
            for (let index = 0; index < scene.lights.length; index++) {
                if (!nodeMap.has(scene.lights[index].id)) {
                    nodeMap.set(scene.lights[index].id, scene.lights[index]);
                }
            }
            for (let index = 0; index < scene.cameras.length; index++) {
                if (!nodeMap.has(scene.cameras[index].id)) {
                    nodeMap.set(scene.cameras[index].id, scene.cameras[index]);
                }
            }
            for (let skeletonIndex = 0; skeletonIndex < scene.skeletons.length; skeletonIndex++) {
                const skeleton = scene.skeletons[skeletonIndex];
                for (let boneIndex = 0; boneIndex < skeleton.bones.length; boneIndex++) {
                    if (!nodeMap.has(skeleton.bones[boneIndex].id)) {
                        nodeMap.set(skeleton.bones[boneIndex].id, skeleton.bones[boneIndex]);
                    }
                }
            }
            for (index = 0, cache = parsedData.animationGroups.length; index < cache; index++) {
                const parsedAnimationGroup = parsedData.animationGroups[index];
                const animationGroup = AnimationGroup.Parse(parsedAnimationGroup, scene, nodeMap);
                container.animationGroups.push(animationGroup);
                animationGroup._parentContainer = container;
                log += index === 0 ? "\n\tAnimationGroups:" : "";
                log += "\n\t\t" + animationGroup.toString(fullDetails);
            }
        }
        // Sprites
        if (parsedData.spriteManagers) {
            for (let index = 0, cache = parsedData.spriteManagers.length; index < cache; index++) {
                const parsedSpriteManager = parsedData.spriteManagers[index];
                const spriteManager = SpriteManager.Parse(parsedSpriteManager, scene, rootUrl);
                container.spriteManagers.push(spriteManager);
                spriteManager._parentContainer = container;
                log += "\n\t\tSpriteManager " + spriteManager.name;
            }
        }
        // Browsing all the graph to connect the dots
        for (index = 0, cache = scene.cameras.length; index < cache; index++) {
            const camera = scene.cameras[index];
            if (camera._waitingParentId !== null) {
                camera.parent = FindParent(camera._waitingParentId, camera._waitingParentInstanceIndex, scene);
                camera._waitingParentId = null;
                camera._waitingParentInstanceIndex = null;
            }
        }
        for (index = 0, cache = scene.lights.length; index < cache; index++) {
            const light = scene.lights[index];
            if (light && light._waitingParentId !== null) {
                light.parent = FindParent(light._waitingParentId, light._waitingParentInstanceIndex, scene);
                light._waitingParentId = null;
                light._waitingParentInstanceIndex = null;
            }
        }
        // Connect parents & children and parse actions and lods
        for (index = 0, cache = scene.transformNodes.length; index < cache; index++) {
            const transformNode = scene.transformNodes[index];
            if (transformNode._waitingParentId !== null) {
                transformNode.parent = FindParent(transformNode._waitingParentId, transformNode._waitingParentInstanceIndex, scene);
                transformNode._waitingParentId = null;
                transformNode._waitingParentInstanceIndex = null;
            }
        }
        for (index = 0, cache = scene.meshes.length; index < cache; index++) {
            const mesh = scene.meshes[index];
            if (mesh._waitingParentId !== null) {
                mesh.parent = FindParent(mesh._waitingParentId, mesh._waitingParentInstanceIndex, scene);
                mesh._waitingParentId = null;
                mesh._waitingParentInstanceIndex = null;
            }
            if (mesh._waitingData.lods) {
                LoadDetailLevels(scene, mesh);
            }
        }
        // link multimats with materials
        for (const multimat of scene.multiMaterials) {
            for (const subMaterial of multimat._waitingSubMaterialsUniqueIds) {
                multimat.subMaterials.push(FindMaterial(subMaterial, scene));
            }
            multimat._waitingSubMaterialsUniqueIds = [];
        }
        // link meshes with materials
        for (const mesh of scene.meshes) {
            if (mesh._waitingMaterialId !== null) {
                mesh.material = FindMaterial(mesh._waitingMaterialId, scene);
                mesh._waitingMaterialId = null;
            }
        }
        // link meshes with morph target managers
        for (const mesh of scene.meshes) {
            if (mesh._waitingMorphTargetManagerId !== null) {
                mesh.morphTargetManager = TempMorphTargetManagerIndexContainer[mesh._waitingMorphTargetManagerId];
                mesh._waitingMorphTargetManagerId = null;
            }
        }
        // link skeleton transform nodes
        for (index = 0, cache = scene.skeletons.length; index < cache; index++) {
            const skeleton = scene.skeletons[index];
            if (skeleton._hasWaitingData) {
                if (skeleton.bones != null) {
                    for (const bone of skeleton.bones) {
                        if (bone._waitingTransformNodeId) {
                            const linkTransformNode = scene.getLastEntryById(bone._waitingTransformNodeId);
                            if (linkTransformNode) {
                                bone.linkTransformNode(linkTransformNode);
                            }
                            bone._waitingTransformNodeId = null;
                        }
                    }
                }
                skeleton._hasWaitingData = null;
            }
        }
        // freeze world matrix application
        for (index = 0, cache = scene.meshes.length; index < cache; index++) {
            const currentMesh = scene.meshes[index];
            if (currentMesh._waitingData.freezeWorldMatrix) {
                currentMesh.freezeWorldMatrix();
                currentMesh._waitingData.freezeWorldMatrix = null;
            }
            else {
                currentMesh.computeWorldMatrix(true);
            }
        }
        // Lights exclusions / inclusions
        for (index = 0, cache = scene.lights.length; index < cache; index++) {
            const light = scene.lights[index];
            // Excluded check
            if (light._excludedMeshesIds.length > 0) {
                for (let excludedIndex = 0; excludedIndex < light._excludedMeshesIds.length; excludedIndex++) {
                    const excludedMesh = scene.getMeshById(light._excludedMeshesIds[excludedIndex]);
                    if (excludedMesh) {
                        light.excludedMeshes.push(excludedMesh);
                    }
                }
                light._excludedMeshesIds = [];
            }
            // Included check
            if (light._includedOnlyMeshesIds.length > 0) {
                for (let includedOnlyIndex = 0; includedOnlyIndex < light._includedOnlyMeshesIds.length; includedOnlyIndex++) {
                    const includedOnlyMesh = scene.getMeshById(light._includedOnlyMeshesIds[includedOnlyIndex]);
                    if (includedOnlyMesh) {
                        light.includedOnlyMeshes.push(includedOnlyMesh);
                    }
                }
                light._includedOnlyMeshesIds = [];
            }
        }
        for (const g of scene.geometries) {
            g._loadedUniqueId = "";
        }
        Parse(parsedData, scene, container, rootUrl);
        // Actions (scene) Done last as it can access other objects.
        for (index = 0, cache = scene.meshes.length; index < cache; index++) {
            const mesh = scene.meshes[index];
            if (mesh._waitingData.actions) {
                ActionManager.Parse(mesh._waitingData.actions, mesh, scene);
                mesh._waitingData.actions = null;
            }
        }
        if (parsedData.actions !== undefined && parsedData.actions !== null) {
            ActionManager.Parse(parsedData.actions, null, scene);
        }
    }
    catch (err) {
        const msg = logOperation("loadAssets", parsedData ? parsedData.producer : "Unknown") + log;
        if (onError) {
            onError(msg, err);
        }
        else {
            Logger.Log(msg);
            throw err;
        }
    }
    finally {
        TempIndexContainer = {};
        TempMaterialIndexContainer = {};
        TempMorphTargetManagerIndexContainer = {};
        if (!addToScene) {
            // Removes any breadcrumb left during the loading like geometries
            container.removeAllFromScene();
            // Unblock entity collection
            scene._blockEntityCollection = false;
        }
        if (log !== null && SceneLoaderFlags.loggingLevel !== 0) {
            Logger.Log(logOperation("loadAssets", parsedData ? parsedData.producer : "Unknown") + (SceneLoaderFlags.loggingLevel !== 1 ? log : ""));
        }
    }
    return container;
};
RegisterSceneLoaderPlugin({
    name: "babylon.js",
    extensions: ".babylon",
    canDirectLoad: (data) => {
        if (data.indexOf("babylon") !== -1) {
            // We consider that the producer string is filled
            return true;
        }
        return false;
    },
    importMesh: (meshesNames, scene, data, rootUrl, meshes, particleSystems, skeletons, onError) => {
        // Entire method running in try block, so ALWAYS logs as far as it got, only actually writes details
        // when SceneLoader.debugLogging = true (default), or exception encountered.
        // Everything stored in var log instead of writing separate lines to support only writing in exception,
        // and avoid problems with multiple concurrent .babylon loads.
        let log = "importMesh has failed JSON parse";
        try {
            // eslint-disable-next-line no-var
            var parsedData = JSON.parse(data);
            log = "";
            const fullDetails = SceneLoaderFlags.loggingLevel === 3;
            if (!meshesNames) {
                meshesNames = null;
            }
            else if (!Array.isArray(meshesNames)) {
                meshesNames = [meshesNames];
            }
            const hierarchyIds = [];
            const parsedIdToNodeMap = new Map();
            // Transform nodes (the overall idea is to load all of them as this is super fast and then get rid of the ones we don't need)
            const loadedTransformNodes = [];
            if (parsedData.transformNodes !== undefined && parsedData.transformNodes !== null) {
                for (let index = 0, cache = parsedData.transformNodes.length; index < cache; index++) {
                    const parsedJSONTransformNode = parsedData.transformNodes[index];
                    const parsedTransformNode = TransformNode.Parse(parsedJSONTransformNode, scene, rootUrl);
                    loadedTransformNodes.push(parsedTransformNode);
                    parsedIdToNodeMap.set(parsedTransformNode._waitingParsedUniqueId, parsedTransformNode);
                    parsedTransformNode._waitingParsedUniqueId = null;
                }
            }
            if (parsedData.meshes !== undefined && parsedData.meshes !== null) {
                const loadedSkeletonsIds = [];
                const loadedMaterialsIds = [];
                const loadedMaterialsUniqueIds = [];
                const loadedMorphTargetManagerIds = [];
                for (let index = 0, cache = parsedData.meshes.length; index < cache; index++) {
                    const parsedMesh = parsedData.meshes[index];
                    if (meshesNames === null || IsDescendantOf(parsedMesh, meshesNames, hierarchyIds)) {
                        if (meshesNames !== null) {
                            // Remove found mesh name from list.
                            delete meshesNames[meshesNames.indexOf(parsedMesh.name)];
                        }
                        //Geometry?
                        if (parsedMesh.geometryId !== undefined && parsedMesh.geometryId !== null) {
                            //does the file contain geometries?
                            if (parsedData.geometries !== undefined && parsedData.geometries !== null) {
                                //find the correct geometry and add it to the scene
                                let found = false;
                                const geoms = ["boxes", "spheres", "cylinders", "toruses", "grounds", "planes", "torusKnots", "vertexData"];
                                for (const geometryType of geoms) {
                                    if (!parsedData.geometries[geometryType] || !Array.isArray(parsedData.geometries[geometryType])) {
                                        continue;
                                    }
                                    const geom = parsedData.geometries[geometryType];
                                    for (const parsedGeometryData of geom) {
                                        if (parsedGeometryData.id === parsedMesh.geometryId) {
                                            switch (geometryType) {
                                                case "vertexData":
                                                    Geometry.Parse(parsedGeometryData, scene, rootUrl);
                                                    break;
                                            }
                                            found = true;
                                            break;
                                        }
                                    }
                                    if (found) {
                                        break;
                                    }
                                }
                                if (found === false) {
                                    Logger.Warn("Geometry not found for mesh " + parsedMesh.id);
                                }
                            }
                        }
                        // Material ?
                        if (parsedMesh.materialUniqueId || parsedMesh.materialId) {
                            // if we have a unique ID, look up and store in loadedMaterialsUniqueIds, else use loadedMaterialsIds
                            const materialArray = parsedMesh.materialUniqueId ? loadedMaterialsUniqueIds : loadedMaterialsIds;
                            let materialFound = materialArray.indexOf(parsedMesh.materialUniqueId || parsedMesh.materialId) !== -1;
                            if (materialFound === false && parsedData.multiMaterials !== undefined && parsedData.multiMaterials !== null) {
                                // Loads a submaterial of a multimaterial
                                const loadSubMaterial = (subMatId, predicate) => {
                                    materialArray.push(subMatId);
                                    const mat = ParseMaterialByPredicate(predicate, parsedData, scene, rootUrl);
                                    if (mat && mat.material) {
                                        TempMaterialIndexContainer[mat.parsedMaterial.uniqueId || mat.parsedMaterial.id] = mat.material;
                                        log += "\n\tMaterial " + mat.material.toString(fullDetails);
                                    }
                                };
                                for (let multimatIndex = 0, multimatCache = parsedData.multiMaterials.length; multimatIndex < multimatCache; multimatIndex++) {
                                    const parsedMultiMaterial = parsedData.multiMaterials[multimatIndex];
                                    if ((parsedMesh.materialUniqueId && parsedMultiMaterial.uniqueId === parsedMesh.materialUniqueId) ||
                                        parsedMultiMaterial.id === parsedMesh.materialId) {
                                        if (parsedMultiMaterial.materialsUniqueIds) {
                                            // if the materials inside the multimat are stored by unique id
                                            for (const subMatId of parsedMultiMaterial.materialsUniqueIds) {
                                                loadSubMaterial(subMatId, (parsedMaterial) => parsedMaterial.uniqueId === subMatId);
                                            }
                                        }
                                        else {
                                            // if the mats are stored by id instead
                                            for (const subMatId of parsedMultiMaterial.materials) {
                                                loadSubMaterial(subMatId, (parsedMaterial) => parsedMaterial.id === subMatId);
                                            }
                                        }
                                        materialArray.push(parsedMultiMaterial.uniqueId || parsedMultiMaterial.id);
                                        const mmat = MultiMaterial.ParseMultiMaterial(parsedMultiMaterial, scene);
                                        TempMaterialIndexContainer[parsedMultiMaterial.uniqueId || parsedMultiMaterial.id] = mmat;
                                        if (mmat) {
                                            materialFound = true;
                                            log += "\n\tMulti-Material " + mmat.toString(fullDetails);
                                        }
                                        break;
                                    }
                                }
                            }
                            if (materialFound === false) {
                                materialArray.push(parsedMesh.materialUniqueId || parsedMesh.materialId);
                                const mat = ParseMaterialByPredicate((parsedMaterial) => (parsedMesh.materialUniqueId && parsedMaterial.uniqueId === parsedMesh.materialUniqueId) || parsedMaterial.id === parsedMesh.materialId, parsedData, scene, rootUrl);
                                if (!mat || !mat.material) {
                                    Logger.Warn("Material not found for mesh " + parsedMesh.id);
                                }
                                else {
                                    TempMaterialIndexContainer[mat.parsedMaterial.uniqueId || mat.parsedMaterial.id] = mat.material;
                                    log += "\n\tMaterial " + mat.material.toString(fullDetails);
                                }
                            }
                        }
                        // Skeleton ?
                        if (parsedMesh.skeletonId !== null &&
                            parsedMesh.skeletonId !== undefined &&
                            parsedData.skeletonId !== -1 &&
                            parsedData.skeletons !== undefined &&
                            parsedData.skeletons !== null) {
                            const skeletonAlreadyLoaded = loadedSkeletonsIds.indexOf(parsedMesh.skeletonId) > -1;
                            if (!skeletonAlreadyLoaded) {
                                for (let skeletonIndex = 0, skeletonCache = parsedData.skeletons.length; skeletonIndex < skeletonCache; skeletonIndex++) {
                                    const parsedSkeleton = parsedData.skeletons[skeletonIndex];
                                    if (parsedSkeleton.id === parsedMesh.skeletonId) {
                                        const skeleton = Skeleton.Parse(parsedSkeleton, scene);
                                        skeletons.push(skeleton);
                                        loadedSkeletonsIds.push(parsedSkeleton.id);
                                        log += "\n\tSkeleton " + skeleton.toString(fullDetails);
                                    }
                                }
                            }
                        }
                        // Morph targets ?
                        if (parsedMesh.morphTargetManagerId > -1 && parsedData.morphTargetManagers !== undefined && parsedData.morphTargetManagers !== null) {
                            const morphTargetManagerAlreadyLoaded = loadedMorphTargetManagerIds.indexOf(parsedMesh.morphTargetManagerId) > -1;
                            if (!morphTargetManagerAlreadyLoaded) {
                                for (let morphTargetManagerIndex = 0; morphTargetManagerIndex < parsedData.morphTargetManagers.length; morphTargetManagerIndex++) {
                                    const parsedManager = parsedData.morphTargetManagers[morphTargetManagerIndex];
                                    if (parsedManager.id === parsedMesh.morphTargetManagerId) {
                                        const morphTargetManager = MorphTargetManager.Parse(parsedManager, scene);
                                        TempMorphTargetManagerIndexContainer[parsedManager.id] = morphTargetManager;
                                        loadedMorphTargetManagerIds.push(parsedManager.id);
                                        log += "\nMorph target manager" + morphTargetManager.toString();
                                    }
                                }
                            }
                        }
                        const mesh = Mesh.Parse(parsedMesh, scene, rootUrl);
                        meshes.push(mesh);
                        parsedIdToNodeMap.set(mesh._waitingParsedUniqueId, mesh);
                        mesh._waitingParsedUniqueId = null;
                        log += "\n\tMesh " + mesh.toString(fullDetails);
                    }
                }
                // link multimats with materials
                for (const multimat of scene.multiMaterials) {
                    for (const subMaterial of multimat._waitingSubMaterialsUniqueIds) {
                        multimat.subMaterials.push(FindMaterial(subMaterial, scene));
                    }
                    multimat._waitingSubMaterialsUniqueIds = [];
                }
                // link meshes with materials
                for (const mesh of scene.meshes) {
                    if (mesh._waitingMaterialId !== null) {
                        mesh.material = FindMaterial(mesh._waitingMaterialId, scene);
                        mesh._waitingMaterialId = null;
                    }
                }
                // link meshes with morph target managers
                for (const mesh of scene.meshes) {
                    if (mesh._waitingMorphTargetManagerId !== null) {
                        mesh.morphTargetManager = TempMorphTargetManagerIndexContainer[mesh._waitingMorphTargetManagerId];
                        mesh._waitingMorphTargetManagerId = null;
                    }
                }
                // Connecting parents and lods
                for (let index = 0, cache = scene.transformNodes.length; index < cache; index++) {
                    const transformNode = scene.transformNodes[index];
                    if (transformNode._waitingParentId !== null) {
                        let parent = parsedIdToNodeMap.get(parseInt(transformNode._waitingParentId)) || null;
                        if (parent === null) {
                            parent = scene.getLastEntryById(transformNode._waitingParentId);
                        }
                        let parentNode = parent;
                        if (transformNode._waitingParentInstanceIndex) {
                            parentNode = parent.instances[parseInt(transformNode._waitingParentInstanceIndex)];
                            transformNode._waitingParentInstanceIndex = null;
                        }
                        transformNode.parent = parentNode;
                        transformNode._waitingParentId = null;
                    }
                }
                let currentMesh;
                for (let index = 0, cache = scene.meshes.length; index < cache; index++) {
                    currentMesh = scene.meshes[index];
                    if (currentMesh._waitingParentId) {
                        let parent = parsedIdToNodeMap.get(parseInt(currentMesh._waitingParentId)) || null;
                        if (parent === null) {
                            parent = scene.getLastEntryById(currentMesh._waitingParentId);
                        }
                        let parentNode = parent;
                        if (currentMesh._waitingParentInstanceIndex) {
                            parentNode = parent.instances[parseInt(currentMesh._waitingParentInstanceIndex)];
                            currentMesh._waitingParentInstanceIndex = null;
                        }
                        currentMesh.parent = parentNode;
                        currentMesh._waitingParentId = null;
                    }
                    if (currentMesh._waitingData.lods) {
                        LoadDetailLevels(scene, currentMesh);
                    }
                }
                // Remove unused transform nodes
                for (const transformNode of loadedTransformNodes) {
                    const childMeshes = transformNode.getChildMeshes(false);
                    if (!childMeshes.length) {
                        transformNode.dispose();
                    }
                }
                // link skeleton transform nodes
                for (let index = 0, cache = scene.skeletons.length; index < cache; index++) {
                    const skeleton = scene.skeletons[index];
                    if (skeleton._hasWaitingData) {
                        if (skeleton.bones != null) {
                            for (const bone of skeleton.bones) {
                                if (bone._waitingTransformNodeId) {
                                    const linkTransformNode = scene.getLastEntryById(bone._waitingTransformNodeId);
                                    if (linkTransformNode) {
                                        bone.linkTransformNode(linkTransformNode);
                                    }
                                    bone._waitingTransformNodeId = null;
                                }
                            }
                        }
                        skeleton._hasWaitingData = null;
                    }
                }
                // freeze and compute world matrix application
                for (let index = 0, cache = scene.meshes.length; index < cache; index++) {
                    currentMesh = scene.meshes[index];
                    if (currentMesh._waitingData.freezeWorldMatrix) {
                        currentMesh.freezeWorldMatrix();
                        currentMesh._waitingData.freezeWorldMatrix = null;
                    }
                    else {
                        currentMesh.computeWorldMatrix(true);
                    }
                }
            }
            // Particles
            if (parsedData.particleSystems !== undefined && parsedData.particleSystems !== null) {
                const parser = GetIndividualParser(SceneComponentConstants.NAME_PARTICLESYSTEM);
                if (parser) {
                    for (let index = 0, cache = parsedData.particleSystems.length; index < cache; index++) {
                        const parsedParticleSystem = parsedData.particleSystems[index];
                        if (hierarchyIds.indexOf(parsedParticleSystem.emitterId) !== -1) {
                            particleSystems.push(parser(parsedParticleSystem, scene, rootUrl));
                        }
                    }
                }
            }
            for (const g of scene.geometries) {
                g._loadedUniqueId = "";
            }
            return true;
        }
        catch (err) {
            const msg = logOperation("importMesh", parsedData ? parsedData.producer : "Unknown") + log;
            if (onError) {
                onError(msg, err);
            }
            else {
                Logger.Log(msg);
                throw err;
            }
        }
        finally {
            if (log !== null && SceneLoaderFlags.loggingLevel !== 0) {
                Logger.Log(logOperation("importMesh", parsedData ? parsedData.producer : "Unknown") + (SceneLoaderFlags.loggingLevel !== 1 ? log : ""));
            }
            TempMaterialIndexContainer = {};
            TempMorphTargetManagerIndexContainer = {};
        }
        return false;
    },
    load: (scene, data, rootUrl, onError) => {
        // Entire method running in try block, so ALWAYS logs as far as it got, only actually writes details
        // when SceneLoader.debugLogging = true (default), or exception encountered.
        // Everything stored in var log instead of writing separate lines to support only writing in exception,
        // and avoid problems with multiple concurrent .babylon loads.
        let log = "importScene has failed JSON parse";
        try {
            // eslint-disable-next-line no-var
            var parsedData = JSON.parse(data);
            log = "";
            // Scene
            if (parsedData.useDelayedTextureLoading !== undefined && parsedData.useDelayedTextureLoading !== null) {
                scene.useDelayedTextureLoading = parsedData.useDelayedTextureLoading && !SceneLoaderFlags.ForceFullSceneLoadingForIncremental;
            }
            if (parsedData.autoClear !== undefined && parsedData.autoClear !== null) {
                scene.autoClear = parsedData.autoClear;
            }
            if (parsedData.clearColor !== undefined && parsedData.clearColor !== null) {
                scene.clearColor = Color4.FromArray(parsedData.clearColor);
            }
            if (parsedData.ambientColor !== undefined && parsedData.ambientColor !== null) {
                scene.ambientColor = Color3.FromArray(parsedData.ambientColor);
            }
            if (parsedData.gravity !== undefined && parsedData.gravity !== null) {
                scene.gravity = Vector3.FromArray(parsedData.gravity);
            }
            if (parsedData.useRightHandedSystem !== undefined) {
                scene.useRightHandedSystem = !!parsedData.useRightHandedSystem;
            }
            // Fog
            if (parsedData.fogMode !== undefined && parsedData.fogMode !== null) {
                scene.fogMode = parsedData.fogMode;
            }
            if (parsedData.fogColor !== undefined && parsedData.fogColor !== null) {
                scene.fogColor = Color3.FromArray(parsedData.fogColor);
            }
            if (parsedData.fogStart !== undefined && parsedData.fogStart !== null) {
                scene.fogStart = parsedData.fogStart;
            }
            if (parsedData.fogEnd !== undefined && parsedData.fogEnd !== null) {
                scene.fogEnd = parsedData.fogEnd;
            }
            if (parsedData.fogDensity !== undefined && parsedData.fogDensity !== null) {
                scene.fogDensity = parsedData.fogDensity;
            }
            log += "\tFog mode for scene:  ";
            switch (scene.fogMode) {
                case 0:
                    log += "none\n";
                    break;
                // getters not compiling, so using hardcoded
                case 1:
                    log += "exp\n";
                    break;
                case 2:
                    log += "exp2\n";
                    break;
                case 3:
                    log += "linear\n";
                    break;
            }
            //Physics
            if (parsedData.physicsEnabled) {
                let physicsPlugin;
                if (parsedData.physicsEngine === "cannon" || parsedData.physicsEngine === CannonJSPlugin.name) {
                    physicsPlugin = new CannonJSPlugin(undefined, undefined, BabylonFileLoaderConfiguration.LoaderInjectedPhysicsEngine);
                }
                else if (parsedData.physicsEngine === "oimo" || parsedData.physicsEngine === OimoJSPlugin.name) {
                    physicsPlugin = new OimoJSPlugin(undefined, BabylonFileLoaderConfiguration.LoaderInjectedPhysicsEngine);
                }
                else if (parsedData.physicsEngine === "ammo" || parsedData.physicsEngine === AmmoJSPlugin.name) {
                    physicsPlugin = new AmmoJSPlugin(undefined, BabylonFileLoaderConfiguration.LoaderInjectedPhysicsEngine, undefined);
                }
                log = "\tPhysics engine " + (parsedData.physicsEngine ? parsedData.physicsEngine : "oimo") + " enabled\n";
                //else - default engine, which is currently oimo
                const physicsGravity = parsedData.gravity ? Vector3.FromArray(parsedData.gravity) : parsedData.physicsGravity ? Vector3.FromArray(parsedData.physicsGravity) : null;
                scene.enablePhysics(physicsGravity, physicsPlugin);
            }
            // Metadata
            if (parsedData.metadata !== undefined && parsedData.metadata !== null) {
                scene.metadata = parsedData.metadata;
            }
            //collisions, if defined. otherwise, default is true
            if (parsedData.collisionsEnabled !== undefined && parsedData.collisionsEnabled !== null) {
                scene.collisionsEnabled = parsedData.collisionsEnabled;
            }
            const container = LoadAssetContainer(scene, data, rootUrl, onError, true);
            if (!container) {
                return false;
            }
            if (parsedData.autoAnimate) {
                scene.beginAnimation(scene, parsedData.autoAnimateFrom, parsedData.autoAnimateTo, parsedData.autoAnimateLoop, parsedData.autoAnimateSpeed || 1.0);
            }
            if (parsedData.activeCameraID !== undefined && parsedData.activeCameraID !== null) {
                scene.setActiveCameraById(parsedData.activeCameraID);
            }
            // Finish
            return true;
        }
        catch (err) {
            const msg = logOperation("importScene", parsedData ? parsedData.producer : "Unknown") + log;
            if (onError) {
                onError(msg, err);
            }
            else {
                Logger.Log(msg);
                throw err;
            }
        }
        finally {
            if (log !== null && SceneLoaderFlags.loggingLevel !== 0) {
                Logger.Log(logOperation("importScene", parsedData ? parsedData.producer : "Unknown") + (SceneLoaderFlags.loggingLevel !== 1 ? log : ""));
            }
        }
        return false;
    },
    loadAssetContainer: (scene, data, rootUrl, onError) => {
        const container = LoadAssetContainer(scene, data, rootUrl, onError);
        return container;
    },
});
//# sourceMappingURL=babylonFileLoader.js.map