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

export class PortalManager {
  private portalContainer: HTMLElement | null = null;
  private originalListboxParent: HTMLElement | null = null;
  private measuredPopupHeight: number | null = null;
  private component: any;
  private clonedListbox: HTMLElement | null = null;
  private cleanupAutoUpdate: (() => void) | null = null;
  private currentPlacement: Placement = 'bottom';
  private currentMiddlewareData: MiddlewareData | 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.className = 'nile-select-portal-append';
    return container;
  }

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

    this.measurePopupHeight();
    this.computeFloatingUIPosition();
  }

  private measurePopupHeight(): void {
    if (this.measuredPopupHeight || !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.measuredPopupHeight = this.portalContainer.offsetHeight;
    
    this.portalContainer.style.visibility = '';
  }

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

    const referenceElement = this.component.combobox || 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 = PortalUtils.getOptimalPlacement(referenceElement);
    const middleware = this.createFloatingUIMiddleware(boundary);

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

  private createFloatingUIMiddleware(boundary: Element[] | undefined): ComputePositionConfig['middleware'] {
    return [
      offset(4),
      size({
        apply: this.handleSizeMiddleware.bind(this),
        padding: 10,
        boundary: boundary
      }),
      flip({
        fallbackPlacements: ['bottom', 'top', 'bottom-start', 'top-start', 'bottom-end', 'top-end'],
        fallbackStrategy: 'bestFit',
        padding: 10,
        boundary: boundary
      }),
      shift({
        padding: 10,
        crossAxis: true,
        boundary: boundary
      })
    ];
  }

  private handleSizeMiddleware({ availableWidth, availableHeight, elements, rects }: {
    availableWidth: number;
    availableHeight: number;
    elements: { floating: HTMLElement };
    rects: { reference: { x: number; y: number; width: number; height: number } };
  }): void {
    const maxHeight = PortalUtils.calculateOptimalHeight(
      rects.reference,
      window.innerHeight,
      this.currentPlacement
    );

    elements.floating.style.maxWidth = `${availableWidth}px`;
    elements.floating.style.maxHeight = `${maxHeight}px`;
    
    elements.floating.style.setProperty('--auto-size-available-width', `${availableWidth}px`);
    elements.floating.style.setProperty('--auto-size-available-height', `${maxHeight}px`);
  }

  private createCustomPlatform() {
    return platform;
  }

  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'
    });

    this.currentPlacement = placement;
    this.currentMiddlewareData = middlewareData;
    
    PortalUtils.applyCollisionData(floatingElement, middlewareData, placement);
    
    const placementClass = placement.split('-')[0];
    floatingElement.className = `nile-select-portal-append select__listbox--${placementClass}`;

    const referenceRect = (referenceElement as HTMLElement).getBoundingClientRect();
    floatingElement.style.width = `${referenceRect.width}px`;
  }

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

    const referenceElement = this.component.combobox || this.component;
    const rect = referenceElement.getBoundingClientRect();
    const viewportHeight = window.innerHeight;
    const popupHeight = this.measuredPopupHeight || 200;
    
    const spaceBelow = viewportHeight - rect.bottom;
    const spaceAbove = rect.top;
    
    let topPosition: number;
    let placementClass: string;
    let maxHeight: number;
    
    if (spaceAbove > spaceBelow) {
      maxHeight = Math.max(spaceAbove - 20, 100);
      topPosition = Math.max(rect.top - maxHeight - 4, 10);
      placementClass = 'top';
    } else {
      maxHeight = Math.max(spaceBelow - 20, 100);
      topPosition = rect.bottom + 4;
      placementClass = 'bottom';
    }

    this.portalContainer.style.left = `${rect.left}px`;
    this.portalContainer.style.top = `${topPosition}px`;
    this.portalContainer.style.width = `${rect.width}px`;
    this.portalContainer.style.maxHeight = `${maxHeight}px`;
    this.portalContainer.style.pointerEvents = 'auto';
    this.portalContainer.className = `nile-select-portal-append select__listbox--${placementClass}`;
    
    this.calculateAndSetAutoSizeProperties(rect, topPosition, placementClass);
  }

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

    const viewportHeight = window.innerHeight;
    const viewportWidth = window.innerWidth;
    
    let availableHeight: number;
    if (placementClass === 'top') {
      availableHeight = rect.top - 10;
    } else {
      availableHeight = viewportHeight - rect.bottom - 10;
    }
    
    const availableWidth = Math.min(rect.width, viewportWidth - rect.left - 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(() => {
        const listbox = this.component.shadowRoot?.querySelector('#listbox') as HTMLElement;
        if (listbox) {
          this.originalListboxParent = listbox.parentElement as HTMLElement;
          
          this.clonedListbox = this.createPortalListbox();
          
          this.portalContainer = this.createPortalAppendContainer();
          this.portalContainer.appendChild(this.clonedListbox);
          document.body.appendChild(this.portalContainer);
          
          this.adoptStylesToPortalAppend();
          
          this.clonedListbox.style.display = '';
          this.positionPortalAppend();
          
          this.setupPortalEventListeners();
          
          this.updatePortalContent();
          
          setTimeout(() => {
            this.updateFooterState();
          }, 50);
          
          this.setupAutoUpdatePositioning();
          
          window.addEventListener('resize', this.handleWindowResize.bind(this));
          
        }
      }, 10);
    });
  }

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

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

  private updatePortalContent(): void {
    if (!this.clonedListbox) return;

    PortalContentUtils.updateClonedOptions(this.clonedListbox, this.component);
    
    this.updateFooterState();
  }

  private updateFooterState(): void {
    const footer = this.clonedListbox?.querySelector('.select__footer');
    if (!footer || !this.component.multiple) return;

    const checkbox = footer.querySelector('nile-checkbox') as any;
    if (checkbox) {
      checkbox.disabled = this.component.selectedOptions.length === 0;
      checkbox.checked = this.component.showSelected;
    }
    
    const clearAllSpan = footer.querySelector('.select__clear') as HTMLElement;
    if (this.component.selectedOptions.length === 0) {
      if (clearAllSpan) {
        clearAllSpan.remove();
      }
    } else {
      if (!clearAllSpan) {
        const newClearAllSpan = document.createElement('span');
        newClearAllSpan.className = 'select__clear';
        newClearAllSpan.textContent = 'Clear All';
        newClearAllSpan.style.cursor = 'pointer';
        footer.appendChild(newClearAllSpan);
      }
    }
  }

  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);
    }
    
    window.removeEventListener('resize', this.handleWindowResize.bind(this));
    
    this.portalContainer = null;
    this.originalListboxParent = null;
    this.clonedListbox = null;
    this.measuredPopupHeight = null;
    this.currentPlacement = 'bottom';
    this.currentMiddlewareData = null;
  }

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

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

  updatePortalOptions(): void {
    if (this.portalContainer && this.clonedListbox) {
      this.updatePortalContent();
      this.forceReposition();
    }
  }

  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.combobox || 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;
  }
}
