import '../../chipper/js/initialize-globals.js';
import '../../assert/js/assert.js';
import '../../tandem/js/PhetioIDUtils.js';
import '../../globals.js';
import validate from '../../axon/js/validate.js';
import { m3 } from '../../dot/js/Matrix3.js';
import InstanceRegistry from '../../phet-core/js/documentation/InstanceRegistry.js';
import optionize from '../../phet-core/js/optionize.js';
import { assertNoAdditionalChildren, FireListener, LayoutConstraint, Node, Path, Rectangle, SceneryConstants, Voicing, WidthSizable } from '../../scenery/js/imports.js';
import checkEmptySolidShape from '../../sherpa/js/fontawesome-4/checkEmptySolidShape.js';
import checkSquareOSolidShape from '../../sherpa/js/fontawesome-4/checkSquareOSolidShape.js';
import sharedSoundPlayers from '../../tambo/js/sharedSoundPlayers.js';
import EventType from '../../tandem/js/EventType.js';
import PhetioAction from '../../tandem/js/PhetioAction.js';
import PhetioObject from '../../tandem/js/PhetioObject.js';
import Tandem from '../../tandem/js/Tandem.js';
import Utterance from '../../utterance-queue/js/Utterance.js';
import sun from './sun.js';
// constants
const BOOLEAN_VALIDATOR = { valueType: 'boolean' };
const SHAPE_MATRIX = m3(0.025, 0, 0, 0, -0.025, 0, 0, 0, 1); // to create a unity-scale icon
const uncheckedShape = checkEmptySolidShape.transformed(SHAPE_MATRIX);
const checkedShape = checkSquareOSolidShape.transformed(SHAPE_MATRIX);
export default class Checkbox extends WidthSizable(Voicing(Node)) {
    constructor(property, content, providedOptions) {
        const options = optionize()({
            // CheckboxOptions
            spacing: 5,
            boxWidth: 21,
            checkboxColor: 'black',
            checkboxColorBackground: 'white',
            touchAreaXDilation: 0,
            touchAreaYDilation: 0,
            mouseAreaXDilation: 0,
            mouseAreaYDilation: 0,
            checkedSoundPlayer: sharedSoundPlayers.get('checkboxChecked'),
            uncheckedSoundPlayer: sharedSoundPlayers.get('checkboxUnchecked'),
            phetioLinkProperty: true,
            // NodeOptions
            cursor: 'pointer',
            disabledOpacity: SceneryConstants.DISABLED_OPACITY,
            // phet-io
            tandem: Tandem.REQUIRED,
            tandemNameSuffix: 'Checkbox',
            phetioEventType: EventType.USER,
            visiblePropertyOptions: { phetioFeatured: true },
            phetioEnabledPropertyInstrumented: true, // opt into default PhET-iO instrumented enabledProperty
            // to support properly passing this to children, see https://github.com/phetsims/tandem/issues/60
            phetioReadOnly: PhetioObject.DEFAULT_OPTIONS.phetioReadOnly,
            phetioFeatured: true,
            // pdom
            tagName: 'input',
            inputType: 'checkbox',
            appendDescription: true,
            // voicing
            voicingCheckedObjectResponse: null,
            voicingUncheckedObjectResponse: null,
            // Utterances to be spoken with a screen reader after the checkbox is pressed. Also used for
            // the voicingContextResponse
            checkedContextResponse: null,
            uncheckedContextResponse: null,
            voiceNameResponseOnSelection: true
        }, providedOptions);
        super();
        // We need to record if the mouse/touch areas are customized, so that we can avoid overwriting them.
        // public for use by CheckboxConstraint only!
        this._isMouseAreaCustomized = false;
        this._isTouchAreaCustomized = false;
        this._isSettingAreas = false;
        // sends out notifications when the checkbox is toggled.
        const toggleAction = new PhetioAction(() => {
            property.value = !property.value;
            validate(property.value, BOOLEAN_VALIDATOR);
            if (property.value) {
                options.checkedSoundPlayer.play();
                options.checkedContextResponse && this.alertDescriptionUtterance(options.checkedContextResponse);
                this.voicingSpeakResponse({
                    nameResponse: options.voiceNameResponseOnSelection ? this.voicingNameResponse : null,
                    objectResponse: Utterance.alertableToText(options.voicingCheckedObjectResponse),
                    contextResponse: Utterance.alertableToText(options.checkedContextResponse)
                });
            }
            else {
                options.uncheckedSoundPlayer.play();
                options.uncheckedContextResponse && this.alertDescriptionUtterance(options.uncheckedContextResponse);
                this.voicingSpeakResponse({
                    nameResponse: options.voiceNameResponseOnSelection ? this.voicingNameResponse : null,
                    objectResponse: Utterance.alertableToText(options.voicingUncheckedObjectResponse),
                    contextResponse: Utterance.alertableToText(options.uncheckedContextResponse)
                });
            }
        }, {
            parameters: [],
            tandem: options.tandem.createTandem('toggleAction'),
            phetioDocumentation: 'Emits when user input causes the checkbox to toggle, emitting a single arg: ' +
                'the new boolean value of the checkbox state.',
            phetioReadOnly: true, // interoperability should be done through the Property, this is just for the data stream event.
            phetioEventType: EventType.USER
        });
        // Create the background.
        // Until we are creating our own shapes, just put a rectangle behind the font awesome checkbox icons.
        this.backgroundNode = new Rectangle(0, -options.boxWidth, options.boxWidth * 0.95, options.boxWidth * 0.95, options.boxWidth * 0.2, options.boxWidth * 0.2, {
            fill: options.checkboxColorBackground
        });
        this.uncheckedNode = new Path(uncheckedShape, {
            fill: options.checkboxColor
        });
        const iconScale = options.boxWidth / this.uncheckedNode.width;
        this.uncheckedNode.scale(iconScale);
        this.checkedNode = new Path(checkedShape, {
            scale: iconScale,
            fill: options.checkboxColor
        });
        const checkboxNode = new Node({ children: [this.backgroundNode, this.checkedNode, this.uncheckedNode] });
        // put a rectangle on top of everything to prevent dead zones when clicking
        const rectangle = new Rectangle({});
        this.children = [
            checkboxNode,
            content,
            rectangle
        ];
        this.constraint = new CheckboxConstraint(this, checkboxNode, this.checkedNode, content, rectangle, options);
        this.constraint.updateLayout();
        content.pickable = false; // since there's a pickable rectangle on top of content
        // In case the content is an instance of a focusable Node. Checkbox icon should not be in the traversal order.
        content.pdomVisible = false;
        // interactivity
        const fireListener = new FireListener({
            fire: () => toggleAction.execute(),
            tandem: options.tandem.createTandem('fireListener')
        });
        this.addInputListener(fireListener);
        // sync with property
        const checkboxCheckedListener = (checked) => {
            this.checkedNode.visible = checked;
            this.uncheckedNode.visible = !checked;
            this.pdomChecked = checked;
        };
        property.link(checkboxCheckedListener);
        // Apply additional options
        this.mutate(options);
        // pdom - to prevent a bug with NVDA and Firefox where the label sibling receives two click events, see
        // https://github.com/phetsims/gravity-force-lab/issues/257
        this.setExcludeLabelSiblingFromInput();
        // must be after the Checkbox is instrumented
        options.phetioLinkProperty && this.addLinkedElement(property, {
            tandemName: 'property'
        });
        // support for binder documentation, stripped out in builds and only runs when ?binder is specified
        assert && phet?.chipper?.queryParameters?.binder && InstanceRegistry.registerDataURL('sun', 'Checkbox', this);
        // Decorating Checkbox with additional content is an anti-pattern, see https://github.com/phetsims/sun/issues/860
        assert && assertNoAdditionalChildren(this);
        this.disposeCheckbox = () => {
            rectangle.dispose();
            this.backgroundNode.dispose();
            this.uncheckedNode.dispose();
            this.checkedNode.dispose();
            checkboxNode.dispose();
            fireListener.dispose();
            if (property.hasListener(checkboxCheckedListener)) {
                property.unlink(checkboxCheckedListener);
            }
            // Private to Checkbox, but we need to clean up tandem.
            toggleAction.dispose();
        };
    }
    dispose() {
        this.constraint.dispose();
        this.disposeCheckbox();
        super.dispose();
    }
    /**
     * Sets the background color of the checkbox.
     */
    setCheckboxColorBackground(value) { this.backgroundNode.fill = value; }
    set checkboxColorBackground(value) { this.setCheckboxColorBackground(value); }
    get checkboxColorBackground() { return this.getCheckboxColorBackground(); }
    /**
     * Gets the background color of the checkbox.
     */
    getCheckboxColorBackground() { return this.backgroundNode.fill; }
    /**
     * Sets the color of the checkbox.
     */
    setCheckboxColor(value) { this.checkedNode.fill = this.uncheckedNode.fill = value; }
    set checkboxColor(value) { this.setCheckboxColor(value); }
    get checkboxColor() { return this.getCheckboxColor(); }
    /**
     * Gets the color of the checkbox.
     */
    getCheckboxColor() { return this.checkedNode.fill; }
    setMouseArea(area) {
        if (!this._isSettingAreas) {
            this._isMouseAreaCustomized = true;
        }
        return super.setMouseArea(area);
    }
    setTouchArea(area) {
        if (!this._isSettingAreas) {
            this._isTouchAreaCustomized = true;
        }
        return super.setTouchArea(area);
    }
}
class CheckboxConstraint extends LayoutConstraint {
    constructor(checkbox, checkboxNode, checkedNode, content, rectangle, options) {
        super(checkbox);
        this.checkbox = checkbox;
        this.checkboxNode = checkboxNode;
        this.checkedNode = checkedNode;
        this.content = content;
        this.rectangle = rectangle;
        this.options = options;
        this.checkbox.localPreferredWidthProperty.lazyLink(this._updateLayoutListener);
        this.addNode(content);
    }
    layout() {
        super.layout();
        // LayoutProxy helps with some layout operations, and will support a non-child content.
        const contentProxy = this.createLayoutProxy(this.content);
        // Should only happen when we are disconnected during disposal
        if (!contentProxy) {
            return;
        }
        const contentWidth = contentProxy.minimumWidth;
        // Our bounds are based on checkboxNode, but our layout is relative to checkedNode
        const checkboxWithoutSpacingWidth = (this.checkedNode.right - this.checkboxNode.left);
        const minimumWidth = checkboxWithoutSpacingWidth + this.options.spacing + contentWidth;
        const preferredWidth = Math.max(minimumWidth, this.checkbox.localPreferredWidth || 0);
        contentProxy.preferredWidth = preferredWidth - checkboxWithoutSpacingWidth - this.options.spacing;
        // For now just position content. Future updates could include widthResizable content?
        contentProxy.left = this.checkedNode.right + this.options.spacing;
        contentProxy.centerY = this.checkedNode.centerY;
        // Our rectangle bounds will cover the checkboxNode and content, and if necessary expand to include the full
        // preferredWidth
        this.rectangle.rectBounds = this.checkboxNode.bounds.union(contentProxy.bounds).withMaxX(Math.max(this.checkboxNode.left + preferredWidth, contentProxy.right));
        // Update pointer areas (if the client hasn't customized them)
        this.checkbox._isSettingAreas = true;
        if (!this.checkbox._isTouchAreaCustomized) {
            this.checkbox.touchArea = this.checkbox.localBounds.dilatedXY(this.options.touchAreaXDilation, this.options.touchAreaYDilation);
        }
        if (!this.checkbox._isMouseAreaCustomized) {
            this.checkbox.mouseArea = this.checkbox.localBounds.dilatedXY(this.options.mouseAreaXDilation, this.options.mouseAreaYDilation);
        }
        this.checkbox._isSettingAreas = false;
        contentProxy.dispose();
        // Set the minimumWidth last, since this may trigger a relayout
        this.checkbox.localMinimumWidth = minimumWidth;
    }
    dispose() {
        this.checkbox.localPreferredWidthProperty.unlink(this._updateLayoutListener);
        super.dispose();
    }
}
sun.register('Checkbox', Checkbox);
//# sourceMappingURL=Checkbox.js.map