import THREE from "./init.js";
export const loadDDSLoader = async () => (await import("three/examples/jsm/loaders/DDSLoader.js")).DDSLoader;
export const loadMTLLoader = async () => (await import("three/examples/jsm/loaders/MTLLoader.js")).MTLLoader;
export const loadOBJLoader = async () => (await import("three/examples/jsm/loaders/OBJLoader.js")).OBJLoader;
export const loadFBXLoader = async () => (await import("three/examples/jsm/loaders/FBXLoader.js")).FBXLoader;
export const loadPLYLoader = async () => (await import("three/examples/jsm/loaders/PLYLoader.js")).PLYLoader;
export const loadColladaLoader = async () => (await import("three/examples/jsm/loaders/ColladaLoader.js")).ColladaLoader;
export const loadSTLLoader = async () => (await import("three/examples/jsm/loaders/STLLoader.js")).STLLoader;
export const loadXYZLoader = async () => (await import("three/examples/jsm/loaders/XYZLoader.js")).XYZLoader;
export const loadTDSLoader = async () => (await import("three/examples/jsm/loaders/TDSLoader.js")).TDSLoader;
export const loadPCDLoader = async () => (await import("three/examples/jsm/loaders/PCDLoader.js")).PCDLoader;
export const loadGLTFLoader = async () => (await import("three/examples/jsm/loaders/GLTFLoader.js")).GLTFLoader;
export const loadDRACOLoader = async () => (await import("three/examples/jsm/loaders/DRACOLoader.js")).DRACOLoader;
export const loadIFCLoader = async () => (await import("./js/loaders/IFCLoader.js")).IFCLoader;
export const loadRoomEnvironment = async () => (await import("three/examples/jsm/environments/RoomEnvironment.js")).RoomEnvironment;
export const loadHDRLoader = async () => (await import("three/examples/jsm/loaders/HDRLoader.js")).HDRLoader;

import { core } from './core.js';
import { fetchSettings, presentationMode } from "./metadata.js";
import { reportViewerError, showToast, toastHelper } from "./viewer-utils.js";

export var outlineClipping;
let environmentTextureCache = {};

const loaderMap = {
  gltf: loadGLTFLoader,
  glb: loadGLTFLoader,
  obj: loadOBJLoader,
  fbx: loadFBXLoader,
  ply: loadPLYLoader,
  stl: loadSTLLoader,
  dae: loadColladaLoader,
  xyz: loadXYZLoader,
  '3ds': loadTDSLoader,
  pcd: loadPCDLoader,
  ifc: loadIFCLoader
};

async function createLoader(ext) {

  const loadLoader = loaderMap[ext];

  if (!loadLoader) {
    throw new Error(`Unsupported format: ${ext}`);
  }

  const LoaderClass = await loadLoader();
  return new LoaderClass();
}

const ENV_BUILD = __BUILD__;
const MODULES_PATH = __MODULES_PATH__;
const ENV_SUBDIR = __ENV_SUBDIR__;
console.log('[loaders] ENV_BUILD:', ENV_BUILD);
console.log('[loaders] MODULES_PATH:', MODULES_PATH);
console.log('[loaders] ENV_SUBDIR:', ENV_SUBDIR);

function normalizeWasmPath(path) {
  if (typeof window === 'undefined' || !path) return path;
  let normalized = path.trim();

  // Force secure scheme for explicit http resources
  if (normalized.startsWith('http://')) {
    normalized = 'https://' + normalized.slice('http://'.length);
  } else if (normalized.startsWith('//')) {
    normalized = `${window.location.protocol}${normalized}`;
  } else if (normalized.startsWith('/')) {
    normalized = `${window.location.protocol}//${window.location.host}${normalized}`;
  } else if (!/^[a-zA-Z][\w+-.]*:/.test(normalized)) {
    normalized = new URL(normalized, window.location.href).href;
  }

  // Normalize duplicate slashes while keeping protocol separator intact
  try {
    const url = new URL(normalized);
    url.pathname = url.pathname.replace(/\/\/{2,}/g, '/');
    normalized = url.href;
  } catch (err) {
    normalized = normalized.replace(/\/\/{2,}/g, '/');
  }

  return normalized;
}

