import '../../../assert/js/assert.js';
import '../../../globals.js';
import { scenery, TrailVisibilityTracker } from '../imports.js';
class FocusDisplayedController {
    constructor(focusProperty) {
        // last Node of the Trail that is focused, referenced so we can add and remove listeners from it
        this.node = null;
        // Observes the Trail to the Node and notifies when it has become invisible
        this.visibilityTracker = null;
        this.focusProperty = focusProperty;
        this.boundVisibilityListener = this.handleTrailVisibilityChange.bind(this);
        this.boundInstancesChangedListener = this.handleInstancesChange.bind(this);
        this.boundNodeDisposedListener = this.handleNodeDisposed.bind(this);
        this.boundFocusListener = this.handleFocusChange.bind(this);
        this.focusProperty.link(this.boundFocusListener);
    }
    /**
     * When Focus changes, remove any listeners that were attached from last Focus and
     * add new listeners if focus has a new value.
     */
    handleFocusChange(focus) {
        this.removeDisplayedListeners();
        if (focus) {
            this.addDisplayedListeners(focus);
        }
    }
    /**
     * When the Trail becomes invisible, Focus should be set to null.
     */
    handleTrailVisibilityChange() {
        if (this.visibilityTracker && !this.visibilityTracker.trailVisibleProperty.value) {
            this.focusProperty.value = null;
        }
    }
    /**
     * If there are no more Instances for the Node with focus it has been removed from
     * the scene graph and so Focus should be set to null.
     */
    handleInstancesChange(instance) {
        if (instance.node && instance.node.instances.length === 0) {
            this.focusProperty.value = null;
        }
    }
    /**
     * While this focus-clear is mostly covered by listening for instance changes, there is an intermediate state between
     * when a Node is disposed, and when the Instance tree is updated to reflect that disposal (during updateDisplay()).
     * This function handles that atypical case (pretty much impossible to get to in PhET sims except during fuzzing).
     */
    handleNodeDisposed() {
        this.focusProperty.value = null;
    }
    /**
     * Add listeners that watch when the Displayed state of the Node with Focus has changed,
     * including visibility of the trail and attachment to a scene graph.
     */
    addDisplayedListeners(focus) {
        assert && assert(this.visibilityTracker === null, 'creating a new TrailVisibilityTracker but the last one was not disposed');
        assert && assert(this.node === null, 'Still a reference to the previously focused Node, possible memory leak');
        this.visibilityTracker = new TrailVisibilityTracker(focus.trail);
        this.visibilityTracker.addListener(this.boundVisibilityListener);
        this.node = focus.trail.lastNode();
        // This is potentially dangerous to listen to generally, but in this case it is safe because the state we change
        // will only affect a separate display's state, not this one.
        this.node.changedInstanceEmitter.addListener(this.boundInstancesChangedListener);
        this.node.disposeEmitter.addListener(this.boundNodeDisposedListener);
    }
    /**
     * Remove any listeners that were added to observables that fire when the Node's displayed
     * state may have changed.
     */
    removeDisplayedListeners() {
        if (this.visibilityTracker) {
            this.visibilityTracker.removeListener(this.boundVisibilityListener);
            this.visibilityTracker.dispose();
            this.visibilityTracker = null;
        }
        if (this.node) {
            this.node.changedInstanceEmitter.removeListener(this.boundInstancesChangedListener);
            this.node.disposeEmitter.removeListener(this.boundNodeDisposedListener);
            this.node = null;
        }
    }
    dispose() {
        // this disposes the TrailVisibilityTracker and removes any listeners on the Node
        this.removeDisplayedListeners();
        this.focusProperty.unlink(this.boundFocusListener);
        this.node = null;
        this.visibilityTracker = null;
    }
}
scenery.register('FocusDisplayedController', FocusDisplayedController);
export default FocusDisplayedController;
//# sourceMappingURL=FocusDisplayedController.js.map