import buttonShapes from "@insite/mobius/Button/buttonShapes";
import buttonSizeVariants from "@insite/mobius/Button/buttonSizeVariants";
import buttonTypes from "@insite/mobius/Button/buttonTypes";
import hoverAnimations from "@insite/mobius/Button/hoverAnimations";
import { ButtonIcon, ButtonPresentationProps, ButtonProps } from "@insite/mobius/Button/Props";
import { BaseTheme } from "@insite/mobius/globals/baseTheme";
import Typography from "@insite/mobius/Typography";
import applyPropBuilder from "@insite/mobius/utilities/applyPropBuilder";
import { HasDisablerContext, withDisabler } from "@insite/mobius/utilities/DisablerContext";
import get from "@insite/mobius/utilities/get";
import getProp from "@insite/mobius/utilities/getProp";
import InjectableCss from "@insite/mobius/utilities/InjectableCss";
import injectCss from "@insite/mobius/utilities/injectCss";
import omitMultiple from "@insite/mobius/utilities/omitMultiple";
import * as React from "react";
import styled, { css, ThemeProps, withTheme } from "styled-components";
import { applyStyleModifiers } from "styled-components-modifiers";

type BWT = Pick<ButtonProps, "shadow"> &
    InjectableCss & {
        _sizeVariant: ButtonProps["sizeVariant"];
        _size: ButtonProps["size"] | null;
        _color: ButtonProps["color"];
        _shape: ButtonProps["shape"];
        // this is needed because styled-components is confused
        disabled: boolean;
    };

// this should be BWT but typescript is not happy with "Type instantiation is excessively deep and possibly infinite." and nothing else was working
const ButtonWrapper = styled.button<any>`
    cursor: pointer;
    font-family: inherit;
    min-height: ${({ _sizeVariant, _size }) => _size || get(buttonSizeVariants, [_sizeVariant, "height"])}px;
    line-height: 1;
    backface-visibility: hidden;
    transition: all ${getProp("theme.transition.duration.regular")}ms ease-in-out;
    word-break: keep-all;
    ${({ shadow, theme }) =>
        shadow &&
        `
        box-shadow: ${get(theme, "shadows.1")};
    `}
    padding: ${({ _sizeVariant }) => get(buttonSizeVariants, [_sizeVariant, "padding"]) || "0 1em"};
    ${applyStyleModifiers(buttonShapes, "_shape")}
    ${applyStyleModifiers(buttonTypes, "buttonType")}
    ${applyStyleModifiers(hoverAnimations, "hoverAnimation")}
    &:disabled {
        cursor: not-allowed;
    }
    ${ButtonIcon} {
        position: relative;
        top: 0.125em;
    }
    &:focus {
        outline-offset: 1px;
        outline-color: ${getProp("theme.focus.color", "#09f")};
        outline-style: ${getProp("theme.focus.style", "solid")};
        outline-width: ${getProp("theme.focus.width", "2px")};
    }
    &::-moz-focus-inner {
        border: none;
    }
    ${injectCss}
`;

const omitKeys = ["color", "shape", "size", "sizeVariant"] as const;

type ButtonContextProps = ThemeProps<BaseTheme>;

const Button: React.FC<ButtonProps & ButtonContextProps & HasDisablerContext> = props => {
    const {
        children,
        css: buttonCss,
        disable,
        disabled,
        forwardAs,
        icon,
        typographyProps,
        theme,
        variant = "primary",
        mergeCss,
        isFocusAfterMount,
        ...otherProps
    } = props;
    const position = icon?.position;
    const src = icon?.src;
    const buttonRef = React.useRef<HTMLButtonElement | null>(null);

    let variantProps: ButtonPresentationProps = {};
    if (variant) {
        variantProps = theme.button[variant];
    }
    const { applyProp, applyStyledProp, spreadProps } = applyPropBuilder(props, {
        component: "button",
        propKey: variant,
    });
    const sizeVariant = applyProp("sizeVariant", "medium") as keyof typeof buttonSizeVariants;
    const size = applyProp("size", null) as number | null;
    const resolvedMergeCss = mergeCss ?? variantProps.mergeCss;

    React.useEffect(() => {
        if (isFocusAfterMount) {
            buttonRef?.current?.focus();
        }
    }, []);

    return (
        <ButtonWrapper
            {...omitMultiple(variantProps, omitKeys)}
            ref={buttonRef}
            as={forwardAs}
            css={applyStyledProp("css", resolvedMergeCss)}
            _color={applyProp("color", "primary")}
            _shape={applyProp("shape", "rectangle")}
            _sizeVariant={sizeVariant}
            _size={size}
            // Because disabled doesn't accept undefined
            // eslint-disable-next-line no-unneeded-ternary
            disabled={disable || disabled ? true : false}
            {...omitMultiple(otherProps, omitKeys)}
        >
            {position === "left" ? (
                <ButtonIcon
                    src={src}
                    size={buttonSizeVariants[sizeVariant].icon}
                    css={css`
                        margin-right: ${buttonSizeVariants[sizeVariant].iconPadding}px;
                    `}
                />
            ) : null}
            {typeof children === "string" ? (
                <Typography
                    size={get(buttonSizeVariants, [sizeVariant, "fontSize"]) || buttonSizeVariants.medium.fontSize}
                    mergeCss={mergeCss}
                    {...spreadProps("typographyProps")}
                >
                    {/* Note: if passing an icon into the button, it will only receive button styles
                    if passed as a `ButtonIcon` */}
                    {children}
                </Typography>
            ) : (
                children
            )}
            {position === "right" ? (
                <ButtonIcon
                    src={src}
                    size={buttonSizeVariants[sizeVariant].icon}
                    css={css`
                        margin-left: ${buttonSizeVariants[sizeVariant].iconPadding}px;
                    `}
                />
            ) : null}
        </ButtonWrapper>
    );
};

Button.displayName = "Button";
/** @component */
export default withDisabler(withTheme(Button));
export * from "./Props";
export { ButtonIcon, ButtonWrapper };
