import type { DomTools } from './domtools.classes.domtools.js';
import * as plugins from './domtools.plugins.js';

export class Scroller {
  public domtoolsInstance: DomTools;

  // Array to store scroll callback functions.
  private scrollCallbacks: Array<() => void> = [];

  // Lenis instance (if activated) or null.
  private lenisInstance: plugins.Lenis | null = null;

  // Bound handlers to allow removal from event listeners.
  private handleNativeScroll = (event: Event): void => {
    this.executeScrollCallbacks();
  };

  private handleLenisScroll = (info: any): void => {
    this.executeScrollCallbacks();
  };

  constructor(domtoolsInstanceArg: DomTools) {
    this.domtoolsInstance = domtoolsInstanceArg;
    // Attach the native scroll listener by default.
    this.attachNativeScrollListener();
  }

  private sweetScroller = new plugins.SweetScroll({});

  /**
   * Scrolls to a given element with options.
   */
  public async toElement(
    elementArg: HTMLElement,
    optionsArg: Parameters<typeof this.sweetScroller.toElement>[1]
  ) {
    this.sweetScroller.toElement(elementArg, optionsArg);
    await plugins.smartdelay.delayFor(optionsArg.duration);
  }

  /**
   * Detects whether native smooth scrolling is enabled.
   */
  public async detectNativeSmoothScroll() {
    const done = plugins.smartpromise.defer<boolean>();
    const sampleSize = 100;
    const acceptableDeltaDifference = 3;
    const minimumSmoothRatio = 0.75;

    const eventDeltas: number[] = [];

    function onWheel(event: WheelEvent) {
      eventDeltas.push(event.deltaY);

      if (eventDeltas.length >= sampleSize) {
        window.removeEventListener('wheel', onWheel);
        analyzeEvents();
      }
    }

    function analyzeEvents() {
      const totalDiffs = eventDeltas.length - 1;
      let smallDiffCount = 0;

      for (let i = 0; i < totalDiffs; i++) {
        const diff = Math.abs(eventDeltas[i + 1] - eventDeltas[i]);
        if (diff <= acceptableDeltaDifference) {
          smallDiffCount++;
        }
      }

      const smoothRatio = smallDiffCount / totalDiffs;
      if (smoothRatio >= minimumSmoothRatio) {
        console.log('Smooth scrolling detected.');
        done.resolve(true);
      } else {
        console.log('Smooth scrolling NOT detected.');
        done.resolve(false);
      }
    }

    window.addEventListener('wheel', onWheel);
    return done.promise;
  }

  /**
   * Enables Lenis scrolling.
   * If optionsArg.disableOnNativeSmoothScroll is true and native smooth scrolling is detected,
   * Lenis will be destroyed immediately.
   */
  public async enableLenisScroll(optionsArg?: { disableOnNativeSmoothScroll?: boolean }) {
    const lenis = new plugins.Lenis({
      autoRaf: true,
    });

    if (optionsArg?.disableOnNativeSmoothScroll) {
      if (await this.detectNativeSmoothScroll()) {
        lenis.destroy();
        return;
      }
    }

    // Activate Lenis scrolling.
    this.lenisInstance = lenis;
    // Switch from native scroll listener to Lenis scroll listener.
    this.detachNativeScrollListener();
    this.attachLenisScrollListener();

    // Monkey-patch the destroy method so that when Lenis is destroyed,
    // the native scroll listener is reattached.
    const originalDestroy = lenis.destroy.bind(lenis);
    lenis.destroy = () => {
      originalDestroy();
      this.detachLenisScrollListener();
      this.attachNativeScrollListener();
      this.lenisInstance = null;
    };
  }

  /**
   * Registers a callback to be executed on scroll.
   * @param callback A function to execute on each scroll event.
   */
  public onScroll(callback: () => void): void {
    this.scrollCallbacks.push(callback);
  }

  /**
   * Executes all registered scroll callbacks concurrently.
   */
  private executeScrollCallbacks(): void {
    // Execute all callbacks in parallel.
    this.scrollCallbacks.forEach((callback) => {
      try {
        callback();
      } catch (error) {
        console.error('Error in scroll callback:', error);
      }
    });
  }

  /**
   * Attaches the native scroll event listener.
   */
  private attachNativeScrollListener(): void {
    window.addEventListener('scroll', this.handleNativeScroll);
  }

  /**
   * Detaches the native scroll event listener.
   */
  private detachNativeScrollListener(): void {
    window.removeEventListener('scroll', this.handleNativeScroll);
  }

  /**
   * Attaches the Lenis scroll event listener.
   */
  private attachLenisScrollListener(): void {
    if (this.lenisInstance) {
      // Assuming that Lenis exposes an `on` method to listen to scroll events.
      this.lenisInstance.on('scroll', this.handleLenisScroll);
    }
  }

  /**
   * Detaches the Lenis scroll event listener.
   */
  private detachLenisScrollListener(): void {
    if (this.lenisInstance) {
      // Assuming that Lenis exposes an `off` method to remove scroll event listeners.
      this.lenisInstance.off('scroll', this.handleLenisScroll);
    }
  }
}