"use client"

import {
    easeOut,
    LayoutGroup,
    motion,
    MotionConfig,
    MotionConfigContext,
    type HTMLMotionProps,
    type MotionConfigProps,
} from "motion/react"
import { ComponentProps, forwardRef, useContext, useMemo } from "react"
import { formatToParts } from "../utils/format-parts"
import { Mask, maskHeight } from "./Mask"
import { NumberSection } from "./NumberSection"

export const DEFAULT_TRANSITION = {
    // We use keyframes and times so the opacity/exit animations can last
    // as long as the layout animation, so Framer Motion doesn't have to
    // remove exiting elements until the layout animation is done.
    // This worked better in testing than safeToRemove() from usePresence()
    opacity: { duration: 1, ease: easeOut }, // perceptual duration of 0.5s
    layout: { type: "spring", duration: 1, bounce: 0 },
    y: { type: "spring", duration: 1, bounce: 0 },
} as const satisfies MotionConfigProps["transition"]

export type AnimateNumberProps = Omit<HTMLMotionProps<"span">, "children"> & {
    children: number | bigint | string
    locales?: Intl.LocalesArgument
    // Scientific and engineering notation are not supported atm:
    format?: Omit<Intl.NumberFormatOptions, "notation"> & {
        notation?: Exclude<
            Intl.NumberFormatOptions["notation"],
            "scientific" | "engineering"
        >
    }
    transition?: ComponentProps<typeof MotionConfig>["transition"]
    suffix?: string
    prefix?: string
}

export const AnimateNumber = forwardRef<HTMLDivElement, AnimateNumberProps>(
    function AnimateNumber(
        {
            children: value,
            locales,
            format,
            transition,
            style,
            suffix,
            prefix,
            ...rest
        },
        ref
    ) {
        // Split the number into parts
        const parts = useMemo(
            () => formatToParts(value, { locales, format }, prefix, suffix),
            [value, locales, format]
        )
        const { pre, integer, fraction, post, formatted } = parts

        const contextTransition = useContext(MotionConfigContext).transition
        transition = transition ?? contextTransition ?? DEFAULT_TRANSITION

        return (
            <LayoutGroup>
                <MotionConfig transition={transition}>
                    <motion.div
                        {...rest}
                        ref={ref}
                        layout // For convenience, b/c it's basically implied
                        style={{
                            lineHeight: 1, // make this one easy to override
                            ...style,
                            display: "inline-flex",
                            isolation: "isolate", // so number can be underneath first/last
                            whiteSpace: "nowrap",
                        }}
                    >
                        <motion.div
                            layout
                            aria-label={formatted}
                            style={{
                                display: "inline-flex",
                                direction: "ltr", // I think this is needed b/c numbers are always LTR?
                                isolation: "isolate", // so number can be underneath pre/post
                                position: "relative",
                                zIndex: -1, // so the whole number is under any first/last
                                // userSelect: "none", // I think adding this to the parent then undoing it on the selectable one might work a little better
                                // pointerEvents: "none",
                            }}
                        >
                            <NumberSection
                                style={{ padding: `calc(${maskHeight}/2) 0` }}
                                aria-hidden={true}
                                justify="right"
                                mode="popLayout"
                                parts={pre}
                                name="pre"
                            />
                            <Mask>
                                <NumberSection
                                    justify="right"
                                    parts={integer}
                                    name="integer"
                                />
                                {/* These last two sections need to have layout animations so that if new characters are added in-flight they know where to go: */}
                                <NumberSection
                                    layout="position"
                                    parts={fraction}
                                    name="fraction"
                                />
                            </Mask>
                            <NumberSection
                                style={{ padding: `calc(${maskHeight}/2) 0` }}
                                aria-hidden={true}
                                layout="position"
                                mode="popLayout"
                                parts={post}
                                name="post"
                            />
                        </motion.div>
                    </motion.div>
                </MotionConfig>
            </LayoutGroup>
        )
    }
)
