import {
  ComponentRef,
  Directive,
  ElementRef,
  HostListener,
  OnDestroy,
  effect,
  inject,
  input
} from '@angular/core';
import {
  ConnectedPosition,
  Overlay,
  OverlayRef
} from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal';

import {
  Tooltip
} from './tooltip/tooltip';
import { TooltipVariant }  from './tooltip/tooltip';

export type TooltipPosition =
  | 'top'
  | 'bottom'
  | 'left'
  | 'right';

@Directive({
  selector: '[appTooltip]',
  standalone: true
})
export class TooltipDirective implements OnDestroy {
  private readonly elementRef =
    inject<ElementRef<HTMLElement>>(ElementRef);

  private readonly overlay = inject(Overlay);

  readonly appTooltip = input.required<string>();

  readonly tooltipPosition =
    input<TooltipPosition>('top');

  readonly tooltipVariant =
    input<TooltipVariant>('default');

  readonly tooltipDisabled = input(false);

  readonly tooltipDelay = input(200);

  private overlayRef: OverlayRef | null = null;

  private tooltipComponentRef:
    ComponentRef<Tooltip> | null = null;

  private showTimeout:
    ReturnType<typeof setTimeout> | null = null;

  constructor() {
    effect(() => {
      const text = this.appTooltip();
      const variant = this.tooltipVariant();

      this.tooltipComponentRef?.setInput('text', text);
      this.tooltipComponentRef?.setInput(
        'variant',
        variant
      );
    });
  }

  @HostListener('mouseenter')
  @HostListener('focusin')
  show(): void {
    const tooltipText = this.appTooltip().trim();

    if (
      this.tooltipDisabled() ||
      tooltipText.length === 0
    ) {
      return;
    }

    this.clearTimeout();

    this.showTimeout = setTimeout(() => {
      this.showTimeout = null;
      this.openTooltip();
    }, Math.max(0, this.tooltipDelay()));
  }

  @HostListener('mouseleave')
  @HostListener('focusout')
  hide(): void {
    this.clearTimeout();
    this.closeTooltip();
  }

  private openTooltip(): void {
    if (this.overlayRef?.hasAttached()) {
      return;
    }

    const positionStrategy = this.overlay
      .position()
      .flexibleConnectedTo(
        this.elementRef.nativeElement
      )
      .withPositions(this.getPositions())
      .withPush(true);

    this.overlayRef = this.overlay.create({
      positionStrategy,
      scrollStrategy:
        this.overlay.scrollStrategies.reposition()
    });

    const portal = new ComponentPortal(Tooltip);

    this.tooltipComponentRef =
      this.overlayRef.attach(portal);

    this.tooltipComponentRef.setInput(
      'text',
      this.appTooltip().trim()
    );

    this.tooltipComponentRef.setInput(
      'variant',
      this.tooltipVariant()
    );
  }

  private closeTooltip(): void {
    this.overlayRef?.dispose();

    this.overlayRef = null;
    this.tooltipComponentRef = null;
  }

  private clearTimeout(): void {
    if (this.showTimeout === null) {
      return;
    }

    clearTimeout(this.showTimeout);
    this.showTimeout = null;
  }

  private getPositions(): ConnectedPosition[] {
    const positions: Record<
      TooltipPosition,
      ConnectedPosition[]
    > = {
      top: [
        {
          originX: 'center',
          originY: 'top',
          overlayX: 'center',
          overlayY: 'bottom',
          offsetY: -8
        },
        {
          originX: 'center',
          originY: 'bottom',
          overlayX: 'center',
          overlayY: 'top',
          offsetY: 8
        }
      ],

      bottom: [
        {
          originX: 'center',
          originY: 'bottom',
          overlayX: 'center',
          overlayY: 'top',
          offsetY: 8
        },
        {
          originX: 'center',
          originY: 'top',
          overlayX: 'center',
          overlayY: 'bottom',
          offsetY: -8
        }
      ],

      left: [
        {
          originX: 'start',
          originY: 'center',
          overlayX: 'end',
          overlayY: 'center',
          offsetX: -8
        },
        {
          originX: 'end',
          originY: 'center',
          overlayX: 'start',
          overlayY: 'center',
          offsetX: 8
        }
      ],

      right: [
        {
          originX: 'end',
          originY: 'center',
          overlayX: 'start',
          overlayY: 'center',
          offsetX: 8
        },
        {
          originX: 'start',
          originY: 'center',
          overlayX: 'end',
          overlayY: 'center',
          offsetX: -8
        }
      ]
    };

    return positions[this.tooltipPosition()];
  }

  ngOnDestroy(): void {
    this.clearTimeout();
    this.closeTooltip();
  }
}