import { 
  autoUpdate, 
  computePosition, 
  flip, 
  offset, 
  shift, 
  platform,
  type Placement,
  type MiddlewareData,
  type ComputePositionConfig
} from '@floating-ui/dom';
import { PortalUtils, PortalContentUtils, PortalEventUtils } from './portal-utils';

export class PopoverPortalManager {
  private portalContainer: HTMLElement | null = null;
  private measuredPopoverHeight: number | null = null;
  private component: any;
  private clonedPopover: HTMLElement | null = null;
  private cleanupAutoUpdate: (() => void) | null = null;
  private currentPlacement: Placement = 'top';
  private currentMiddlewareData: MiddlewareData | null = null;
  private boundHandleWindowResize: (() => void) | null = null;

  constructor(component: any) {
    this.component = component;
  }

  private createPortalAppendContainer(): HTMLElement {
    const container = document.createElement('div');
    container.style.position = 'absolute';
    container.style.zIndex = '9999';
    container.style.pointerEvents = 'none';
    container.style.width = 'auto';
    container.style.maxWidth = 'none';
    container.style.minWidth = '200px';
    container.className = 'nile-popover-portal-append';
    return container;
  }

  positionPortalAppend(): void {
    if (!this.portalContainer || !this.component.isShow) return;

    this.measurePopoverHeight();
    this.computeFloatingUIPosition();
  }

  private measurePopoverHeight(): void {
    if (this.measuredPopoverHeight || !this.portalContainer) return;

    this.portalContainer.style.position = 'absolute';
    this.portalContainer.style.visibility = 'hidden';
    this.portalContainer.style.top = '0px';
    this.portalContainer.style.left = '0px';
    
    this.portalContainer.offsetHeight;
    
    this.measuredPopoverHeight = this.portalContainer.offsetHeight;
    
    this.portalContainer.style.visibility = '';
  }

  private async computeFloatingUIPosition(): Promise<void> {
    if (!this.portalContainer) return;

    const referenceElement = this.component.querySelector('[slot="anchor"]') || this.component;
    const floatingElement = this.portalContainer;

    try {
      const { x, y, placement, middlewareData } = await this.calculateFloatingUIPosition(
        referenceElement,
        floatingElement
      );

      this.applyFloatingUIPosition(floatingElement, referenceElement, x, y, placement, middlewareData);

    } catch (error) {
      console.warn('Floating UI positioning failed, falling back to simple positioning:', error);
      this.fallbackPositioning();
    }
  }

  private async calculateFloatingUIPosition(
    referenceElement: HTMLElement,
    floatingElement: HTMLElement
  ): Promise<{ x: number; y: number; placement: Placement; middlewareData: MiddlewareData }> {
    const boundary = PortalUtils.findBoundaryElements(referenceElement);
    const initialPlacement = this.getInitialPlacement();
    const middleware = this.createFloatingUIMiddleware(boundary);

    return await computePosition(referenceElement, floatingElement, {
      placement: initialPlacement,
      strategy: 'fixed',
      middleware,
      platform: this.createCustomPlatform()
    });
  }

  private getInitialPlacement(): Placement {
    return this.component.placement || 'top';
  }

  private createFloatingUIMiddleware(boundary: Element[] | undefined): ComputePositionConfig['middleware'] {
    return [
      offset(this.component.distance || 18),
      flip({
        fallbackPlacements: this.getFallbackPlacements(),
        fallbackStrategy: 'bestFit',
        padding: 10,
        boundary: boundary
      }),
      shift({
        padding: 10,
        crossAxis: true,
        boundary: boundary
      })
    ];
  }

  private getFallbackPlacements(): Placement[] {
    const basePlacement = this.component.placement || 'top';
    const placements: Placement[] = [];
    
    if (basePlacement.startsWith('top')) {
      placements.push('bottom', 'right', 'left', 'top-start', 'top-end', 'bottom-start', 'bottom-end');
    } else if (basePlacement.startsWith('bottom')) {
      placements.push('top', 'right', 'left', 'bottom-start', 'bottom-end', 'top-start', 'top-end');
    } else if (basePlacement.startsWith('left')) {
      placements.push('right', 'top', 'bottom', 'left-start', 'left-end', 'right-start', 'right-end');
    } else if (basePlacement.startsWith('right')) {
      placements.push('left', 'top', 'bottom', 'right-start', 'right-end', 'left-start', 'left-end');
    }
    
    return placements;
  }

