import type { DirectiveBinding, ObjectDirective } from 'vue';
import type { VHoverVariant } from './vHover';

const OVERLAY_EL = Symbol('vOverlayOverlayEl');
const OVERLAY_PINNED_ATTR = 'data-v-overlay-active';

/**
 * Binding accepted by the `v-overlay` directive.
 */
type VOverlayBinding =
  | VHoverVariant
  | false
  | null
  | undefined
  | VOverlayBindingObject;

/**
 * Object form of {@link VOverlayBinding} used when both variant and activation
 * modes need to be configured explicitly.
 */
interface VOverlayBindingObject {
  /**
   * Overlay tier variant to render.
   */
  variant: VHoverVariant;
  /**
   * Pin the overlay as active independent of hover state.
   * @default true
   */
  active?: boolean;
  /**
   * Additionally enable the overlay on hover.
   * @default false
   */
  hover?: boolean;
}

/**
 * Fully resolved overlay configuration computed from a binding.
 */
interface OverlayConfig {
  /**
   * Overlay tier variant to render.
   */
  variant: VHoverVariant;
  /**
   * Whether the overlay is pinned as active.
   */
  active: boolean;
  /**
   * Whether hover-driven activation is enabled.
   */
  hover: boolean;
}

function resolveConfig(
  binding: DirectiveBinding<VOverlayBinding>,
): OverlayConfig | null {
  const value = binding.value;
  if (value === false || value === null || value === undefined) return null;

  if (typeof value === 'object' && value !== null && 'variant' in value) {
    const { active = true, hover = false, variant } = value;
    if (variant === 'subtle' || variant === 'default' || variant === 'strong') {
      return { active, hover, variant };
    }
    return null;
  }

  if (value === 'subtle' || value === 'default' || value === 'strong') {
    return { active: true, hover: false, variant: value };
  }

  return null;
}

function detach(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');
  el.removeAttribute(OVERLAY_PINNED_ATTR);
}

function syncPinnedActive(el: HTMLElement, pinned: boolean): void {
  if (pinned) el.setAttribute(OVERLAY_PINNED_ATTR, '');
  else el.removeAttribute(OVERLAY_PINNED_ATTR);
}

function attach(el: HTMLElement, config: OverlayConfig): void {
  detach(el);
  el.classList.add('v-hover-root');

  const overlay = document.createElement('span');
  overlay.className = 'v-hover-overlay';
  overlay.setAttribute('data-v-hover-variant', config.variant);
  overlay.setAttribute('aria-hidden', 'true');

  el.prepend(overlay);
  (el as unknown as Record<symbol, HTMLElement>)[OVERLAY_EL] = overlay;
  syncPinnedActive(el, config.active);
}

function updateOverlay(el: HTMLElement, config: OverlayConfig): void {
  const overlay = (el as unknown as Record<symbol, HTMLElement>)[OVERLAY_EL];
  if (!overlay?.isConnected) {
    attach(el, config);
    return;
  }

  overlay.setAttribute('data-v-hover-variant', config.variant);
  syncPinnedActive(el, config.active);
}

export const vOverlay: ObjectDirective<HTMLElement, VOverlayBinding> = {
  mounted(el, binding) {
    const config = resolveConfig(binding);
    if (!config || (!config.active && !config.hover)) return;
    attach(el, config);
  },

  updated(el, binding) {
    const config = resolveConfig(binding);
    if (!config || (!config.active && !config.hover)) {
      detach(el);
      return;
    }

    updateOverlay(el, config);
  },

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