/**
 * Copyright Aquera Inc 2023
 *
 * This source code is licensed under the BSD-3-Clause license found in the
 * LICENSE file in the root directory of this source tree.
 */

import { LitElement, CSSResultArray, TemplateResult } from 'lit';
import { styles } from './nile-tooltip.css';
import '../nile-popup/nile-popup';
import { animateTo, parseDuration, stopAnimations } from '../internal/animate';
import { classMap } from 'lit/directives/class-map.js';
import { customElement, property, query } from 'lit/decorators.js';
import {
  getAnimation,
  setDefaultAnimation,
} from '../utilities/animation-registry';
import { html } from 'lit';
// import { LocalizeController } from '../utilities/localize';
import { waitForEvent } from '../internal/event';
import { watch } from '../internal/watch';
import NileElement from '../internal/nile-element';
import type { CSSResultGroup } from 'lit';
import type NilePopup from '../nile-popup/nile-popup';
import { VisibilityManager } from '../utilities/visibility-manager.js';

/**
 * Nile icon component.
 *
 * @tag nile-tooltip
 *
 */
@customElement('nile-tooltip')
export class NileTooltip extends NileElement {
  /**
   * The styles for Tooltip
   * @remarks If you are extending this class you can extend the base styles with super. Eg `return [super(), myCustomStyles]`
   */
  public static get styles(): CSSResultArray {
    return [styles];
  }

  private hoverTimeout: number;
  // private readonly localize = new LocalizeController(this);

  @query('slot:not([name])') defaultSlot: HTMLSlotElement;
  @query('.tooltip__body') body: HTMLElement;
  @query('nile-popup') popup: NilePopup;

  /** The tooltip's content. If you need to display HTML, use the `content` slot instead. */
  @property({ type: String, reflect: true }) content = '';

  /** Size Property to decide the tool tip size */
  @property({ reflect: true }) size: 'small' | 'large' = 'small';

  /**
   * The preferred placement of the tooltip. Note that the actual placement may vary as needed to keep the tooltip
   * inside of the viewport.
   */
  @property() placement:
    | 'top'
    | 'top-start'
    | 'top-end'
    | 'right'
    | 'right-start'
    | 'right-end'
    | 'bottom'
    | 'bottom-start'
    | 'bottom-end'
    | 'left'
    | 'left-start'
    | 'left-end' = 'top';

  /** Disables the tooltip so it won't show when triggered. */
  @property({ type: Boolean, reflect: true }) disabled = false;

  /** The distance in pixels from which to offset the tooltip away from its target. */
  @property({ type: Number }) distance = 8;

  /** Indicates whether or not the tooltip is open. You can use this in lieu of the show/hide methods. */
  @property({ type: Boolean, reflect: true }) open = false;

  /** The distance in pixels from which to offset the tooltip along its target. */
  @property({ type: Number }) skidding = 0;

  /**
   * Controls how the tooltip is activated. Possible options include `click`, `hover`, `focus`, and `manual`. Multiple
   * options can be passed by separating them with a space. When manual is used, the tooltip must be activated
   * programmatically.
   */
  @property() trigger = 'hover focus';

  /**
   * Enable this option to prevent the tooltip from being clipped when the component is placed inside a container with
   * `overflow: auto|hidden|scroll`. Hoisting uses a fixed positioning strategy that works in many, but not all,
   * scenarios.
   */
  @property({ type: Boolean }) hoist = false;

  private visibilityManager?: VisibilityManager;

@property({ type: Boolean, reflect: true }) enableVisibilityEffect = false;
@property({ type: Boolean, reflect: true }) enableTabClose = false;


  connectedCallback() {
    super.connectedCallback();
    this.handleBlur = this.handleBlur.bind(this);
    this.handleClick = this.handleClick.bind(this);
    this.handleFocus = this.handleFocus.bind(this);
    this.handleKeyDown = this.handleKeyDown.bind(this);
    this.handleMouseOver = this.handleMouseOver.bind(this);
    this.handleMouseOut = this.handleMouseOut.bind(this);

    this.updateComplete.then(() => {
      this.addEventListener('blur', this.handleBlur, true);
      this.addEventListener('focus', this.handleFocus, true);
      this.addEventListener('click', this.handleClick);
      this.addEventListener('keydown', this.handleKeyDown);
      this.addEventListener('mouseover', this.handleMouseOver);
      this.addEventListener('mouseout', this.handleMouseOut);
    });
  }

  firstUpdated() {
    this.body.hidden = !this.open;

    // If the tooltip is visible on init, update its position
    if (this.open) {
      this.popup.active = true;
      this.popup.reposition();
    }

  const anchorSlot = this.renderRoot.querySelector('slot:not([name])') as HTMLSlotElement | null;
  const anchorEl = anchorSlot?.assignedElements({ flatten: true })[0] as HTMLElement | undefined;

  this.visibilityManager = new VisibilityManager({
    host: this,
    target: anchorEl || null,
    enableVisibilityEffect: this.enableVisibilityEffect,
    enableTabClose: this.enableTabClose,
    isOpen: () => this.open,
    onAnchorOutOfView: () => {
      this.hide();
      this.emit('nile-visibility-change', {
        visible: false,
        reason: 'anchor-out-of-view',
      });
    },
    onDocumentHidden: () => {
      this.hide();
      this.emit('nile-visibility-change', {
        visible: false,
        reason: 'document-hidden',
      });
    },
    emit: (event, detail) => this.emit(`nile-${event}`, detail),
  });
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    this.visibilityManager?.cleanup();
    this.removeEventListener('blur', this.handleBlur, true);
    this.removeEventListener('focus', this.handleFocus, true);
    this.removeEventListener('click', this.handleClick);
    this.removeEventListener('keydown', this.handleKeyDown);
    this.removeEventListener('mouseover', this.handleMouseOver);
    this.removeEventListener('mouseout', this.handleMouseOut);
  }