function sanitizeModuleAssetBasePath(input) {
  if (!input || typeof input !== 'string') {
    return '';
  }

  let basePath = input.trim().replace(/\/$/, '');

  if (!basePath) {
    return '';
  }

  if (/^[a-zA-Z][\w+-.]*:\/\//.test(basePath)) {
    try {
      const url = new URL(basePath);
      const hostSegment = `/${url.host}`;

      if (url.pathname.startsWith(hostSegment)) {
        url.pathname = url.pathname.slice(hostSegment.length) || '/';
      }

      url.pathname = url.pathname.replace(/\/\/{2,}/g, '/');
      return url.href.replace(/\/$/, '');
    } catch (_err) {
      return basePath;
    }
  }

  if (/^[^\/]+\.[^\/]+\/.+/.test(basePath)) {
    const parts = basePath.split('/');
    const host = parts.shift();
    const remainder = `/${parts.join('/')}`.replace(/\/\/{2,}/g, '/');

    if (
      host &&
      typeof window !== 'undefined' &&
      (host === window.location.host || /^\w[\w.-]*\.[a-z]{2,}$/i.test(host))
    ) {
      return remainder.replace(/\/$/, '');
    }
  }

  if (/^\/[^\/]+\.[^\/]+\/.+/.test(basePath)) {
    const parts = basePath.split('/').filter(Boolean);
    const host = parts.shift();
    const remainder = `/${parts.join('/')}`.replace(/\/\/{2,}/g, '/');

    if (
      host &&
      typeof window !== 'undefined' &&
      (host === window.location.host || /^\w[\w.-]*\.[a-z]{2,}$/i.test(host))
    ) {
      return remainder.replace(/\/$/, '');
    }
  }

  return basePath.replace(/\/\/{2,}/g, '/');
}

function prepareOutlineClipping(_object) {
  core.outlineClipping = _object.clone(true);
  var gutsMaterial = new THREE.MeshBasicMaterial({
    color: "crimson",
    side: THREE.BackSide,
    clippingPlanes: core.clippingPlanes,
    clipShadows: true,
    polygonOffset: true,
    polygonOffsetFactor: 1,
    polygonOffsetUnits: 1,
  });

  core.outlineClipping.traverse(function (child) {
    if (child.type == "Mesh" || child.type == "Object3D") {
      child.material = gutsMaterial;
    }
  });
  core.outlineClipping.visible = false;
  return core.outlineClipping;
}

function setupSingleMaterial(materials, material) {
  if (material.map) {
    material.map.anisotropy = 16;
    material.map.colorSpace = THREE.SRGBColorSpace;
  }
  material.envMapIntensity = 0.76;
  material.roughness = Math.min(material.roughness * 1.35, 1);
  material.clipShadows = true;
  material.side = core.PRESENTATION_MODE ? THREE.DoubleSide : THREE.FrontSide;
  material.clippingPlanes = core.PRESENTATION_MODE ? [] : core.clippingPlanes;
  //material.clipIntersection = false;
  material.onBeforeCompile = (shader) => {
    shader.fragmentShader = shader.fragmentShader.replace(
      'reflectedLight.directSpecular += directSpecular;',
      `
        reflectedLight.directSpecular += directSpecular * 0.15;
      `
    );
  };

  material.needsUpdate = true;
  if (material.name === "") material.name = material.uuid;
  var newMaterial = { name: material.name, uuid: material.uuid };
  if (!materials.some((item) => item.uuid === newMaterial.uuid)) materials.push(newMaterial);
}

