import React, {
  type ComponentProps,
  isValidElement,
  type ReactElement,
  type ReactNode,
} from "react";

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

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

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

const sizeToIconSize: Record<
  IconButtonProps["size"] & NonNullable<unknown>,
  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;
    }
  });
}

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

  const buttonElement = (
    <button
      className={twMerge(classes.root, ESCAPE_className)}
      disabled={disabled}
      {...restProps}
    >
      {children !== undefined ? (
        adaptIcon(Array.isArray(children) ? children : [children], name, size)
      ) : (
        <Icon
          name={name}
          color={color}
          weight={weight ?? sizeToIconWeight[size]}
          size={sizeToIconSize[size]}
        />
      )}
    </button>
  );

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

  return buttonElement;
};
