class VirtualScroll {
  constructor(options) {
    this.container = options.container;
    this.defaultItemHeight = options.itemHeight || options.defaultItemHeight || 120;
    this.containerHeight = options.containerHeight || 400;
    this.renderItem = options.renderItem;
    this.data = options.data || [];
    this.overscan = options.overscan || 3;

    this.itemHeights = new Map();
    this.itemPositions = new Map();
    this.measuredItems = new Set();
    this.selectedIndex = -1;

    this.scrollTop = 0;
    this.visibleStart = 0;
    this.visibleEnd = 0;
    this.renderedItems = new Map();
    this.isScrolling = false;
    this.scrollTimer = null;
    this.renderTimer = null;

    this.handleScroll = this.handleScroll.bind(this);
    this.handleResize = this.handleResize.bind(this);

    this.init();
  }

  init() {
    try {
      this.setupContainer();
      this.setupScrollArea();
      this.setupEventListeners();
      this.updateItemPositions();
      this.updateVisibleRange();
      this.render();
    } catch (error) {
      console.error('VirtualScroll initialization failed:', error);
    }
  }

  reload() {
    this.itemHeights.clear();
    this.itemPositions.clear();
    this.measuredItems.clear();
    this.cleanupAllItems();
    this.updateItemPositions();
    this.updateVisibleRange();
    this.render();
  }

  setupContainer() {
    if (!this.container) {
      throw new Error('Container element is required');
    }

    this.container.className += ' virtual-scroll-container';
  }

  setupScrollArea() {
    this.scrollArea = document.createElement('div');
    const totalHeight = this.data.length * this.defaultItemHeight;
    this.scrollArea.style.height = `${totalHeight}px`;
    this.scrollArea.style.position = 'relative';
    this.scrollArea.className = 'virtual-scroll-area';

    this.viewport = document.createElement('div');
    this.viewport.style.position = 'absolute';
    this.viewport.style.top = '0';
    this.viewport.style.left = '0';
    this.viewport.style.right = '0';
    this.viewport.className = 'virtual-scroll-viewport';

    this.scrollArea.appendChild(this.viewport);
    this.container.appendChild(this.scrollArea);
  }

  setupEventListeners() {
    this.container.addEventListener('scroll', this.handleScroll, { passive: true });

    if (typeof ResizeObserver !== 'undefined') {
      this.resizeObserver = new ResizeObserver(this.handleResize);
      this.resizeObserver.observe(this.container);
    }
  }

  handleScroll() {
    this.scrollTop = this.container.scrollTop;

    if (!this.isScrolling) {
      this.isScrolling = true;
      this.container.classList.add('scrolling');
    }

    if (this.scrollTimer) {
      clearTimeout(this.scrollTimer);
    }
    if (this.renderTimer) {
      cancelAnimationFrame(this.renderTimer);
    }

    this.updateVisibleRange();

    this.renderTimer = requestAnimationFrame(() => {
      this.render();
    });

    this.scrollTimer = setTimeout(() => {
      this.isScrolling = false;
      this.container.classList.remove('scrolling');
    }, 150);
  }

  handleResize() {
    this.updateContainerHeight();
  }

  updateVisibleRange() {
    const scrollTop = this.scrollTop;
    const containerHeight = this.containerHeight;

    const estimatedStartIndex = Math.floor(scrollTop / this.defaultItemHeight);
    const estimatedEndIndex = Math.ceil((scrollTop + containerHeight) / this.defaultItemHeight);

    let startIndex = estimatedStartIndex;
    let endIndex = estimatedEndIndex;

    if (this.measuredItems.size > 0) {
      for (let i = 0; i < this.data.length; i++) {
        const itemTop = this.getItemTop(i);
        const itemBottom = itemTop + this.getItemHeight(i);
        if (itemBottom > scrollTop) {
          startIndex = i;
          break;
        }
      }

      for (let i = startIndex; i < this.data.length; i++) {
        const itemTop = this.getItemTop(i);
        if (itemTop > scrollTop + containerHeight) {
          endIndex = i - 1;
          break;
        }
      }
    }

    this.visibleStart = Math.max(0, startIndex - this.overscan);
    this.visibleEnd = Math.min(this.data.length - 1, endIndex + this.overscan);
  }

  render() {
    try {
      if (this.data.length === 0) {
        this.cleanupAllItems();
        return;
      }

      this.cleanupInvisibleItems();

      for (let i = this.visibleStart; i <= this.visibleEnd; i++) {
        if (!this.renderedItems.has(i)) {
          this.renderItemAtIndex(i);
        } else {
          this.renderedItems.get(i).style.height = `${this.getItemHeight(i)}px`;
          this.renderedItems.get(i).style.top = `${this.getItemTop(i)}px`;
        }
      }

    } catch (error) {
      console.error('VirtualScroll render error:', error);
    }
  }

