import * as React from 'react';

import {mergeCallback} from '@workday/canvas-kit-react/common';
import {
  Placement,
  Popper,
  defaultFallbackPlacements,
  getTransformFromPlacement,
} from '@workday/canvas-kit-react/popup';

import {TooltipContainer} from './TooltipContainer';
import {useTooltip} from './useTooltip';

/**
 * Look for an element within the tree for an overflow element (auto, scroll, clip, or hidden).
 * This could be the passed element, or a descendant. If no element is found, `null` is returned.
 */
export const findOverflowElement = (element: Element): Element | null => {
  const style = getComputedStyle(element);
  if (
    style.overflow === 'auto' ||
    style.overflow === 'scroll' ||
    style.overflow === 'clip' ||
    style.overflow === 'hidden'
  ) {
    return element;
  } else if (element.children) {
    for (let i = 0; i < element.children.length; i++) {
      const overflowElement = findOverflowElement(element.children[i]);
      if (overflowElement) {
        return overflowElement;
      }
    }
    return null;
  }
  return null;
};

/**
 * Look for an element within the tree for a `text-overflow` CSS property of `ellipsis`.
 * This could be the passed element, or a descendant. If no element is found, `null` is returned.
 */
export const findEllipsisElement = (element: Element): Element | null => {
  const style = getComputedStyle(element);
  if (style.textOverflow === 'ellipsis' || Number(style.webkitLineClamp) > 0) {
    return element;
  } else {
    for (let i = 0; i < element.children.length; i++) {
      const overflowElement = findEllipsisElement(element.children[i]);
      if (overflowElement) {
        return overflowElement;
      }
    }
    return null;
  }
};

const getInnerText = (element: Element): string => {
  const ownerWindow = element.ownerDocument?.defaultView;
  return ownerWindow && element instanceof ownerWindow.HTMLElement ? element.innerText : '';
};

const isOverflowed = (element: Element) => {
  const overflowElement = findEllipsisElement(element) || findOverflowElement(element);

  if (overflowElement) {
    return (
      overflowElement.scrollWidth > overflowElement.clientWidth ||
      overflowElement.scrollHeight > overflowElement.clientHeight
    );
  }
  return false;
};

export interface OverflowTooltipProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {
  /**
   * The target (anchor element) for the Tooltip.
   *
   * **Note:** This **must** be a single Element, StyledComponent, or any other component that
   * forwards extra props to an Element. Tooltip works running `React.cloneElement` on the children
   * and adds extra properties like aria attributes and event handlers. This is currently a
   * limitation of the Tooltip component. Functionality will not work if this condition isn't met.
   */
  children: React.ReactElement<any>;
  /**
   * Sets the placement preference used by PopperJS.
   * @default 'top'
   */
  placement?: Placement;
  /**
   * Define fallback placements by providing a list of {@link Placement} in array (in order of preference).
   * The default preference is following the order of `top`, `right`, `bottom`, and `left`. Once the initial
   * and opposite placements are not available, the fallback placements will be in use. Use an empty array to
   * disable the fallback placements.
   */
  fallbackPlacements?: Placement[];
  /**
   * Amount of time (in ms) to delay before showing the Tooltip
   */
  showDelay?: number;
  /**
   * Amount of time (in ms) to delay before hiding the Tooltip
   */
  hideDelay?: number;
}

function mergeCallbacks<T extends {[key: string]: any}>(
  elemProps: {[key: string]: any},
  componentProps: T,
  keys: (keyof T)[] = Object.keys(componentProps)
) {
  return (keys as string[]).reduce(
    (mergedProps, key) => {
      if (typeof elemProps[key] === 'function') {
        mergedProps[key] = mergeCallback(componentProps[key], elemProps[key]);
      } else {
        mergedProps[key] = componentProps[key];
      }
      return mergedProps;
    },
    {} as {[key: string]: any}
  );
}

export const OverflowTooltip = ({
  placement = 'top',
  fallbackPlacements = defaultFallbackPlacements,
  children,
  showDelay,
  hideDelay,
  ...elemProps
}: OverflowTooltipProps) => {
  const [titleText, setTitleText] = React.useState('');
  const {targetProps, popperProps, tooltipProps} = useTooltip({
    type: 'muted',
    showDelay,
    hideDelay,
  });

  const onMouseEnter = (event: React.MouseEvent) => {
    const target = event.currentTarget;
    setTitleText(getInnerText(target));
    if (isOverflowed(target)) {
      targetProps.onMouseEnter(event as React.MouseEvent);
    }
  };
  const onFocus = (event: React.FocusEvent) => {
    const target = event.currentTarget;
    setTitleText(getInnerText(target));
    if (isOverflowed(target)) {
      targetProps.onFocus(event as React.FocusEvent);
    }
  };

  return (
    <React.Fragment>
      {React.cloneElement(children, {
        ...targetProps,
        onMouseEnter,
        onFocus,
        'aria-label': undefined, // we don't need a label, the accessible name will be the content
        ...mergeCallbacks({...targetProps, onMouseEnter, onFocus}, children.props),
      })}
      <Popper placement={placement} fallbackPlacements={fallbackPlacements} {...popperProps}>
        {({placement}) => {
          const transformOrigin = getTransformFromPlacement(placement);
          return (
            <TooltipContainer transformOrigin={transformOrigin} {...tooltipProps} {...elemProps}>
              {titleText}
            </TooltipContainer>
          );
        }}
      </Popper>
    </React.Fragment>
  );
};
