import { isEscapeKey, isNotNull } from '@alfa-client/tech-shared';
import { InteractivityChecker } from '@angular/cdk/a11y';
import {
  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 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);

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

  @HostListener('mouseenter')
  @HostListener('focusin')
  createTooltip(): void {
    if (isNotNull(this.componentRef)) {
      return;
    }

    const nativeElement: HTMLElement = this.elementRef.nativeElement;
    const attachedToFocused: boolean = nativeElement.contains(document.activeElement);
    this.componentRef = this.viewContainerRef.createComponent(TooltipComponent);
    nativeElement.appendChild(this.componentRef.location.nativeElement);
    this.setAriaDescribedBy();
    this.setTooltipProperties(attachedToFocused);
  }

  @HostListener('mouseleave')
  @HostListener('window:scroll')
  @HostListener('focusout')
  destroyTooltip(): void {
    this.destroy();
  }

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

  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;
  }

  setAriaDescribedBy(): void {
    const nativeElement: HTMLElement = this.elementRef.nativeElement;
    this.tooltipId = uniqueId('tooltip');
    this.focusableElement =
      this.interactivityChecker.isFocusable(nativeElement) ? nativeElement : this.getFocusableElement(nativeElement);
    this.renderer.setAttribute(this.focusableElement, 'aria-describedby', this.tooltipId);
  }

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

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

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

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