import _ from 'lodash';
import '../../globals.js';
// Copyright 2016-2024, University of Colorado Boulder
/**
 * Shows a track on a slider.  Must be supplied a Node for rendering the track.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Jesse Greenberg (PhET Interactive Simulations)
 */
import DerivedProperty from '../../axon/js/DerivedProperty.js';
import TinyProperty from '../../axon/js/TinyProperty.js';
import Dimension2 from '../../dot/js/Dimension2.js';
import LinearFunction from '../../dot/js/LinearFunction.js';
import Range from '../../dot/js/Range.js';
import optionize from '../../phet-core/js/optionize.js';
import { DragListener, Node, WidthSizable } from '../../scenery/js/imports.js';
import ValueChangeSoundPlayer from '../../tambo/js/sound-generators/ValueChangeSoundPlayer.js';
import Tandem from '../../tandem/js/Tandem.js';
import Slider from './Slider.js';
import sun from './sun.js';
export default class SliderTrack extends WidthSizable(Node) {
    constructor(valueProperty, trackNode, range, providedOptions) {
        super();
        this.rangeProperty = range instanceof Range ? new TinyProperty(range) : range;
        const options = optionize()({
            size: new Dimension2(100, 5),
            startDrag: _.noop, // called when a drag sequence starts
            drag: _.noop, // called at the beginning of a drag event, before any other drag work happens
            endDrag: _.noop, // called when a drag sequence ends
            constrainValue: _.identity, // called before valueProperty is set
            enabledRangeProperty: this.rangeProperty,
            soundGenerator: Slider.DEFAULT_SOUND_GENERATOR,
            valueChangeSoundGeneratorOptions: {},
            voicingOnEndResponse: _.noop,
            leftVisualOverflow: 0,
            rightVisualOverflow: 0,
            // phet-io
            tandem: Tandem.REQUIRED,
            tandemNameSuffix: 'TrackNode'
        }, providedOptions);
        // If no sound generator was provided, create the default.
        if (options.soundGenerator === Slider.DEFAULT_SOUND_GENERATOR) {
            // NOTE: We'll want to update ValueChangeSoundPlayer for dynamic ranges if it's used more for that
            options.soundGenerator = new ValueChangeSoundPlayer(this.rangeProperty.value, options.valueChangeSoundGeneratorOptions || {});
        }
        else if (options.soundGenerator === null) {
            options.soundGenerator = ValueChangeSoundPlayer.NO_SOUND;
        }
        this.leftVisualOverflow = options.leftVisualOverflow;
        this.rightVisualOverflow = options.rightVisualOverflow;
        this.minimumSize = options.size;
        this.minimumWidth = this.minimumSize.width;
        this.internalWidthProperty = new DerivedProperty([this.localPreferredWidthProperty], localPreferredWidth => {
            // Our preferred width should be subtracted out by the anticipated overflow, so that our size can be slightly
            // smaller.
            return (localPreferredWidth === null
                ? this.minimumSize.width
                : Math.max(this.minimumSize.width, localPreferredWidth)) - options.leftVisualOverflow - options.rightVisualOverflow;
        });
        this.sizeProperty = new DerivedProperty([
            this.internalWidthProperty
        ], width => new Dimension2(width, this.minimumSize.height));
        // NOTE: Slider needs to make a lot of assumptions about how this works (in order to figure out proper layout).
        // DO NOT change without taking a CLOSE CLOSE look at Slider's layout code.
        this.valueToPositionProperty = new DerivedProperty([this.rangeProperty, this.internalWidthProperty], (range, width) => {
            return new LinearFunction(range.min, range.max, 0, width, true /* clamp */);
        });
        // click in the track to change the value, continue dragging if desired
        const handleTrackEvent = (event, trail) => {
            const oldValue = valueProperty.value;
            const transform = trail.subtrailTo(this).getTransform();
            const x = transform.inversePosition2(event.pointer.point).x;
            const value = this.valueToPositionProperty.value.inverse(x);
            const valueInRange = options.enabledRangeProperty.value.constrainValue(value);
            const newValue = options.constrainValue(valueInRange);
            valueProperty.set(newValue);
            // Down events on the track can cause value changes.  If that's what just happened, play a sound.
            if (event.type === 'down') {
                options.soundGenerator.playSoundIfThresholdReached(newValue, oldValue);
            }
        };
        this.addChild(trackNode);
        let valueOnStart = valueProperty.value;
        this.dragListener = new DragListener({
            tandem: options.tandem.createTandem('dragListener'),
            start: (event, listener) => {
                options.startDrag(event);
                valueOnStart = valueProperty.value;
                handleTrackEvent(event, listener.pressedTrail);
            },
            drag: (event, listener) => {
                options.drag(event);
                // Reuse the same handleTrackEvent but make sure the startedCallbacks call is made before the value changes
                handleTrackEvent(event, listener.pressedTrail);
            },
            end: event => {
                options.endDrag(event);
                options.voicingOnEndResponse(valueOnStart);
            }
        });
        trackNode.addInputListener(this.dragListener);
        this.mutate(options);
        this.disposeSliderTrack = () => {
            trackNode.removeInputListener(this.dragListener);
            this.dragListener.dispose();
        };
    }
    dispose() {
        this.disposeSliderTrack();
        super.dispose();
    }
}
sun.register('SliderTrack', SliderTrack);
//# sourceMappingURL=SliderTrack.js.map