import '../../globals.js';
// Copyright 2022-2024, University of Colorado Boulder
/**
 * An utterance to be handed off to the AlertQueue, which manages the order of accessibility alerts
 * read by a screen reader.
 *
 * An utterance to be provided to the AlertQueue. An utterance can be one of TAlertable.
 *
 * A single Utterance can be added to the utteranceQueue multiple times. This may be so that a
 * number of alerts associated with the utterance get read in order (see alert in options). Or it
 * may be that changes are being alerted rapidly from the same source. An Utterance is considered
 * "unstable" if it is being added rapidly to the utteranceQueue. By default, utterances are only
 * announced when they are "stable", and stop getting added to the queue. This will prevent
 * a large number of alerts from the same interaction from spamming the user. See related options alertStableDelay,
 * and alertMaximumDelay.
 *
 * @author Jesse Greenberg
 * @author Michael Kauzmann (PhET Interactive Simulations)
 */
import DerivedProperty from '../../axon/js/DerivedProperty.js';
import Disposable from '../../axon/js/Disposable.js';
import DynamicProperty from '../../axon/js/DynamicProperty.js';
import NumberProperty from '../../axon/js/NumberProperty.js';
import Property from '../../axon/js/Property.js';
import TinyProperty from '../../axon/js/TinyProperty.js';
import { isTReadOnlyProperty } from '../../axon/js/TReadOnlyProperty.js';
import optionize from '../../phet-core/js/optionize.js';
import IOType from '../../tandem/js/types/IOType.js';
import NullableIO from '../../tandem/js/types/NullableIO.js';
import NumberIO from '../../tandem/js/types/NumberIO.js';
import OrIO from '../../tandem/js/types/OrIO.js';
import StringIO from '../../tandem/js/types/StringIO.js';
import responseCollector from './responseCollector.js';
import ResponsePacket from './ResponsePacket.js';
import utteranceQueueNamespace from './utteranceQueueNamespace.js';
// constants
const DEFAULT_PRIORITY = 1;
let globalIdCounter = 1;
class Utterance extends Disposable {
    constructor(providedOptions) {
        const options = optionize()({
            alert: null,
            predicate: function () { return true; },
            canAnnounceProperties: [],
            descriptionCanAnnounceProperties: [],
            voicingCanAnnounceProperties: [],
            alertStableDelay: 200,
            alertMaximumDelay: Number.MAX_VALUE,
            announcerOptions: {},
            priority: DEFAULT_PRIORITY
        }, providedOptions);
        super(options);
        this.id = globalIdCounter++;
        this._alert = options.alert;
        this.predicate = options.predicate;
        this.canAnnounceProperty = new AnnouncingControlProperty({
            dependentProperties: options.canAnnounceProperties
        });
        this.descriptionCanAnnounceProperty = new AnnouncingControlProperty({
            dependentProperties: options.descriptionCanAnnounceProperties
        });
        this.voicingCanAnnounceProperty = new AnnouncingControlProperty({
            dependentProperties: options.voicingCanAnnounceProperties
        });
        this.alertStableDelay = options.alertStableDelay;
        this.alertMaximumDelay = options.alertMaximumDelay;
        this.announcerOptions = options.announcerOptions;
        this.priorityProperty = new NumberProperty(options.priority);
        this.previousAlertText = null;
    }
    /**
     * @param alert
     * @param respectResponseCollectorProperties - if false, then do not listen to the value of responseCollector
     *                                              for creating the ResponsePacket conglomerate (just combine all available).
     */
    static getAlertStringFromResponsePacket(alert, respectResponseCollectorProperties) {
        const responsePacketOptions = alert.serialize();
        if (!respectResponseCollectorProperties) {
            responsePacketOptions.ignoreProperties = true;
        }
        return responseCollector.collectResponses(responsePacketOptions);
    }
    /**
     * Get the string to alert. This function has potential to run a function to generate the string. Use this with caution.
     * Most often UtteranceQueue should be the only spot calling this.
     * @param respectResponseCollectorProperties=false - if false, then do not listen to the value of responseCollector
     *                                              for creating the ResponsePacket conglomerate (just combine all that are supplied).
     */
    getAlertText(respectResponseCollectorProperties = false) {
        const alert = Utterance.alertableToText(this._alert, respectResponseCollectorProperties);
        this.previousAlertText = alert;
        return alert;
    }
    getAlert() {
        return this._alert;
    }
    get alert() { return this.getAlert(); }
    set alert(alert) { this.setAlert(alert); }
    setAlert(alert) {
        this._alert = alert;
    }
    /**
     * Set the alertStableDelay time, see alertStableDelay option for more information.
     *
     * BEWARE! Why does the delay time need to be changed during the lifetime of an Utterance? It did for
     * https://github.com/phetsims/gravity-force-lab-basics/issues/146, but does it for you? Be sure there is good
     * reason changing this value.
     */
    setAlertStableDelay(delay) {
        this.alertStableDelay = delay;
    }
    toString() {
        return `Utterance_${this.id}#${this.getAlertText()}`;
    }
    toStateObject() {
        return {
            alert: NullableIO(OrIO([StringIO, NumberIO])).toStateObject(this.getAlertText())
        };
    }
    reset() {
        this.previousAlertText = null;
    }
    /**
     * Set the Properties controlling whether this Utterance can announce. All Properties must be
     * true for the alert content of this Utterance to be announced. These Properties are used in the implementation
     * of this.canAnnounceProperty. Setting new canAnnounceProperties has no impact on the listeners added to
     * this.canAnnounceProperty.
     */
    setCanAnnounceProperties(canAnnounceProperties) {
        this.canAnnounceProperty.setDependentProperties(canAnnounceProperties);
    }
    set canAnnounceProperties(canAnnounceProperties) {
        this.setCanAnnounceProperties(canAnnounceProperties);
    }
    get canAnnounceProperties() { return this.getCanAnnounceProperties(); }
    /**
     * Get the Properties that control whether the alert content for this Utterance can be announced.
     * All must be true for the announcement to occur.
     */
    getCanAnnounceProperties() {
        return this.canAnnounceProperty.getDependentProperties();
    }
    /**
     * Set the Properties controlling whether this Utterance can announce to AriaLiveAnnouncer. All Properties must be
     * true for the alert content of this Utterance to be announced. These Properties are used in the implementation
     * of this.descriptionCanAnnounceProperty. Setting new descriptionCanAnnounceProperties has no impact on the
     * listeners added to this.descriptionCanAnnounceProperty. To announce to AriaLiveAnnouncer, this.canAnnounceProperty
     * must also be true
     */
    setDescriptionCanAnnounceProperties(descriptionCanAnnounceProperties) {
        this.descriptionCanAnnounceProperty.setDependentProperties(descriptionCanAnnounceProperties);
    }
    set descriptionCanAnnounceProperties(descriptionCanAnnounceProperties) {
        this.setDescriptionCanAnnounceProperties(descriptionCanAnnounceProperties);
    }
    get descriptionCanAnnounceProperties() { return this.getDescriptionCanAnnounceProperties(); }
    /**
     * Get the Properties that control whether the alert content for this Utterance can be announced.
     * All must be true for the announcement to occur.
     */
    getDescriptionCanAnnounceProperties() {
        return this.descriptionCanAnnounceProperty.getDependentProperties();
    }
    /**
     * Set the Properties controlling whether this Utterance can announce to SpeechSynthesisAnnouncer. All Properties
     * must be true for the alert content of this Utterance to be announced. These Properties are used in the
     * implementation of this.voicingCanAnnounceProperty. Setting new voicingCanAnnounceProperties has no impact
     * on the listeners added to this.voicingCanAnnounceProperty. To announce to SpeechSynthesisAnnouncer,
     * this.canAnnounceProperty must also be true.
     */
    setVoicingCanAnnounceProperties(voicingCanAnnounceProperties) {
        this.voicingCanAnnounceProperty.setDependentProperties(voicingCanAnnounceProperties);
    }
    set voicingCanAnnounceProperties(voicingCanAnnounceProperties) {
        this.setVoicingCanAnnounceProperties(voicingCanAnnounceProperties);
    }
    get voicingCanAnnounceProperties() { return this.getVoicingCanAnnounceProperties(); }
    /**
     * Get the Properties that control whether the alert content for this Utterance can be announced.
     * All must be true for the announcement to occur.
     */
    getVoicingCanAnnounceProperties() {
        return this.voicingCanAnnounceProperty.getDependentProperties();
    }
    /**
     * Make eligible for garbage collection.
     */
    dispose() {
        this.canAnnounceProperty.dispose();
        this.descriptionCanAnnounceProperty.dispose();
        this.voicingCanAnnounceProperty.dispose();
        this.priorityProperty.dispose();
        super.dispose();
    }
    /**
     * @param alertable
     * @param respectResponseCollectorProperties=false - if false, then do not listen to the value of responseCollector
     *                                              for creating the ResponsePacket conglomerate (just combine all that are supplied).
     */
    static alertableToText(alertable, respectResponseCollectorProperties = false) {
        let alert;
        if (typeof alertable === 'function') {
            alert = alertable();
        }
        else if (alertable instanceof ResponsePacket) {
            alert = Utterance.getAlertStringFromResponsePacket(alertable, respectResponseCollectorProperties);
        }
        else if (alertable instanceof Utterance) {
            return alertable.getAlertText(respectResponseCollectorProperties);
        }
        else if (isTReadOnlyProperty(alertable)) {
            alert = alertable.value;
        }
        else {
            // Nothing to process from the TAlertable, it is raw content
            alert = alertable;
        }
        return alert;
    }
}
// Priority levels that can be used by Utterances providing the `announcerOptions.priority` option.
Utterance.TOP_PRIORITY = 10;
Utterance.HIGH_PRIORITY = 5;
Utterance.MEDIUM_PRIORITY = 2;
Utterance.DEFAULT_PRIORITY = DEFAULT_PRIORITY;
Utterance.LOW_PRIORITY = 0;
Utterance.UtteranceIO = new IOType('UtteranceIO', {
    valueType: Utterance,
    documentation: 'Announces text to a specific browser technology (like aria-live or web speech)',
    toStateObject: (utterance) => utterance.toStateObject(),
    stateSchema: {
        alert: NullableIO(OrIO([StringIO, NumberIO]))
    }
});
class AnnouncingControlProperty extends DynamicProperty {
    constructor(providedOptions) {
        const options = optionize()({
            dependentProperties: []
        }, providedOptions);
        const implementationProperty = new Property(new TinyProperty(false));
        super(implementationProperty);
        this._dependentProperties = [];
        this.implementationProperty = implementationProperty;
        this.setDependentProperties(options.dependentProperties);
    }
    /**
     * Set the Properties controlling this Property's value. All Properties must be true for this Property to be true.
     */
    setDependentProperties(dependentProperties) {
        const previousImplementationProperty = this.implementationProperty.value;
        // If no dependentProperties provided, use a dummy Property that will always allow this Utterance to announce.
        const dependencyProperties = dependentProperties.length === 0 ? [new TinyProperty(true)] : dependentProperties;
        this.implementationProperty.value = DerivedProperty.and(dependencyProperties);
        // Since the implementationProperty is used in a DynamicProperty, clean this up after the listener change from the
        // above value set.
        previousImplementationProperty.dispose();
        this._dependentProperties = dependentProperties;
    }
    set dependentProperties(dependentProperties) { this.setDependentProperties(dependentProperties); }
    get dependentProperties() { return this.getDependentProperties(); }
    /**
     * Get the Properties that control whether the alert content for this Utterance can be announced.
     * All must be true for the announcement to occur.
     */
    getDependentProperties() {
        return this._dependentProperties.slice(0); // defensive copy
    }
    dispose() {
        this.implementationProperty.dispose();
        this._dependentProperties = [];
        super.dispose();
    }
}
utteranceQueueNamespace.register('Utterance', Utterance);
export default Utterance;
//# sourceMappingURL=Utterance.js.map