import { 
  type Placement,
  type MiddlewareData,
  type ComputePositionConfig,
  type Boundary
} from '@floating-ui/dom';

export class PortalUtils {
  static calculateAvailableSpace(referenceElement: HTMLElement): {
    spaceAbove: number;
    spaceBelow: number;
    viewportHeight: number;
  } {
    const rect = referenceElement.getBoundingClientRect();
    const viewportHeight = window.innerHeight;
    const spaceBelow = viewportHeight - rect.bottom;
    const spaceAbove = rect.top;
    
    return { spaceAbove, spaceBelow, viewportHeight };
  }

  static getOptimalPlacement(referenceElement: HTMLElement): Placement {
    const { spaceAbove, spaceBelow } = this.calculateAvailableSpace(referenceElement);
    
    if (spaceBelow < 200 && spaceAbove > spaceBelow) {
      return 'top';
    }
    
    return 'bottom';
  }

  static findBoundaryElements(component: HTMLElement): Element[] | undefined {
    const boundaryElements: Element[] = [];
    
    let currentElement = component.parentElement;
    
    while (currentElement && currentElement !== document.body) {
      const computedStyle = window.getComputedStyle(currentElement);
      const overflow = computedStyle.overflow;
      const overflowY = computedStyle.overflowY;
      const overflowX = computedStyle.overflowX;
      
      if (overflow === 'auto' || overflow === 'scroll' || 
          overflowY === 'auto' || overflowY === 'scroll' ||
          overflowX === 'auto' || overflowX === 'scroll') {
        boundaryElements.push(currentElement);
      }
      
      if (currentElement.hasAttribute('data-floating-boundary') ||
          currentElement.classList.contains('floating-boundary') ||
          currentElement.classList.contains('scroll-container')) {
        boundaryElements.push(currentElement);
      }
      
      currentElement = currentElement.parentElement;
    }
    
    return boundaryElements.length > 0 ? boundaryElements : undefined;
  }

  static calculateOptimalHeight(
    referenceRect: { x: number; y: number; width: number; height: number },
    viewportHeight: number,
    placement: Placement
  ): number {
    const spaceBelow = viewportHeight - (referenceRect.y + referenceRect.height);
    const spaceAbove = referenceRect.y;
    
    if (spaceAbove > spaceBelow) {
      return Math.max(spaceAbove - 20, 100);
    }
    else if (spaceBelow > spaceAbove) {
      return Math.max(spaceBelow - 20, 100);
    }
    
    return Math.max(Math.min(spaceAbove, spaceBelow) - 20, 100);
  }

  static extractStylesAsCSS(styles: any): string {
    if (typeof styles === 'string') {
      return styles;
    }
    
    if (Array.isArray(styles)) {
      return styles.map(style => this.extractStylesAsCSS(style)).join('\n');
    }
    
    if (styles && typeof styles === 'object' && styles.cssText) {
      return styles.cssText;
    }
    
    return '';
  }

  static generateStyleId(): string {
    return `nile-select-styles-${Math.random().toString(36).substring(2, 11)}`;
  }

  static isPositioningOptimal(
    placement: Placement,
    referenceElement: HTMLElement
  ): boolean {
    const { spaceAbove, spaceBelow } = this.calculateAvailableSpace(referenceElement);
    
    const isAbove = placement.startsWith('top');
    const isBelow = placement.startsWith('bottom');
    
    if (isAbove && spaceBelow > spaceAbove) return false;
    if (isBelow && spaceAbove > spaceBelow) return false;
    
    return true;
  }

  static applyCollisionData(
    element: HTMLElement,
    middlewareData: MiddlewareData,
    placement: Placement
  ): void {
    if (middlewareData.flip) {
      const { overflows } = middlewareData.flip;
      
      element.setAttribute('data-placement', placement);
      
      if (overflows && overflows.length > 0) {
        const overflowPlacements = overflows.map(overflow => overflow.placement).join(',');
        element.setAttribute('data-overflow', overflowPlacements);
      } else {
        element.removeAttribute('data-overflow');
      }
    }

    if (middlewareData.shift) {
      const { x, y } = middlewareData.shift;
      
      if (x !== undefined && y !== undefined && (x !== 0 || y !== 0)) {
        element.setAttribute('data-shift', `${x},${y}`);
      } else {
        element.removeAttribute('data-shift');
      }
    }

    if (middlewareData.size) {
      const { availableWidth, availableHeight } = middlewareData.size;
      
      if (availableWidth !== undefined) {
        element.setAttribute('data-available-width', availableWidth.toString());
      }
      if (availableHeight !== undefined) {
        element.setAttribute('data-available-height', availableHeight.toString());
      }
    }
  }

