import type { ComponentChildren, FunctionalComponent, VNode } from 'preact';
import { createPortal } from 'preact/compat';
import { useEffect, useMemo, useRef } from 'preact/hooks';
import { computeAnchoredPosition, autoUpdateAnchoredPosition, type VirtualElementLike } from '../../core/positioning';
import {
  isContainedOverlayType,
  isDocumentBodyTarget,
  resolveOverlayPortalTarget,
  type OverlayPortalTarget,
} from '../../core/overlayMounts';

import { OverlayHeader } from './OverlayHeader';
import { ScrollArea } from '../core/ScrollArea';

export type OverlayType =
  | 'floating'
  | 'modal'
  | 'drawer'
  | 'panel-left'
  | 'panel-right'
  | 'panel-bottom'
  | 'panel-top'
  | 'popover'
  | 'inline';

export interface AnchorBox {
  left: number;
  top: number;
  width: number;
  height: number;
}

export interface OverlayHostProps {
  type: OverlayType;
  title: string;
  icon?: any;
  canBack?: boolean;
  onBack?: () => void;
  onClose?: () => void;
  /**
   * When false, the overlay is considered "hidden" (kept mounted).
   * Used to re-trigger enter animations when the overlay becomes visible again.
   */
  isVisible?: boolean;

  /**
   * When true, the overlay should play its exit animation.
   *
   * This is used for crossfade transitions when switching modules (A -> B):
   * we render the previous overlay long enough to animate out, then notify the
   * store to hide/unmount it.
   */
  isExiting?: boolean;
  /** Called once an exit animation has finished (crossfade). */
  onExited?: () => void;
  /** For popover anchoring (fixed positioning). */
  anchorBox?: AnchorBox | null;
  /**
   * Popover anchor strategy:
   * - 'anchored' => position relative to the anchor box (default Floating UI behavior)
   * - 'replace-anchor' => smart contextual placement around the anchor box, clamped in viewport
   */
  popoverAnchorMode?: 'anchored' | 'replace-anchor';
  /** Extra attributes for DOM semantics (data-asuka-...). */
  attrs?: Record<string, string>;
  /** Root z-index class (defaults are applied if omitted). */
  zClass?: string;
  /** Use compact header variant. */
  compactHeader?: boolean;
  /** When true, modal becomes fullscreen (used by Studio). */
  fullscreen?: boolean;
  /** Optional DOM target or selector where this overlay should be portaled. */
  portalTarget?: OverlayPortalTarget;
  /** Keep the surrounding designer interactive while this overlay is open. */
  allowBackgroundInteraction?: boolean;
  children: ComponentChildren;
}

/**
 * OverlayHost (F9.1)
 * -----------------
 * Unique rendering host for all non-inline overlays.
 *
 * This component centralizes:
 * - type -> container mapping (modal/drawer/panels/popover/floating)
 * - shared header (back/close/title)
 * - popover anchoring (Floating UI + virtual element)
 */
