///////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2002-2025, Open Design Alliance (the "Alliance").
// All rights reserved.
//
// This software and its documentation and related materials are owned by
// the Alliance. The software may only be incorporated into application
// programs owned by members of the Alliance, subject to a signed
// Membership Agreement and Supplemental Software License Agreement with the
// Alliance. The structure and organization of this software are the valuable
// trade secrets of the Alliance and its suppliers. The software is also
// protected by copyright law and international treaty provisions. Application
// programs incorporating this software must include the following statement
// with their copyright notices:
//
//   This application incorporates Open Design Alliance software pursuant to a
//   license agreement with Open Design Alliance.
//   Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance.
//   All rights reserved.
//
// By use of this software, its documentation or related materials, you
// acknowledge and accept the above terms.
///////////////////////////////////////////////////////////////////////////////

import { Box3, Object3D } from "three";
import { IInfo, Info } from "@inweb/viewer-core";

import { ModelImpl } from "../../models/ModelImpl";
import { DynamicGltfLoader } from "./DynamicGltfLoader.js";

// Dynamic model implementation.

export class DynamicModelImpl extends ModelImpl {
  public gltfLoader: DynamicGltfLoader;

  override getInfo(): IInfo {
    const stats = this.gltfLoader.getStats();

    const info = new Info();

    info.scene.objects = stats.scene.beforeOptimization.objects;
    info.scene.triangles = stats.scene.beforeOptimization.triangles;
    info.scene.lines = stats.scene.beforeOptimization.lines;
    info.scene.edges = stats.scene.beforeOptimization.edges;

    info.optimizedScene.objects = stats.scene.afterOptimization.objects;
    info.optimizedScene.triangles = stats.scene.afterOptimization.triangles;
    info.optimizedScene.lines = stats.scene.afterOptimization.lines;
    info.optimizedScene.edges = stats.scene.afterOptimization.edges;

    info.memory.geometries = stats.memory.geometries.count;
    info.memory.geometryBytes = stats.memory.geometries.bytes;
    info.memory.textures = stats.memory.textures.count;
    info.memory.materials = stats.memory.materials.count;
    info.memory.totalEstimatedGpuBytes = stats.memory.totalEstimatedGpuBytes;

    return info;
  }

  override getExtents(target: Box3): Box3 {
    return target.union(this.gltfLoader.getTotalGeometryExtent());
  }

  override getObjects(): Object3D[] {
    const objects = [];
    this.gltfLoader.originalObjects.forEach((object: Object3D) => {
      objects.push(object);
    });
    return objects;
  }

  override getVisibleObjects(): Object3D[] {
    return this.gltfLoader.getOriginalObjectForSelect();
  }

  override hasObject(object: any): boolean {
    return this.gltfLoader.originalObjects.has(object);
  }

  override getObjectsByHandles(handles: string | string[]): Object3D[] {
    const ownHandles = this.getOwnHandles(handles);
    if (ownHandles.length === 0) return [];

    const handlesSet = new Set(ownHandles);

    const objects = [];
    handlesSet.forEach((handle) => {
      objects.push(...this.gltfLoader.getObjectsByHandle(handle));
    });

    return objects;
  }

  override getHandlesByObjects(objects: Object3D | Object3D[]): string[] {
    const ownObjects = this.getOwnObjects(objects);
    if (ownObjects.length === 0) return [];

    const handleSet = new Set<string>();
    ownObjects.forEach((object) => {
      const handle = object.userData.handle;
      if (handle) handleSet.add(handle);
    });

    return Array.from(handleSet);
  }

  override hideObjects(objects: Object3D | Object3D[]): this {
    const handles = this.getHandlesByObjects(objects);
    this.gltfLoader.hideObjects(handles);
    return this;
  }

  override isolateObjects(objects: Object3D | Object3D[]): this {
    const handles = this.getHandlesByObjects(objects);
    this.gltfLoader.isolateObjects(new Set(handles));
    return this;
  }

  override showObjects(objects: Object3D | Object3D[]): this {
    const handles = this.getHandlesByObjects(objects);
    this.gltfLoader.showObjects(handles);
    return this;
  }

  override showAllObjects(): this {
    this.gltfLoader.showAllHiddenObjects();
    return this;
  }

  override showOriginalObjects(objects: Object3D | Object3D[]): this {
    this.gltfLoader.showOriginalObjects(objects);
    return this;
  }

  override hideOriginalObjects(objects: Object3D | Object3D[]): this {
    this.gltfLoader.hideOriginalObjects(objects);
    return this;
  }
}