  static createFloatingUIMiddleware(
    boundary: Boundary,
    onSizeApply: (params: {
      availableWidth: number;
      availableHeight: number;
      elements: { floating: HTMLElement };
      rects: { reference: { x: number; y: number; width: number; height: number } };
    }) => void
  ): ComputePositionConfig['middleware'] {
    return [];
  }
}

export class PortalContentUtils {
  static createBaseListbox(component: any): HTMLElement {
    const listbox = document.createElement('div');
    listbox.className = `select__listbox ${component.searchEnabled ? 'select__search-enabled ' : ''}`;
    listbox.setAttribute('tabindex', '-1');
    
    return listbox;
  }

  static addSearchSection(listbox: HTMLElement, component: any): void {
    if (!component.searchEnabled) return;

    const searchDiv = document.createElement('div');
    searchDiv.className = 'select__search';
    searchDiv.innerHTML = `
      <nile-input size="small" clearable value="${component.searchValue}" placeholder="${component.internalSearchPlaceHolder}">
        <nile-icon name="var(--nile-icon-search, var(--ng-icon-search-lg))" method="var(--nile-svg-method-fill, var(--ng-svg-method-stroke))" slot="suffix"></nile-icon>
      </nile-input>
    `;
    listbox.appendChild(searchDiv);
  }

  static addLoadingSection(listbox: HTMLElement, component: any): void {
    if (!component.optionsLoading) return;

    const loaderSpan = document.createElement('span');
    loaderSpan.className = 'select__loader';
    loaderSpan.innerHTML = `
      <nile-icon class="select__loader--icon" name="var(--nile-icon-button-loading-blue, var(--ng-icon-button-loading-blue))" method="var(--nile-svg-method-fill, var(--ng-svg-method-stroke))"></nile-icon>
    `;
    listbox.appendChild(loaderSpan);
  }

  static addOptionsSection(listbox: HTMLElement, component: any): void {
    const optionsContainer = document.createElement('div');
    optionsContainer.className = `select__options ${component.searchEnabled ? 'select__options__search-enabled' : ''}`;
    
    this.addNoResultsMessage(optionsContainer, component);
    this.addClonedOptions(optionsContainer, component);
    
    listbox.appendChild(optionsContainer);
  }

  static addNoResultsMessage(container: HTMLElement, component: any): void {
    if (!component.showNoResults) return;

    const noResultsDiv = document.createElement('div');
    noResultsDiv.className = 'select__no-results';
    noResultsDiv.textContent = component.noResultsMessage;
    container.appendChild(noResultsDiv);
  }

  static addClonedOptions(container: HTMLElement, component: any): void {
    const options = Array.from(component.querySelectorAll('nile-option'));
    
    options.forEach((option: Element) => {
      const clonedOption = this.cloneOption(option as HTMLElement);
      container.appendChild(clonedOption);
    });
  }

  static cloneOption(option: HTMLElement): HTMLElement {
    const clonedOption = option.cloneNode(true) as HTMLElement;
    
    Array.from(option.attributes).forEach((attr: Attr) => {
      clonedOption.setAttribute(attr.name, attr.value);
    });
    
    this.copyOptionProperties(option, clonedOption);
    
    return clonedOption;
  }

  static copyOptionProperties(originalOption: HTMLElement, clonedOption: HTMLElement): void {
    const properties = ['selected', 'disabled', 'current', 'hidden'];
    
    properties.forEach(prop => {
      if ((originalOption as any)[prop] !== undefined) {
        (clonedOption as any)[prop] = (originalOption as any)[prop];
      }
    });
  }

