///////////////////////////////////////////////////////////////////////////////
// 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 { Endpoint } from "./Endpoint";
import type { File } from "./File";
import type { Assembly } from "./Assembly";
import type { IFileReferences } from "./IFile";
import type { IModelTransformMatrix } from "./IAssembly";

/**
 * Provides properties and methods for working with view of the {@link File | file} or
 * {@link Assembly| assembly}. For example, for `dwg` it is a `Model` space or layout, and for `rvt` files
 * it is a `3D` view.
 */
export class Model extends Endpoint {
  private _data: any;
  private _file: File | Assembly;

  /**
   * @param data - Raw model data received from the server.
   * @param file - The file/assembly instance that owns the model.
   */
  constructor(data: any, file: File | Assembly) {
    super(`${file.path}/downloads`, file.httpClient);
    this._data = data;
    this._file = file;
  }

  /**
   * The `Assembly` instance that owns the model.
   *
   * @readonly
   */
  get assembly(): Assembly {
    return this._file as Assembly;
  }

  /**
   * Raw model data received from the server.
   *
   * @readonly
   */
  get data(): any {
    return this._data;
  }

  private set data(value: any) {
    this._data = value;
  }

  /**
   * Scene description resource file name. Use {@link downloadResource | downloadResource()} to download
   * scene description file.
   *
   * @readonly
   */
  get database(): string {
    return this.data.database;
  }

  /**
   * `true` if this is default model.
   *
   * @readonly
   */
  get default(): boolean {
    return this.data.default;
  }

  /**
   * The `File` instance that owns the model.
   *
   * @readonly
   */
  get file(): File {
    return this._file as File;
  }

  /**
   * The ID of the file that owns the model.
   *
   * @readonly
   */
  get fileId(): string {
    return this.data.fileId;
  }

  /**
   * The list of geometry data resource files. Use {@link downloadResource | downloadResource()} to
   * download geometry data files.
   *
   * @readonly
   */
  get geometry(): string[] {
    return this.data.geometry;
  }

  /**
   * Unique model ID.
   *
   * @readonly
   */
  get id(): string {
    return this.data.id;
  }

  /**
   * Model name.
   *
   * @readonly
   */
  get name(): string {
    return this.data.name;
  }

  /**
   * Model owner type, matches the file extension this is model of the file, or `assembly` for
   * assemblies.
   *
   * @readonly
   */
  get type(): string {
    return this.file.type;
  }

  // Reserved for future use.
  get version(): string {
    return this.data.version;
  }

  /**
   * Returns model list with one item `self`.
   */
  getModels(): Promise<Model[]> {
    return Promise.resolve([this]);
  }

  /**
   * Returns a model transformation.
   *
   * @param handle - Model handle.
   */
  getModelTransformMatrix(handle: string): IModelTransformMatrix {
    return this.file.getModelTransformMatrix(handle);
  }

  /**
   * Sets or removes a model transformation.
   *
   * @param handle - Model handle.
   * @param transform - Transformation matrix. Specify `undefined` to remove transformation.
   */
  setModelTransformMatrix(handle: string, transform?: IModelTransformMatrix): Promise<this> {
    return this.file.setModelTransformMatrix(handle, transform).then(() => this);
  }

  /**
   * Returns a list of viewpoints of the owner file/assembly.
   */
  getViewpoints(): Promise<any[]> {
    return this._file
      .getViewpoints()
      .then((array) =>
        array.filter(
          ({ custom_fields = {} }) => custom_fields.modelId === this.id || custom_fields.modelName === this.name
        )
      );
  }

  /**
   * Saves a new model owner file/assembly viewpoint to the server. To create a new viewpoint use
   * `Viewer.createViewpoint()`.
   *
   * @param viewpoint - Viewpoint object.
   */
  saveViewpoint(viewpoint: any): Promise<any> {
    return this._file.saveViewpoint({
      ...viewpoint,
      custom_fields: { ...viewpoint.custom_fields, modelId: this.id, modelName: this.name },
    });
  }

  /**
   * Deletes the specified viewpoint from the owner file/assembly.
   *
   * @param guid - Viewpoint GUID.
   * @returns Returns the raw data of a deleted viewpoint.
   */
  deleteViewpoint(guid: string): Promise<any> {
    return this._file.deleteViewpoint(guid);
  }

