import React, { Component } from 'react';
import { connect } from 'react-redux';

import { IReduxState, IStore } from '../../../app/types';
import { AudioElement } from '../../media/components/AbstractAudio';
import { Audio } from '../../media/components/index';
import { _addAudioElement, _removeAudioElement } from '../actions';
import { Sound } from '../reducer';

/**
 * {@link SoundCollection}'s properties.
 */
interface IProps {

    /**
     * Dispatches {@link _ADD_AUDIO_ELEMENT} Redux action which will store the
     * {@link AudioElement} for a sound in the Redux store.
     */
    _addAudioElement: Function;

    /**
     * Dispatches {@link _REMOVE_AUDIO_ELEMENT} Redux action which will remove
     * the sound's {@link AudioElement} from the Redux store.
     */
    _removeAudioElement: Function;

    /**
     * It's the 'base/sounds' reducer's state mapped to a property. It's used to
     * render audio elements for every registered sound.
     */
    _sounds: Map<string, Sound>;
}

/**
 * Collections of all global sounds used by the app for playing audio
 * notifications in response to various events. It renders <code>Audio</code>
 * element for each sound registered in the base/sounds feature. When the audio
 * resource is loaded it will emit add/remove audio element actions which will
 * attach the element to the corresponding {@link Sound} instance in the Redux
 * state. When that happens the sound can be played using the {@link playSound}
 * action.
 */
class SoundCollection extends Component<IProps> {
    /**
     * Implements React's {@link Component#render()}.
     *
     * @inheritdoc
     * @returns {ReactElement}
     */
    override render() {
        let key = 0;
        const sounds = [];

        for (const [ soundId, sound ] of this.props._sounds.entries()) {
            const { options, src } = sound;

            sounds.push(
                React.createElement(
                    Audio, {
                        key,
                        setRef: this._setRef.bind(this, soundId),
                        src,
                        loop: options?.loop
                    }));
            key += 1;
        }

        return sounds;
    }

    /**
     * Set the (reference to the) {@link AudioElement} object which implements
     * the audio playback functionality.
     *
     * @param {string} soundId - The sound Id for the audio element for which
     * the callback is being executed.
     * @param {AudioElement} element - The {@link AudioElement} instance
     * which implements the audio playback functionality.
     * @protected
     * @returns {void}
     */
    _setRef(soundId: string, element?: AudioElement) {
        if (element) {
            this.props._addAudioElement(soundId, element);
        } else {
            this.props._removeAudioElement(soundId);
        }
    }
}

/**
 * Maps (parts of) the Redux state to {@code SoundCollection}'s props.
 *
 * @param {Object} state - The redux state.
 * @private
 * @returns {{
 *     _sounds: Map<string, Sound>
 * }}
 */
function _mapStateToProps(state: IReduxState) {
    return {
        _sounds: state['features/base/sounds']
    };
}

/**
 * Maps dispatching of some actions to React component props.
 *
 * @param {Function} dispatch - Redux action dispatcher.
 * @private
 * @returns {{
 *     _addAudioElement: void,
 *     _removeAudioElement: void
 * }}
 */
export function _mapDispatchToProps(dispatch: IStore['dispatch']) {
    return {
        /**
         * Dispatches action to store the {@link AudioElement} for
         * a {@link Sound} identified by given <tt>soundId</tt> in the Redux
         * store, so that the playback can be controlled through the Redux
         * actions.
         *
         * @param {string} soundId - A global identifier which will be used to
         * identify the {@link Sound} instance for which an audio element will
         * be added.
         * @param {AudioElement} audioElement - The {@link AudioElement}
         * instance that will be stored in the Redux state of the base/sounds
         * feature, as part of the {@link Sound} object. At that point the sound
         * will be ready for playback.
         * @private
         * @returns {void}
         */
        _addAudioElement(soundId: string, audioElement: AudioElement) {
            dispatch(_addAudioElement(soundId, audioElement));
        },

        /**
         * Dispatches action to remove {@link AudioElement} from the Redux
         * store for specific {@link Sound}, because it is no longer part of
         * the DOM tree and the audio resource will be released.
         *
         * @param {string} soundId - The id of the {@link Sound} instance for
         * which an {@link AudioElement} will be removed from the Redux store.
         * @private
         * @returns {void}
         */
        _removeAudioElement(soundId: string) {
            dispatch(_removeAudioElement(soundId));
        }
    };
}

export default connect(_mapStateToProps, _mapDispatchToProps)(SoundCollection);