  renderItemAtIndex(index) {
    const item = this.data[index];
    if (!item) return;

    try {
      if (!this.measuredItems.has(index)) {
        const measuredHeight = this.measureItemHeightAtIndex(index);
        this.setItemHeight(index, measuredHeight);

        this.updateItemPositions();
        this.updateScrollArea();

        this.updateVisibleRange();
      }

      const itemElement = document.createElement('div');
      itemElement.className = 'virtual-scroll-item' + (item.selected ? ' selected' : '');
      itemElement.style.position = 'absolute';
      itemElement.style.left = '0';
      itemElement.style.right = '0';

      const itemTop = this.getItemTop(index);
      const itemHeight = this.getItemHeight(index);

      itemElement.style.top = `${itemTop}px`;
      itemElement.style.height = `${itemHeight}px`;

      const content = this.renderItem(item, index);
      itemElement.appendChild(content);

      itemElement.addEventListener('click', (e) => {
        this.selectItem(index, itemElement);
      });

      this.viewport.appendChild(itemElement);
      this.renderedItems.set(index, itemElement);
    } catch (error) {
      console.error(`Error rendering item at index ${index}:`, error);
    }
  }

  handleItemClick(event, item, index) {
    try {
      this.clearSelection();

      const itemElement = this.renderedItems.get(index);
      if (itemElement) {
        itemElement.classList.add('selected');
      }

      const customEvent = new CustomEvent('itemSelect', {
        detail: { item, index, element: itemElement }
      });
      this.container.dispatchEvent(customEvent);

    } catch (error) {
      console.error('Error handling item click:', error);
    }
  }

  clearSelection() {
    const items = this.container.querySelectorAll('.virtual-scroll-item');
    items.forEach(item => {
      item.classList.remove('selected');
    });
  }

  cleanupInvisibleItems() {
    const toRemove = [];

    this.renderedItems.forEach((element, index) => {
      if (index < this.visibleStart || index > this.visibleEnd) {
        toRemove.push(index);
      }
    });

    toRemove.forEach(index => {
      this.removeItemElement(index);
    });
  }

  cleanupAllItems() {
    const allIndices = Array.from(this.renderedItems.keys());
    allIndices.forEach(index => {
      this.removeItemElement(index);
    });
  }

  removeItemElement(index) {
    const element = this.renderedItems.get(index);
    if (element) {
      if (element._clickHandler) {
        element.removeEventListener('click', element._clickHandler);
        delete element._clickHandler;
      }

      if (element.parentNode) {
        element.parentNode.removeChild(element);
      }

      this.renderedItems.delete(index);
    }
  }

  measureItemHeightAtIndex(index) {
    try {
      const item = this.data[index];
      if (!item) return this.defaultItemHeight;

      const tempItem = this.createTempItemForMeasurement(index);

      tempItem.style.position = 'absolute';
      tempItem.style.visibility = 'hidden';
      tempItem.style.left = '-9999px';
      tempItem.style.width = (this.scrollArea.offsetWidth) + 'px';

      this.container.appendChild(tempItem);

      const height = tempItem.offsetHeight;

      this.container.removeChild(tempItem);

      return Math.max(height, this.defaultItemHeight);

    } catch (error) {
      console.error(`Error measuring item ${index}:`, error);
      return this.defaultItemHeight;
    }
  }

  createTempItemForMeasurement(index) {
    const item = this.data[index];
    const tempElement = document.createElement('div');
    tempElement.className = 'virtual-scroll-item';

    if (item.selected) {
      tempElement.classList.add('selected');
    }

    const content = this.renderItem(item, index);
    tempElement.appendChild(content);

    if (item.selected) {
      const addressElement = tempElement.querySelector('.contentAddress');
      const operatingTimeElement = tempElement.querySelector('.operatingTime');
      const locationTypeElement = tempElement.querySelector('.contentLocationType');
      const cityElement = tempElement.querySelector('.contentCity');

      if (addressElement) addressElement.classList.add('display-block');
      if (operatingTimeElement) operatingTimeElement.classList.add('display-block');
      if (locationTypeElement) locationTypeElement.classList.add('display-block');
      if (cityElement) cityElement.classList.add('display-none');
    }

    return tempElement;
  }

  getTotalHeight() {
    if (this.data.length === 0) return 0;

    let totalHeight = 0;
    for (let i = 0; i < this.data.length; i++) {
      totalHeight += this.getItemHeight(i);
    }
    return totalHeight;
  }

  getItemHeight(index) {
    if (this.itemHeights.has(index)) {
      return this.itemHeights.get(index);
    }

    return this.defaultItemHeight;
  }

  setItemHeight(index, height) {
    this.itemHeights.set(index, height);
    this.measuredItems.add(index);
    this.updateItemPositions();
    this.updateScrollArea();
  }