function setupMaterials(_object) {
  var materials = [];
  if (_object.isMesh) {
    _object.castShadow = true;
    _object.receiveShadow = true;
    if (
      _object.geometry &&
      typeof _object.geometry.computeVertexNormals === "function" &&
      !_object.geometry.getAttribute?.("normal")
    ) {
      _object.geometry.computeVertexNormals();
    }
    if (_object.material.isMaterial) {
      setupSingleMaterial(materials, _object.material);
    } else if (Array.isArray(_object.material)) {
      _object.material.forEach((material) =>
        setupSingleMaterial(materials, material)
      );
    }
  }
  return materials;
}

function getMaterialByID(_object, _uuid) {
  var _material;
  _object.traverse(function (child) {
    if (
      child.isMesh &&
      child.material.isMaterial &&
      child.material.uuid === _uuid
    ) {
      _material = child.material;
    }
  });
  return _material;
}

function traverseMesh(object) {
  setupMaterials(object);

  object.traverse(function (child) {
    setupMaterials(child);
  });

  if (window.Viewer?.initializeMaterialsEditor && core.PRESENTATION_MODE !== true) {
    window.Viewer.initializeMaterialsEditor(object);
  }
}

function getEnvironmentTextureForPreset(renderer, preset = "neutral") {
  if (!renderer) return Promise.resolve(null);

  core.scene.environmentIntensity = core.environmentMapIntensity || 0.5;
  
  // Initialize cache for this preset if not exists
  if (!environmentTextureCache[preset]) {
    environmentTextureCache[preset] = (async () => {
      const pmrem = new THREE.PMREMGenerator(renderer);
      try {
        if (preset === "studio") {
          // Studio uses RoomEnvironment
          const TempRoomEnvironment = await loadRoomEnvironment();
          return pmrem.fromScene(new TempRoomEnvironment()).texture;
        } else if (preset === "neutral" || preset === "sunny" || preset === "goldenHour") {
          // Load HDR map for other presets
          const HDRLoader = await loadHDRLoader();
          const loader = new HDRLoader();
          const baseModulePath = core.CONFIG?.baseModulePath || core.DFG_ASSETS || '/assets';
          const mapFilename = preset === "goldenHour" ? "golden_hour.hdr" : `${preset}.hdr`;
          const mapUrl = `${baseModulePath.replace(/\/$/, '')}/maps/${mapFilename}`;
          
          const texture = await new Promise((resolve, reject) => {
            loader.load(mapUrl, resolve, undefined, reject);
          });
          texture.mapping = THREE.EquirectangularReflectionMapping;
          return pmrem.fromEquirectangular(texture).texture;
        }
        return null;
      } finally {
        pmrem.dispose();
      }
    })();
  }
  
  return environmentTextureCache[preset];
}

function getEnvironmentTexture(renderer) {
  // Legacy function for backwards compatibility
  return getEnvironmentTextureForPreset(renderer, "neutral");
}

function markEnvironmentMaterialsDirty(root) {
  root?.traverse?.((child) => {
    const materials = child?.material
      ? (Array.isArray(child.material) ? child.material : [child.material])
      : [];
    materials.forEach((material) => {
      if (material?.isMeshStandardMaterial || material?.isMeshPhysicalMaterial) {
        material.needsUpdate = true;
      }
    });
  });
}

export async function syncSceneEnvironment(enabled = true, preset = null) {
  if (!core.scene) return;
  
  // Use provided preset or fall back to viewer's preset, then neutral
  const effectivePreset = preset || window.viewer?.environmentMapPreset || "neutral";
  
  if (enabled) {
    core.scene.environment = await getEnvironmentTextureForPreset(core.renderer, effectivePreset);
    if (core.scene.environmentIntensity === 0) {
      core.scene.environmentIntensity = 0.5;
    }
  } else {
    core.scene.environment = null;
    core.scene.environmentIntensity = 0;
  }
  
  markEnvironmentMaterialsDirty(core.scene);
}

