import { forwardRef, memo, useCallback, useState } from "react";

import { StyledButton } from "./button.styled";

import type { WithTestId } from "../../types";

interface ButtonProps extends WithTestId<React.ComponentProps<typeof StyledButton>> {
  /**
   * @deprecated Will be removed in 1.0.0. Use `children` instead
   */
  text?: string | null | undefined;
  /**
   * If passed as string the button will automatically handle the styling.
   * Should be non-interactive phrasing content.
   */
  children?: React.ReactNode;
  /**
   * Pass an extra element whose position relative `children` is controlled by the button. Typically this would be an icon.
   * Should be non-interactive phrasing content
   */
  extra?: React.ReactNode;
  /**
   * Sets the position of `extra` relative to `children`
   */
  extraPosition?: "left" | "right";
}

const createRipple: React.MouseEventHandler = (evt) => {
  const button = evt.currentTarget;
  const circle = document.createElement("span");
  const rect = button.getBoundingClientRect();
  const clickX = evt.clientX - rect.left;
  const clickY = evt.clientY - rect.top;
  const maxX = Math.max(clickX, rect.width - clickX);
  const maxY = Math.max(clickY, rect.height - clickY);
  const diameter = Math.sqrt(maxX * maxX + maxY * maxY) * 2;

  circle.style.width = circle.style.height = `${diameter}px`;
  circle.style.left = `${clickX - diameter / 2}px`;
  circle.style.top = `${clickY - diameter / 2}px`;
  circle.classList.add("ripple");

  const ripple = button.getElementsByClassName("ripple")[0];

  if (ripple) ripple.remove();

  button.appendChild(circle);
};

// eslint-disable-next-line max-lines-per-function
const ButtonComponent = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
  const {
    extra,
    extraPosition = "right",
    size = "l",
    text,
    children,
    css,
    type,
    onMouseDown,
    onMouseUp,
    className,
    ...rest
  } = props;
  const [shouldFadeOut, setShouldFadeOut] = useState(false);

  const startRippleEffect: React.MouseEventHandler<HTMLButtonElement> = useCallback(
    (evt) => {
      setShouldFadeOut(false);
      createRipple(evt);
      onMouseDown?.(evt);
    },
    [onMouseDown]
  );

  const stopRippleEffect: React.MouseEventHandler<HTMLButtonElement> = useCallback(
    (evt) => {
      setShouldFadeOut(true);
      onMouseUp?.(evt);
    },
    [onMouseUp]
  );

  if (text && children) {
    throw new Error(
      "Don't use `text` and `children` at the same time. `text` is deprecated use `children` instead!"
    );
  }

  return (
    <StyledButton
      ref={ref}
      css={{
        ...css,
        flexDirection: extraPosition === "left" ? "row-reverse" : "row",
        ...(shouldFadeOut && {
          "&& span.ripple": {
            opacity: 0,
            transition: "opacity 400ms cubic-bezier(0.87, 0, 0.13, 1)",
          },
        }),
      }}
      size={size}
      type={type ?? "button"}
      {...rest}
      onMouseDown={startRippleEffect}
      onMouseUp={stopRippleEffect}
      className={[className, "cui-button"].filter(Boolean).join(" ")}
    >
      {children ?? text}
      {extra}
    </StyledButton>
  );
});

ButtonComponent.displayName = "Button";

/**
 * A Reusable `<Button>` component.
 */
export const Button = memo(ButtonComponent);

Button.toString = () => ".cui-button";