  static addFooterSection(listbox: HTMLElement, component: any): void {
    const hasCustomFooter = component.querySelectorAll('[slot="pre-footer"]').length > 0;
    const hasBuiltInFooter = component.multiple;

    if (!hasCustomFooter && !hasBuiltInFooter) return;

    const footerArea = document.createElement('div');
    footerArea.setAttribute('part', 'footer-area');
    footerArea.className = 'select__footer-area';

    if (hasCustomFooter) {
      this.addCustomFooter(footerArea, component);
    }

    if (hasBuiltInFooter) {
      const builtInFooter = this.createFooter(component);
      footerArea.appendChild(builtInFooter);
    }

    listbox.appendChild(footerArea);
  }

  static addCustomFooter(container: HTMLElement, component: any): void {
    const footerContent = Array.from(component.querySelectorAll('[slot="pre-footer"]'));
    if (footerContent.length === 0) return;

    const customFooter = document.createElement('div');
    customFooter.className = 'select__custom-footer';
    customFooter.setAttribute('part', 'custom-footer');

    footerContent.forEach((el: Element) => {
      customFooter.appendChild(el.cloneNode(true));
    });

    customFooter.addEventListener('click', () => component.hide());

    container.appendChild(customFooter);
  }

  static createFooter(component: any): HTMLElement {
    const footerDiv = document.createElement('div');
    footerDiv.setAttribute('part', 'footer');
    footerDiv.className = 'select__footer';
    
    this.addShowSelectedToggle(footerDiv, component);
    this.addClearAllButton(footerDiv, component);
    
    return footerDiv;
  }

  static addShowSelectedToggle(footer: HTMLElement, component: any): void {
    const showSelectedSpan = document.createElement('span');
    showSelectedSpan.style.cursor = 'pointer';
    
    const checkbox = document.createElement('nile-checkbox');
    checkbox.disabled = component.selectedOptions.length === 0;
    checkbox.checked = component.showSelected;
    checkbox.innerHTML = '&nbsp; Show Selected';
    
    showSelectedSpan.appendChild(checkbox);
    footer.appendChild(showSelectedSpan);
  }

  static addClearAllButton(footer: HTMLElement, component: any): void {
    if (component.selectedOptions.length === 0) return;

    const clearAllSpan = document.createElement('span');
    clearAllSpan.className = 'select__clear';
    clearAllSpan.textContent = 'Clear All';
    clearAllSpan.style.cursor = 'pointer';
    footer.appendChild(clearAllSpan);
  }

  static updateClonedOptions(clonedListbox: HTMLElement, component: any): void {
    const optionsContainer = clonedListbox.querySelector('.select__options') as HTMLElement;
    if (!optionsContainer) return;

    const originalOptions = Array.from(component.querySelectorAll('nile-option'));
    
    const clonedOptions = Array.from(optionsContainer.querySelectorAll('nile-option'));
    
    originalOptions.forEach((originalOption: Element, index: number) => {
      const clonedOption = clonedOptions[index] as HTMLElement;
      if (clonedOption) {
        this.updateClonedOption(originalOption as HTMLElement, clonedOption);
      }
    });
  }

  static updateClonedOption(originalOption: HTMLElement, clonedOption: HTMLElement): void {
    (clonedOption as any).selected = (originalOption as any).selected;
    (clonedOption as any).disabled = (originalOption as any).disabled;
    (clonedOption as any).current = (originalOption as any).current;
    (clonedOption as any).hidden = (originalOption as any).hidden;
    
    this.updateClonedOptionAttributes(originalOption, clonedOption);
  }

  static updateClonedOptionAttributes(originalOption: HTMLElement, clonedOption: HTMLElement): void {
    const attributes = [
      { prop: 'selected', attr: 'selected' },
      { prop: 'disabled', attr: 'disabled' },
      { prop: 'current', attr: 'current' },
      { prop: 'hidden', attr: 'hidden' }
    ];

    attributes.forEach(({ prop, attr }) => {
      if ((originalOption as any)[prop]) {
        clonedOption.setAttribute(attr, '');
      } else {
        clonedOption.removeAttribute(attr);
      }
    });
  }

  static createPortalListbox(component: any): HTMLElement {
    const listbox = this.createBaseListbox(component);
    
    this.addSearchSection(listbox, component);
    this.addLoadingSection(listbox, component);
    this.addOptionsSection(listbox, component);
    this.addFooterSection(listbox, component);
    
    return listbox;
  }
}