function reportLoadError(error, context = "") {
  const message = reportViewerError(error, {
    context,
    consoleLabel: "Viewer load error:",
  });
  core.circle?.complete?.(1200);
  if (typeof core.EXIT_CODE !== "undefined") core.EXIT_CODE = 1;
  return message;
}

export async function loadModel() {
  core.loadingLog?.start?.();
  let modelPath = core.fileObject.filename.startsWith('blob:') ? core.fileObject.filename : core.fileObject.path + core.fileObject.filename;
  if (core.CONFIG.entity.proxyPath !== undefined && !core.fileObject.filename.startsWith('blob:')) {
    modelPath = core.getProxyPath(modelPath, core.CONFIG, core.fileObject);
  }

  function loadAsync(loader, url, progressHandler = onProgress) {
    return new Promise((resolve, reject) => {
      loader.load(url, resolve, progressHandler, reject);
    });
  }

  function updateLoadingStage(stageKey, progressValue = null) {
    core.circle?.setStage?.(stageKey, progressValue);
    core.loadingLog?.setStage?.(stageKey, progressValue);
  }

  async function afterLoad({ object }) {
    if (object === null || typeof object === "undefined") {
      throw new Error("Loaded object is null or undefined.");
    }
    updateLoadingStage("loadingLog.loadingTextures", 99);

    // Keep authoring transforms in presentation mode to avoid collapsing model parts.
    if (!core.PRESENTATION_MODE) {
      // Reset transform to ensure consistent positioning
      if (Array.isArray(object)) {
        object.forEach(obj => {
          obj.position.set(0, 0, 0);
          obj.rotation.set(0, 0, 0);
          obj.scale.set(1, 1, 1);
          obj.updateMatrixWorld(true);
        });
      } else {
        object.position.set(0, 0, 0);
        object.rotation.set(0, 0, 0);
        object.scale.set(1, 1, 1);
        object.updateMatrixWorld(true);
      }
      core.handHint.hidden = true;
    }

    window.viewer.modelLoaded = true;
    updateLoadingStage("loadingLog.preparingGeometry", 99);
    traverseMesh(object);

    if (!core.PRESENTATION_MODE) {
      const isArchiveDerivedPath = /\/[^/]+_(ZIP|RAR|TAR|XZ|GZ)\/gltf\/$/i.test(core.fileObject.path);
      if (!isArchiveDerivedPath) {
        if (core.fileObject.extension.toLowerCase() === "gltf" || core.fileObject.extension.toLowerCase() === "glb") {
          core.fileObject.path = core.fileObject.path.replace("/gltf/", "/");
        } else {
          core.fileObject.path = core.fileObject.path.replace("gltf/", "");
        }
      }
      updateLoadingStage("loadingLog.fetchingMetadata", 99);
      await fetchSettings(object);

      updateLoadingStage("loadingLog.settingUpMaterials", 99);
      core.outlineClipping = prepareOutlineClipping(object);
      if (Array.isArray(object)) {
        core.helperObjects.push(object[0]);
      } else {
        core.helperObjects.push(object);
      }
      core.scene.add(core.outlineClipping);
    } else {
      updateLoadingStage("loadingLog.settingUpMaterials", 99);
      presentationMode(object, null).catch(error => {
        reportLoadError(error, "Presentation mode setup failed");
        showToast("toasts.presentationModeError", "error");
      });
    }

    updateLoadingStage("loadingLog.settingUpLighting", 99);
    if (Array.isArray(object)) {
      object.forEach(o => core.scene.add(o));
    } else {
      core.scene.add(object);
    }
    core.mainObject.push(object);

    updateLoadingStage("loadingLog.compilingShaders", 99);
    await syncSceneEnvironment(core.environmentMapEnabled !== false);
  }

  async function loadOBJWithMTL() {
    const DDSLoader = await loadDDSLoader();
    const MTLLoader = await loadMTLLoader();
    const OBJLoader = await loadOBJLoader();
    const manager = new THREE.LoadingManager();
    manager.onLoad = () => toastHelper("objLoaded", "success");
    manager.addHandler(/\.dds$/i, new DDSLoader());

    const basename = core.fileObject.filename.replace(/\.[^/.]+$/, "");
    const filename = core.fileObject.filename;

    if (!core.CONFIG.noMTL) {
      try {
        const materials = await new Promise((resolve, reject) => {
          new MTLLoader(manager)
          .setPath(core.fileObject.path)
          .load(basename + ".mtl", resolve, undefined, reject);
        });
        materials.preload();

        const obj = await new Promise((resolve, reject) => {
          new OBJLoader(manager)
          .setMaterials(materials)
          .setPath(core.fileObject.path)
          .load(filename, resolve, onProgress, reject);
        });

        obj.position.set(0, 0, 0);
        return obj;
      } catch (error) {
        core.CONFIG.noMTL = true;
        toastHelper("mtlLoadError", "error");
        console.warn("MTL load failed, falling back to OBJ-only load.", error);
      }
    }

  const obj = await new Promise((resolve, reject) => {
      new OBJLoader()
      .setPath(core.fileObject.path)
      .load(filename, resolve, onProgress, reject);
    });

    obj.position.set(0, 0, 0);
    return obj;
  }

  function normalizePath(path) {
    if (!path || typeof path !== 'string') {
      return path;
    }

    if (/^[a-zA-Z][\w+-.]*:\/\//.test(path)) {
      try {
        const url = new URL(path);
        url.pathname = url.pathname.replace(/\/{2,}/g, '/');
        return url.href;
      } catch (_err) {
        return path;
      }
    }

    return path.replace(/\/{2,}/g, '/');
  }

  async function resolveIfcWasmPath(basePath) {
    const candidates = [
      normalizePath(basePath.replace(/\/$/, '') + '/ifc/'),
      normalizePath(basePath.replace(/\/$/, '') + '/ifc'),
    ];

    for (const candidate of candidates) {
      const wasmUrl = candidate.replace(/\/$/, '') + '/web-ifc.wasm';
      try {
        const res = await fetch(wasmUrl, { method: 'HEAD', cache: 'no-store' });
        if (res.ok) {
          return candidate;
        }
      } catch (err) {
        // ignored, try next candidate
      }
    }
    return null;
  }

  async function loadGLTFModel() {
    let gltfModelPath = core.fileObject.filename.startsWith('blob:') ? core.fileObject.filename : core.fileObject.path + core.fileObject.basename + "." + core.fileObject.extension;
    if (core.CONFIG.entity.proxyPath !== undefined && !core.fileObject.filename.startsWith('blob:')) {
      gltfModelPath = core.getProxyPath(gltfModelPath);
    }

    const dracoBase = normalizePath(normalizeWasmPath(`${getModuleAssetBasePath()}/draco/gltf/`));

    const loader = await createLoader(core.fileObject.extension.toLowerCase());
    const DRACOLoader = await loadDRACOLoader();
    const draco = new DRACOLoader();
    if (ENV_BUILD !== 'test' && ENV_BUILD !== 'dev') {
      draco.setDecoderConfig({ type: 'js' });
    }
    draco.setDecoderPath(dracoBase);
    loader.setDRACOLoader(draco);

    try {
      const gltf = await new Promise((resolve, reject) => {
        loader.load(
          gltfModelPath,
          resolve,
          (xhr) => {
            progressLoaderHandler(xhr);
          },
          reject
        );
      });
      return gltf.scene;
    } finally {
      draco.dispose();
    }
  }

  try {
    switch (core.fileObject.extension.toLowerCase()) {
      case "obj": {
        const object = await loadOBJWithMTL();
        await afterLoad({ object });
        break;
      }

      case "fbx": {
        const loader = await createLoader(core.fileObject.extension.toLowerCase());
        const object = await loadAsync(loader, modelPath, onProgress);
        object.position.set(0, 0, 0);
        await afterLoad({ object });
        break;
      }

      case "ply": {
        const loader = await createLoader(core.fileObject.extension.toLowerCase());
        const geometry = await loadAsync(loader, modelPath, onProgress);
        if (!geometry.getAttribute?.("normal")) {
          geometry.computeVertexNormals();
        }
        const material = new THREE.MeshStandardMaterial({ color: 0x0055ff, flatShading: true });
        const object = new THREE.Mesh(geometry, material);
        object.position.set(0, 0, 0);
        object.castShadow = true;
        object.receiveShadow = true;
        await afterLoad({ object });
        break;
      }

      case "dae": {
        const loader = await createLoader(core.fileObject.extension.toLowerCase());
        const collada = await loadAsync(loader, modelPath, onProgress);
        const object = collada.scene;
        object.position.set(0, 0, 0);
        await afterLoad({ object });
        break;
      }

      case "ifc": {
        const loader = await createLoader(core.fileObject.extension.toLowerCase());
        const basePath = getModuleAssetBasePath();

        let ifcWasmPath = await resolveIfcWasmPath(basePath);

        if (!ifcWasmPath) {
          const errorMsg = `[loadModel] IFC WASM not found in ${basePath}/ifc; please verify path and permissions`;
          console.error(errorMsg);
          throw new Error(errorMsg);
        }

        const normalizedIfcWasmPath = normalizeWasmPath(ifcWasmPath);
        console.log('[loadModel] IFC WASM path:', normalizedIfcWasmPath);
        loader.ifcManager.setWasmPath(normalizedIfcWasmPath, true);
        const object = await loadAsync(loader, modelPath, onProgress);
        await afterLoad({ object });
        break;
      }

      case "stl": {
        const loader = await createLoader(core.fileObject.extension.toLowerCase());
        const geometry = await loadAsync(loader, modelPath, onProgress);
        let meshMaterial = new THREE.MeshPhongMaterial({ color: 0xff5533, specular: 0x111111, shininess: 200 });
        if (geometry.hasColors) {
          meshMaterial = new THREE.MeshPhongMaterial({ opacity: geometry.alpha, vertexColors: true });
        }
        const object = new THREE.Mesh(geometry, meshMaterial);
        object.position.set(0, 0, 0);
        object.castShadow = true;
        object.receiveShadow = true;
        await afterLoad({ object });
        break;
      }

      case "xyz": {
        const loader = await createLoader(core.fileObject.extension.toLowerCase());
        const geometry = await loadAsync(loader, modelPath, onProgress);
        geometry.center();
        const material = new THREE.PointsMaterial({ size: 0.1, vertexColors: geometry.hasAttribute("color") === true });
        const object = new THREE.Points(geometry, material);
        object.position.set(0, 0, 0);
        await afterLoad({ object });
        break;
      }

      case "pcd": {
        const loader = await createLoader(core.fileObject.extension.toLowerCase());
        const mesh = await loadAsync(loader, modelPath, onProgress);
        mesh.geometry?.center?.();
        if (mesh.material) {
          mesh.material.size = Math.max(mesh.material.size ?? 0, 0.1);
        }
        await afterLoad({ object: mesh });
        break;
      }

      case "json": {
        const loader = new THREE.ObjectLoader();
        const object = await loadAsync(loader, modelPath, onProgress);
        object.position.set(0, 0, 0);
        await afterLoad({ object });
        break;
      }

      case "3ds": {
        const loader = await createLoader(core.fileObject.extension.toLowerCase());
        loader.setResourcePath(core.fileObject.path);
        let mp = core.fileObject.path;
        if (core.CONFIG.entity.proxyPath !== undefined) mp = core.getProxyPath(mp);
        const object = await loadAsync(loader, mp + core.fileObject.basename + "." + core.fileObject.extension, onProgress);
        await afterLoad({ object });
        break;
      }

      case "glb":
      case "gltf": {
        const object = await loadGLTFModel();
        await afterLoad({ object });
        break;
      }
      default:
        toastHelper("unsupportedExtension", "warning");
        core.loadingLog?.fail?.();
        return;
    }

    updateLoadingStage("loadingLog.modelLoaded", 100);
    core.circle?.complete?.(2600);
    core.editorToolbar?.classList.remove('editorToolbar-hidden');
    core.editorToolbar?.classList.add('editorToolbar-visible');
    core.loadingLog?.finish?.();
    if (!core.PRESENTATION_MODE) {
      toastHelper("modelLoaded", "success", {
        filename: core.fileObject.filename
      });
    } else {
      toastHelper("presentationModeReady", "success");
    }
    if (typeof core.EXIT_CODE !== "undefined") core.EXIT_CODE = 0;
    core.UltraLoader?.finish();
    core.poller?.updateSteps(2);
  } catch (error) {
    core.loadingLog?.fail?.();
    reportLoadError(error, `Failed to load ${core.fileObject.filename}`);
    throw error;
  }
}

