import '../../../globals.js';
// Copyright 2022-2024, University of Colorado Boulder
/**
 * KeypadDialog is a Dialog sub-type that handles the creation and management of a Keypad.
 *
 * The KeypadDialog is shown when requested through the beginEdit() method, which occurs when the user presses on a
 * BallValuesPanelNumberDisplay, to allow the user to manipulate a Ball Property. Edits must be within a specified
 * range. There will be a 'Enter' button to allow the user to submit a edit, and edits are canceled if the user hides
 * the Dialog.
 *
 * KeypadDialog is created at the start of the sim and is never disposed, so no dispose method is necessary and
 * internal links are left as-is.
 *
 * @author Brandon Li
 */
import PatternStringProperty from '../../../axon/js/PatternStringProperty.js';
import optionize, { combineOptions } from '../../../phet-core/js/optionize.js';
import Keypad from '../../../scenery-phet/js/keypad/Keypad.js';
import PhetFont from '../../../scenery-phet/js/PhetFont.js';
import { Color, KeyboardListener, Node, Rectangle, RichText, Text, VBox } from '../../../scenery/js/imports.js';
import RectangularPushButton from '../../../sun/js/buttons/RectangularPushButton.js';
import Dialog from '../../../sun/js/Dialog.js';
import Tandem from '../../../tandem/js/Tandem.js';
import sceneryPhet from '../sceneryPhet.js';
import SceneryPhetStrings from '../SceneryPhetStrings.js';
class KeypadDialog extends Dialog {
    constructor(providedOptions) {
        const options = optionize()({
            font: new PhetFont(15),
            valueBoxWidth: 85,
            valueYMargin: 3,
            contentSpacing: 10,
            useRichTextRange: false,
            keypadLayout: Keypad.PositiveAndNegativeFloatingPointLayout,
            keypadDefaultTextColor: Color.BLACK,
            keypadErrorTextColor: Color.RED,
            enterButtonOptions: {
            // baseColor: CollisionLabColors.KEYPAD_ENTER_BUTTON,
            },
            keypadOptions: {},
            tandem: Tandem.OPTIONAL // TODO: is this optional or required? https://github.com/phetsims/scenery-phet/issues/540
        }, providedOptions);
        //----------------------------------------------------------------------------------------
        // Reference the content of the Dialog. Children are added later.
        const contentNode = new VBox({ spacing: options.contentSpacing, align: 'center' });
        const keypad = new Keypad(options.keypadLayout, combineOptions({}, options.keypadOptions, {
            tandem: Tandem.OPT_OUT
        }));
        options.focusOnShowNode = keypad;
        super(contentNode, options);
        // Reference to the function called when a edit in-range successfully occurs. If null, the Keypad dialog is currently
        // not shown.
        this.editValue = null;
        // Reference to the Range of the value that the Keypad is editing, if non-null.
        this.valueRange = null;
        // Reference to a potential callback function for when the Keypad is finished editing. This is provided by the client
        // in the beginEdit() method.
        this.editFinishedCallback = null;
        // So we can dispose it
        this.rangeStringProperty = null;
        this.defaultTextColor = options.keypadDefaultTextColor;
        this.errorTextColor = options.keypadErrorTextColor;
        this.keypad = keypad;
        this.rangeText = options.useRichTextRange
            ? new RichText('', { font: options.font, maxWidth: this.keypad.width })
            : new Text('', { font: options.font, maxWidth: this.keypad.width });
        this.valueText = new Text('', { font: options.font });
        // Create the Background to the valueText Node.
        const valueBackgroundNode = new Rectangle(0, 0, options.valueBoxWidth, this.height + 2 * options.valueYMargin, {
            cornerRadius: 3,
            fill: Color.WHITE,
            stroke: Color.BLACK
        });
        const valueDisplayBox = new Node({ children: [valueBackgroundNode, this.valueText] });
        // Create the enterButton, which allows the user to submit an Edit.
        const enterText = new Text(SceneryPhetStrings.key.enterStringProperty, {
            font: options.font,
            maxWidth: this.keypad.width // constrain width for i18n
        });
        const enterButton = new RectangularPushButton(combineOptions({
            listener: this.submitEdit.bind(this),
            content: enterText,
            accessibleName: SceneryPhetStrings.key.enterStringProperty,
            tandem: Tandem.OPT_OUT
        }, options.enterButtonOptions));
        // Set the children of the content of the KeypadDialog, in the correct rendering order.
        contentNode.children = [
            this.rangeText,
            valueDisplayBox,
            this.keypad,
            enterButton
        ];
        //----------------------------------------------------------------------------------------
        // Observe when the Keypad is edited and update our valueText display. Link is never disposed as KeypadDialogs
        // are never disposed.
        this.keypad.stringProperty.link(string => {
            this.valueText.string = string;
            this.valueText.center = valueBackgroundNode.center;
        });
        // Observe when a key is pressed and reset text colors. Link is never disposed.
        this.keypad.accumulatedKeysProperty.link(() => {
            this.valueText.fill = this.defaultTextColor;
            this.rangeText.fill = this.defaultTextColor;
        });
        const submitFromKeypadListener = new KeyboardListener({
            keys: ['space', 'enter'],
            fireOnDown: false,
            fire: () => this.submitEdit()
        });
        this.keypad.addInputListener(submitFromKeypadListener);
        this.disposeKeypadDialog = () => {
            submitFromKeypadListener.dispose();
            this.keypad.dispose();
            this.rangeStringProperty && this.rangeStringProperty.dispose();
            enterText.dispose(); // linked to a translated string Property
        };
    }
    /**
     * Begins an edit by showing the KeypadDialog. Called when the user presses on a BallValuesPanelNumberDisplay to allow
     * the user to manipulate a Ball.
     *
     * @param editValue - the function called when a edit in-range successfully occurs.
     * @param valueRange - the Range that the user can edit the valueProperty
     * @param patternStringProperty - the template string that formats the text on the rangeText.
     * @param editFinishedCallback - callback when edit is entered or canceled.
     */
    beginEdit(editValue, valueRange, patternStringProperty, editFinishedCallback) {
        this.keypad.clear();
        // Update references. These references are released when the edit is canceled or finished.
        this.editValue = editValue;
        this.valueRange = valueRange;
        this.editFinishedCallback = editFinishedCallback;
        // Clear a previous value out if it exists
        this.rangeStringProperty && this.rangeStringProperty.dispose();
        this.rangeStringProperty = new PatternStringProperty(patternStringProperty, {
            min: valueRange.min,
            max: valueRange.max
        }, { tandem: Tandem.OPT_OUT });
        this.rangeText.mutate({
            stringProperty: this.rangeStringProperty
        });
        // Display the KeypadDialog.
        this.show();
    }
    /**
     * Attempts to submit the current Keypad edit. If the edit is valid, the valueProperty is set and the edit is
     * finished. Otherwise, the edit is invalid, and the warnOutOfRange() method is invoked.
     *
     * This is called when the user presses the 'Enter' button.
     */
    submitEdit() {
        // If the user didn't enter anything, treat this as a cancel.
        if (this.keypad.stringProperty.value === '') {
            this.finishEdit();
            return;
        }
        // Retrieve the value from the Keypad
        const value = this.keypad.valueProperty.value;
        // If the edit is valid, the valueProperty is set and the edit.
        if (value !== null && Number.isFinite(value) && (!this.valueRange || this.valueRange.contains(value))) {
            this.editValue !== null && this.editValue(value);
            this.finishEdit();
        }
        else {
            this.warnOutOfRange();
        }
    }
    /**
     * Changes the text colors of the Value and the Range Text to indicate that a entered Edit is out of range.
     */
    warnOutOfRange() {
        this.valueText.fill = this.errorTextColor;
        this.rangeText.fill = this.errorTextColor;
        this.keypad.setClearOnNextKeyPress(true);
    }
    /**
     * Convenience method to finish the KeypadDialog.
     *
     * This method is invoked when a edit is canceled or when a valid edit is entered.
     */
    finishEdit() {
        this.hide(); // Hide the KeypadDialog
        this.keypad.clear(); // Clear the Keypad
        // Release references.
        this.valueRange = null;
        this.editFinishedCallback = null;
    }
    /**
     * Hides the dialog. Overridden to also call the editFinishedCallback function when edits are canceled.
     */
    hide() {
        this.editFinishedCallback && this.editFinishedCallback();
        super.hide();
    }
    dispose() {
        this.disposeKeypadDialog();
        super.dispose();
    }
}
sceneryPhet.register('KeypadDialog', KeypadDialog);
export default KeypadDialog;
//# sourceMappingURL=KeypadDialog.js.map