  private createCustomPlatform() {
    return platform;
  }

  private positionArrow(floatingElement: HTMLElement, referenceElement: HTMLElement, placement: Placement): void {
    if (!this.component.arrow) return;
    
    const arrowElement = floatingElement.querySelector('.popup__arrow') as HTMLElement;
    if (!arrowElement) return;
    
    const referenceRect = referenceElement.getBoundingClientRect();
    const floatingRect = floatingElement.getBoundingClientRect();
    
    const arrowSize = 18;
    
    let top = '';
    let right = '';
    let bottom = '';
    let left = '';
    
    const basePlacement = placement.split('-')[0];
    
    switch (basePlacement) {
      case 'top':
        bottom = `calc(var(--arrow-size-diagonal) * -1)`;
        left = this.calculateArrowXPosition(referenceRect, floatingRect, placement, arrowSize);
        break;
      case 'bottom':
        top = `calc(var(--arrow-size-diagonal) * -1)`;
        left = this.calculateArrowXPosition(referenceRect, floatingRect, placement, arrowSize);
        break;
      case 'left':
        right = `calc(var(--arrow-size-diagonal) * -1)`;
        top = this.calculateArrowYPosition(referenceRect, floatingRect, placement, arrowSize);
        break;
      case 'right':
        left = `calc(var(--arrow-size-diagonal) * -1)`;
        top = this.calculateArrowYPosition(referenceRect, floatingRect, placement, arrowSize);
        break;
    }
    
    Object.assign(arrowElement.style, {
      top,
      right,
      bottom,
      left
    });
  }

  private calculateArrowXPosition(referenceRect: DOMRect, floatingRect: DOMRect, placement: Placement, arrowSize: number): string {
    const referenceCenter = referenceRect.left + referenceRect.width / 2;
    const floatingLeft = floatingRect.left;
    const arrowOffset = referenceCenter - floatingLeft - arrowSize;
    
    if (placement.includes('start')) {
      return `${arrowSize}px`;
    } else if (placement.includes('end')) {
      return `calc(100% - ${arrowSize * 2}px)`;
    } else {
      return `${Math.max(arrowSize, Math.min(arrowOffset, floatingRect.width - arrowSize * 2))}px`;
    }
  }

  private calculateArrowYPosition(referenceRect: DOMRect, floatingRect: DOMRect, placement: Placement, arrowSize: number): string {
    const referenceCenter = referenceRect.top + referenceRect.height / 2;
    const floatingTop = floatingRect.top;
    const arrowOffset = referenceCenter - floatingTop - arrowSize;
    
    if (placement.includes('start')) {
      return `${arrowSize}px`;
    } else if (placement.includes('end')) {
      return `calc(100% - ${arrowSize * 2}px)`;
    } else {
      return `${Math.max(arrowSize, Math.min(arrowOffset, floatingRect.height - arrowSize * 2))}px`;
    }
  }

  private applyFloatingUIPosition(
    floatingElement: HTMLElement,
    referenceElement: HTMLElement,
    x: number,
    y: number,
    placement: Placement,
    middlewareData: MiddlewareData
  ): void {
    Object.assign(floatingElement.style, {
      left: `${x}px`,
      top: `${y}px`,
      position: 'fixed',
      pointerEvents: 'auto',
      width: 'auto',
      maxWidth: 'none',
      minWidth: '200px'
    });

    this.currentPlacement = placement;
    this.currentMiddlewareData = middlewareData;
    
    PortalUtils.applyCollisionData(floatingElement, middlewareData, placement);
    
    floatingElement.className = `nile-popover-portal-append`;

    const referenceRect = (referenceElement as HTMLElement).getBoundingClientRect();
    floatingElement.style.minWidth = `${Math.max(referenceRect.width, 200)}px`;
    
    this.positionArrow(floatingElement, referenceElement, placement);
  }