export const getModuleAssetBasePath = function() {
  const configuredPath = sanitizeModuleAssetBasePath(core.CONFIG?.baseModulePath);
  let basePath = configuredPath || sanitizeModuleAssetBasePath(core.DFG_ASSETS);
  if (!basePath && typeof import.meta !== 'undefined' && import.meta.url) {
    const moduleUrl = new URL(import.meta.url);
    basePath = moduleUrl.pathname.includes('/assets/')
      ? new URL('../assets/', moduleUrl).pathname.replace(/\/$/, '')
      : new URL('./assets/', moduleUrl).pathname.replace(/\/$/, '');
  }
  if (!basePath) {
    basePath = '/assets';
  }
  // Standalone local dev servers expose assets at /assets; embedded hosts provide baseModulePath.
  if (core.isLocalPreview && !configuredPath) {
    basePath = '/assets';
  }
  basePath = sanitizeModuleAssetBasePath(basePath);
  console.log('[loaders] resolved ModuleAssetBasePath:', basePath);
  core.CONFIG.baseModulePath = basePath;
  core.DFG_ASSETS = basePath;
  return basePath;
};

export const onError = function (_event) {
  reportLoadError(_event, "Loader error");
};

export const onErrorMTL = async function (_event) {
  core.CONFIG.noMTL = true;
  toastHelper("mtlLoadError", "error");
  await loadModel();
};

export const onErrorGLB = async function (_event, params, loadedTimes) {
  console.log("Loader error: " + _event);
  if (window.__E2E__ && window.viewer) {
    window.viewer.errors ??= [];
    window.viewer.errors.push(String(_event));
  }
  core.loadedFile = params.path + params.basename + core.loadedFile + "gltf/";
  if (typeof _event !== undefined && loadedTimes <= 1 && window.viewer.modelLoaded === false) {
    await loadModel();
    loadedTimes++;
  } else {
    toastHelper("glbLoadError", "error");
  }
};

export const onProgress = function (xhr) {
  progressLoaderHandler(xhr);
};

const progressLoaderHandler = function (xhr) {
  if (!core.circle) return;
  const total = xhr.total || xhr.loaded || 1;
  const percentComplete = Math.min((xhr.loaded / total) * 100, 99);
  if (!Number.isFinite(percentComplete)) return;
  core.circle.show();
  core.circle.set(percentComplete, 100);
  core.editorToolbar?.classList.remove('editorToolbar-hidden');
  core.editorToolbar?.classList.add('editorToolbar-visible');
  core.loadingLog?.update?.(percentComplete);
  core.UltraLoader?.set(percentComplete);
}
