import '../../../../assert/js/assert.js';
import '../../../../globals.js';
import NumberProperty from '../../../../axon/js/NumberProperty.js';
import Bounds2 from '../../../../dot/js/Bounds2.js';
import arrayRemove from '../../../../phet-core/js/arrayRemove.js';
import { combineOptions, optionize3 } from '../../../../phet-core/js/optionize.js';
import { AlignBox, scenery } from '../../imports.js';
import Disposable from '../../../../axon/js/Disposable.js';
import Orientation from '../../../../phet-core/js/Orientation.js';
let globalId = 1;
const DEFAULT_OPTIONS = {
    matchHorizontal: true,
    matchVertical: true
};
export default class AlignGroup extends Disposable {
    /**
     * Creates an alignment group that can be composed of multiple boxes.
     *
     * Use createBox() to create alignment boxes. You can dispose() individual boxes, or call dispose() on this
     * group to dispose all of them.
     *
     * It is also possible to create AlignBox instances independently and assign their 'group' to this AlignGroup.
     */
    constructor(providedOptions) {
        assert && assert(providedOptions === undefined || Object.getPrototypeOf(providedOptions) === Object.prototype, 'Extra prototype on options object is a code smell');
        const options = optionize3()({}, DEFAULT_OPTIONS, providedOptions);
        assert && assert(typeof options.matchHorizontal === 'boolean');
        assert && assert(typeof options.matchVertical === 'boolean');
        super(options);
        this._alignBoxes = [];
        this._matchHorizontal = options.matchHorizontal;
        this._matchVertical = options.matchVertical;
        // Gets locked when certain layout is performed.
        this._resizeLock = false;
        this._maxWidthProperty = new NumberProperty(0);
        this._maxHeightProperty = new NumberProperty(0);
        this.id = globalId++;
    }
    /**
     * Returns the current maximum width of the grouped content.
     */
    getMaxWidth() {
        return this._maxWidthProperty.value;
    }
    get maxWidth() { return this.getMaxWidth(); }
    /**
     * Returns the Property holding the current maximum width of the grouped content.
     */
    getMaxWidthProperty() {
        return this._maxWidthProperty;
    }
    get maxWidthProperty() { return this.getMaxWidthProperty(); }
    /**
     * Returns the current maximum height of the grouped content.
     */
    getMaxHeight() {
        return this._maxHeightProperty.value;
    }
    get maxHeight() { return this.getMaxHeight(); }
    /**
     * Returns the Property holding the current maximum height of the grouped content.
     */
    getMaxHeightProperty() {
        return this._maxHeightProperty;
    }
    get maxHeightProperty() { return this.getMaxHeightProperty(); }
    getMaxSizeProperty(orientation) {
        return orientation === Orientation.HORIZONTAL ? this._maxWidthProperty : this._maxHeightProperty;
    }
    /**
     * Creates an alignment box with the given content and options.
     */
    createBox(content, options) {
        // Setting the group should call our addAlignBox()
        return new AlignBox(content, combineOptions({
            group: this
        }, options));
    }
    /**
     * Sets whether the widths of the align boxes should all match. If false, each box will use its preferred width
     * (usually equal to the content width + horizontal margins).
     */
    setMatchHorizontal(matchHorizontal) {
        if (this._matchHorizontal !== matchHorizontal) {
            this._matchHorizontal = matchHorizontal;
            // Update layout, since it will probably change
            this.updateLayout();
        }
        return this;
    }
    set matchHorizontal(value) { this.setMatchHorizontal(value); }
    get matchHorizontal() { return this.getMatchHorizontal(); }
    /**
     * Returns whether boxes currently are horizontally matched. See setMatchHorizontal() for details.
     */
    getMatchHorizontal() {
        return this._matchHorizontal;
    }
    /**
     * Sets whether the heights of the align boxes should all match. If false, each box will use its preferred height
     * (usually equal to the content height + vertical margins).
     */
    setMatchVertical(matchVertical) {
        if (this._matchVertical !== matchVertical) {
            this._matchVertical = matchVertical;
            // Update layout, since it will probably change
            this.updateLayout();
        }
        return this;
    }
    set matchVertical(value) { this.setMatchVertical(value); }
    get matchVertical() { return this.getMatchVertical(); }
    /**
     * Returns whether boxes currently are vertically matched. See setMatchVertical() for details.
     */
    getMatchVertical() {
        return this._matchVertical;
    }
    /**
     * Updates the localBounds and alignment for each alignBox.
     *
     * NOTE: Calling this will usually not be necessary outside of Scenery, but this WILL trigger bounds revalidation
     *       for every alignBox, which can force the layout code to run.
     */
    updateLayout() {
        if (this._resizeLock) {
            return;
        }
        this._resizeLock = true;
        sceneryLog && sceneryLog.AlignGroup && sceneryLog.AlignGroup(`AlignGroup#${this.id} updateLayout`);
        sceneryLog && sceneryLog.AlignGroup && sceneryLog.push();
        sceneryLog && sceneryLog.AlignGroup && sceneryLog.AlignGroup('AlignGroup computing maximum dimension');
        sceneryLog && sceneryLog.AlignGroup && sceneryLog.push();
        // Compute the maximum dimension of our alignBoxs' content
        let maxWidth = 0;
        let maxHeight = 0;
        for (let i = 0; i < this._alignBoxes.length; i++) {
            const alignBox = this._alignBoxes[i];
            const width = alignBox.getMinimumWidth();
            const height = alignBox.getMinimumHeight();
            // Ignore bad bounds
            if (isFinite(width) && width > 0) {
                maxWidth = Math.max(maxWidth, width);
            }
            if (isFinite(height) && height > 0) {
                maxHeight = Math.max(maxHeight, height);
            }
        }
        sceneryLog && sceneryLog.AlignGroup && sceneryLog.pop();
        sceneryLog && sceneryLog.AlignGroup && sceneryLog.AlignGroup('AlignGroup applying to boxes');
        sceneryLog && sceneryLog.AlignGroup && sceneryLog.push();
        this._maxWidthProperty.value = maxWidth;
        this._maxHeightProperty.value = maxHeight;
        if (maxWidth > 0 && maxHeight > 0) {
            // Apply that maximum dimension for each alignBox
            for (let i = 0; i < this._alignBoxes.length; i++) {
                this.setBoxBounds(this._alignBoxes[i], maxWidth, maxHeight);
            }
        }
        sceneryLog && sceneryLog.AlignGroup && sceneryLog.pop();
        sceneryLog && sceneryLog.AlignGroup && sceneryLog.pop();
        this._resizeLock = false;
    }
    /**
     * Sets a box's bounds based on our maximum dimensions.
     */
    setBoxBounds(alignBox, maxWidth, maxHeight) {
        let alignBounds;
        // If we match both dimensions, we don't have to inspect the box's preferred size
        if (this._matchVertical && this._matchHorizontal) {
            alignBounds = new Bounds2(0, 0, maxWidth, maxHeight);
        }
        else {
            // Grab the preferred size
            const contentBounds = alignBox.getContentBounds();
            // Match one orientation
            if (this._matchVertical) {
                alignBounds = new Bounds2(0, 0, isFinite(contentBounds.width) ? contentBounds.width : maxWidth, maxHeight);
            }
            else if (this._matchHorizontal) {
                alignBounds = new Bounds2(0, 0, maxWidth, isFinite(contentBounds.height) ? contentBounds.height : maxHeight);
            }
            // If not matching anything, just use its preferred size
            else {
                alignBounds = contentBounds;
            }
        }
        alignBox.alignBounds = alignBounds;
    }
    /**
     * Lets the group know that the alignBox has had its content resized. Called by the AlignBox
     * (scenery-internal)
     */
    onAlignBoxResized(alignBox) {
        // NOTE: in the future, we could only update this specific alignBox if the others don't need updating.
        this.updateLayout();
    }
    /**
     * Adds the AlignBox to the group -- Used in AlignBox --- do NOT use in public code
     * (scenery-internal)
     */
    addAlignBox(alignBox) {
        this._alignBoxes.push(alignBox);
        // Trigger an update when a alignBox is added
        this.updateLayout();
    }
    /**
     * Removes the AlignBox from the group
     * (scenery-internal)
     */
    removeAlignBox(alignBox) {
        arrayRemove(this._alignBoxes, alignBox);
        // Trigger an update when a alignBox is removed
        this.updateLayout();
    }
    /**
     * Dispose all the boxes.
     */
    dispose() {
        for (let i = this._alignBoxes.length - 1; i >= 0; i--) {
            this._alignBoxes[i].dispose();
        }
        super.dispose();
    }
}
scenery.register('AlignGroup', AlignGroup);
//# sourceMappingURL=AlignGroup.js.map