// Copyright 2015-2025, University of Colorado Boulder
/**
 * Front of the HeaterCoolerNode.  It is independent from the HeaterCoolerBack so that one can easily layer objects
 * inside of the HeaterCoolerNode.  The HeaterCoolerFront contains the heater body, labels, and control slider.
 *
 * @author Siddhartha Chinthapally (Actual Concepts) on 20-11-2014.
 * @author Jesse Greenberg
 *
 */
import { string_scenery_phet_heat_StringProperty } from './strings/heat.js';
import { string_scenery_phet_cool_StringProperty } from './strings/cool.js';
import { string_scenery_phet_keyboardHelpDialog_heatCoolOff_StringProperty } from './strings/keyboardHelpDialog/heatCoolOff.js';
import '../../globals.js';
import BooleanProperty from '../../axon/js/BooleanProperty.js';
import Property from '../../axon/js/Property.js';
import Dimension2 from '../../dot/js/Dimension2.js';
import Range from '../../dot/js/Range.js';
import Shape from '../../kite/js/Shape.js';
import optionize, { combineOptions } from '../../phet-core/js/optionize.js';
import HotkeyData from '../../scenery/js/input/HotkeyData.js';
import KeyboardListener from '../../scenery/js/listeners/KeyboardListener.js';
import Node from '../../scenery/js/nodes/Node.js';
import Path from '../../scenery/js/nodes/Path.js';
import Text from '../../scenery/js/nodes/Text.js';
import Color from '../../scenery/js/util/Color.js';
import LinearGradient from '../../scenery/js/util/LinearGradient.js';
import VSlider from '../../sun/js/VSlider.js';
import Tandem from '../../tandem/js/Tandem.js';
import HeaterCoolerBack from './HeaterCoolerBack.js';
import PhetFont from './PhetFont.js';
import sceneryPhet from './sceneryPhet.js';
const DEFAULT_WIDTH = 120; // in screen coords, much of the rest of the size of the stove derives from this value
class HeaterCoolerFront extends Node {
    /**
     * @param heatCoolAmountProperty +1 for max heating, -1 for max cooling
     * @param providedOptions
     */
    constructor(heatCoolAmountProperty, providedOptions) {
        super();
        const options = optionize()({
            // SelfOptions
            baseColor: HeaterCoolerBack.DEFAULT_BASE_COLOR,
            width: 120,
            heatEnabled: true,
            coolEnabled: true,
            snapToZero: true,
            snapToZeroThreshold: 0.1,
            heatString: string_scenery_phet_heat_StringProperty,
            coolString: string_scenery_phet_cool_StringProperty,
            labelFont: new PhetFont(14),
            labelMaxWidth: 35,
            thumbSize: new Dimension2(45, 22),
            thumbTouchAreaXDilation: 11,
            thumbTouchAreaYDilation: 11,
            thumbMouseAreaXDilation: 0,
            thumbMouseAreaYDilation: 0,
            thumbFill: '#71edff',
            thumbFillHighlighted: '#bff7ff',
            heaterCoolerBack: null,
            sliderOptions: {
                trackSize: new Dimension2(10, DEFAULT_WIDTH / 2), // height of the track depends on the width
                trackFillEnabled: new LinearGradient(0, 0, DEFAULT_WIDTH / 2, 0)
                    .addColorStop(0, '#0A00F0')
                    .addColorStop(1, '#EF000F'),
                thumbLineWidth: 1.4,
                thumbCenterLineStroke: 'black',
                majorTickLength: 15,
                minorTickLength: 12
            },
            snapToZeroPropertyOptions: {
                phetioDocumentation: 'whether the slider will snap to the off position when released',
                phetioFeatured: true
            },
            phetioInstrument: true,
            // NodeOptions
            tandem: Tandem.REQUIRED,
            tandemNameSuffix: 'HeaterCoolerNode',
            phetioType: Node.NodeIO
        }, providedOptions);
        ;
        ;
        // Dimensions for the rest of the stove, dependent on the specified stove width.  Empirically determined, and could
        // be made into options if needed.
        const height = DEFAULT_WIDTH * 0.75;
        const burnerOpeningHeight = DEFAULT_WIDTH * HeaterCoolerBack.OPENING_HEIGHT_SCALE;
        const bottomWidth = DEFAULT_WIDTH * 0.80;
        // Create the body of the stove.
        const stoveBodyShape = new Shape()
            .ellipticalArc(DEFAULT_WIDTH / 2, burnerOpeningHeight / 4, DEFAULT_WIDTH / 2, burnerOpeningHeight / 2, 0, 0, Math.PI, false)
            .lineTo((DEFAULT_WIDTH - bottomWidth) / 2, height + burnerOpeningHeight / 2)
            .ellipticalArc(DEFAULT_WIDTH / 2, height + burnerOpeningHeight / 4, bottomWidth / 2, burnerOpeningHeight, 0, Math.PI, 0, true).lineTo(DEFAULT_WIDTH, burnerOpeningHeight / 2);
        const stoveBaseColor = Color.toColor(options.baseColor);
        const stoveBody = new Path(stoveBodyShape, {
            stroke: 'black',
            fill: new LinearGradient(0, 0, DEFAULT_WIDTH, 0)
                .addColorStop(0, stoveBaseColor.brighterColor(0.5))
                .addColorStop(1, stoveBaseColor.darkerColor(0.5))
        });
        this.snapToZeroProperty = new BooleanProperty(options.snapToZero, combineOptions({
            tandem: options.tandem.createTandem('snapToZeroProperty')
        }, options.snapToZeroPropertyOptions));
        const sliderRange = new Range(options.coolEnabled ? -1 : 0, options.heatEnabled ? 1 : 0);
        /**
         * determines if the slider is close enough to zero to snap to zero (even when snapToZeroProperty is false). It's
         * only applicable when both heating and cooling are enabled because that is the only configuration where it was
         * difficult for a user to set the slider to 0. This feature was requested by designers,
         * see https://github.com/phetsims/scenery-phet/issues/568.
         */
        const sliderIsCloseToZero = () => {
            return options.coolEnabled && options.heatEnabled && (heatCoolAmountProperty.value < 0 && heatCoolAmountProperty.value / sliderRange.min < options.snapToZeroThreshold ||
                heatCoolAmountProperty.value > 0 && heatCoolAmountProperty.value / sliderRange.max < options.snapToZeroThreshold);
        };
        const setSliderToZero = () => {
            heatCoolAmountProperty.set(0);
        };
        this.slider = new VSlider(heatCoolAmountProperty, sliderRange, combineOptions({
            thumbTouchAreaXDilation: options.thumbTouchAreaXDilation,
            thumbTouchAreaYDilation: options.thumbTouchAreaYDilation,
            thumbMouseAreaXDilation: options.thumbMouseAreaXDilation,
            thumbMouseAreaYDilation: options.thumbMouseAreaYDilation,
            thumbFill: options.thumbFill,
            thumbSize: options.thumbSize,
            thumbFillHighlighted: options.thumbFillHighlighted,
            endDrag: event => {
                // This only happens for mouse/touch events because it is a poor user experience to reset back to zero
                // every time a key is released. For keyboard input, it is reset to zero when the slider loses focus.
                const isFromPDOM = event && event.isFromPDOM();
                if (!isFromPDOM && this.snapToZeroProperty.value || sliderIsCloseToZero()) {
                    setSliderToZero();
                }
            },
            centerY: stoveBody.centerY,
            right: stoveBody.right - DEFAULT_WIDTH / 8,
            tandem: options.tandem.createTandem('slider')
        }, options.sliderOptions));
        // Set the slider back to zero when it loses focus.
        this.slider.addInputListener({
            blur: () => { this.snapToZeroProperty.value && setSliderToZero(); }
        });
        // A shortcut to easily return the value to zero when using the keyboard.
        const keyboardListener = new KeyboardListener({
            keyStringProperties: HeaterCoolerFront.SET_TO_ZERO_HOTKEY_DATA.keyStringProperties,
            fire: setSliderToZero
        });
        this.slider.addInputListener(keyboardListener);
        // Create the tick labels.
        const labelOptions = {
            font: options.labelFont,
            maxWidth: options.labelMaxWidth
        };
        let heatTickText;
        if (options.heatEnabled) {
            heatTickText = new Text(options.heatString, labelOptions); // dispose required, may link to a StringProperty
            this.slider.addMajorTick(1, heatTickText);
        }
        this.slider.addMinorTick(0);
        let coolTickText;
        if (options.coolEnabled) {
            coolTickText = new Text(options.coolString, labelOptions); // dispose required, may link to a StringProperty
            this.slider.addMajorTick(-1, coolTickText);
        }
        this.addChild(stoveBody);
        this.addChild(this.slider);
        if (!options.phetioInstrument) {
            options.tandem = Tandem.OPT_OUT;
        }
        this.mutate(options);
        // update the back component if provided
        if (options.heaterCoolerBack) {
            const heaterCoolerBack = options.heaterCoolerBack;
            this.opacityProperty.lazyLink(opacity => {
                heaterCoolerBack.opacity = opacity;
            });
            this.pickableProperty.lazyLink(pickable => {
                heaterCoolerBack.pickable = pickable;
            });
            this.visibleProperty.lazyLink(visible => {
                heaterCoolerBack.visible = visible;
            });
        }
        // return the slider to its origin if snapToZero is changed to true
        this.snapToZeroProperty.link(snapToZero => {
            snapToZero && setSliderToZero();
        });
        this.disposeHeaterCoolerFront = () => {
            heatTickText && heatTickText.dispose();
            coolTickText && coolTickText.dispose();
            keyboardListener.dispose();
        };
    }
    dispose() {
        this.disposeHeaterCoolerFront();
        super.dispose();
    }
}
HeaterCoolerFront.SET_TO_ZERO_HOTKEY_DATA = new HotkeyData({
    keyStringProperties: [new Property('0')],
    repoName: sceneryPhet.name,
    keyboardHelpDialogLabelStringProperty: string_scenery_phet_keyboardHelpDialog_heatCoolOff_StringProperty,
    binderName: 'Turn off heater/cooler'
});
export default HeaterCoolerFront;
//# sourceMappingURL=HeaterCoolerFront.js.map