///////////////////////////////////////////////////////////////////////////////
// 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 { EventEmitter2 } from "@inweb/eventemitter2";
import { IOptions, RGB, defaultOptions } from "./IOptions";

export class Options implements IOptions {
  protected _emitter?: EventEmitter2;
  protected _data: IOptions;

  constructor(emitter?: EventEmitter2) {
    this._emitter = emitter;
    this._data = defaultOptions();
    this.loadFromStorage();
  }

  static defaults(): IOptions {
    return defaultOptions();
  }

  notifierChangeEvent(): void {
    console.warn(
      "Options.notifierChangeEvent() has been deprecated since 25.3 and will be removed in a future release, use Options.change() instead."
    );
    this.change();
  }

  change(): void {
    if (this._emitter !== undefined) {
      this.saveToStorage();
      this._emitter.emit({ type: "optionschange", data: this });
    }
  }

  saveToStorage(): void {
    if (typeof window !== "undefined")
      try {
        localStorage.setItem("od-client-settings", JSON.stringify(this.data));
      } catch (error) {
        console.error("Cannot save client settings.", error);
      }
  }

  loadFromStorage(): void {
    if (typeof window !== "undefined")
      try {
        const item = localStorage.getItem("od-client-settings");
        if (item) {
          const data = JSON.parse(item);
          this.data = { ...data };
        }
      } catch (error) {
        console.error("Cannot load client settings.", error);
      }
  }

  /**
   * Resets options to default values.
   *
   * @param fields - Name of fields to be reset. Specify `undefined` to reset all.
   */
  resetToDefaults(fields?: string[]): void {
    if (fields !== undefined) {
      const defaults = Options.defaults();
      const resetData = fields.reduce((acc, field) => {
        acc[field] = defaults[field];
        return acc;
      }, {});
      this.data = { ...this.data, ...resetData };
    } else {
      this.data = { ...this.data, ...Options.defaults() };
    }
  }

  get data(): IOptions {
    return this._data;
  }

  set data(value: IOptions) {
    const enablePartialMode = value.enableStreamingMode ? value.enablePartialMode : false;
    const sceneGraph = enablePartialMode ? false : value.sceneGraph;
    this._data = { ...Options.defaults(), ...this._data, ...value, enablePartialMode, sceneGraph };
    this.change();
  }

  get showWCS(): boolean {
    return this._data.showWCS;
  }

  set showWCS(value: boolean) {
    this._data.showWCS = value;
    this.change();
  }

  get cameraAnimation(): boolean {
    return this._data.cameraAnimation;
  }

  set cameraAnimation(value: boolean) {
    this._data.cameraAnimation = value;
    this.change();
  }

  get antialiasing(): boolean {
    return this._data.antialiasing;
  }

  set antialiasing(value: boolean) {
    this._data.antialiasing = value;
    this.change();
  }

  get groundShadow(): boolean {
    return this._data.groundShadow;
  }

  set groundShadow(value: boolean) {
    this._data.groundShadow = value;
    this.change();
  }

  get shadows(): boolean {
    return this._data.shadows;
  }

  set shadows(value: boolean) {
    this._data.shadows = value;
    this.change();
  }

  get cameraAxisXSpeed(): number {
    return this._data.cameraAxisXSpeed;
  }

  set cameraAxisXSpeed(value: number) {
    this._data.cameraAxisXSpeed = value;
    this.change();
  }

  get cameraAxisYSpeed(): number {
    return this._data.cameraAxisYSpeed;
  }

  set cameraAxisYSpeed(value: number) {
    this.cameraAxisYSpeed = value;
    this.change();
  }

  get ambientOcclusion(): boolean {
    return this._data.ambientOcclusion;
  }

  set ambientOcclusion(value: boolean) {
    this._data.ambientOcclusion = value;
    this.change();
  }

  get enableStreamingMode(): boolean {
    return this._data.enableStreamingMode;
  }

  set enableStreamingMode(value: boolean) {
    this._data.enableStreamingMode = value;
    if (!value) this._data.enablePartialMode = false;
    this.change();
  }

  get enablePartialMode(): boolean {
    return this._data.enablePartialMode;
  }

  set enablePartialMode(value: boolean) {
    this._data.enablePartialMode = value;
    if (value) {
      this._data.enableStreamingMode = true;
      this._data.sceneGraph = false;
    }
    this.change();
  }

  get memoryLimit(): number {
    return this._data.memoryLimit;
  }

  set memoryLimit(value: number) {
    this._data.memoryLimit = value;
    this.change();
  }

  get cuttingPlaneFillColor(): RGB {
    return this._data.cuttingPlaneFillColor;
  }

  set cuttingPlaneFillColor(value: RGB) {
    this._data.cuttingPlaneFillColor = value;
    this.change();
  }

  get edgesColor() {
    return this._data.edgesColor;
  }

  set edgesColor(value) {
    this._data.edgesColor = value;
    this.change();
  }

  get facesColor() {
    return this._data.facesColor;
  }

  set facesColor(value) {
    this._data.facesColor = value;
    this.change();
  }

  get edgesVisibility() {
    return this._data.edgesVisibility;
  }

  set edgesVisibility(value) {
    this._data.edgesVisibility = value;
    this.change();
  }

  get edgesOverlap() {
    return this._data.edgesOverlap;
  }

  set edgesOverlap(value) {
    this._data.edgesOverlap = value;
    this.change();
  }

  get facesOverlap() {
    return this._data.facesOverlap;
  }

  set facesOverlap(value) {
    this._data.facesOverlap = value;
    this.change();
  }

  get facesTransparancy() {
    return this._data.facesTransparancy;
  }

  set facesTransparancy(value) {
    this._data.facesTransparancy = value;
    this.change();
  }

  get enableCustomHighlight() {
    return this._data.enableCustomHighlight;
  }

  set enableCustomHighlight(value) {
    this._data.enableCustomHighlight = value;
    this.change();
  }

  get sceneGraph() {
    return this._data.sceneGraph;
  }

  set sceneGraph(value) {
    this._data.sceneGraph = value;
    if (value) this._data.enablePartialMode = false;
    this.change();
  }

  get edgeModel() {
    return Boolean(this._data.edgeModel);
  }

  set edgeModel(value) {
    this._data.edgeModel = Boolean(value);
    this.change();
  }

  get reverseZoomWheel() {
    return this._data.reverseZoomWheel;
  }

  set reverseZoomWheel(value: boolean) {
    this._data.reverseZoomWheel = !!value;
    this.change();
  }

  get enableZoomWheel() {
    return this._data.enableZoomWheel;
  }

  set enableZoomWheel(value: boolean) {
    this._data.enableZoomWheel = !!value;
    this.change();
  }

  get enableGestures() {
    return this._data.enableGestures;
  }

  set enableGestures(value: boolean) {
    this._data.enableGestures = !!value;
    this.change();
  }

  get geometryType() {
    return this._data.geometryType;
  }

  set geometryType(value: string) {
    this._data.geometryType = value;
    this.change();
  }

  get rulerUnit(): string {
    return this._data.rulerUnit;
  }

  set rulerUnit(value: string) {
    this._data.rulerUnit = value;
    this.change();
  }
}
