import { IconButton as MUIIconButton } from "@mui/material";
import { twJoin } from "tailwind-merge";
import React, {
  HTMLAttributes,
  isValidElement,
  ReactElement,
  ReactNode,
} from "react";

import { Icon, IconProps } from "../icon/Icon";
import { Tooltip, TooltipProps } from "../tooltip/Tooltip";
import { twMerge } from "../../../tailwind.config";

export interface IconButtonProps
  extends Omit<
    HTMLAttributes<HTMLButtonElement>,
    "style" | "color" | "className"
  > {
  type?: "submit" | "reset" | "button";
  name: IconProps["name"] | undefined;
  weight?: IconProps["weight"];
  color?: IconProps["color"];
  size?: "20" | "24" | "28" | "32";
  disabled?: boolean;
  /**
   * Human-readable description of what this button does.
   *
   * Examples: "Settings", "Delete this row", "Bold".
   */
  "aria-label": string;
  onClick?: (e?: React.MouseEvent<HTMLButtonElement>) => void;
  toggled?: boolean;
  tooltip?: string | ReactElement;
  tooltipProps?: Partial<TooltipProps>;
  /** Escape hatch to override the content. Use sparingly. */
  children?: ReactNode;
  ESCAPE_className?: string;
}

// The `& {}` TS magic is to remove `undefined` from the type union
const sizeToIconWeight: Record<
  IconButtonProps["size"] & {},
  IconProps["weight"]
> = {
  "20": "bold",
  "24": "bold",
  "28": "semibold",
  "32": "medium",
};

const sizeToIconSize: Record<IconButtonProps["size"] & {}, IconProps["size"]> =
  {
    "20": "12",
    "24": "16",
    "28": "20",
    "32": "24",
  };

function childIsIcon(child: ReactNode): child is ReactElement<IconProps> {
  return isValidElement(child) && child.type === Icon;
}

function adaptIcon(
  chlidren: ReactNode[],
  name: IconButtonProps["name"],
  size: IconButtonProps["size"] = "32"
) {
  return React.Children.map(chlidren, (child) => {
    if (childIsIcon(child)) {
      return React.cloneElement(child, {
        name,
        color: "current",
        weight: sizeToIconWeight[size],
        size: sizeToIconSize[size],
      });
    } else {
      return child;
    }
  });
}

const toggledStyle = twJoin(
  "bg-B75 bg-opacity-[0.32] text-B400",
  "hover:bg-opacity-[0.64] hover:text-B500"
);

const defaultStyle = twJoin("text-N100", "hover:bg-N20A hover:text-N700");

export function useIconButtonStyles({
  toggled,
}: Pick<IconButtonProps, "toggled">) {
  const classes = {
    root: twJoin(
      "bg-transparent border-0 rounded-6 p-4 inline-flex cursor-pointer outline-none",
      "focus-visible:ring-3 ring-B400FR",
      "disabled:cursor-default disabled:text-N60 disabled:bg-transparent",
      toggled ? toggledStyle : defaultStyle
    ),
  };

  return classes;
}

export const IconButton = ({
  name,
  weight = undefined,
  size = "32",
  toggled,
  onClick,
  tooltip,
  tooltipProps = {},
  children = undefined,
  color = "current",
  type = "button",
  disabled = false,
  ESCAPE_className,
  ...props
}: IconButtonProps) => {
  const classes = useIconButtonStyles({ toggled });

  const buttonProps = {
    classes: {
      ...classes,
      root: twMerge(classes.root, ESCAPE_className),
    },
    disableFocusRipple: true,
    disableRipple: true,
    onClick,
    type,
    disabled,
    ...props,
  };

  const buttonElement = (
    <MUIIconButton {...buttonProps}>
      {children !== undefined ? (
        adaptIcon(Array.isArray(children) ? children : [children], name, size)
      ) : (
        <Icon
          name={name!}
          color={color}
          weight={weight ?? sizeToIconWeight[size]}
          size={sizeToIconSize[size]}
        />
      )}
    </MUIIconButton>
  );

  if (tooltip) {
    return (
      <Tooltip tooltip={tooltip} {...tooltipProps}>
        {disabled ? <span>{buttonElement}</span> : buttonElement}
      </Tooltip>
    );
  }

  return buttonElement;
};
