/**
 * Copyright Aquera Inc 2025
 *
 * This source code is licensed under the BSD-3-Clause license found in the
 * LICENSE file in the root directory of this source tree.
 */

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

export class ComboboxPortalManager {
  private portalContainer: HTMLElement | null = null;
  private originalListboxParent: HTMLElement | null = null;
  private measuredPopupHeight: number | null = null;
  private component: any;
  private cleanupAutoUpdate: (() => void) | null = null;
  private currentPlacement: Placement = 'bottom';

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

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

  private createPortalContainer(): HTMLElement {
    const container = document.createElement('div');
    container.style.position = 'absolute';
    container.style.zIndex = '9999';
    container.style.pointerEvents = 'none';
    container.className = 'nile-combobox-portal';
    return container;
  }

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

    const referenceElement = this.component.combobox || this.component;
    const floatingElement = this.portalContainer;

    try {
      const boundary = PortalUtils.findBoundaryElements(referenceElement);
      const initialPlacement = PortalUtils.getOptimalPlacement(referenceElement);

      const { x, y, placement } = await computePosition(
        referenceElement,
        floatingElement,
        {
          placement: initialPlacement,
          strategy: 'fixed',
          middleware: [
            offset(4),
            size({
              apply: ({ availableWidth, availableHeight, elements, rects }) => {
                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`);
              },
              padding: 10,
              boundary,
            }),
            flip({
              fallbackPlacements: ['bottom', 'top', 'bottom-start', 'top-start'],
              fallbackStrategy: 'bestFit',
              padding: 10,
              boundary,
            }),
            shift({ padding: 10, crossAxis: true, boundary }),
          ],
          platform,
        },
      );

      const referenceRect = referenceElement.getBoundingClientRect();
      Object.assign(floatingElement.style, {
        left: `${x}px`,
        top: `${y}px`,
        position: 'fixed',
        pointerEvents: 'auto',
        width: `${referenceRect.width}px`,
      });

      this.currentPlacement = placement;
      const placementClass = placement.split('-')[0];
      floatingElement.className = `nile-combobox-portal combobox__listbox--${placementClass}`;
    } catch {
      this.fallbackPositioning();
    }
  }

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

    const ref = this.component.combobox || this.component;
    const rect = ref.getBoundingClientRect();
    const vh = window.innerHeight;
    const spaceBelow = vh - rect.bottom;
    const spaceAbove = rect.top;

    let top: number;
    let maxH: number;
    let cls: string;

    if (spaceAbove > spaceBelow) {
      maxH = Math.max(spaceAbove - 20, 100);
      top = Math.max(rect.top - maxH - 4, 10);
      cls = 'top';
    } else {
      maxH = Math.max(spaceBelow - 20, 100);
      top = rect.bottom + 4;
      cls = 'bottom';
    }

    Object.assign(this.portalContainer.style, {
      left: `${rect.left}px`,
      top: `${top}px`,
      width: `${rect.width}px`,
      maxHeight: `${maxH}px`,
      pointerEvents: 'auto',
    });
    this.portalContainer.className = `nile-combobox-portal combobox__listbox--${cls}`;
    this.portalContainer.style.setProperty('--auto-size-available-height', `${maxH}px`);
    this.portalContainer.style.setProperty('--auto-size-available-width', `${rect.width}px`);
  }

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

  private injectStyles(): void {
    if (!this.portalContainer) return;
    const styleId = `nile-combobox-styles-${Math.random().toString(36).substring(2, 11)}`;
    if (document.getElementById(styleId)) return;

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

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

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

    this.component.updateComplete.then(() => {
      const listbox = this.component.shadowRoot?.querySelector('#listbox') as HTMLElement;
      if (!listbox) return;

      this.originalListboxParent = listbox.parentElement as HTMLElement;
      this.portalContainer = this.createPortalContainer();
      this.portalContainer.appendChild(listbox);
      document.body.appendChild(this.portalContainer);
      this.injectStyles();
      listbox.style.display = '';
      this.computePosition();

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

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

    if (this.portalContainer && this.portalContainer.parentNode) {
      const listbox = this.portalContainer.querySelector('#listbox') as HTMLElement;
      if (listbox && this.originalListboxParent) {
        this.originalListboxParent.appendChild(listbox);
        listbox.style.display = this.component.portal ? 'none' : '';
      }

      const sid = (this.portalContainer as any).__injectedStyleId;
      if (sid) document.getElementById(sid)?.remove();

      this.portalContainer.parentNode.removeChild(this.portalContainer);
    }

    this.portalContainer = null;
    this.originalListboxParent = null;
    this.measuredPopupHeight = null;
    this.currentPlacement = 'bottom';
  }

  updatePosition(): void {
    if (this.component.portal && this.portalContainer) {
      this.computePosition();
    }
  }

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

  async resetScrollPosition(): Promise<void> {
    await this.component.updateComplete;

    requestAnimationFrame(() => {
      let listbox: HTMLElement | null = null;

      if (this.component.portal && this.portalContainer) {
        listbox = this.portalContainer.querySelector('#listbox') as HTMLElement;
      } else {
        listbox = this.component.shadowRoot?.querySelector('#listbox') as HTMLElement;
      }

      if (!listbox || !listbox.isConnected) return;
      listbox.scrollTop = 0;

      const virtualized = listbox.querySelector('.virtualized') as HTMLElement;
      if (virtualized && virtualized.isConnected) {
        const fewItems = this.component.filteredData?.length < 5;
        if (fewItems) {
          virtualized.style.overflowY = 'hidden';
          virtualized.style.maxHeight = 'none';
          listbox.style.overflowY = 'hidden';
          listbox.style.maxHeight = 'fit-content';
        } else {
          virtualized.style.overflowY = 'auto';
          virtualized.style.maxHeight = '';
          listbox.style.overflowY = 'auto';
          listbox.style.maxHeight = '';
        }
        virtualized.scrollTop = 0;
      }

      const virtualizer = listbox.querySelector('lit-virtualizer') as HTMLElement;
      if (virtualizer && virtualizer.isConnected) {
        virtualizer.scrollTop = 0;
      }
    });
  }
}
