import type { OverlayType } from '../types/layout';

export type OverlayPortalTarget = string | HTMLElement | null | undefined;
export type OverlayMountKind = OverlayType | 'tooltip';

const isHTMLElement = (value: unknown): value is HTMLElement =>
  typeof HTMLElement !== 'undefined' && value instanceof HTMLElement;

const normalizeSelector = (value: string): string => value.trim();

export function resolveDomTarget(target: OverlayPortalTarget): HTMLElement | null {
  if (typeof document === 'undefined') return null;
  if (!target) return null;
  if (isHTMLElement(target)) return target;
  if (typeof target !== 'string') return null;

  const selector = normalizeSelector(target);
  if (!selector) return null;
  if (selector === 'body' || selector === 'document.body') return document.body;

  try {
    return document.querySelector<HTMLElement>(selector);
  } catch {
    return null;
  }
}

/**
 * Observe a selector-backed mount target across delayed rendering and DOM
 * replacements. This is useful for host applications such as WooCommerce
 * Blocks, where the requested portal can appear after the designer itself.
 */
export function observeDomTarget(
  target: OverlayPortalTarget,
  onChange: (element: HTMLElement | null) => void,
): () => void {
  let current: HTMLElement | null | undefined;
  const sync = () => {
    const next = resolveDomTarget(target);
    if (next === current) return;
    current = next;
    onChange(next);
  };

  sync();

  if (
    typeof document === 'undefined'
    || typeof MutationObserver === 'undefined'
    || typeof target !== 'string'
    || !target.trim()
    || target.trim() === 'body'
    || target.trim() === 'document.body'
    || !document.documentElement
  ) {
    return () => undefined;
  }

  const observer = new MutationObserver(sync);
  observer.observe(document.documentElement, {
    childList: true,
    subtree: true,
    attributes: true,
    attributeFilter: ['class', 'id'],
  });

  return () => observer.disconnect();
}

/**
 * Resolve a custom portal selector through an owned child host. Frameworks
 * controlling the target node can replace its children at any time; recreating
 * this host keeps the Asuka portal mounted without taking ownership of the
 * host application's own markup.
 */
export function observeDomPortalTarget(
  target: OverlayPortalTarget,
  hostKey: string,
  onChange: (element: HTMLElement | null) => void,
): () => void {
  const normalizedTarget = typeof target === 'string' ? target.trim() : '';
  if (
    typeof target !== 'string'
    || normalizedTarget === 'body'
    || normalizedTarget === 'document.body'
  ) {
    return observeDomTarget(target, onChange);
  }

  const normalizedHostKey = String(hostKey || 'module').trim() || 'module';
  let currentHost: HTMLElement | null = null;
  let lastEmitted: HTMLElement | null | undefined;

  const emit = (next: HTMLElement | null) => {
    if (next === lastEmitted) return;
    lastEmitted = next;
    onChange(next);
  };

  const sync = () => {
    const parent = resolveDomTarget(target);
    if (!parent) {
      if (currentHost?.isConnected) currentHost.remove();
      currentHost = null;
      emit(null);
      return;
    }

    if (currentHost?.isConnected && currentHost.parentElement === parent) {
      emit(currentHost);
      return;
    }

    if (currentHost?.isConnected && currentHost.parentElement !== parent) {
      currentHost.remove();
    }
    const existing = Array.from(parent.children).find((child) => (
      child instanceof HTMLElement
      && child.getAttribute('data-asuka-portal-host') === normalizedHostKey
    ));
    currentHost = existing instanceof HTMLElement
      ? existing
      : parent.ownerDocument.createElement('div');

    if (!existing) {
      currentHost.className = 'ask-page-module-host';
      currentHost.setAttribute('data-asuka-portal-host', normalizedHostKey);
      parent.appendChild(currentHost);
    }
    emit(currentHost);
  };

  sync();

  if (
    typeof document === 'undefined'
    || typeof MutationObserver === 'undefined'
    || !normalizedTarget
    || !document.documentElement
  ) {
    return () => undefined;
  }

  const observer = new MutationObserver(sync);
  observer.observe(document.documentElement, {
    childList: true,
    subtree: true,
    attributes: true,
    attributeFilter: ['class', 'id'],
  });

  return () => {
    observer.disconnect();
    if (
      currentHost?.isConnected
      && currentHost.getAttribute('data-asuka-portal-host') === normalizedHostKey
    ) {
      currentHost.remove();
    }
  };
}

export function getDefaultOverlayPortalTarget(type: OverlayMountKind): HTMLElement | null {
  if (typeof document === 'undefined') return null;

  // Modals and tooltips are intentionally viewport-level by default.
  // Drawers are intentionally contained by ModuleHost, because the correct
  // designer instance/main region is only known from the layout tree.
  if (type === 'modal' || type === 'tooltip') return document.body;

  return null;
}

export function resolveOverlayPortalTarget(type: OverlayMountKind, target?: OverlayPortalTarget): HTMLElement | null {
  return resolveDomTarget(target) ?? getDefaultOverlayPortalTarget(type);
}

export function isDocumentBodyTarget(target: HTMLElement | null | undefined): boolean {
  return !!target && typeof document !== 'undefined' && target === document.body;
}

export function isContainedOverlayType(type: OverlayMountKind): boolean {
  return (
    type === 'modal' ||
    type === 'drawer' ||
    type === 'panel-left' ||
    type === 'panel-right' ||
    type === 'panel-top' ||
    type === 'panel-bottom'
  );
}