export const OverlayHost: FunctionalComponent<OverlayHostProps> = ({
  type,
  title,
  icon,
  canBack,
  onBack,
  onClose,
  isVisible = true,
  isExiting = false,
  onExited,
  anchorBox,
  popoverAnchorMode = 'anchored',
  attrs,
  zClass,
  compactHeader,
  fullscreen = false,
  portalTarget,
  allowBackgroundInteraction = false,
  children,
}) => {
  const rootRef = useRef<HTMLDivElement | null>(null);
  const backdropRef = useRef<HTMLDivElement | null>(null);
  const panelRef = useRef<HTMLDivElement | null>(null);
  const popoverRef = useRef<HTMLDivElement | null>(null);
  const isClosingRef = useRef(false);
  const restoreFocusRef = useRef<HTMLElement | null>(null);
  const requestCloseRef = useRef<() => void>(() => undefined);

  // WAAPI is widely supported in modern browsers. If not available, we simply render with no animation.
  const canAnimate = typeof window !== 'undefined' && typeof (Element.prototype as any).animate === 'function';

  const resolvedPortalTarget = resolveOverlayPortalTarget(type, portalTarget);
  const canClose = Boolean(onClose);
  const isContainedPortal =
    !!resolvedPortalTarget &&
    !isDocumentBodyTarget(resolvedPortalTarget) &&
    isContainedOverlayType(type);

  const withPortal = (node: VNode) =>
    resolvedPortalTarget ? createPortal(node, resolvedPortalTarget) : node;

  const motion = useMemo(() => {
    // Keep these tiny and “Figma-like”.
    const easeOut = 'cubic-bezier(0.2, 0.8, 0.2, 1)';
    const easeIn = 'cubic-bezier(0.4, 0, 1, 1)';
    const fast = 160;
    const normal = 220;

    const base = {
      enter: { duration: normal, easing: easeOut, fill: 'both' as const },
      exit: { duration: fast, easing: easeIn, fill: 'both' as const },
    };

    // Per overlay type “feel”.
    const byType: Record<OverlayType, { panelIn: Keyframe[]; panelOut: Keyframe[]; backdropIn?: Keyframe[]; backdropOut?: Keyframe[] }>= {
      modal: {
        backdropIn: [{ opacity: 0 }, { opacity: 1 }],
        backdropOut: [{ opacity: 1 }, { opacity: 0 }],
        panelIn: [
          { opacity: 0, transform: 'translateY(6px) scale(0.98)' },
          { opacity: 1, transform: 'translateY(0px) scale(1)' },
        ],
        panelOut: [
          { opacity: 1, transform: 'translateY(0px) scale(1)' },
          { opacity: 0, transform: 'translateY(8px) scale(0.98)' },
        ],
      },
      drawer: {
        backdropIn: [{ opacity: 0 }, { opacity: 1 }],
        backdropOut: [{ opacity: 1 }, { opacity: 0 }],
        panelIn: [
          { transform: 'translateX(12px)', opacity: 0 },
          { transform: 'translateX(0px)', opacity: 1 },
        ],
        panelOut: [
          { transform: 'translateX(0px)', opacity: 1 },
          { transform: 'translateX(12px)', opacity: 0 },
        ],
      },
      'panel-left': {
        panelIn: [
          { transform: 'translateX(-10px)', opacity: 0 },
          { transform: 'translateX(0px)', opacity: 1 },
        ],
        panelOut: [
          { transform: 'translateX(0px)', opacity: 1 },
          { transform: 'translateX(-10px)', opacity: 0 },
        ],
      },
      'panel-right': {
        panelIn: [
          { transform: 'translateX(10px)', opacity: 0 },
          { transform: 'translateX(0px)', opacity: 1 },
        ],
        panelOut: [
          { transform: 'translateX(0px)', opacity: 1 },
          { transform: 'translateX(10px)', opacity: 0 },
        ],
      },
      'panel-top': {
        panelIn: [
          { transform: 'translateY(-10px)', opacity: 0 },
          { transform: 'translateY(0px)', opacity: 1 },
        ],
        panelOut: [
          { transform: 'translateY(0px)', opacity: 1 },
          { transform: 'translateY(-10px)', opacity: 0 },
        ],
      },
      'panel-bottom': {
        panelIn: [
          { transform: 'translateY(10px)', opacity: 0 },
          { transform: 'translateY(0px)', opacity: 1 },
        ],
        panelOut: [
          { transform: 'translateY(0px)', opacity: 1 },
          { transform: 'translateY(10px)', opacity: 0 },
        ],
      },
      popover: {
        panelIn: [
          { opacity: 0, transform: 'translateY(-4px) scale(0.98)' },
          { opacity: 1, transform: 'translateY(0px) scale(1)' },
        ],
        panelOut: [
          { opacity: 1, transform: 'translateY(0px) scale(1)' },
          { opacity: 0, transform: 'translateY(-3px) scale(0.98)' },
        ],
      },
      floating: {
        panelIn: [
          { opacity: 0, transform: 'translateY(6px) scale(0.99)' },
          { opacity: 1, transform: 'translateY(0px) scale(1)' },
        ],
        panelOut: [
          { opacity: 1, transform: 'translateY(0px) scale(1)' },
          { opacity: 0, transform: 'translateY(6px) scale(0.99)' },
        ],
      },
      inline: { panelIn: [{ opacity: 1 }], panelOut: [{ opacity: 1 }] },
    };

    return { ...base, byType };
  }, []);

  const playEnter = () => {
    if (!canAnimate) return;
    const panel = panelRef.current;
    const backdrop = backdropRef.current;
    const def = motion.byType[type];
    try {
      if (backdrop) backdrop.style.willChange = 'opacity';
      if (panel) panel.style.willChange = 'transform, opacity';
      backdrop?.animate(def.backdropIn ?? [{ opacity: 1 }], motion.enter);
      panel?.animate(def.panelIn, motion.enter);
    } catch {
      // no-op
    }
  };

  const playExit = async () => {
    if (!canAnimate) return;
    const panel = panelRef.current;
    const backdrop = backdropRef.current;
    const def = motion.byType[type];
    try {
      const animations: Animation[] = [];
      if (backdrop && def.backdropOut) animations.push(backdrop.animate(def.backdropOut, motion.exit));
      if (panel) animations.push(panel.animate(def.panelOut, motion.exit));
      await Promise.all(animations.map((a) => a.finished.catch(() => undefined)));
      if (backdrop) backdrop.style.willChange = '';
      if (panel) panel.style.willChange = '';
    } catch {
      // no-op
    }
  };

  const resetAfterHidden = () => {
    // When overlays are kept mounted (keepMounted) we may "close" them via an exit animation,
    // then hide them with display:none. Because our animations use `fill: both`, the panel/backdrop
    // would otherwise remain at the *end* state (opacity:0/translated) when shown again.
    const panel = panelRef.current;
    const backdrop = backdropRef.current;

    // Reset closing lock so close works again after a reopen.
    isClosingRef.current = false;

    if (!canAnimate) {
      // Soft reset (no WAAPI)
      if (panel) {
        panel.style.opacity = '';
        panel.style.transform = '';
        panel.style.willChange = '';
      }
      if (backdrop) {
        backdrop.style.opacity = '';
        backdrop.style.willChange = '';
      }
      return;
    }

    try {
      panel?.getAnimations().forEach((a) => a.cancel());
      backdrop?.getAnimations().forEach((a) => a.cancel());
    } catch {
      // ignore
    }

    if (panel) {
      panel.style.opacity = '';
      panel.style.transform = '';
      panel.style.willChange = '';
    }
    if (backdrop) {
      backdrop.style.opacity = '';
      backdrop.style.willChange = '';
    }
  };

  const requestClose = async () => {
    if (!onClose) return;
    if (isClosingRef.current) return;
    isClosingRef.current = true;
    await playExit();
    onClose();
  };
  requestCloseRef.current = () => { void requestClose(); };

  const requestExitForCrossfade = async () => {
    if (isClosingRef.current) return;
    isClosingRef.current = true;
    await playExit();
    onExited?.();
  };

  useEffect(() => {
    if (type !== 'popover') return;
    if (!popoverRef.current) return;

    const el = popoverRef.current;
    let cancelled = false;

    const reference: VirtualElementLike = {
      getBoundingClientRect: () => {
        if (anchorBox) {
          return new DOMRect(anchorBox.left, anchorBox.top, anchorBox.width, anchorBox.height);
        }
        // Fallback: center-ish reference
        return new DOMRect(window.innerWidth / 2, window.innerHeight / 2, 1, 1);
      },
    };

    const update = () => {
      if (popoverAnchorMode === 'replace-anchor' && anchorBox) {
        const panelRect = el.getBoundingClientRect();
        const padding = 8;
        const gap = 14;

        const viewportWidth = window.innerWidth || document.documentElement.clientWidth || 0;
        const viewportHeight = window.innerHeight || document.documentElement.clientHeight || 0;

        const maxX = Math.max(padding, viewportWidth - panelRect.width - padding);
        const maxY = Math.max(padding, viewportHeight - panelRect.height - padding);

        const clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max);
        const centeredX = anchorBox.left + (anchorBox.width - panelRect.width) / 2;
        const centeredY = anchorBox.top + (anchorBox.height - panelRect.height) / 2;

        const spaceBelow = viewportHeight - (anchorBox.top + anchorBox.height) - padding;
        const spaceAbove = anchorBox.top - padding;
        const spaceRight = viewportWidth - (anchorBox.left + anchorBox.width) - padding;
        const spaceLeft = anchorBox.left - padding;

        const candidates = [
          {
            name: 'bottom',
            fits: spaceBelow >= panelRect.height + gap,
            score: spaceBelow,
            x: clamp(centeredX, padding, maxX),
            y: anchorBox.top + anchorBox.height + gap,
          },
          {
            name: 'top',
            fits: spaceAbove >= panelRect.height + gap,
            score: spaceAbove,
            x: clamp(centeredX, padding, maxX),
            y: anchorBox.top - panelRect.height - gap,
          },
          {
            name: 'right',
            fits: spaceRight >= panelRect.width + gap,
            score: spaceRight,
            x: anchorBox.left + anchorBox.width + gap,
            y: clamp(centeredY, padding, maxY),
          },
          {
            name: 'left',
            fits: spaceLeft >= panelRect.width + gap,
            score: spaceLeft,
            x: anchorBox.left - panelRect.width - gap,
            y: clamp(centeredY, padding, maxY),
          },
        ];

        const preferred =
          candidates.find((candidate) => candidate.fits) ??
          candidates.slice().sort((a, b) => b.score - a.score)[0];

        const x = clamp(preferred.x, padding, maxX);
        const y = clamp(preferred.y, padding, maxY);

        el.style.left = `${x}px`;
        el.style.top = `${y}px`;
        el.setAttribute('data-asuka-popover-placement', preferred.name);
        return;
      }

      computeAnchoredPosition(reference, el, {
        placement: 'bottom-start',
        offset: 10,
        flip: true,
        shift: true,
        padding: 8,
      })
        .then(({ x, y }) => {
          if (cancelled) return;
          el.style.left = `${x}px`;
          el.style.top = `${y}px`;
        })
        .catch(() => {
          // soft fail
        });
    };

    update();
    const cleanupAutoUpdate = autoUpdateAnchoredPosition(update);
    return () => {
      cancelled = true;
      cleanupAutoUpdate();
    };
  }, [type, anchorBox, popoverAnchorMode]);

  useEffect(() => {
    if (type === 'inline' || !isVisible || isExiting || !canClose || allowBackgroundInteraction) return;
    const activeElement = document.activeElement;
    if (activeElement instanceof HTMLElement && !rootRef.current?.contains(activeElement)) {
      restoreFocusRef.current = activeElement;
    }

    const focusableSelector = [
      'button:not([disabled])',
      '[href]',
      'input:not([disabled])',
      'select:not([disabled])',
      'textarea:not([disabled])',
      '[tabindex]:not([tabindex="-1"])',
    ].join(',');
    const focusFirstControl = () => {
      const target = panelRef.current?.querySelector<HTMLElement>(focusableSelector) ?? panelRef.current;
      target?.focus({ preventScroll: true });
    };
    const frame = requestAnimationFrame(focusFirstControl);

    const onKeyDown = (event: KeyboardEvent) => {
      if (event.key === 'Escape') {
        event.preventDefault();
        event.stopPropagation();
        requestCloseRef.current();
        return;
      }
      if (event.key !== 'Tab') return;
      const panel = panelRef.current;
      if (!panel) return;
      const controls = Array.from(panel.querySelectorAll<HTMLElement>(focusableSelector))
        .filter((element) => !element.hidden && element.getClientRects().length > 0);
      if (controls.length === 0) {
        event.preventDefault();
        panel.focus({ preventScroll: true });
        return;
      }
      const first = controls[0];
      const last = controls[controls.length - 1];
      if (event.shiftKey && (document.activeElement === first || !panel.contains(document.activeElement))) {
        event.preventDefault();
        last.focus({ preventScroll: true });
      } else if (!event.shiftKey && (document.activeElement === last || !panel.contains(document.activeElement))) {
        event.preventDefault();
        first.focus({ preventScroll: true });
      }
    };
    document.addEventListener('keydown', onKeyDown, true);
    return () => {
      cancelAnimationFrame(frame);
      document.removeEventListener('keydown', onKeyDown, true);
      const target = restoreFocusRef.current;
      restoreFocusRef.current = null;
      if (target?.isConnected) queueMicrotask(() => target.focus({ preventScroll: true }));
    };
  }, [type, isVisible, isExiting, canClose, allowBackgroundInteraction]);

  useEffect(() => {
    // Entrance animation on mount *and* when a kept-mounted overlay becomes visible again.
    if (type === 'inline') return;
    if (!isVisible) return;
    resetAfterHidden();
    playEnter();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isVisible, type]);

  useEffect(() => {
    // Crossfade exit: when a module is no longer active but needs to animate out.
    if (type === 'inline') return;
    if (!isExiting) return;
    // Avoid running exit if the overlay isn't even visible.
    if (!isVisible) return;
    requestExitForCrossfade();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isExiting]);

  // Inline is not handled here.
  if (type === 'inline') return null;

  // During crossfade exits, the previous overlay must never block pointer events.
  // (Otherwise it can sit above the new overlay and prevent clicks.)
  const exitPointerStyle = isExiting ? ({ pointerEvents: 'none' } as const) : undefined;

  // Hidden keepMounted overlays may be rendered through a portal, so hiding the
  // wrapper in ModuleHost is not enough: the portaled backdrop would otherwise
  // stay in the target container and keep intercepting clicks. Keep the component
  // mounted for state preservation, but make the actual overlay root inert.
  const hiddenPointerStyle = !isVisible && !isExiting
    ? ({ display: 'none', pointerEvents: 'none' } as const)
    : undefined;

  // Z-index hard fallback.
  // In some host integrations, Tailwind (or the `ask-` prefix) can be misconfigured,
  // which may cause z-index utility classes like `ask-z-40` to not apply. That makes
  // overlays appear "invisible" because they render behind the canvas.
  // This inline zIndex ensures overlays remain visible regardless of CSS compilation.
  const zIndexFallback = (() => {
    if (type === 'modal' || type === 'drawer') return 9999;
    if (type === 'floating' || type === 'popover') return 9000;
    if (type === 'panel-left' || type === 'panel-right' || type === 'panel-bottom' || type === 'panel-top') return 8000;
    return 9000;
  })();

  const overlayRootStyle = {
    ...(exitPointerStyle as any),
    ...(hiddenPointerStyle as any),
    zIndex: zIndexFallback,
  } as const;

  const rootClass = (base: string) =>
    `${base} ${isContainedPortal ? 'ask-overlay--contained' : ''}`.trim();

  const header = (
    <OverlayHeader
      title={title}
      icon={icon}
      canBack={Boolean(canBack)}
      onBack={canBack ? onBack : undefined}
      onClose={onClose ? requestClose : undefined}
      compact={compactHeader}
    />
  );

  if (type === 'modal') {
    return withPortal(
      <div
        ref={rootRef}
        class={rootClass(`ask-overlay ask-overlay--modal ${zClass ?? 'ask-z-40'}`)}
        {...(attrs ?? {})}
        role="dialog"
        aria-modal={allowBackgroundInteraction ? 'false' : 'true'}
        aria-label={title}
        style={overlayRootStyle as any}
      >
        <div
          ref={backdropRef}
          class="ask-overlay__backdrop ask-overlay__backdrop--modal"
          onClick={onClose ? requestClose : undefined}
          aria-hidden="true"
          style={allowBackgroundInteraction ? { pointerEvents: 'none' } : undefined}
        />
        <div ref={panelRef} tabIndex={-1} class={`ask-overlay__panel ask-overlay__panel--modal ${fullscreen ? "ask-overlay__panel--fullscreen" : ""}`}>
          {header}
          {fullscreen ? (
            <div class="ask-overlay__content ask-overlay__content--fullscreen ask-flex-1 ask-min-h-0">{children}</div>
          ) : (
            <ScrollArea className="ask-overlay__content ask-overlay__content--modal-scroll" viewportClassName="ask-h-full">{children}</ScrollArea>
          )}
        </div>
      </div>
    );
  }

  if (type === 'drawer') {
    return withPortal(
      <div
        ref={rootRef}
        class={rootClass(`ask-overlay ask-overlay--drawer ask-overlay--drawer-right ${zClass ?? 'ask-z-40'}`)}
        {...(attrs ?? {})}
        role="dialog"
        aria-modal={allowBackgroundInteraction ? 'false' : 'true'}
        aria-label={title}
        style={overlayRootStyle as any}
      >
        <div
          ref={backdropRef}
          class="ask-overlay__backdrop ask-overlay__backdrop--drawer"
          onClick={onClose ? requestClose : undefined}
          aria-hidden="true"
          style={allowBackgroundInteraction ? { pointerEvents: 'none' } : undefined}
        />
        <div ref={panelRef} tabIndex={-1} class="ask-overlay__panel ask-overlay__panel--drawer-right">
          {header}
          <ScrollArea className="ask-overlay__content ask-flex-1 ask-min-h-0" viewportClassName="ask-h-full">{children}</ScrollArea>
        </div>
      </div>
    );
  }

  if (type === 'panel-left') {
    return withPortal(
      <div
        ref={rootRef}
        class={rootClass(`ask-overlay ask-overlay--panel ask-overlay--panel-left ${zClass ?? 'ask-z-20'}`)}
        {...(attrs ?? {})}
        style={overlayRootStyle as any}
      >
        <div ref={panelRef} class="ask-overlay__panel ask-overlay__panel--panel-left">
          {header}
          <ScrollArea className="ask-overlay__content ask-flex-1 ask-min-h-0" viewportClassName="ask-h-full">{children}</ScrollArea>
        </div>
      </div>
    );
  }

  if (type === 'panel-right') {
    return withPortal(
      <div
        ref={rootRef}
        class={rootClass(`ask-overlay ask-overlay--panel ask-overlay--panel-right ${zClass ?? 'ask-z-20'}`)}
        {...(attrs ?? {})}
        style={overlayRootStyle as any}
      >
        <div ref={panelRef} class="ask-overlay__panel ask-overlay__panel--panel-right">
          {header}
          <ScrollArea className="ask-overlay__content ask-flex-1 ask-min-h-0" viewportClassName="ask-h-full">{children}</ScrollArea>
        </div>
      </div>
    );
  }

  if (type === 'panel-bottom') {
    return withPortal(
      <div
        ref={rootRef}
        class={rootClass(`ask-overlay ask-overlay--panel ask-overlay--panel-bottom ${zClass ?? 'ask-z-20'}`)}
        {...(attrs ?? {})}
        style={overlayRootStyle as any}
      >
        <div ref={panelRef} class="ask-overlay__panel ask-overlay__panel--panel-bottom">
          {header}
          <ScrollArea className="ask-overlay__content ask-flex-1 ask-min-h-0" viewportClassName="ask-h-full">{children}</ScrollArea>
        </div>
      </div>
    );
  }


  if (type === 'panel-top') {
    return withPortal(
      <div
        ref={rootRef}
        class={rootClass(`ask-overlay ask-overlay--panel ask-overlay--panel-top ${zClass ?? 'ask-z-20'}`)}
        {...(attrs ?? {})}
        style={overlayRootStyle as any}
      >
        <div ref={panelRef} class="ask-overlay__panel ask-overlay__panel--panel-top">
          {header}
          <ScrollArea className="ask-overlay__content ask-flex-1 ask-min-h-0" viewportClassName="ask-h-full">{children}</ScrollArea>
        </div>
      </div>
    );
  }

  if (type === 'popover') {
    return withPortal(
      <div
        ref={popoverRef}
        class={rootClass(`ask-overlay ask-overlay--popover ${zClass ?? 'ask-z-30'} ask-fixed`)}
        {...(attrs ?? {})}
        role="dialog"
        aria-modal="false"
        aria-label={title}
        style={{ left: '0px', top: '0px', ...(overlayRootStyle as any) }}
      >
        <div ref={panelRef} tabIndex={-1} class="ask-overlay__panel ask-overlay__panel--popover">
          {header}
          <ScrollArea className="ask-overlay__content ask-flex-1 ask-min-h-0" viewportClassName="ask-h-full">{children}</ScrollArea>
        </div>
      </div>
    );
  }

  // Default: floating
  return withPortal(
    <div
      ref={rootRef}
      class={rootClass(`ask-overlay ask-overlay--floating ${zClass ?? 'ask-z-30'}`)}
      {...(attrs ?? {})}
      role="dialog"
      aria-modal="false"
      aria-label={title}
      style={overlayRootStyle as any}
    >
      <div ref={panelRef} tabIndex={-1} class="ask-overlay__panel ask-overlay__panel--floating">
        {header}
        <ScrollArea className="ask-overlay__content ask-flex-1 ask-min-h-0" viewportClassName="ask-h-full">{children}</ScrollArea>
      </div>
    </div>
  );
};
