import React, { useCallback, useEffect, useRef, useState } from 'react';
import {
  cancelScheduledCallback,
  scheduleIdleCallback,
  type IdleCallbackHandle,
} from '../../../../../helpers';
import classes from './TagsRenderer.scss';

export const TagsRenderer = (val: unknown): JSX.Element => {
  const containerRef = useRef<HTMLDivElement>(null);
  const [visibleCount, setVisibleCount] = useState<number | null>(null);

  const prevValRef = useRef<string[]>([]);
  const valRef = useRef(val);
  valRef.current = val; // Update on every render

  // Estimate tag width based on text content without DOM manipulation
  const estimateTagWidth = useCallback((text: string): number => {
    const container = containerRef.current;
    if (!container) {
      return 0;
    }

    // Get computed font size from the container
    const computedStyle = window.getComputedStyle(container);
    const fontSize = parseFloat(computedStyle.fontSize) || 14;

    // Average character width is approximately 0.6 of font size for typical fonts
    // This is a reasonable approximation for most monospace and sans-serif fonts
    const avgCharWidth = fontSize * 0.55;

    // Tag padding is 5px on each side (10px total) from SCSS
    const padding = 10;

    // Estimate text width
    const textWidth = text.length * avgCharWidth;

    return textWidth + padding;
  }, []);

  // Shared calculation function that uses valRef for current value
  const calculateVisibleItems = useCallback((): void => {
    const currentVal = valRef.current;
    const container = containerRef.current;

    if (!container || !Array.isArray(currentVal) || currentVal.length === 0) {
      return;
    }

    const containerWidth = container.offsetWidth;
    const gap = 5; // Gap between items from SCSS
    const overflowIndicatorWidth = 60; // Approximate width for "... +X"

    // Estimate tag widths based on text content
    const tagWidths: number[] = (currentVal as string[]).map((item) =>
      estimateTagWidth(item),
    );

    // Calculate how many items can fit
    let totalWidth = 0;
    let newVisibleCount = 0;

    for (let i = 0; i < (currentVal as string[]).length; i++) {
      const itemWidth = tagWidths[i] + (i > 0 ? gap : 0);
      const wouldNeedOverflow = i < (currentVal as string[]).length - 1;
      const requiredWidth =
        totalWidth +
        itemWidth +
        (wouldNeedOverflow ? overflowIndicatorWidth + gap : 0);

      if (requiredWidth <= containerWidth) {
        totalWidth += itemWidth;
        newVisibleCount++;
      } else {
        break;
      }
    }

    // If we can't fit all items, reserve space for overflow indicator
    if (
      newVisibleCount < (currentVal as string[]).length &&
      newVisibleCount > 0
    ) {
      // Re-calculate with overflow indicator space reserved
      totalWidth = 0;
      newVisibleCount = 0;

      for (let i = 0; i < (currentVal as string[]).length; i++) {
        const itemWidth = tagWidths[i] + (i > 0 ? gap : 0);
        const requiredWidth =
          totalWidth + itemWidth + overflowIndicatorWidth + gap;

        if (
          requiredWidth <= containerWidth &&
          i < (currentVal as string[]).length - 1
        ) {
          totalWidth += itemWidth;
          newVisibleCount++;
        } else {
          break;
        }
      }
    }

    // Update state with the new visible count
    setVisibleCount(newVisibleCount);
  }, [estimateTagWidth]);

  // Effect 1: Handle tag changes (runs when val changes)
  useEffect(() => {
    if (!Array.isArray(val) || val.length === 0) {
      setVisibleCount(0);
      prevValRef.current = [];
      return;
    }

    // Check if array contents actually changed
    const valArray = val as string[];
    const hasChanged =
      valArray.length !== prevValRef.current.length ||
      valArray.some((item, index) => item !== prevValRef.current[index]);

    if (hasChanged) {
      calculateVisibleItems();
      prevValRef.current = [...valArray];
    }
  }, [val, calculateVisibleItems]);

  // Ref to store the idle callback ID
  const idleCallbackRef = useRef<IdleCallbackHandle | null>(null);

  // // Effect 2: Set up ResizeObserver (runs once on mount)
  useEffect(() => {
    if (!containerRef.current) {
      return;
    }

    const debouncedCalculate = (): void => {
      if (idleCallbackRef.current) {
        cancelScheduledCallback(idleCallbackRef.current);
      }
      idleCallbackRef.current = scheduleIdleCallback(() => {
        calculateVisibleItems();
      });
    };

    // const resizeObserver = new ResizeObserver(calculateVisibleItems);
    const resizeObserver = new ResizeObserver(debouncedCalculate);
    resizeObserver.observe(containerRef.current);

    return () => {
      if (idleCallbackRef.current) {
        cancelScheduledCallback(idleCallbackRef.current);
      }
      resizeObserver.disconnect();
    };
  }, [calculateVisibleItems]);

  const stringArray = Array.isArray(val) ? (val as string[]) : [];

  // Don't render items until we've calculated the visible count
  if (visibleCount === null) {
    return (
      <div
        ref={containerRef}
        className={classes.container}
        data-test-id="tags-container"
        title={stringArray.join(', ')}
      />
    );
  }

  const visibleItems = stringArray.slice(0, visibleCount);
  const hiddenItems = stringArray.slice(visibleCount);

  // Create tooltip text for hidden items
  const tooltipText = hiddenItems.length > 0 ? hiddenItems.join(', ') : '';

  return (
    <div
      ref={containerRef}
      className={classes.container}
      data-test-id="tags-container"
      title={stringArray.join(', ')}
    >
      {visibleItems.map((item, index) => (
        <div key={`${item}-${index}`} className={classes.tag}>
          {item}
        </div>
      ))}
      {hiddenItems.length > 0 && (
        <div className={classes.overflowIndicator} title={tooltipText}>
          {`+${hiddenItems.length}`}
        </div>
      )}
    </div>
  );
};