  remeasureItem(index) {
    const newHeight = this.measureItemHeightAtIndex(index);
    this.setItemHeight(index, newHeight);
    return newHeight;
  }

  updateItemPositions() {
    let currentTop = 0;
    for (let i = 0; i < this.data.length; i++) {
      this.itemPositions.set(i, currentTop);
      currentTop += this.getItemHeight(i);
    }
  }

  getItemTop(index) {
    return this.itemPositions.get(index) || 0;
  }

  updateScrollArea() {
    if (this.scrollArea) {
      const totalHeight = this.getTotalHeight();
      this.scrollArea.style.height = `${totalHeight}px`;
    }
  }

  updateContainerHeight() {
    try {
      const rect = this.container.getBoundingClientRect();
      const newHeight = rect.height;

      if (newHeight !== this.containerHeight && newHeight > 0) {
        this.containerHeight = newHeight;
        this.updateVisibleRange();

        if (this.renderTimer) {
          cancelAnimationFrame(this.renderTimer);
        }
        this.renderTimer = requestAnimationFrame(() => {
          this.render();
        });
      }
    } catch (error) {
      console.error('Error updating container height:', error);
    }
  }

  updateData(newData) {
    this.data = newData;
    this.itemHeights.clear();
    this.itemPositions.clear();
    this.measuredItems.clear();
    this.selectedIndex = -1;

    this.updateItemPositions();

    const estimatedHeight = this.data.length * this.defaultItemHeight;
    if (this.scrollArea) {
      this.scrollArea.style.height = `${estimatedHeight}px`;
    }

    this.cleanupAllItems();
    this.updateVisibleRange();
    this.render();
  }

  scrollToItem(index, align = 'start') {
    if (index < 0 || index >= this.data.length) return;

    try {
      const itemTop = this.getItemTop(index);
      const itemHeight = this.getItemHeight(index);
      let scrollTop;

      switch (align) {
        case 'center':
          scrollTop = itemTop - (this.containerHeight / 2) + (itemHeight / 2);
          break;
        case 'end':
          scrollTop = itemTop - this.containerHeight + itemHeight;
          break;
        default: // 'start'
          scrollTop = itemTop;
      }

      scrollTop = Math.max(0, Math.min(scrollTop, this.getTotalHeight() - this.containerHeight));

      if (this.container.scrollTo) {
        this.container.scrollTo({
          top: scrollTop,
          behavior: 'smooth'
        });
      } else {
        this.container.scrollTop = scrollTop;
      }

    } catch (error) {
      console.error('Error scrolling to item:', error);
    }
  }

  getVisibleItems() {
    const visible = [];
    for (let i = this.visibleStart; i <= this.visibleEnd; i++) {
      if (this.data[i]) {
        visible.push(i);
      }
    }
    return visible;
  }

  findItemIndex(targetItem) {
    return this.data.findIndex(item =>
      item && item.locationId === targetItem.locationId
    );
  }

  setSelected(index, selected) {
    if (this.data[index]) {
      this.data[index].selected = selected;
    }
  }

  selectItem(index, element) {
    if (this.selectedIndex !== -1 && this.selectedIndex !== index) {
      this.setSelected(this.selectedIndex, false);
      this.remeasureItem(this.selectedIndex);
      this.clearSelection();
    }

    this.selectedIndex = index;
    this.setSelected(index, true);

    const expandedHeight = this.remeasureItem(index);

    this.render();

    const event = new CustomEvent('itemSelect', {
      detail: { item: this.data[index], index, element }
    });
    this.container.dispatchEvent(event);
  }

  measureItemHeight(index, element) {
    try {
      const tempItem = this.createTempItemForMeasurement(index);

      tempItem.style.position = 'absolute';
      tempItem.style.visibility = 'hidden';
      tempItem.style.left = '-9999px';
      tempItem.style.width = element.offsetWidth + 'px';

      this.container.appendChild(tempItem);

      const height = tempItem.offsetHeight;

      this.container.removeChild(tempItem);

      return Math.max(height, this.defaultItemHeight);

    } catch (error) {
      console.error('Error measuring item height:', error);
      return this.defaultItemHeight * 2;
    }
  }

  destroy() {
    try {
      if (this.scrollTimer) {
        clearTimeout(this.scrollTimer);
      }
      if (this.renderTimer) {
        cancelAnimationFrame(this.renderTimer);
      }

      if (this.resizeObserver) {
        this.resizeObserver.disconnect();
      }

      this.container.removeEventListener('scroll', this.handleScroll);

      this.cleanupAllItems();

      this.container.innerHTML = '';

    } catch (error) {
      console.error('Error destroying VirtualScroll:', error);
    }
  }
}

if (typeof module !== 'undefined' && module.exports) {
  module.exports = VirtualScroll;
} else {
  window.VirtualScroll = VirtualScroll;
}