  private fallbackPositioning(): void {
    if (!this.portalContainer) return;

    const referenceElement = this.component.querySelector('[slot="anchor"]') || this.component;
    const rect = referenceElement.getBoundingClientRect();
    const viewportHeight = window.innerHeight;
    const viewportWidth = window.innerWidth;
    
    const spaceBelow = viewportHeight - rect.bottom;
    const spaceAbove = rect.top;
    const spaceRight = viewportWidth - rect.right;
    const spaceLeft = rect.left;
    
    let topPosition: number;
    let leftPosition: number = rect.left;
    let placementClass: string;
    let maxHeight: number;
    
    if (spaceBelow > spaceAbove && spaceBelow > 200) {
      maxHeight = Math.max(spaceBelow - 20, 100);
      topPosition = rect.bottom + (this.component.distance || 18);
      placementClass = 'bottom';
    } else if (spaceAbove > spaceBelow && spaceAbove > 200) {
      maxHeight = Math.max(spaceAbove - 20, 100);
      topPosition = Math.max(rect.top - maxHeight - (this.component.distance || 18), 10);
      placementClass = 'top';
    } else if (spaceRight > spaceLeft && spaceRight > 200) {
      maxHeight = Math.max(Math.min(spaceRight, spaceBelow, spaceAbove) - 20, 100);
      topPosition = rect.top;
      leftPosition = rect.right + (this.component.distance || 18);
      placementClass = 'right';
    } else {
      maxHeight = Math.max(Math.min(spaceLeft, spaceBelow, spaceAbove) - 20, 100);
      topPosition = rect.top;
      leftPosition = Math.max(rect.left - 200 - (this.component.distance || 18), 10);
      placementClass = 'left';
    }

    if (placementClass === 'left' || placementClass === 'right') {
      this.portalContainer.style.left = `${leftPosition}px`;
      this.portalContainer.style.top = `${topPosition}px`;
    } else {
      this.portalContainer.style.left = `${rect.left}px`;
      this.portalContainer.style.top = `${topPosition}px`;
    }
    
    this.portalContainer.style.maxHeight = `${maxHeight}px`;
    this.portalContainer.style.pointerEvents = 'auto';
    this.portalContainer.style.width = 'auto';
    this.portalContainer.style.maxWidth = 'none';
    this.portalContainer.style.minWidth = '200px';
    this.portalContainer.className = `nile-popover-portal-append popover__box popover__box--${placementClass}`;
    
    this.calculateAndSetAutoSizeProperties(rect, topPosition, placementClass);
    
    this.positionArrow(this.portalContainer, referenceElement, placementClass as Placement);
  }

  private calculateAndSetAutoSizeProperties(rect: DOMRect, topPosition: number, placementClass: string): void {
    if (!this.portalContainer) return;

    const viewportHeight = window.innerHeight;
    const viewportWidth = window.innerWidth;
    
    let availableHeight: number;
    let availableWidth: number = viewportWidth - rect.left - 10;
    
    if (placementClass === 'top') {
      availableHeight = rect.top - 10;
    } else if (placementClass === 'bottom') {
      availableHeight = viewportHeight - rect.bottom - 10;
    } else if (placementClass === 'left') {
      availableHeight = Math.min(viewportHeight - rect.top, rect.bottom);
      availableWidth = rect.left - 10;
    } else {
      availableHeight = Math.min(viewportHeight - rect.top, rect.bottom);
      availableWidth = viewportWidth - rect.right - 10;
    }
    
    this.portalContainer.style.setProperty('--auto-size-available-height', `${Math.max(availableHeight, 100)}px`);
    this.portalContainer.style.setProperty('--auto-size-available-width', `${Math.max(availableWidth, 200)}px`);
  }

  updatePortalAppendPosition(): void {
    if (this.component.portal && this.portalContainer) {
      this.positionPortalAppend();
    }
  }

  handleWindowResize(): void {
    if (this.component.portal && this.portalContainer) {
      this.positionPortalAppend();
    }
  }

  private setupAutoUpdatePositioning(): void {
    if (!this.portalContainer || !this.component) return;

    this.cleanupAutoUpdatePositioning();

    this.cleanupAutoUpdate = autoUpdate(
      this.component,
      this.portalContainer,
      () => {
        this.computeFloatingUIPosition();
      },
      {
        ancestorScroll: true,
        ancestorResize: true,
        elementResize: true,
        layoutShift: true,
        animationFrame: true
      }
    );
  }

  private cleanupAutoUpdatePositioning(): void {
    if (this.cleanupAutoUpdate) {
      this.cleanupAutoUpdate();
      this.cleanupAutoUpdate = null;
    }
  }

