/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

/**
 * Shared loading utilities used across all IFC loading hooks.
 *
 * Consolidates the guarded spatial-index build pattern that was
 * duplicated across useIfcLoader, useIfcCache, useIfcServer, and
 * useIfcFederation.
 */

import type { MeshData } from '@ifc-lite/geometry';
import type { IfcDataStore } from '@ifc-lite/parser';
import { buildSpatialIndexAsync } from '@ifc-lite/spatial';
import { useViewerStore } from '../store.js';

/**
 * Build a spatial index in the background (time-sliced, non-blocking)
 * with a guard against stale loads.
 *
 * The guard captures the dataStore reference and compares it to the
 * current store when the async build completes. If the store has been
 * replaced (e.g. user loaded a new file), the result is discarded.
 *
 * @param meshes - Final mesh array with correct IDs and world-space positions
 * @param dataStore - The IfcDataStore to attach the spatial index to
 * @param setIfcDataStore - Store setter to trigger re-render
 */
export function buildSpatialIndexGuarded(
  meshes: MeshData[],
  dataStore: IfcDataStore,
  setIfcDataStore: (store: IfcDataStore) => void,
): void {
  if (meshes.length === 0) return;

  const capturedStore = dataStore;
  buildSpatialIndexAsync(meshes).then(spatialIndex => {
    const { ifcDataStore: currentStore } = useViewerStore.getState();
    if (currentStore !== capturedStore) return;
    capturedStore.spatialIndex = spatialIndex;
    setIfcDataStore({ ...capturedStore });
  }).catch(err => {
    console.warn('[loadingUtils] Failed to build spatial index:', err);
  });
}

/**
 * Build a spatial index for a specific (e.g. federated) model.
 *
 * Unlike {@link buildSpatialIndexGuarded}, this never touches the active-model
 * slot: a federated model is usually not the active one, so guarding on / writing
 * through `ifcDataStore` (`setIfcDataStore`) would either discard the index or
 * mutate the wrong model. Instead it guards on the target model still holding the
 * same store and publishes through `updateModel(modelId, ...)`.
 *
 * @param meshes - Final mesh array with correct IDs and world-space positions
 * @param modelId - The federated model to attach the spatial index to
 * @param dataStore - That model's IfcDataStore (mutated in place)
 */
export function buildSpatialIndexForModel(
  meshes: MeshData[],
  modelId: string,
  dataStore: IfcDataStore,
): void {
  if (meshes.length === 0) return;

  buildSpatialIndexAsync(meshes).then(spatialIndex => {
    const state = useViewerStore.getState();
    const model = state.models.get(modelId);
    // Model removed, or its store was replaced since this build started.
    if (!model || model.ifcDataStore !== dataStore) return;
    dataStore.spatialIndex = spatialIndex;
    state.updateModel(modelId, { ifcDataStore: dataStore });
  }).catch(err => {
    console.warn('[loadingUtils] Failed to build spatial index for model:', err);
  });
}