  private handleBlur() {
    if (this.hasTrigger('focus')) {
      this.hide();
    }
  }

  private handleClick() {
    if (this.hasTrigger('click')) {
      if (this.open) {
        this.hide();
      } else {
        this.show();
      }
    }
  }

  private handleFocus() {
    if (this.hasTrigger('focus')) {
      this.show();
    }
  }

  private handleKeyDown(event: KeyboardEvent) {
    // Pressing escape when the target element has focus should dismiss the tooltip
    if (this.open && event.key === 'Escape') {
      event.stopPropagation();
      this.hide();
    }
  }

  private handleMouseOver() {
    if (this.hasTrigger('hover')) {
      const delay = parseDuration(
        getComputedStyle(this).getPropertyValue('--show-delay')
      );
      clearTimeout(this.hoverTimeout);
      this.hoverTimeout = window.setTimeout(() => this.show(), delay);
    }
  }

  private handleMouseOut() {
    if (this.hasTrigger('hover')) {
      const delay = parseDuration(
        getComputedStyle(this).getPropertyValue('--hide-delay')
      );
      clearTimeout(this.hoverTimeout);
      this.hoverTimeout = window.setTimeout(() => this.hide(), delay);
    }
  }

  private hasTrigger(triggerType: string) {
    const triggers = this.trigger.split(' ');
    return triggers.includes(triggerType);
  }

  @watch('open', { waitUntilFirstUpdate: true })
  async handleOpenChange() {
    if (this.open) {
      this.visibilityManager?.setup();
      if (this.disabled) {
        return;
      }

      // Show
      this.emit('nile-show');

      await stopAnimations(this.body);
      this.body.hidden = false;
      this.popup.active = true;
      const { keyframes, options } = getAnimation(this, 'tooltip.show', {
        dir: '',
      });
      await animateTo(this.popup.popup, keyframes, options);

      this.emit('nile-after-show');
    } else {
      this.visibilityManager?.cleanup();
      // Hide
      this.emit('nile-hide');

      await stopAnimations(this.body);
      const { keyframes, options } = getAnimation(this, 'tooltip.hide', {
        dir: '',
      });
      await animateTo(this.popup.popup, keyframes, options);
      this.popup.active = false;
      this.body.hidden = true;

      this.emit('nile-after-hide');
    }
  }

  @watch(['content', 'distance', 'hoist', 'placement', 'skidding'])
  async handleOptionsChange() {
    if (this.hasUpdated) {
      await this.updateComplete;
      this.popup.reposition();
    }
  }

  @watch('disabled')
  handleDisabledChange() {
    if (this.disabled && this.open) {
      this.hide();
    }
  }

  /** Shows the tooltip. */
  async show() {
    if (this.open || !this.content?.trim().length) {
      return undefined;
    }

    this.open = true;
    return waitForEvent(this, 'nile-after-show');
  }

  /** Hides the tooltip */
  async hide() {
    if (!this.open) {
      return undefined;
    }

    this.open = false;
    return waitForEvent(this, 'nile-after-hide');
  }

  render() {
    return html`
      <nile-popup
        part="base"
        exportparts="
          popup:base__popup,
          arrow:base__arrow
        "
        class=${classMap({
          tooltip: true,
          'tooltip--open': this.open,
        })}
        placement=${this.placement}
        distance=${this.distance}
        skidding=${this.skidding}
        strategy=${this.hoist ? 'fixed' : 'absolute'}
        flip
        shift
        arrow
      >
        <slot slot="anchor" aria-describedby="tooltip"></slot>

        <slot
          name="content"
          part="body"
          id="tooltip"
          class=${classMap({
            tooltip__body: true,
            'tooltip__body--large': this.size === 'large',
          })}
          role="tooltip"
          aria-live=${this.open ? 'polite' : 'off'}
        >
          ${this.content}
        </slot>
      </nile-popup>
    `;
  }
}

setDefaultAnimation('tooltip.show', {
  keyframes: [
    { opacity: 0, scale: 0.8 },
    { opacity: 1, scale: 1 },
  ],
  options: { duration: 150, easing: 'ease' },
});

setDefaultAnimation('tooltip.hide', {
  keyframes: [
    { opacity: 1, scale: 1 },
    { opacity: 0, scale: 0.8 },
  ],
  options: { duration: 150, easing: 'ease' },
});

export default NileTooltip;

declare global {
  interface HTMLElementTagNameMap {
    'nile-tooltip': NileTooltip;
  }
}