import { Directive, ElementRef, AfterViewInit, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';
import { debounceTime, takeUntil } from 'rxjs/operators';

@Directive({
  selector: '[autoResizeText]'
})
export class AutoResizeTextDirective implements AfterViewInit, OnDestroy {
  private resizeObserver!: ResizeObserver;
  private mutationObserver!: MutationObserver;
  private resize$ = new Subject<void>();
  private destroy$ = new Subject<void>();

  constructor(private el: ElementRef) { }

  ngAfterViewInit() {
    this.adjustFontSizes();

    // Initialisiere den ResizeObserver
    this.resizeObserver = new ResizeObserver(() => {
      this.resize$.next();
    });
    this.resizeObserver.observe(this.el.nativeElement);

    // Initialisiere den MutationObserver
    this.mutationObserver = new MutationObserver(() => {
      this.resize$.next();
    });
    this.mutationObserver.observe(this.el.nativeElement, {
      childList: true,
      subtree: true,
      characterData: true
    });

    // Debounce der Resize-Events, um übermäßige Aufrufe zu vermeiden
    this.resize$.pipe(
       takeUntil(this.destroy$)
    ).subscribe(() => {
      this.adjustFontSizes();
    });
  }

  ngOnDestroy() {
    if (this.resizeObserver) {
      this.resizeObserver.disconnect();
    }
    if (this.mutationObserver) {
      this.mutationObserver.disconnect();
    }
    this.destroy$.next();
    this.destroy$.complete();
  }

  /**
   * Passt die Schriftgrößen der einzelnen Divs individuell an.
   */
  private adjustFontSizes() {
    const container: HTMLElement = this.el.nativeElement;
    const divs: HTMLElement[] = Array.from(container.querySelectorAll('div'));

    // Original-Schriftgrößen speichern, falls noch nicht gespeichert
    divs.forEach(div => {
      if (!div.dataset['originalFontSizePx']) {
        const computedStyle = window.getComputedStyle(div);
        const originalFontSizePx = parseFloat(computedStyle.fontSize);
        div.dataset['originalFontSizePx'] = originalFontSizePx.toString();
      }
    });

    // Setze die Schriftgrößen auf die Originalwerte zurück
    divs.forEach(div => {
      const originalFontSizePx = parseFloat(div.dataset['originalFontSizePx'] || '16');
      div.style.fontSize = `${originalFontSizePx}px`;
    });

    // Wenn der Inhalt überläuft, passe die Schriftgrößen an
    if (this.isOverflowing(container)) {
      // Berechne den Skalierungsfaktor, der benötigt wird, um den Inhalt anzupassen
      let scalingFactor = this.calculateScalingFactor(container, divs);

      // Wende den Skalierungsfaktor auf alle Divs an
      divs.forEach(div => {
        const originalFontSizePx = parseFloat(div.dataset['originalFontSizePx'] || '16');
        let newFontSizePx = originalFontSizePx * scalingFactor;

        // Stelle sicher, dass die Schriftgröße nicht unter einen Mindestwert fällt
        const minFontSizePx = 12;
        if (newFontSizePx < minFontSizePx) {
          newFontSizePx = minFontSizePx;
        }

        div.style.fontSize = `${newFontSizePx}px`;
      });
    }
  }

  /**
   * Berechnet den Skalierungsfaktor, um den gesamten Inhalt anzupassen.
   * @param container Das Container-Element.
   * @param divs Die Liste der Div-Elemente.
   * @returns Der Skalierungsfaktor als Dezimalzahl.
   */
  private calculateScalingFactor(container: HTMLElement, divs: HTMLElement[]): number {
    const minFontSizePx = 12; // Mindest-Schriftgröße
    let low = 0.1; // 10% Skalierung
    let high = 1;  // 100% Skalierung
    let mid = 1;

    // Binäre Suche, um den optimalen Skalierungsfaktor zu finden
    while (high - low > 0.01) {
      mid = (low + high) / 2;

      // Anwenden des Skalierungsfaktors
      divs.forEach(div => {
        const originalFontSizePx = parseFloat(div.dataset['originalFontSizePx'] || '16');
        let newFontSizePx = originalFontSizePx * mid;
        if (newFontSizePx < minFontSizePx) {
          newFontSizePx = minFontSizePx;
        }
        div.style.fontSize = `${newFontSizePx}px`;
      });

      // Überprüfen, ob der Inhalt überläuft
      if (this.isOverflowing(container)) {
        high = mid;
      } else {
        low = mid;
      }
    }

    return low;
  }

  /**
   * Überprüft, ob der Inhalt des Containers überläuft.
   * @param container Das zu überprüfende HTMLElement.
   * @returns Wahr, wenn ein Überlauf vorliegt, sonst falsch.
   */
  private isOverflowing(container: HTMLElement): boolean {
    return container.scrollWidth > container.clientWidth || container.scrollHeight > container.clientHeight;
  }
}