  /**
   * Returns viewpoint snapshot as base64-encoded
   * {@link https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/Data_URIs | Data URL}.
   *
   * @param guid - Viewpoint GUID.
   */
  getSnapshot(guid: string): Promise<string> {
    return this._file.getSnapshot(guid);
  }

  /**
   * Returns viewpoint snapshot data.
   *
   * @param guid - Viewpoint GUID.
   * @param bitmapGuid - Bitmap GUID.
   */
  getSnapshotData(guid: string, bitmapGuid: string): Promise<string> {
    return this._file.getSnapshotData(guid, bitmapGuid);
  }

  /**
   * Downloads a resource file. Resource files are files that contain model scene descriptions, or
   * geometry data.
   *
   * @param dataId - Resource file name.
   * @param onProgress - Download progress callback.
   * @param signal - An
   *   {@link https://developer.mozilla.org/docs/Web/API/AbortController | AbortController} signal. Allows
   *   to communicate with a fetch request and abort it if desired.
   */
  downloadResource(
    dataId: string,
    onProgress?: (progress: number, chunk: Uint8Array) => void,
    signal?: AbortSignal
  ): Promise<ArrayBuffer> {
    return this._file.downloadResource(dataId, onProgress, signal);
  }

  /**
   * Downloads a part of resource file. Resource files are files that contain model scene descriptions,
   * or geometry data.
   *
   * @param dataId - Resource file name.
   * @param ranges - A range of resource file contents to download.
   * @param requestId - Request ID for download progress callback.
   * @param onProgress - Download progress callback.
   * @param signal - An
   *   {@link https://developer.mozilla.org/docs/Web/API/AbortController | AbortController} signal. Allows
   *   to communicate with a fetch request and abort it if desired.
   */
  downloadResourceRange(
    dataId: string,
    requestId: number,
    ranges: Array<{ begin: number; end: number; requestId: number }>,
    onProgress?: (progress: number, chunk: Uint8Array, requestId: number) => void,
    signal?: AbortSignal
  ): Promise<ArrayBuffer> {
    return this._file.downloadResourceRange(dataId, requestId, ranges, onProgress, signal);
  }

  /**
   * Deprecated since `25.3`. Use {@link downloadResource | downloadResource()} instead.
   *
   * @deprecated
   */
  partialDownloadResource(
    dataId: string,
    onProgress?: (progress: number, chunk: Uint8Array) => void,
    signal?: AbortSignal
  ): Promise<ArrayBuffer> {
    console.warn(
      "Model.partialDownloadResource() has been deprecated since 25.3 and will be removed in a future release, use Model.downloadResource() instead."
    );
    return this.downloadResource(dataId, onProgress, signal);
  }

  /**
   * Deprecated since `25.3`. Use {@link downloadResourceRange | downloadResourceRange()} instead.
   *
   * @deprecated
   */
  async downloadFileRange(
    requestId: number,
    records: any | null,
    dataId: string,
    onProgress?: (progress: number, chunk: Uint8Array, requestId: number) => void,
    signal?: AbortSignal
  ): Promise<void> {
    if (!records) return;

    let ranges = [];
    if (records.length) {
      ranges = records.map((record) => ({
        begin: Number(record.begin),
        end: Number(record.end),
        requestId: record.reqId,
      }));
    } else {
      for (let i = 0; i < records.size(); i++) {
        const record = records.get(i);
        ranges.push({ begin: Number(record.begin), end: Number(record.end), requestId });
        record.delete();
      }
    }

    await this.downloadResourceRange(dataId, requestId, ranges, onProgress, signal);
  }

  /**
   * Returns a list of references of the owner file/assembly.
   *
   * References are images, fonts, or any other files to correct rendering of the file.
   *
   * @param signal - An
   *   {@link https://developer.mozilla.org/docs/Web/API/AbortController | AbortController} signal, which
   *   can be used to abort waiting as desired.
   */
  getReferences(signal?: AbortSignal): Promise<IFileReferences> {
    return this._file.getReferences(signal);
  }
}
