/*
 * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
 * Ministerpräsidenten des Landes Schleswig-Holstein
 * Staatskanzlei
 * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
 *
 * Lizenziert unter der EUPL, Version 1.2 oder - sobald
 * diese von der Europäischen Kommission genehmigt wurden -
 * Folgeversionen der EUPL ("Lizenz");
 * Sie dürfen dieses Werk ausschließlich gemäß
 * dieser Lizenz nutzen.
 * Eine Kopie der Lizenz finden Sie hier:
 *
 * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
 *
 * Sofern nicht durch anwendbare Rechtsvorschriften
 * gefordert oder in schriftlicher Form vereinbart, wird
 * die unter der Lizenz verbreitete Software "so wie sie
 * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
 * ausdrücklich oder stillschweigend - verbreitet.
 * Die sprachspezifischen Genehmigungen und Beschränkungen
 * unter der Lizenz sind dem Lizenztext zu entnehmen.
 */
import { isEscapeKey, isNotNull } from '@alfa-client/tech-shared';
import { InteractivityChecker } from '@angular/cdk/a11y';
import {
  AfterViewInit,
  ComponentRef,
  Directive,
  ElementRef,
  HostListener,
  inject,
  Input,
  OnDestroy,
  Renderer2,
  ViewContainerRef,
} from '@angular/core';
import { isNull, uniqueId } from 'lodash-es';
import { TooltipComponent } from './tooltip.component';

const OUTLINE_INDENT = 4; // Outline offset (2) + outline width (2)

@Directive({
  selector: '[tooltip]',
  standalone: true,
})
export class TooltipDirective implements AfterViewInit, OnDestroy {
  @Input() tooltip: string = '';

  componentRef: ComponentRef<TooltipComponent> = null;
  focusableElement: HTMLElement = null;
  tooltipId: string;

  public viewContainerRef: ViewContainerRef = inject(ViewContainerRef);
  public elementRef: ElementRef<HTMLElement> = inject(ElementRef);
  public renderer: Renderer2 = inject(Renderer2);
  public interactivityChecker: InteractivityChecker = inject(InteractivityChecker);

  ngAfterViewInit(): void {
    this.createTooltip();
  }

  ngOnDestroy(): void {
    this.destroy();
  }

  @HostListener('mouseenter')
  @HostListener('focusin')
  showTooltip(): void {
    const nativeElement: HTMLElement = this.elementRef.nativeElement;
    const attachedToFocused: boolean = nativeElement.contains(document.activeElement);
    this.setTooltipProperties(attachedToFocused);
  }

  @HostListener('mouseleave')
  @HostListener('window:scroll')
  @HostListener('focusout')
  @HostListener('window:resize')
  hideTooltip(): void {
    this.hide();
  }

  @HostListener('keydown', ['$event'])
  onKeydown(e: KeyboardEvent): void {
    if (isEscapeKey(e)) {
      this.hide();
    }
  }

  createTooltip(): void {
    if (isNotNull(this.componentRef)) {
      return;
    }

    const nativeElement: HTMLElement = this.elementRef.nativeElement;
    this.componentRef = this.viewContainerRef.createComponent(TooltipComponent);
    this.focusableElement =
      this.interactivityChecker.isFocusable(nativeElement) ? nativeElement : this.getFocusableElement(nativeElement);
    this.focusableElement.appendChild(this.componentRef.location.nativeElement);
    this.setAriaLabeledBy();
  }

  setTooltipProperties(attachedToFocused = false): void {
    if (isNull(this.componentRef)) {
      return;
    }

    const { left, right, bottom } = this.elementRef.nativeElement.getBoundingClientRect();
    this.componentRef.instance.left = (right + left) / 2;
    this.componentRef.instance.top = attachedToFocused ? bottom + OUTLINE_INDENT : bottom;
    this.componentRef.instance.text = this.tooltip;
    this.componentRef.instance.id = this.tooltipId;
    this.componentRef.instance.show = true;
  }

  setAriaLabeledBy(): void {
    this.tooltipId = uniqueId('tooltip');
    this.renderer.setAttribute(this.focusableElement, 'aria-labeledby', this.tooltipId);
  }

  removeAriaLabeledBy(): void {
    this.renderer.removeAttribute(this.focusableElement, 'aria-labeledby');
  }

  getFocusableElement(element: HTMLElement): HTMLElement {
    return element.querySelector('a[href], button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])');
  }

  hide(): void {
    if (isNull(this.componentRef)) {
      return;
    }

    this.componentRef.instance.show = false;
  }

  destroy(): void {
    if (isNull(this.componentRef)) {
      return;
    }

    this.componentRef.destroy();
    this.componentRef = null;
    this.removeAriaLabeledBy();
    this.focusableElement = null;
  }
}