///////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2002-2026, 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-2026 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 { IEventEmitter } from "@inweb/eventemitter2";
import { CameraMode, defaultOptions, IOptions, RGB } from "./IOptions";

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

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

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

  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 {
    const defaults = Options.defaults();
    if (fields !== undefined) {
      const resetData: Partial<IOptions> = {};
      for (const field of fields) {
        if (field in defaults) resetData[field] = defaults[field];
      }
      this.data = resetData;
    } else {
      this.data = defaults;
    }
  }

  setProperty<K extends keyof IOptions>(key: K, value = Options.defaults()[key]): void {
    // for object-valued fields any new literal counts as a change
    if (this._data[key] !== value) {
      this._data[key] = value;
      this.change();
    }
  }

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

  set data(value: Partial<IOptions>) {
    const defaults = Options.defaults();
    const merged: IOptions = { ...defaults, ...this._data, ...value };
    // replace undefined to default value for known properties
    for (const key of Object.keys(defaults)) {
      if (merged[key] === undefined) merged[key] = defaults[key];
    }
    this._data = merged;
    // partial mode first
    if (this._data.enablePartialMode) {
      this._data.enableStreamingMode = true;
      this._data.sceneGraph = false;
    }
    // sectionFillColor since 27.5
    if (!value.sectionFillColor && value.cuttingPlaneFillColor)
      this._data.sectionFillColor = {
        r: value.cuttingPlaneFillColor.red,
        g: value.cuttingPlaneFillColor.green,
        b: value.cuttingPlaneFillColor.blue,
      };
    this.change();
  }

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

  set showWCS(value: boolean) {
    this.setProperty("showWCS", value);
  }

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

  set cameraAnimation(value: boolean) {
    this.setProperty("cameraAnimation", value);
  }

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

  set antialiasing(value: boolean | string) {
    this.setProperty("antialiasing", value);
  }

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

  set groundShadow(value: boolean) {
    this.setProperty("groundShadow", value);
  }

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

  set shadows(value: boolean) {
    this.setProperty("shadows", value);
  }

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

  set cameraAxisXSpeed(value: number) {
    this.setProperty("cameraAxisXSpeed", value);
  }

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

  set cameraAxisYSpeed(value: number) {
    this.setProperty("cameraAxisYSpeed", value);
  }

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

  set ambientOcclusion(value: boolean) {
    this.setProperty("ambientOcclusion", value);
  }

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

  set enableStreamingMode(value: boolean) {
    this.setProperty("enableStreamingMode", value);
    if (!value) {
      this.setProperty("enablePartialMode", false);
    }
  }

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

  set enablePartialMode(value: boolean) {
    this.setProperty("enablePartialMode", value);
    if (value) {
      this.setProperty("enableStreamingMode", true);
      this.setProperty("sceneGraph", false);
    }
  }

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

  set memoryLimit(value: number) {
    this.setProperty("memoryLimit", value);
  }

  get cuttingPlaneFillColor(): RGB {
    console.warn(
      "Options.cuttingPlaneFillColor has been deprecated since 27.5 and will be removed in a future release, use sectionFillColor instead"
    );
    return {
      red: this._data.sectionFillColor.r,
      green: this._data.sectionFillColor.g,
      blue: this._data.sectionFillColor.b,
    };
  }

  set cuttingPlaneFillColor(value: RGB) {
    console.warn(
      "Options.cuttingPlaneFillColor has been deprecated since 27.5 and will be removed in a future release, use sectionFillColor instead"
    );
    this.setProperty("sectionFillColor", {
      r: value.red,
      g: value.green,
      b: value.blue,
    });
  }

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

  set enableSectionFill(value: boolean) {
    this.setProperty("enableSectionFill", value);
  }

  get sectionFillColor(): { r: number; g: number; b: number } {
    return this._data.sectionFillColor;
  }

  set sectionFillColor(value: { r: number; g: number; b: number }) {
    this.setProperty("sectionFillColor", value);
  }

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

  set sectionUseObjectColor(value: boolean) {
    this.setProperty("sectionUseObjectColor", value);
  }

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

  set enableSectionHatch(value: boolean) {
    this.setProperty("enableSectionHatch", value);
  }

  get sectionHatchColor(): { r: number; g: number; b: number } {
    return this._data.sectionHatchColor;
  }

  set sectionHatchColor(value: { r: number; g: number; b: number }) {
    this.setProperty("sectionHatchColor", value);
  }

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

  set sectionHatchScale(value: number) {
    this.setProperty("sectionHatchScale", value);
  }

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

  set enableSectionOutline(value: boolean) {
    this.setProperty("enableSectionOutline", value);
  }

  get sectionOutlineColor(): { r: number; g: number; b: number } {
    return this._data.sectionOutlineColor;
  }

  set sectionOutlineColor(value: { r: number; g: number; b: number }) {
    this.setProperty("sectionOutlineColor", value);
  }

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

  set sectionOutlineWidth(value: number) {
    this.setProperty("sectionOutlineWidth", value);
  }

  get edgesColor(): { r: number; g: number; b: number } {
    return this._data.edgesColor;
  }

  set edgesColor(value: { r: number; g: number; b: number }) {
    this.setProperty("edgesColor", value);
  }

  get facesColor(): { r: number; g: number; b: number } {
    return this._data.facesColor;
  }

  set facesColor(value: { r: number; g: number; b: number }) {
    this.setProperty("facesColor", value);
  }

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

  set edgesVisibility(value: boolean) {
    this.setProperty("edgesVisibility", value);
  }

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

  set edgesOverlap(value: boolean) {
    this.setProperty("edgesOverlap", value);
  }

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

  set facesOverlap(value: boolean) {
    this.setProperty("facesOverlap", value);
  }

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

  set facesTransparancy(value: number) {
    this.setProperty("facesTransparancy", value);
  }

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

  set enableCustomHighlight(value: boolean) {
    this.setProperty("enableCustomHighlight", value);
  }

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

  set sceneGraph(value: boolean) {
    this.setProperty("sceneGraph", value);
    if (value) {
      this.setProperty("enablePartialMode", false);
    }
  }

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

  set edgeModel(value: boolean) {
    this.setProperty("edgeModel", value);
  }

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

  set reverseZoomWheel(value: boolean) {
    this.setProperty("reverseZoomWheel", value);
  }

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

  set enableZoomWheel(value: boolean) {
    this.setProperty("enableZoomWheel", value);
  }

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

  set enableGestures(value: boolean) {
    this.setProperty("enableGestures", value);
  }

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

  set geometryType(value: string) {
    this.setProperty("geometryType", value);
  }

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

  set rulerUnit(value: string) {
    this.setProperty("rulerUnit", value);
  }

  get rulerPrecision(): "Default" | "Auto" | number {
    return this._data.rulerPrecision;
  }

  set rulerPrecision(value: "Default" | "Auto" | number) {
    this.setProperty("rulerPrecision", value);
  }

  get cameraMode(): CameraMode {
    return this._data.cameraMode || "perspective";
  }

  set cameraMode(value: CameraMode) {
    this.setProperty("cameraMode", value);
  }

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

  set snapshotMimeType(value: string) {
    this.setProperty("snapshotMimeType", value);
  }

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

  set snapshotQuality(value: number) {
    this.setProperty("snapshotQuality", value);
  }
}