  private injectStylesToDocument(): void {
    if (!this.portalContainer) return;

    const styleId = PortalUtils.generateStyleId();
    
    if (document.getElementById(styleId)) return;

    const componentStyles = (this.component.constructor as any).styles;
    if (!componentStyles) return;

    const styleElement = document.createElement('style');
    styleElement.id = styleId;
    styleElement.textContent = PortalUtils.extractStylesAsCSS(componentStyles);
    
    document.head.appendChild(styleElement);
    
    (this.portalContainer as any).__injectedStyleId = styleId;
  }

  private adoptStylesToPortalAppend(): void {
    if (!this.portalContainer) return;
    this.injectStylesToDocument();
  }

  setupPortalAppend(): void {
    if (!this.component.portal) return;

    this.component.updateComplete.then(() => {
      setTimeout(() => {
        if (this.component.isShow) {
          this.clonedPopover = this.createPortalPopover();
          
          this.portalContainer = this.createPortalAppendContainer();
          this.portalContainer.appendChild(this.clonedPopover);
          document.body.appendChild(this.portalContainer);
          
          this.adoptStylesToPortalAppend();
          
          this.clonedPopover.style.display = '';
          this.positionPortalAppend();
          
          this.setupPortalEventListeners();
          
          this.setupAutoUpdatePositioning();
          
          this.boundHandleWindowResize = this.handleWindowResize.bind(this);
          window.addEventListener('resize', this.boundHandleWindowResize);
          
        }
      }, 10);
    });
  }

  private createPortalPopover(): HTMLElement {
    return PortalContentUtils.createPortalPopover(this.component);
  }

  private setupPortalEventListeners(): void {
    PortalEventUtils.setupPortalEventListeners(this.clonedPopover!, this.component);
  }

  cleanupPortalAppend(): void {
    this.cleanupAutoUpdatePositioning();
    
    if (this.portalContainer && this.portalContainer.parentNode) {
      const injectedStyleId = (this.portalContainer as any).__injectedStyleId;
      if (injectedStyleId) {
        const styleElement = document.getElementById(injectedStyleId);
        if (styleElement) {
          styleElement.remove();
        }
      }
      
      this.portalContainer.parentNode.removeChild(this.portalContainer);
    }
    
    if (this.boundHandleWindowResize) {
      window.removeEventListener('resize', this.boundHandleWindowResize);
      this.boundHandleWindowResize = null;
    }
    
    this.portalContainer = null;
    this.clonedPopover = null;
    this.measuredPopoverHeight = null;
    this.currentPlacement = 'top';
    this.currentMiddlewareData = null;
  }

  get portalContainerElement(): HTMLElement | null {
    return this.portalContainer;
  }

  resetMeasuredHeight(): void {
    this.measuredPopoverHeight = null;
  }

  forceReposition(): void {
    if (this.portalContainer) {
      this.computeFloatingUIPosition();
    }
  }

  getCurrentPlacement(): Placement {
    return this.currentPlacement;
  }

  getCurrentMiddlewareData(): MiddlewareData | null {
    return this.currentMiddlewareData;
  }

  isUsingFloatingUI(): boolean {
    return this.cleanupAutoUpdate !== null;
  }

  updatePositioningConfig(config: {
    offset?: number;
    padding?: number;
    boundary?: Element[] | 'viewport';
    fallbackPlacements?: Placement[];
  }): void {
    this.forceReposition();
  }

  handleViewportChange(): void {
    if (this.portalContainer) {
      this.resetMeasuredHeight();
      this.forceReposition();
    }
  }

  isPositioningOptimal(): boolean {
    if (!this.portalContainer || !this.currentMiddlewareData) return true;

    const referenceElement = this.component.querySelector('[slot="anchor"]') || this.component;
    const rect = referenceElement.getBoundingClientRect();
    const viewportHeight = window.innerHeight;
    const spaceBelow = viewportHeight - rect.bottom;
    const spaceAbove = rect.top;
    
    const isAbove = this.currentPlacement.startsWith('top');
    const isBelow = this.currentPlacement.startsWith('bottom');
    
    if (isAbove && spaceBelow > spaceAbove) return false;
    if (isBelow && spaceAbove > spaceBelow) return false;
    
    return true;
  }
}
