"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Stepper = void 0;
const react_1 = __importStar(require("react"));
const css_utilities_1 = require("@shopify/css-utilities");
const AppContext_1 = require("../AppContext");
const Theme_1 = require("../Theme");
const BlockStack_1 = require("../BlockStack");
const TextField_1 = require("../TextField");
const Icon_1 = require("../Icon");
const InlineError_1 = require("../InlineError");
const id_1 = require("../../utilities/id");
const Stepper_css_1 = __importDefault(require("./Stepper.css"));
const createId = id_1.createIdCreator('StepperField');
const SpinButtons = ({ handleButtonPress, handleTap, minReached, maxReached, separator, }) => {
    const translate = AppContext_1.useTranslate();
    return (<div className={css_utilities_1.classNames(Stepper_css_1.default.SpinButtonGroup)} role="group">
      <button type="button" aria-label={translate('decrement') || 'Decrement'} onTouchStart={() => handleTap(-1)} onMouseDown={(evt) => handleButtonPress(evt, -1)} className={css_utilities_1.classNames(Stepper_css_1.default.SpinButton)} disabled={minReached} tabIndex={-1}>
        <Icon_1.Icon source="minus" size="base"/>
      </button>
      {separator && <div className={css_utilities_1.classNames(Stepper_css_1.default.SpinButtonSeparator)}/>}
      <button type="button" aria-label={translate('increment') || 'Increment'} onTouchStart={() => handleTap(1)} onMouseDown={(evt) => handleButtonPress(evt, 1)} className={css_utilities_1.classNames(Stepper_css_1.default.SpinButton)} disabled={maxReached} tabIndex={-1}>
        <Icon_1.Icon source="plus" size="base"/>
      </button>
    </div>);
};
function Stepper({ accessibilityDescription, disabled, label, error, name, min = 0, max, value, step = 1, id: explicitId, required, onChange, onBlur, onFocus, readonly, }) {
    const { stepper: { separator = true }, } = Theme_1.useThemeConfiguration();
    const [quantity, setQuantity] = react_1.useState(value);
    const [spinning, setSpinning] = react_1.useState(false);
    const [minReached, setMinReached] = react_1.useState(false);
    const [maxReached, setMaxReached] = react_1.useState(false);
    const buttonPressTimer = react_1.useRef();
    const id = id_1.useId(explicitId, createId);
    react_1.useEffect(() => {
        setQuantity(Number(value));
    }, [value]);
    react_1.useEffect(() => {
        setMinReached(() => {
            return quantity !== undefined && min !== undefined && quantity <= min;
        });
    }, [quantity, min]);
    react_1.useEffect(() => {
        setMaxReached(() => {
            return quantity !== undefined && max !== undefined && quantity >= max;
        });
    }, [quantity, max]);
    // because we do not register value changes until the blur, we need to manually handle disabling/enabling spin buttons onInput
    const handleMinOrMaxReached = (value) => {
        const numVal = Number(value);
        if (numVal <= min)
            setMinReached(true);
        if (max !== undefined && numVal >= max)
            setMaxReached(true);
        if (numVal > min)
            setMinReached(false);
        if (max !== undefined && numVal < max)
            setMaxReached(false);
    };
    const errorMarkup = error && (<InlineError_1.InlineError controlID={id}>{error}</InlineError_1.InlineError>);
    const calculateQuantity = react_1.useCallback(({ factor, prevQuantity, max, min, step }) => {
        const normalizedMax = max === undefined ? Infinity : max;
        const normalizedMin = min === undefined ? -Infinity : min;
        const quantity = prevQuantity ? prevQuantity : 0;
        if (isNaN(quantity)) {
            return 0;
        }
        // Returns the length of decimal places in a number
        const decimalLength = (num) => (num.toString().split('.')[1] || []).length;
        // Making sure the new value has the same length of decimal places as the
        // step / value has.
        const decimalPlaces = Math.max(decimalLength(quantity), decimalLength(step));
        const newQuantity = Math.min(Number(normalizedMax), Math.max(quantity + factor * step, Number(normalizedMin)));
        return parseFloat(newQuantity.toFixed(decimalPlaces));
    }, []);
    const handleLocalQuantityChange = react_1.useCallback((factor) => {
        setQuantity((prevQuantity) => {
            return calculateQuantity({ factor, prevQuantity, max, min, step });
        });
    }, [step, max, min, calculateQuantity]);
    const handleTap = react_1.useCallback((factor) => {
        setQuantity((prevQuantity) => {
            const val = calculateQuantity({ factor, prevQuantity, max, min, step });
            onChange === null || onChange === void 0 ? void 0 : onChange(`${val}`);
            return val;
        });
    }, [step, max, min, calculateQuantity, onChange]);
    /* this mimics native spinner press and hold
     * and is lifted from polaris-react
     * https://github.com/Shopify/polaris-react/blob/main/src/components/TextField/TextField.tsx#L320-L346
     **/
    const handleButtonRelease = react_1.useCallback(() => {
        clearTimeout(buttonPressTimer.current);
        setSpinning(false);
        setQuantity((newQuantity) => {
            if (newQuantity !== undefined) {
                onChange === null || onChange === void 0 ? void 0 : onChange(`${newQuantity}`);
            }
            return newQuantity === undefined ? quantity : newQuantity;
        });
    }, [onChange, quantity]);
    const handleButtonPress = react_1.useCallback((evt, factor) => {
        // only increment/decrement if it's left click
        if ((evt === null || evt === void 0 ? void 0 : evt.button) !== 0)
            return;
        // if somehow spinning already stop it
        if (spinning) {
            handleButtonRelease();
            return;
        }
        const minInterval = 50;
        const decrementBy = 10;
        let interval = 200;
        const onChangeInterval = () => {
            if (!spinning)
                setSpinning(true);
            if (interval > minInterval)
                interval -= decrementBy;
            handleLocalQuantityChange(factor);
            buttonPressTimer.current = window.setTimeout(onChangeInterval, interval);
        };
        buttonPressTimer.current = window.setTimeout(onChangeInterval, 0);
        document.addEventListener('mouseup', handleButtonRelease, {
            once: true,
        });
        return () => {
            document.removeEventListener('mouseup', handleButtonRelease);
        };
    }, [handleButtonRelease, handleLocalQuantityChange, spinning]);
    return (<BlockStack_1.BlockStack spacing="tight">
      <TextField_1.TextFieldInternal accessibilityDescription={accessibilityDescription} aria-required={required} disabled={disabled} error={Boolean(error)} id={id} label={label} max={max} min={min} name={name} readonly={readonly} required={required} step={step} type="number" value={quantity === undefined ? '' : `${quantity}`} onInput={handleMinOrMaxReached} onChange={(value) => {
        setQuantity(Number(value));
        onChange === null || onChange === void 0 ? void 0 : onChange(value);
    }} onBlur={onBlur} onFocus={onFocus}>
        {!disabled && !readonly && (<SpinButtons handleButtonPress={handleButtonPress} handleTap={handleTap} maxReached={maxReached} minReached={minReached} separator={separator}/>)}
      </TextField_1.TextFieldInternal>
      {errorMarkup}
    </BlockStack_1.BlockStack>);
}
exports.Stepper = Stepper;
