// Copyright 2017-2024, University of Colorado Boulder
/**
 * A key accumulator that collects user input for integer and floating point values, intended for use in conjunction
 * with the common-code keypad.
 *
 * @author Aadish Gupta
 * @author John Blanco
 * @author Chris Malley (PixelZoom, Inc.)
 */
import '../../../globals.js';
import DerivedProperty from '../../../axon/js/DerivedProperty.js';
import DerivedStringProperty from '../../../axon/js/DerivedStringProperty.js';
import optionize from '../../../phet-core/js/optionize.js';
import Tandem from '../../../tandem/js/Tandem.js';
import NullableIO from '../../../tandem/js/types/NullableIO.js';
import NumberIO from '../../../tandem/js/types/NumberIO.js';
import AbstractKeyAccumulator from './AbstractKeyAccumulator.js';
import KeyID from './KeyID.js';
// constants
const NEGATIVE_CHAR = '\u2212';
const DECIMAL_CHAR = '.';
// Define the maximum integer that can be handled.  The portion with the explicit numeric value is necessary for IE11
// support, see https://github.com/phetsims/scenery-phet/issues/332.
const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
const MAX_DIGITS = MAX_SAFE_INTEGER.toString().length - 1;
class NumberAccumulator extends AbstractKeyAccumulator {
    constructor(providedOptions) {
        const options = optionize()({
            maxDigitsRightOfMantissa: 0,
            maxDigits: MAX_DIGITS,
            tandem: Tandem.REQUIRED,
            tandemNameSuffix: 'NumberAccumulator'
        }, providedOptions);
        // verify option values
        ;
        ;
        // Validators to be passed to AbstractKeyAccumulator
        const validators = [
            (proposedKeys) => {
                return this.getNumberOfDigits(proposedKeys) <= options.maxDigits
                    && !(this.getNumberOfDigits(proposedKeys) === options.maxDigits
                        && proposedKeys[proposedKeys.length - 1] === KeyID.DECIMAL)
                    && this.getNumberOfDigitsRightOfMantissa(proposedKeys) <= options.maxDigitsRightOfMantissa;
            }
        ];
        super(validators);
        this.stringProperty = new DerivedStringProperty([this.accumulatedKeysProperty], accumulatedKeys => {
            return this.keysToString(accumulatedKeys);
        }, {
            tandem: options.tandem.createTandem('stringProperty') // eslint-disable-line phet/bad-sim-text
        });
        this.valueProperty = new DerivedProperty(
        // Use the accumulated keys rather than the string as a dependency since it avoids issues with dependencies,
        // see https://github.com/phetsims/scenery-phet/issues/833.
        [this.accumulatedKeysProperty], accumulatedKeys => {
            const stringValue = this.keysToString(accumulatedKeys);
            return this.stringToInteger(stringValue);
        }, {
            tandem: options.tandem.createTandem('valueProperty'),
            phetioValueType: NullableIO(NumberIO)
        });
    }
    /**
     * Invoked when a key is pressed and creates proposed set of keys to be passed to the validator
     * @param keyIdentifier - identifier for the key pressed
     */
    handleKeyPressed(keyIdentifier) {
        const newArray = this.handleClearOnNextKeyPress(keyIdentifier);
        if (this.isDigit(keyIdentifier)) {
            this.removeLeadingZero(newArray);
            newArray.push(keyIdentifier);
        }
        else if (keyIdentifier === KeyID.BACKSPACE) {
            newArray.pop();
        }
        else if (keyIdentifier === KeyID.PLUS_MINUS) {
            // check if first element of array is instance of this class
            if (newArray.length > 0 && newArray[0] === KeyID.PLUS_MINUS) {
                newArray.shift();
            }
            else {
                newArray.unshift(keyIdentifier);
            }
        }
        else if (keyIdentifier === KeyID.DECIMAL) {
            if (!this.containsFloatingPoint(newArray)) {
                newArray.push(keyIdentifier);
            }
        }
        else {
            ;
        }
        // Validate and update the keys
        this.validateKeys(newArray) && this.updateKeys(newArray);
    }
    /**
     * Removes leading zeros from the array.
     */
    removeLeadingZero(array) {
        if (this.valueProperty.get() === 0 && !this.containsFloatingPoint(array)) {
            array.pop();
        }
    }
    /**
     * Converts a set of keys to a string.
     */
    keysToString(keys) {
        let returnValue = '';
        let i = 0;
        // the plus/minus key (if present) will be first key, and indicates that the number is negative
        if (keys.length > 0 && keys[i] === KeyID.PLUS_MINUS) {
            returnValue = NEGATIVE_CHAR;
            i++;
        }
        // process remaining keys
        for (; i < keys.length; i++) {
            if (keys[i] === KeyID.DECIMAL) {
                returnValue = returnValue + DECIMAL_CHAR;
            }
            else {
                // the plus/minus key should be first if present
                ;
                returnValue = returnValue + keys[i];
            }
        }
        return returnValue;
    }
    /**
     * Converts a string representation to a number.
     */
    stringToInteger(stringValue) {
        let returnValue = null;
        // if stringValue contains something other than just a minus sign...
        if (stringValue.length > 0
            && !(stringValue.length === 1 && stringValue.startsWith(NEGATIVE_CHAR))
            && (this.getNumberOfDigitsLeftOfMantissa(this.accumulatedKeysProperty.get()) > 0 ||
                this.getNumberOfDigitsRightOfMantissa(this.accumulatedKeysProperty.get()) > 0)) {
            // replace Unicode minus with vanilla '-', or parseInt will fail for negative numbers
            returnValue = Number(stringValue.replace(NEGATIVE_CHAR, '-').replace(DECIMAL_CHAR, '.'));
            ;
        }
        return returnValue;
    }
    /**
     * Gets the number of digits to the left of mantissa in the accumulator.
     */
    getNumberOfDigitsLeftOfMantissa(keys) {
        let numberOfDigits = 0;
        for (let i = 0; i < keys.length; i++) {
            if (this.isDigit(keys[i])) {
                numberOfDigits++;
            }
            if (keys[i] === KeyID.DECIMAL) {
                break;
            }
        }
        return numberOfDigits;
    }
    /**
     * Gets the number of digits to the right of mantissa in the accumulator.
     */
    getNumberOfDigitsRightOfMantissa(keys) {
        const decimalKeyIndex = keys.indexOf(KeyID.DECIMAL);
        let numberOfDigits = 0;
        if (decimalKeyIndex >= 0) {
            for (let i = decimalKeyIndex; i < keys.length; i++) {
                if (this.isDigit(keys[i])) {
                    numberOfDigits++;
                }
            }
        }
        return numberOfDigits;
    }
    /**
     * Gets the number of digits in the accumulator.
     */
    getNumberOfDigits(keys) {
        let numberOfDigits = 0;
        for (let i = 0; i < keys.length; i++) {
            if (this.isDigit(keys[i])) {
                numberOfDigits++;
            }
        }
        return numberOfDigits;
    }
    /**
     * Gets the number of digits in the accumulator.
     */
    containsFloatingPoint(keys) {
        return keys.includes(KeyID.DECIMAL);
    }
    /**
     * Returns whether the character is valid digit or not
     */
    isDigit(char) {
        return char >= '0' && char <= '9';
    }
    /**
     * clear the accumulator
     */
    clear() {
        super.clear();
        this.setClearOnNextKeyPress(false);
    }
    dispose() {
        this.valueProperty.dispose();
        this.stringProperty.dispose();
        super.dispose();
    }
}
export default NumberAccumulator;
//# sourceMappingURL=NumberAccumulator.js.map