import '../../assert/js/assert.js';
import '../../globals.js';
// Copyright 2019-2025, University of Colorado Boulder
/**
 * Event & listener abstraction for a single "event" type. The type provides extra functionality beyond just notifying
 * listeners. It adds PhET-iO instrumentation capabilities as well as validation. For the lightest-weight, fastest
 * solution with the smallest memory footprint, see `TinyEmitter`.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Michael Kauzmann (PhET Interactive Simulations)
 */
import optionize from '../../phet-core/js/optionize.js';
import IOTypeCache from '../../tandem/js/IOTypeCache.js';
import PhetioDataHandler from '../../tandem/js/PhetioDataHandler.js';
import Tandem from '../../tandem/js/Tandem.js';
import ArrayIO from '../../tandem/js/types/ArrayIO.js';
import FunctionIO from '../../tandem/js/types/FunctionIO.js';
import IOType from '../../tandem/js/types/IOType.js';
import NullableIO from '../../tandem/js/types/NullableIO.js';
import StringIO from '../../tandem/js/types/StringIO.js';
import VoidIO from '../../tandem/js/types/VoidIO.js';
import axon from './axon.js';
import TinyEmitter from './TinyEmitter.js';
// By default, Emitters are not stateful
const PHET_IO_STATE_DEFAULT = false;
class Emitter extends PhetioDataHandler {
    constructor(providedOptions) {
        const options = optionize()({
            reentrantNotificationStrategy: 'stack',
            phetioOuterType: Emitter.EmitterIO,
            phetioState: PHET_IO_STATE_DEFAULT
        }, providedOptions);
        super(options);
        this.tinyEmitter = new TinyEmitter(null, options.hasListenerOrderDependencies, options.reentrantNotificationStrategy);
    }
    /**
     * Emit to notify listeners
     */
    emit(...args) {
        assert && assert(this.tinyEmitter instanceof TinyEmitter, 'Emitter should not emit until constructor complete');
        assert && this.validateArguments(...args);
        // Although this is not the idiomatic pattern (since it is guarded in the phetioStartEvent), this function is
        // called so many times that it is worth the optimization for PhET brand.
        Tandem.PHET_IO_ENABLED && this.isPhetioInstrumented() && this.phetioStartEvent('emitted', {
            data: this.getPhetioData(...args)
        });
        this.tinyEmitter.emit(...args);
        Tandem.PHET_IO_ENABLED && this.isPhetioInstrumented() && this.phetioEndEvent();
    }
    /**
     * Disposes an Emitter. All listeners are removed.
     */
    dispose() {
        this.tinyEmitter.dispose();
        super.dispose();
    }
    /**
     * Adds a listener which will be called during emit.
     */
    addListener(listener, options) {
        this.tinyEmitter.addListener(listener);
        // If a disposer is specified, then automatically remove this listener when the disposer is disposed.
        options?.disposer && this.addDisposerAction('listener', listener, options.disposer, () => this.removeListener(listener));
    }
    /**
     * Removes a listener
     */
    removeListener(listener) {
        this.tinyEmitter.removeListener(listener);
        // undo addDisposer (see above)
        this.removeDisposerAction('listener', listener);
    }
    /**
     * Removes all the listeners
     */
    removeAllListeners() {
        this.tinyEmitter.removeAllListeners();
        // undo addDisposer (see above)
        this.removeAllDisposerActions('listener');
    }
    /**
     * Checks whether a listener is registered with this Emitter
     */
    hasListener(listener) {
        return this.tinyEmitter.hasListener(listener);
    }
    /**
     * Returns true if there are any listeners.
     */
    hasListeners() {
        return this.tinyEmitter.hasListeners();
    }
    /**
     * Returns the number of listeners.
     */
    getListenerCount() {
        return this.tinyEmitter.getListenerCount();
    }
    /**
     * Convenience function for debugging a Property's value. It prints the new value on registration and when changed.
     * @param name - debug name to be printed on the console
     * @returns - the handle to the listener added in case it needs to be removed later
     */
    debug(name) {
        const listener = (...args) => console.log(name, ...args);
        this.addListener(listener);
        return listener;
    }
}
/**
 * PhET-iO Type for Emitter.
 *
 * Providing validators to instrumented Emitters:
 * Instrumented Emitters should have their `validators` for each argument passed via EmitterIO (the phetioType).
 * To provide validators, there are two methods. First, by default each IOType has its own
 * validator that will be used. So specifying an argument object like `{ type: NumberIO }` will automatically use
 * `NumberIO.validator` as the validator. This can be overridden with the `validator` key (second option), like
 * { type: NumberIO, validator: { isValidValue: v=> typeof v === 'number' &&  v < 5 } }`
 * NOTE: currently the implementation is either/or, if a validator is provided via the `validator` key, the validator
 * from the `type` will be ignored.
 * see https://github.com/phetsims/axon/issues/204 for more details.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Michael Kauzmann (PhET Interactive Simulations)
 * @author Andrew Adare (PhET Interactive Simulations)
 */
Emitter.EmitterIO = (parameterTypes) => {
    const key = parameterTypes.map(getTypeName).join(',');
    if (!cache.has(key)) {
        cache.set(key, new IOType(`EmitterIO<${parameterTypes.map(getTypeName).join(', ')}>`, {
            valueType: Emitter,
            documentation: 'Emits when an event occurs and calls added listeners.',
            parameterTypes: parameterTypes,
            events: ['emitted'],
            metadataDefaults: {
                phetioState: PHET_IO_STATE_DEFAULT
            },
            methods: {
                addListener: {
                    returnType: VoidIO,
                    parameterTypes: [FunctionIO(VoidIO, parameterTypes)],
                    implementation: Emitter.prototype.addListener,
                    documentation: 'Adds a listener which will be called when the emitter emits.'
                },
                removeListener: {
                    returnType: VoidIO,
                    parameterTypes: [FunctionIO(VoidIO, parameterTypes)],
                    implementation: Emitter.prototype.removeListener,
                    documentation: 'Remove a listener.'
                },
                emit: {
                    returnType: VoidIO,
                    parameterTypes: parameterTypes,
                    // Match `Emitter.emit`'s dynamic number of arguments
                    implementation: function (...values) {
                        this.emit(...values);
                    },
                    documentation: 'Emits a single event to all listeners.',
                    invocableForReadOnlyElements: false
                },
                getValidationErrors: {
                    returnType: ArrayIO(NullableIO(StringIO)),
                    parameterTypes: parameterTypes,
                    implementation: function (...values) {
                        return this.getValidationErrors(...values);
                    },
                    documentation: 'Checks to see if the proposed values are valid. Returns an array of length N where each element is an error (string) or null if the value is valid.'
                }
            }
        }));
    }
    return cache.get(key);
};
export default Emitter;
const getTypeName = (ioType) => ioType.typeName;
// {Map.<string, IOType>} - Cache each parameterized IOType so that
// it is only created once.
const cache = new IOTypeCache();
axon.register('Emitter', Emitter);
//# sourceMappingURL=Emitter.js.map