import applyPropBuilder from "@insite/mobius/utilities/applyPropBuilder";
import get from "@insite/mobius/utilities/get";
import { StyledProp } from "@insite/mobius/utilities/InjectableCss";
import injectCss from "@insite/mobius/utilities/injectCss";
import MobiusStyledComponentProps from "@insite/mobius/utilities/MobiusStyledComponentProps";
import resolveColor from "@insite/mobius/utilities/resolveColor";
import * as React from "react";
import styled, { keyframes, withTheme } from "styled-components";

interface LoadingSpinnerPresentationProps extends React.SVGAttributes<SVGElement> {
    /** The stroke color of the circular segment.
     * @themable */
    color?: string;
    /** CSS string or styled-components function to be injected into this component.
     * @themable */
    css?: StyledProp<LoadingSpinnerComponentProps>;
    /** The height and width of the loading spinner.
     * @themable */
    size?: number;
    /**
     * Indicates how the `css` property is combined with the variant `css` property from the theme.
     * If true, the variant css is applied first and then the component css is applied after causing
     * a merge, much like normal CSS. If false, only the component css is applied, overriding the variant css in the theme.
     */
    mergeCss?: boolean;
}

type LoadingSpinnerComponentProps = MobiusStyledComponentProps<"svg", {}>;

export type LoadingSpinnerProps = LoadingSpinnerPresentationProps & LoadingSpinnerComponentProps;

const duration = "1.4s";
const dashOffset = 187;

const rotator = keyframes`
    0% {
        transform: rotate(0deg);
    }
    100% {
        transform: rotate(270deg);
    }
`;

const dash = keyframes`
    0% {
        stroke-dashoffset: ${dashOffset}px;
    }
    50% {
        stroke-dashoffset: ${dashOffset / 4}px;
        transform: rotate(135deg);
    }
    100% {
        stroke-dashoffset: ${dashOffset}px;
        transform: rotate(450deg);
    }
`;

type Attributes = {
    viewBox: string;
    xmlns?: string;
    children: React.ReactNode;
};

const spinnerSvgAttributes: Attributes = {
    viewBox: "0 0 66 66",
    xmlns: "http://www.w3.org/2000/svg",
    children: <circle cx="33" cy="33" r="30" />,
};

const LoadingSpinnerStyle = styled.svg.attrs<LoadingSpinnerPresentationProps, LoadingSpinnerPresentationProps>(
    () => spinnerSvgAttributes,
)`
    height: ${({ size, theme }) => size || get(theme, "loadingSpinner.defaultProps.size") || "40"}px;
    width: ${({ size, theme }) => size || get(theme, "loadingSpinner.defaultProps.size") || "40"}px;
    animation: ${rotator} ${duration} linear infinite;
    circle {
        fill: none;
        stroke: ${({ color, theme }) => {
            const colorOverride = color || get(theme, "loadingSpinner.defaultProps.color");
            return colorOverride ? resolveColor(colorOverride, theme) : "currentColor";
        }};
        stroke-dasharray: ${dashOffset}px;
        stroke-dashoffset: 0;
        stroke-linecap: round;
        stroke-width: 6px;
        transform-origin: center;
        animation: ${dash} ${duration} ease-in-out infinite;
    }
    ${props => get(props, "css") || get(props, "theme.loadingSpinner.defaultProps.css")}
    ${injectCss}
`;

const LoadingSpinner: React.FC<LoadingSpinnerProps> = withTheme(props => {
    const { mergeCss, ...otherProps } = props;
    const { applyStyledProp } = applyPropBuilder(props, { component: "loadingSpinner" });
    const resolvedMergeCss = mergeCss ?? otherProps?.theme?.loadingSpinner?.defaultProps?.mergeCss;

    return <LoadingSpinnerStyle css={applyStyledProp("css", resolvedMergeCss)} {...otherProps} />;
});

/** @component */
export default LoadingSpinner;

export { LoadingSpinnerStyle };
