import { hash } from '@ember/helper';
import { on } from '@ember/modifier';
import { isTesting } from '@embroider/macros';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import type { ModifierLike, WithBoundArgs } from '@glint/template';
import { modifier } from 'ember-modifier';
import floatingUi from '../private/modifiers/floating-ui.ts';

export interface AuTooltipSignature {
  Args: {
    placement?: 'top' | 'right' | 'bottom' | 'left';
  };
  Blocks: {
    default: [
      {
        Content: WithBoundArgs<
          typeof TooltipContent,
          | 'targetElement'
          | 'isShown'
          | 'placement'
          | 'show'
          | 'hide'
          | 'hideInstantly'
        >;
        isShown: boolean;
        target: ModifierLike<{ Element: HTMLElement }>;
      },
    ];
  };
}

export default class AuTooltip extends Component<AuTooltipSignature> {
  // We use declare here, so TS doesn't consider `undefined` as part of the type since the initialisation happens after the constructor.
  @tracked declare targetElement: HTMLElement;
  @tracked isShown: boolean = false;
  delayTimeoutId?: number;

  get placement() {
    return this.args.placement || 'top';
  }

  get delay() {
    // We use a small delay when opening and closing the tooltip for a couple of reasons:
    // - When opening this ensures that users intentionally open the tooltip instead of just shortly moving their mouse over the target
    // - When closing it allows users to move their mouse to the tooltip contents without closing the tooltip
    return isTesting() ? 0 : 300;
  }

  show = () => {
    this.clearDelayTimer();
    this.delayTimeoutId = setTimeout(() => {
      this.isShown = true;
      this.clearDelayTimer();
    }, this.delay);
  };

  hide = () => {
    this.clearDelayTimer();
    this.delayTimeoutId = setTimeout(() => {
      this.isShown = false;
      this.clearDelayTimer();
    }, this.delay);
  };

  hideInstantly = () => {
    this.isShown = false;
    this.clearDelayTimer();
  };

  clearDelayTimer() {
    if (this.delayTimeoutId) {
      clearTimeout(this.delayTimeoutId);
      this.delayTimeoutId = undefined;
    }
  }

  target = modifier((element: HTMLElement) => {
    this.targetElement = element;

    element.addEventListener('mouseenter', this.show);
    element.addEventListener('focus', this.show);
    element.addEventListener('mouseleave', this.hide);
    element.addEventListener('blur', this.hide);

    return () => {
      element.removeEventListener('mouseenter', this.show);
      element.removeEventListener('focus', this.show);
      element.removeEventListener('mouseleave', this.hide);
      element.removeEventListener('blur', this.hide);
    };
  });

  <template>
    {{yield
      (hash
        Content=(component
          TooltipContent
          targetElement=this.targetElement
          placement=this.placement
          isShown=this.isShown
          show=this.show
          hide=this.hide
          hideInstantly=this.hideInstantly
        )
        isShown=this.isShown
        target=this.target
      )
    }}
  </template>
}

interface TooltipContentSignature {
  Args: {
    targetElement: HTMLElement;
    isShown: boolean;
    placement: NonNullable<AuTooltipSignature['Args']['placement']>;
    show: () => void;
    hide: () => void;
    hideInstantly: () => void;
  };
  Blocks: {
    default: [];
  };
  Element: HTMLDivElement;
}

class TooltipContent extends Component<TooltipContentSignature> {
  @tracked declare arrowElement: HTMLElement;

  arrow = modifier((element: HTMLElement) => {
    this.arrowElement = element;
  });

  closeOnEscapePress = modifier(() => {
    const listener = (event: KeyboardEvent) => {
      if (event.key === 'Escape') {
        this.args.hideInstantly();
      }
    };

    document.addEventListener('keydown', listener);

    return () => {
      document.removeEventListener('keydown', listener);
    };
  });

  get floatingUiOptions() {
    return {
      floater: {
        offset: 10, // 2px button outline offset, 3px button outline, 5px arrow
      },
    };
  }

  <template>
    {{#if @isShown}}
      <div
        class="au-c-tooltip"
        role="tooltip"
        ...attributes
        {{floatingUi
          @targetElement
          this.arrowElement
          defaultPlacement=@placement
          options=this.floatingUiOptions
        }}
        {{on "mouseenter" @show}}
        {{on "mouseleave" @hide}}
        {{this.closeOnEscapePress}}
        {{maybeLargeTooltip}}
      >
        <div {{this.arrow}} class="au-c-tooltip__arrow"></div>
        <div class="au-c-tooltip__content">{{yield}}</div>
      </div>
    {{/if}}
  </template>
}

// The Webuniversum tooltip has some logic to switch to the "large" styling if there are more than 80 characters.
const LARGE_TOOLTIP_BREAKPOINT = 80;
const maybeLargeTooltip = modifier(function (tooltipElement: HTMLElement) {
  const contentElement = tooltipElement.querySelector<HTMLElement>(
    '.au-c-tooltip__content',
  );
  if ((contentElement?.innerText?.length ?? 0) > LARGE_TOOLTIP_BREAKPOINT) {
    tooltipElement.classList.add('au-c-tooltip--large');
  }
});
