import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import clsx from 'clsx';
import debounce from 'lodash/debounce';

import MuiTooltip from '@mui/material/Tooltip';
import { styled } from '@mui/material/styles';

import type { Theme } from '../@styles/theme-provider';
import { isElementOverflowing, useResizeObserverEffect } from '../../utils';

import type { TextWithTooltipProps } from './types';

/**
 * The minimum length of a text list for the overflowLabel to be displayed.
 */
const minTextListLength = 1;

const isElementVisible = (element: HTMLElement, container: HTMLElement) => {
  const containerRect = container.getBoundingClientRect();
  const elementRect = element.getBoundingClientRect();
  return (
    elementRect.bottom <= containerRect.bottom &&
    (elementRect.top <= containerRect.top
      ? containerRect.top - elementRect.top <= elementRect.height
      : elementRect.bottom - containerRect.bottom <= elementRect.height)
  );
};

type StyledTextProps = {
  numOfLines?: string;
};

const StyledText = styled('span', {
  shouldForwardProp: prop => prop !== 'numOfLines'
})<StyledTextProps>(({ numOfLines, theme }: StyledTextProps & { theme: Theme }) => ({
  ...theme.typography.subtitle1,
  position: 'relative',
  overflow: 'hidden',
  textOverflow: 'ellipsis',
  whiteSpace: 'normal',
  wordWrap: 'break-word',
  display: '-webkit-box',
  WebkitBoxOrient: 'vertical',
  WebkitLineClamp: numOfLines
}));

const StyledOverflowLabel = styled('span')(({ theme }: { theme: Theme }) => ({
  position: 'absolute',
  right: 0,
  bottom: 0,
  float: 'right',
  marginLeft: theme.spacing(0.5),
  color: theme.palette.secondary[500]
}));

const StyledTextContainer = styled('span')({
  '&.breakAll': {
    wordBreak: 'break-all'
  },
  '&.breakWord': {
    wordBreak: 'break-word'
  }
});

const StyledTextListItem = styled('span')({
  display: 'inline',
  '&:not(:last-of-type):after': {
    content: 'attr(data-separator)',
    whitespace: 'break-spaces'
  }
});

const TextWithTooltip = (props: TextWithTooltipProps) => {
  const {
    text,
    textSeparator = '',
    title,
    numOfLines = 2,
    renderDelay = 0,
    resizeDebounceTime = 40,
    showLimitedItemsOnly,
    classes,
    textClassName,
    disableHoverListener,
    ...otherProps
  } = props;
  const textListLength = Array.isArray(text) ? text.length : -1;

  const ref = useRef<HTMLSpanElement>(null);

  // Delayed rendering
  const [renderReady, setRenderReady] = useState(false);
  useEffect(() => {
    let timeoutId: ReturnType<typeof setTimeout>;
    if (typeof renderDelay !== 'undefined' && renderDelay >= 0)
      timeoutId = setTimeout(() => setRenderReady(true), renderDelay);
    else setRenderReady(true);
    return () => timeoutId && clearTimeout(timeoutId);
  }, [renderDelay]);

  const compareSize = useCallback(() => {
    const textElement = ref?.current;
    if (renderReady && textElement) {
      setHover(isElementOverflowing(textElement));

      // enable limit items count
      if (textListLength > minTextListLength) {
        const textListItems = Array.from(
          textElement.querySelectorAll('.textListItem')
        ) as HTMLElement[];
        const visibleItems = getVisibleElements(textListItems, textElement);
        setLimitItemsCount(textListLength - visibleItems.length);
      }

      const overflowLabelElement = textElement.parentElement?.querySelector('.overflowLabel');
      const width = overflowLabelElement?.clientWidth;
      setOverflowLabelSize(width ? width + 4 : 0);
    }

    function getVisibleElements(elements: HTMLElement[], container: HTMLElement) {
      return elements.filter(el => isElementVisible(el, container));
    }
  }, [renderReady, textListLength]);

  const debouncedCompareSize = useMemo(
    () =>
      typeof resizeDebounceTime !== 'undefined' && resizeDebounceTime >= 0
        ? debounce(compareSize, resizeDebounceTime)
        : () => compareSize(),
    [compareSize, resizeDebounceTime]
  );

  useResizeObserverEffect(debouncedCompareSize, ref?.current);

  // Define state and function to update the value
  const [hoverStatus, setHover] = useState(false);
  const [limitItemsCount, setLimitItemsCount] = useState(0);
  const [overflowLabelSize, setOverflowLabelSize] = useState(0);

  // Tooltip title content
  let tooltipTitle = title || text;
  if (!title && textListLength > -1) {
    tooltipTitle = (text as string[])
      .slice(showLimitedItemsOnly ? -limitItemsCount : 0)
      .join(textSeparator);
  }

  const styledText = (
    <StyledText
      ref={ref}
      data-testid="text"
      numOfLines={`${numOfLines || ''}`}
      className={clsx(
        !renderReady && otherProps.className,
        classes?.text,
        textClassName,
        limitItemsCount > 0 && classes?.withOverflowLabel
      )}
      style={{
        paddingRight: limitItemsCount > 0 && overflowLabelSize ? overflowLabelSize : ''
      }}
    >
      {textListLength > minTextListLength && (
        <MuiTooltip disableInteractive title={tooltipTitle}>
          <StyledOverflowLabel className={clsx('overflowLabel', classes?.overflowLabel)}>
            {limitItemsCount > 0 ? `+${limitItemsCount}` : ''}
          </StyledOverflowLabel>
        </MuiTooltip>
      )}
      <StyledTextContainer
        className={clsx(numOfLines === 1 ? 'breakAll' : 'breakWord', classes?.textContainer)}
      >
        {Array.isArray(text)
          ? text?.map(item => (
              <StyledTextListItem
                key={`key_${item}`}
                data-separator={textSeparator}
                className={clsx('textListItem', classes?.textListItem)}
              >
                {item}
              </StyledTextListItem>
            ))
          : text}
      </StyledTextContainer>
    </StyledText>
  );

  return renderReady ? (
    <MuiTooltip
      disableHoverListener={
        typeof disableHoverListener === 'boolean'
          ? disableHoverListener
          : !hoverStatus || textListLength > minTextListLength
      }
      disableInteractive
      title={tooltipTitle}
      {...otherProps}
    >
      {styledText}
    </MuiTooltip>
  ) : (
    styledText
  );
};

const m = memo(TextWithTooltip);
export { m as TextWithTooltip };
