import { clamp } from 'lodash';
import React, { useLayoutEffect, useMemo, useRef } from 'react';
import { SilkeBox } from '../silke-box';
import { PopoverAnchor, PopoverOrigin, SilkePopover } from '../silke-popover';
import { SilkeTitle } from '../silke-text';
import styles from './silke-tooltip.scss';
import { createTooltipPath } from './utils';

export type SilkeTooltipSize = 'tiny' | 'small' | 'base' | 'large' | 'auto';
export type SilkeTooltipKind = 'default' | 'info';
export type TooltipHorizontalPosition = 'center' | 'left' | 'right';
let CLIP_PATH_ID = 0;

function getOppositeOrigin(origin: PopoverOrigin = 'bottom-center'): PopoverOrigin {
  const [v, h] = origin.split('-');
  if (v === 'bottom') return ('top-' + h) as PopoverOrigin;
  if (v === 'top') return ('bottom-' + h) as PopoverOrigin;

  if (h === 'left') return (v + '-right') as PopoverOrigin;
  if (h === 'right') return (v + '-left') as PopoverOrigin;

  return origin;
}

export type SilkeTooltipProps = {
  kind?: SilkeTooltipKind;
  noArrow?: boolean;

  /**
   * Element to show on
   */
  anchor: PopoverAnchor;
  image?: string;
  show: boolean;
  size?: SilkeTooltipSize;
  targetOrigin?: PopoverOrigin;
  anchorOrigin?: PopoverOrigin;
  title?: string;
  children?: React.ReactNode;
  offsetX?: number;
  offsetY?: number;
  zIndex?: number;
  onRequestClose?: () => void;
  onClick?: () => void;
  onImageClick?: () => void;
};

/**
 * Tooltip component. Needs an anchor, shows a little tip.
 */
export function SilkeTooltip({
  kind,
  anchor,
  anchorOrigin,
  targetOrigin,
  show,
  children,
  noArrow,
  image,
  size,
  title,
  offsetX,
  offsetY,
  zIndex,
  onClick,
  onRequestClose,
  onImageClick,
}: SilkeTooltipProps) {
  const targetRef = useRef<HTMLDivElement>(null);
  const pathRef = useRef<SVGPathElement>(null);
  const clipPathRef = useRef<SVGPathElement>(null);
  const clipPathId = useMemo(() => 'silke-clip-path-' + CLIP_PATH_ID++, []);

  const anchorItem = 'current' in anchor ? anchor.current : anchor;

  if (!kind) kind = size ? 'info' : 'default';
  if (!size && (kind === 'info' || image)) size = 'base';

  useLayoutEffect(() => {
    if (!show) return;

    const tooltipEl = targetRef.current;
    if (tooltipEl) {
      const updatePath = () => {
        const anchorRect = [0, 0, 0, 0];
        if ('current' in anchor) {
          const anchorEl = anchor.current;
          if (anchorEl) {
            const rect = anchorEl.getBoundingClientRect();
            anchorRect[0] = rect.left;
            anchorRect[1] = rect.top;
            anchorRect[2] = rect.width;
            anchorRect[3] = rect.height;
          } else return;
        } else if (Array.isArray(anchor)) {
          anchorRect[0] = anchor[0] || 0;
          anchorRect[1] = anchor[1] || 0;
        }

        const { top, left, width, height, bottom, right } = tooltipEl.getBoundingClientRect();
        if (!width || !height || bottom < 0) return;
        let pointDir: 'up' | 'down' | 'left' | 'right' = 'up';
        let offset = 0.5;

        if (bottom < anchorRect[1]) {
          pointDir = 'down';
        } else if (right < anchorRect[0]) {
          pointDir = 'right';
        } else if (left > anchorRect[0] + anchorRect[2]) {
          pointDir = 'left';
        }
        if (pointDir === 'down' || pointDir === 'up') {
          offset = clamp((anchorRect[0] - left + anchorRect[2] / 2) / width, 0, 1);
        } else {
          offset = clamp((anchorRect[1] - top + anchorRect[3] / 2) / height, 0, 1);
        }

        const d = createTooltipPath(pointDir, offset, width, height, kind === 'info' ? 8 : 4);
        clipPathRef.current?.setAttribute('d', d);
        pathRef.current?.setAttribute('d', d);
      };

      const observer = new ResizeObserver(updatePath);
      observer.observe(tooltipEl);
      updatePath();

      setTimeout(updatePath, 50);

      const interval = setInterval(updatePath, 500);

      return () => {
        observer.disconnect();
        clearInterval(interval);
      };
    }
  }, [show, anchor, noArrow, anchorItem, children, kind]);

  if (!show) return null;

  if (!anchorOrigin) anchorOrigin = 'bottom-center';
  if (!targetOrigin) targetOrigin = getOppositeOrigin(anchorOrigin);
  const [anchorV, anchorH] = anchorOrigin.split('-');

  let cl = styles.root;
  cl += ' ' + styles[kind || 'default'];

  if (size) cl += ' ' + styles[size];

  return (
    <SilkePopover
      zIndex={zIndex}
      anchor={anchor}
      hide={!show}
      anchorOrigin={anchorOrigin}
      targetOrigin={targetOrigin}
      offsetY={(anchorV === 'center' ? 0 : anchorV === 'top' ? -6 : 6) + (offsetY || 0)}
      offsetX={(anchorH === 'center' ? 0 : anchorH === 'left' ? -6 : 6) + (offsetX || 0)}
      onRequestClose={onRequestClose}
    >
      <div className={cl} ref={targetRef}>
        <svg>
          <defs>
            <clipPath id={clipPathId}>
              <path ref={clipPathRef} />
            </clipPath>
          </defs>
          <path ref={pathRef} />
        </svg>
        <SilkeBox column style={{ clipPath: `url(#${clipPathId})` }} onClick={onClick}>
          {image && <img className={styles.img} src={image} alt="" onClick={onImageClick} />}
          <SilkeBox column className={styles.content}>
            {title && (
              <SilkeTitle kind="xxs" weight="strong">
                {title}
              </SilkeTitle>
            )}
            {children}
          </SilkeBox>
        </SilkeBox>
      </div>
    </SilkePopover>
  );
}