export class PortalEventUtils {
  static setupPortalEventListeners(clonedListbox: HTMLElement, component: any): void {
    if (!clonedListbox) return;

    this.setupOptionClickListeners(clonedListbox, component);
    this.setupScrollListeners(clonedListbox, component);
    this.setupSearchListeners(clonedListbox, component);
    this.setupFooterListeners(clonedListbox, component);
    this.setupSlotListeners(clonedListbox, component);
  }

  static setupOptionClickListeners(clonedListbox: HTMLElement, component: any): void {
    if (!clonedListbox) return;

    clonedListbox.addEventListener('mouseup', (event) => {
      const target = event.target as HTMLElement;
      const option = target.closest('nile-option');
      
      if (option) {
        this.handleOptionClick(option as HTMLElement, component);
      }
    });
  }

  static handleOptionClick(option: HTMLElement, component: any): void {
    const originalOption = component.querySelector(`nile-option[value="${option.getAttribute('value')}"]`) as HTMLElement;
    if (originalOption) {
      const syntheticEvent = new MouseEvent('mouseup', {
        bubbles: true,
        cancelable: true,
        view: window
      });
      
      Object.defineProperty(syntheticEvent, 'target', {
        value: originalOption,
        writable: false
      });
      
      if (component.handleOptionClick) {
        component.handleOptionClick(syntheticEvent);
      }
    }
  }

  static setupScrollListeners(clonedListbox: HTMLElement, component: any): void {
    if (!clonedListbox) return;

    clonedListbox.addEventListener('scroll', (event) => {
      if (component.handleScroll) {
        component.handleScroll(event);
      }
    });
  }

  static setupSearchListeners(clonedListbox: HTMLElement, component: any): void {
    if (!clonedListbox) return;

    const searchInput = clonedListbox.querySelector('nile-input') as HTMLElement;
    if (searchInput) {
      searchInput.addEventListener('nile-input', (event) => {
        if (component.handleSearchChange) {
          component.handleSearchChange(event);
        }
      });
      
      searchInput.addEventListener('nile-focus', (event) => {
        if (component.handleSearchFocus) {
          component.handleSearchFocus();
        }
      });
      
      searchInput.addEventListener('nile-blur', (event) => {
        if (component.handleSearchBlur) {
          component.handleSearchBlur();
        }
      });
    }
  }

  static setupFooterListeners(clonedListbox: HTMLElement, component: any): void {
    if (!clonedListbox) return;

    const footer = clonedListbox.querySelector('.select__footer') as HTMLElement;
    if (footer) {
      this.setupFooterClickHandlers(footer);
      this.setupShowSelectedToggle(footer, component);
      this.setupClearAllHandler(footer, component);
    }

    this.setupCustomFooterListeners(clonedListbox, component);
  }

  static setupCustomFooterListeners(clonedListbox: HTMLElement, component: any): void {
    const customFooter = clonedListbox.querySelector('.select__custom-footer') as HTMLElement;
    if (!customFooter) return;

    customFooter.addEventListener('click', (event) => {
      event.stopPropagation();
    });
  }

  static setupFooterClickHandlers(footer: HTMLElement): void {
    footer.addEventListener('click', (event) => {
      event.stopPropagation();
      event.preventDefault();
    });
  }

  static setupShowSelectedToggle(footer: HTMLElement, component: any): void {
    const showSelectedSpan = footer.querySelector('span[style*="cursor: pointer"]');
    if (showSelectedSpan) {
      showSelectedSpan.addEventListener('click', (event) => {
        event.stopPropagation();
        event.preventDefault();
        if (component.toggleShowSelected) {
          component.toggleShowSelected(event);
        }
      });
    }
  }

  static setupClearAllHandler(footer: HTMLElement, component: any): void {
    footer.addEventListener('click', (event) => {
      const target = event.target as HTMLElement;
      if (target.classList.contains('select__clear')) {
        event.stopPropagation();
        event.preventDefault();
        if (component.unSlectAll) {
          component.unSlectAll();
        }
      }
    });
  }

  static setupSlotListeners(clonedListbox: HTMLElement, component: any): void {
    if (!clonedListbox) return;

    const slot = clonedListbox.querySelector('slot');
    if (slot) {
      slot.addEventListener('slotchange', () => {
        if (component.updatePortalContent) {
          component.updatePortalContent();
        }
      });
    }
  }
}
