import type { ObjectDirective } from 'vue';

export type VHoverVariant = 'subtle' | 'default' | 'strong';

const OVERLAY_EL = Symbol('vHoverOverlayEl');

/**
 * Value accepted by the `v-hover` directive and {@link bindVHover}.
 */
export type VHoverBindingValue =
  | VHoverVariant
  | false
  | null
  | undefined
  | {
      /** Overlay tier used when resolving the directive binding. */
      variant: VHoverVariant;
    };

/**
 * Resolves a binding value to a concrete overlay variant, or `null` when hover should be disabled.
 * @param value
 */
export function resolveVHoverBindingValue(
  value: VHoverBindingValue,
): VHoverVariant | null {
  if (value === false || value === null || value === undefined) return null;
  if (typeof value === 'object' && value !== null && 'variant' in value) {
    const { variant } = value;
    if (variant === 'subtle' || variant === 'default' || variant === 'strong')
      return variant;
    return null;
  }
  if (value === 'subtle' || value === 'default' || value === 'strong')
    return value;
  return null;
}

/**
 * Removes the hover overlay and `v-hover-root` class from `el`.
 * @param el
 */
export function detachVHover(el: HTMLElement): void {
  const overlay = (el as unknown as Record<symbol, HTMLElement>)[OVERLAY_EL];
  overlay?.remove();
  delete (el as unknown as Record<symbol, unknown>)[OVERLAY_EL];
  el.classList.remove('v-hover-root');
}

/**
 * Updates the overlay tier on an element that already has a v-hover overlay attached.
 * @param el
 * @param variant
 */
export function setVHoverOverlayVariant(
  el: HTMLElement,
  variant: VHoverVariant,
): void {
  const overlay = (el as unknown as Record<symbol, HTMLElement>)[OVERLAY_EL];
  if (overlay?.isConnected) {
    overlay.setAttribute('data-v-hover-variant', variant);
  }
}

/**
 * Attaches the v-hover overlay to `el` (replacing any existing one). No-op if `variant` is `null`.
 * @param el
 * @param variant
 */
export function attachVHover(el: HTMLElement, variant: VHoverVariant): void {
  detachVHover(el);

  el.classList.add('v-hover-root');

  const overlay = document.createElement('span');
  overlay.className = 'v-hover-overlay';
  overlay.setAttribute('data-v-hover-variant', variant);
  overlay.setAttribute('aria-hidden', 'true');
  el.prepend(overlay);
  (el as unknown as Record<symbol, HTMLElement>)[OVERLAY_EL] = overlay;
}

/**
 * Applies v-hover according to the same rules as the directive: attaches when `value` resolves to a variant, otherwise detaches.
 * @param el
 * @param value
 */
export function bindVHover(el: HTMLElement, value: VHoverBindingValue): void {
  const variant = resolveVHoverBindingValue(value);
  if (!variant) {
    detachVHover(el);
    return;
  }
  const existing = (el as unknown as Record<symbol, HTMLElement>)[OVERLAY_EL];
  if (existing?.isConnected) {
    setVHoverOverlayVariant(el, variant);
  } else {
    attachVHover(el, variant);
  }
}

export const vHover: ObjectDirective<HTMLElement, VHoverBindingValue> = {
  mounted(el, binding) {
    bindVHover(el, binding.value);
  },

  updated(el, binding) {
    bindVHover(el, binding.value);
  },

  unmounted(el) {
    detachVHover(el);
  },
};
