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

export class PortalUtils {
  static calculateAvailableSpace(referenceElement: HTMLElement): {
    spaceAbove: number;
    spaceBelow: number;
    spaceLeft: number;
    spaceRight: number;
    viewportHeight: number;
    viewportWidth: number;
  } {
    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;
    
    return { spaceAbove, spaceBelow, spaceLeft, spaceRight, viewportHeight, viewportWidth };
  }

  static getOptimalPlacement(referenceElement: HTMLElement): Placement {
    const { spaceAbove, spaceBelow, spaceLeft, spaceRight } = this.calculateAvailableSpace(referenceElement);
    
    if (spaceBelow >= 200 && spaceBelow >= spaceAbove) {
      return 'bottom';
    } else if (spaceAbove >= 200 && spaceAbove > spaceBelow) {
      return 'top';
    }
    
    if (spaceRight >= 200 && spaceRight >= spaceLeft) {
      return 'right';
    } else if (spaceLeft >= 200) {
      return 'left';
    }
    
    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 (placement.startsWith('top')) {
      return Math.max(spaceAbove - 20, 100);
    } else if (placement.startsWith('bottom')) {
      return Math.max(spaceBelow - 20, 100);
    } else if (placement.startsWith('left') || placement.startsWith('right')) {
      return Math.max(Math.min(spaceAbove, 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-dropdown-styles-${Math.random().toString(36).substring(2, 11)}`;
  }

  static isPositioningOptimal(
    placement: Placement,
    referenceElement: HTMLElement
  ): boolean {
    const { spaceAbove, spaceBelow, spaceLeft, spaceRight } = this.calculateAvailableSpace(referenceElement);
    
    const isAbove = placement.startsWith('top');
    const isBelow = placement.startsWith('bottom');
    const isLeft = placement.startsWith('left');
    const isRight = placement.startsWith('right');
    
    if (isAbove && spaceBelow > spaceAbove) return false;
    if (isBelow && spaceAbove > spaceBelow) return false;
    if (isLeft && spaceRight > spaceLeft) return false;
    if (isRight && spaceLeft > spaceRight) 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());
      }
    }
  }
}

export class PortalContentUtils {
  static createPortalPanel(component: any): HTMLElement {
    // Find the panel slot content (typically a nile-menu)
    const panelSlot = component.shadowRoot?.querySelector('.dropdown__panel') as HTMLSlotElement;
    if (!panelSlot) {
      // Fallback: try to find in light DOM
      const slot = component.querySelector('[slot]');
      if (!slot || slot.getAttribute('slot') === 'trigger') {
        return document.createElement('div');
      }
    }

    // Get assigned elements from the slot
    let assignedElements: Element[] = panelSlot?.assignedElements({ flatten: true }) as Element[] || [];
    
    // If no assigned elements, try direct children that aren't in trigger slot
    if (assignedElements.length === 0) {
      const children = Array.from(component.children) as Element[];
      assignedElements = children.filter(child => {
        const slot = child.getAttribute('slot');
        return !slot || slot !== 'trigger';
      });
    }

    // Create a container for the cloned panel
    const panelContainer = document.createElement('div');
    panelContainer.className = 'dropdown__panel';
    panelContainer.setAttribute('part', 'panel');
    panelContainer.setAttribute('aria-hidden', 'false');
    panelContainer.setAttribute('aria-labelledby', 'dropdown');

    // Clone all assigned elements
    assignedElements.forEach((element: Element) => {
      const clonedElement = element.cloneNode(true) as HTMLElement;
      this.processInlineEventHandlers(clonedElement);
      panelContainer.appendChild(clonedElement);
    });

    return panelContainer;
  }

  static updatePortalPanel(clonedPanel: HTMLElement, component: any): void {
    if (!clonedPanel) return;

    // Clear existing content
    clonedPanel.innerHTML = '';

    // Re-clone the panel content
    const panelSlot = component.shadowRoot?.querySelector('.dropdown__panel') as HTMLSlotElement;
    let assignedElements: Element[] = panelSlot?.assignedElements({ flatten: true }) as Element[] || [];
    
    if (assignedElements.length === 0) {
      const children = Array.from(component.children) as Element[];
      assignedElements = children.filter(child => {
        const slot = child.getAttribute('slot');
        return !slot || slot !== 'trigger';
      });
    }

    assignedElements.forEach((element: Element) => {
      const clonedElement = element.cloneNode(true) as HTMLElement;
      this.processInlineEventHandlers(clonedElement);
      clonedPanel.appendChild(clonedElement);
    });
  }

  static processInlineEventHandlers(element: HTMLElement): void {
    const eventHandlerAttrs = ['onclick', 'onchange', 'oninput', 'onsubmit', 'onfocus', 'onblur', 'onkeydown', 'onkeyup'];
    
    eventHandlerAttrs.forEach(attr => {
      element.removeAttribute(attr);
      element.querySelectorAll(`[${attr}]`).forEach(child => {
        child.removeAttribute(attr);
      });
    });
  }
}

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

    this.setupMenuSelectListeners(clonedPanel, component);
    this.setupKeyDownListeners(clonedPanel, component);
    this.setupEventForwarding(clonedPanel, component);
  }

  static setupMenuSelectListeners(clonedPanel: HTMLElement, component: any): void {
    if (!clonedPanel) return;

    // Listen for nile-select events from menu items
    clonedPanel.addEventListener('nile-select', (event: Event) => {
      const customEvent = event as CustomEvent;
      // Forward the event to the original component
      if (component.handlePanelSelect) {
        component.handlePanelSelect(customEvent);
      }
    });
  }

  static setupKeyDownListeners(clonedPanel: HTMLElement, component: any): void {
    if (!clonedPanel) return;

    // Listen for keydown events (like Escape)
    clonedPanel.addEventListener('keydown', (event: KeyboardEvent) => {
      if (component.handleKeyDown) {
        component.handleKeyDown(event);
      }
    });
  }

  static setupEventForwarding(clonedPanel: HTMLElement, component: any): void {
    if (!clonedPanel) return;

    const panelSlot = component.shadowRoot?.querySelector('.dropdown__panel') as HTMLSlotElement;
    if (!panelSlot) return;

    let assignedElements: Element[] = panelSlot.assignedElements({ flatten: true }) as Element[] || [];
    if (assignedElements.length === 0) {
      const children = Array.from(component.children) as Element[];
      assignedElements = children.filter(child => {
        const slot = child.getAttribute('slot');
        return !slot || slot !== 'trigger';
      });
    }

    const originalMenu = assignedElements[0];
    if (!originalMenu) return;

    const findOriginalElement = (clonedElement: HTMLElement): HTMLElement | null => {
      const clonedMenuItem = clonedElement.closest('nile-menu-item');
      if (clonedMenuItem) {
        const clonedItems = Array.from(clonedPanel.querySelectorAll('nile-menu-item'));
        const originalItems = Array.from(originalMenu.querySelectorAll('nile-menu-item'));
        const index = clonedItems.indexOf(clonedMenuItem);
        const originalMenuItem = originalItems[index];
        
        if (originalMenuItem) {
          if (clonedElement === clonedMenuItem) {
            return originalMenuItem;
          }
          
          const clonedTagName = clonedElement.tagName.toLowerCase();
          const clonedElements = Array.from(clonedMenuItem.querySelectorAll(clonedTagName));
          const originalElements = Array.from(originalMenuItem.querySelectorAll(clonedTagName));
          const elementIndex = clonedElements.indexOf(clonedElement);
          
          if (elementIndex >= 0 && originalElements[elementIndex]) {
            return originalElements[elementIndex] as HTMLElement;
          }
          
          return originalMenuItem;
        }
      }
      return null;
    };

    const forwardEvent = (event: Event, originalElement: HTMLElement) => {
      const originalEvent = event as any;
      let newEvent: Event;
      
      if (originalEvent instanceof MouseEvent) {
        newEvent = new MouseEvent(event.type, {
          bubbles: true,
          cancelable: true,
          composed: true,
          button: originalEvent.button,
          buttons: originalEvent.buttons,
          clientX: originalEvent.clientX,
          clientY: originalEvent.clientY,
          ctrlKey: originalEvent.ctrlKey,
          shiftKey: originalEvent.shiftKey,
          altKey: originalEvent.altKey,
          metaKey: originalEvent.metaKey
        });
      } else if (originalEvent instanceof KeyboardEvent) {
        newEvent = new KeyboardEvent(event.type, {
          bubbles: true,
          cancelable: true,
          composed: true,
          key: originalEvent.key,
          code: originalEvent.code,
          keyCode: originalEvent.keyCode,
          which: originalEvent.which,
          ctrlKey: originalEvent.ctrlKey,
          shiftKey: originalEvent.shiftKey,
          altKey: originalEvent.altKey,
          metaKey: originalEvent.metaKey
        });
      } else if (originalEvent instanceof CustomEvent) {
        newEvent = new CustomEvent(event.type, {
          bubbles: true,
          cancelable: true,
          composed: true,
          detail: originalEvent.detail
        });
      } else {
        newEvent = new Event(event.type, {
          bubbles: true,
          cancelable: true,
          composed: true
        });
      }
      
      originalElement.dispatchEvent(newEvent);
    };

    const syncElementState = (clonedElement: HTMLElement, originalElement: HTMLElement) => {
      if (clonedElement.tagName.toLowerCase() === 'nile-checkbox' && 
          originalElement.tagName.toLowerCase() === 'nile-checkbox') {
        const clonedCheckbox = clonedElement as any;
        const originalCheckbox = originalElement as any;
        if ('checked' in originalCheckbox && 'checked' in clonedCheckbox) {
          clonedCheckbox.checked = originalCheckbox.checked;
        }
        if ('indeterminate' in originalCheckbox && 'indeterminate' in clonedCheckbox) {
          clonedCheckbox.indeterminate = originalCheckbox.indeterminate;
        }
      }
    };

    const eventTypes = ['click', 'change', 'input', 'submit', 'focus', 'blur', 'keydown', 'keyup', 'mousedown', 'mouseup'];
    
    eventTypes.forEach(eventType => {
      clonedPanel.addEventListener(eventType, (event: Event) => {
        const target = event.target as HTMLElement;
        if (!target) return;

        const originalItem = findOriginalElement(target);
        if (originalItem) {
          if (eventType === 'click') {
            if (target.tagName.toLowerCase() === 'nile-checkbox') {
              event.stopImmediatePropagation();
              event.preventDefault();
              
              const originalCheckbox = originalItem as any;
              if (originalCheckbox && typeof originalCheckbox.click === 'function') {
                originalCheckbox.click();
                setTimeout(() => {
                  syncElementState(target, originalItem);
                }, 10);
              } else {
                forwardEvent(event, originalItem);
                setTimeout(() => {
                  syncElementState(target, originalItem);
                }, 10);
              }
            } else {
              event.stopImmediatePropagation();
              event.preventDefault();
              forwardEvent(event, originalItem);
            }
          } else {
            forwardEvent(event, originalItem);
          }
        }
      }, true);
    });

    const clonedCheckboxes = Array.from(clonedPanel.querySelectorAll('nile-checkbox'));
    const originalCheckboxes = Array.from(originalMenu.querySelectorAll('nile-checkbox'));
    
    clonedCheckboxes.forEach((clonedCheckbox, index) => {
      const originalCheckbox = originalCheckboxes[index];
      if (originalCheckbox) {
        originalCheckbox.addEventListener('nile-change', () => {
          syncElementState(clonedCheckbox as HTMLElement, originalCheckbox as HTMLElement);
        });
      }
    });

    clonedPanel.addEventListener('nile-change', (event: CustomEvent) => {
      const target = event.target as HTMLElement;
      if (!target) return;

      const originalElement = findOriginalElement(target);
      if (originalElement) {
        const originalEvent = event as CustomEvent;
        
        if (target.tagName.toLowerCase() === 'nile-checkbox' && originalEvent.detail?.checked !== undefined) {
          const clonedCheckbox = target as any;
          clonedCheckbox.checked = originalEvent.detail.checked;
        }
        
        const newEvent = new CustomEvent('nile-change', {
          bubbles: true,
          cancelable: true,
          composed: true,
          detail: originalEvent.detail
        });
        originalElement.dispatchEvent(newEvent);
      }
    }, true);
  }
}

