import '../../../assert/js/assert.js';
import '../../../globals.js';
// Copyright 2022-2024, University of Colorado Boulder
/**
 * Sizable is a trait that provides a minimum and preferred width/height (both WidthSizable and HeightSizable,
 * but with added features that allow convenience of working with both dimensions at once).
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */
import Dimension2 from '../../../dot/js/Dimension2.js';
import assertMutuallyExclusiveOptions from '../../../phet-core/js/assertMutuallyExclusiveOptions.js';
import memoize from '../../../phet-core/js/memoize.js';
import { DelayedMutate, HEIGHT_SIZABLE_OPTION_KEYS, HeightSizable, REQUIRES_BOUNDS_OPTION_KEYS, scenery, WIDTH_SIZABLE_OPTION_KEYS, WidthSizable } from '../imports.js';
export const SIZABLE_SELF_OPTION_KEYS = [
    'preferredSize',
    'minimumSize',
    'localPreferredSize',
    'localMinimumSize',
    'sizable'
];
export const SIZABLE_OPTION_KEYS = [
    'preferredSize',
    'minimumSize',
    'localPreferredSize',
    'localMinimumSize',
    'sizable',
    ...WIDTH_SIZABLE_OPTION_KEYS,
    ...HEIGHT_SIZABLE_OPTION_KEYS
];
const Sizable = memoize((Type) => {
    const SizableTrait = DelayedMutate('Sizable', SIZABLE_SELF_OPTION_KEYS, class SizableTrait extends WidthSizable(HeightSizable(Type)) {
        constructor(...args) {
            super(...args);
            // We've added code to conditionally update the preferred/minimum opposite dimensions, so we'll need to
            // cross-link the listeners we've created in WidthSizable/HeightSizable
            this.preferredWidthProperty.lazyLink(this._updateLocalPreferredHeightListener);
            this.preferredHeightProperty.lazyLink(this._updateLocalPreferredWidthListener);
            this.localPreferredWidthProperty.lazyLink(this._updatePreferredHeightListener);
            this.localPreferredHeightProperty.lazyLink(this._updatePreferredWidthListener);
            this.minimumWidthProperty.lazyLink(this._updateLocalMinimumHeightListener);
            this.minimumHeightProperty.lazyLink(this._updateLocalMinimumWidthListener);
            this.localMinimumWidthProperty.lazyLink(this._updateMinimumHeightListener);
            this.localMinimumHeightProperty.lazyLink(this._updateMinimumWidthListener);
        }
        get preferredSize() {
            assert && assert((this.preferredWidth === null) === (this.preferredHeight === null), 'Cannot get a preferredSize when one of preferredWidth/preferredHeight is null');
            if (this.preferredWidth === null || this.preferredHeight === null) {
                return null;
            }
            else {
                return new Dimension2(this.preferredWidth, this.preferredHeight);
            }
        }
        set preferredSize(value) {
            this.preferredWidth = value === null ? null : value.width;
            this.preferredHeight = value === null ? null : value.height;
        }
        get localPreferredSize() {
            assert && assert((this.localPreferredWidth === null) === (this.localPreferredHeight === null), 'Cannot get a preferredSize when one of preferredWidth/preferredHeight is null');
            if (this.localPreferredWidth === null || this.localPreferredHeight === null) {
                return null;
            }
            else {
                return new Dimension2(this.localPreferredWidth, this.localPreferredHeight);
            }
        }
        set localPreferredSize(value) {
            this.localPreferredWidth = value === null ? null : value.width;
            this.localPreferredHeight = value === null ? null : value.height;
        }
        get minimumSize() {
            assert && assert((this.minimumWidth === null) === (this.minimumHeight === null), 'Cannot get a minimumSize when one of minimumWidth/minimumHeight is null');
            if (this.minimumWidth === null || this.minimumHeight === null) {
                return null;
            }
            else {
                return new Dimension2(this.minimumWidth, this.minimumHeight);
            }
        }
        set minimumSize(value) {
            this.minimumWidth = value === null ? null : value.width;
            this.minimumHeight = value === null ? null : value.height;
        }
        get localMinimumSize() {
            assert && assert((this.localMinimumWidth === null) === (this.localMinimumHeight === null), 'Cannot get a minimumSize when one of minimumWidth/minimumHeight is null');
            if (this.localMinimumWidth === null || this.localMinimumHeight === null) {
                return null;
            }
            else {
                return new Dimension2(this.localMinimumWidth, this.localMinimumHeight);
            }
        }
        set localMinimumSize(value) {
            this.localMinimumWidth = value === null ? null : value.width;
            this.localMinimumHeight = value === null ? null : value.height;
        }
        get sizable() {
            assert && assert(this.widthSizable === this.heightSizable, 'widthSizable and heightSizable not the same, which is required for the sizable getter');
            return this.widthSizable;
        }
        set sizable(value) {
            this.widthSizable = value;
            this.heightSizable = value;
        }
        get extendsSizable() { return true; }
        validateLocalPreferredSize() {
            if (assert) {
                this.validateLocalPreferredWidth();
                this.validateLocalPreferredHeight();
            }
        }
        mutate(options) {
            assertMutuallyExclusiveOptions(options, ['preferredSize'], ['preferredWidth', 'preferredHeight']);
            assertMutuallyExclusiveOptions(options, ['localPreferredSize'], ['localPreferredWidth', 'localPreferredHeight']);
            assertMutuallyExclusiveOptions(options, ['minimumSize'], ['minimumWidth', 'minimumHeight']);
            assertMutuallyExclusiveOptions(options, ['localMinimumSize'], ['localMinimumWidth', 'localMinimumHeight']);
            assertMutuallyExclusiveOptions(options, ['sizable'], ['widthSizable', 'heightSizable']);
            return super.mutate(options);
        }
        // Override the calculation to potentially include the opposite dimension (if we have a rotation of that type)
        _calculateLocalPreferredWidth() {
            if (this.matrix.isAxisAligned()) {
                if (this.matrix.isAligned()) {
                    if (this.preferredWidth !== null) {
                        return Math.abs(this.transform.inverseDeltaX(this.preferredWidth));
                    }
                }
                // If we're height-sizable and we have an orientation swap, set the correct preferred width!
                else if (this.preferredHeight !== null) {
                    return Math.abs(this.transform.getInverse().m01() * this.preferredHeight);
                }
            }
            return null;
        }
        // Override the calculation to potentially include the opposite dimension (if we have a rotation of that type)
        _calculateLocalPreferredHeight() {
            if (this.matrix.isAxisAligned()) {
                if (this.matrix.isAligned()) {
                    if (this.preferredHeight !== null) {
                        return Math.abs(this.transform.inverseDeltaY(this.preferredHeight));
                    }
                }
                // If we're width-sizable and we have an orientation swap, set the correct preferred height!
                else if (this.preferredWidth !== null) {
                    return Math.abs(this.transform.getInverse().m10() * this.preferredWidth);
                }
            }
            return null;
        }
        // Override the calculation to potentially include the opposite dimension (if we have a rotation of that type)
        _calculatePreferredWidth() {
            if (this.matrix.isAxisAligned()) {
                if (this.matrix.isAligned()) {
                    if (this.localPreferredWidth !== null) {
                        return Math.abs(this.transform.transformDeltaX(this.localPreferredWidth));
                    }
                }
                else if (this.localPreferredHeight !== null) {
                    return Math.abs(this.transform.matrix.m01() * this.localPreferredHeight);
                }
            }
            return null;
        }
        // Override the calculation to potentially include the opposite dimension (if we have a rotation of that type)
        _calculatePreferredHeight() {
            if (this.matrix.isAxisAligned()) {
                if (this.matrix.isAligned()) {
                    if (this.localPreferredHeight !== null) {
                        return Math.abs(this.transform.transformDeltaY(this.localPreferredHeight));
                    }
                }
                else if (this.localPreferredWidth !== null) {
                    return Math.abs(this.transform.matrix.m10() * this.localPreferredWidth);
                }
            }
            return null;
        }
        // We'll need to cross-link because we might need to update either the width or height when the other changes
        _onReentrantLocalMinimumWidth() {
            this._updateMinimumWidthListener();
            this._updateMinimumHeightListener();
        }
        // We'll need to cross-link because we might need to update either the width or height when the other changes
        _onReentrantLocalMinimumHeight() {
            this._updateMinimumWidthListener();
            this._updateMinimumHeightListener();
        }
        // We'll need to cross-link because we might need to update either the width or height when the other changes
        _onReentrantPreferredWidth() {
            this._updateLocalPreferredWidthListener();
            this._updateLocalPreferredHeightListener();
        }
        // We'll need to cross-link because we might need to update either the width or height when the other changes
        _onReentrantPreferredHeight() {
            this._updateLocalPreferredWidthListener();
            this._updateLocalPreferredHeightListener();
        }
        // Override the calculation to potentially include the opposite dimension (if we have a rotation of that type)
        _calculateLocalMinimumWidth() {
            if (this.matrix.isAxisAligned()) {
                if (this.matrix.isAligned()) {
                    if (this.minimumWidth !== null) {
                        return Math.abs(this.transform.inverseDeltaX(this.minimumWidth));
                    }
                }
                else if (this.minimumHeight !== null) {
                    return Math.abs(this.transform.getInverse().m01() * this.minimumHeight);
                }
            }
            return null;
        }
        // Override the calculation to potentially include the opposite dimension (if we have a rotation of that type)
        _calculateLocalMinimumHeight() {
            if (this.matrix.isAxisAligned()) {
                if (this.matrix.isAligned()) {
                    if (this.minimumHeight !== null) {
                        return Math.abs(this.transform.inverseDeltaY(this.minimumHeight));
                    }
                }
                else if (this.minimumWidth !== null) {
                    return Math.abs(this.transform.getInverse().m10() * this.minimumWidth);
                }
            }
            return null;
        }
        // Override the calculation to potentially include the opposite dimension (if we have a rotation of that type)
        _calculateMinimumWidth() {
            if (this.matrix.isAxisAligned()) {
                if (this.matrix.isAligned()) {
                    if (this.localMinimumWidth !== null) {
                        return Math.abs(this.transform.transformDeltaX(this.localMinimumWidth));
                    }
                }
                else if (this.localMinimumHeight !== null) {
                    return Math.abs(this.transform.matrix.m01() * this.localMinimumHeight);
                }
            }
            return null;
        }
        // Override the calculation to potentially include the opposite dimension (if we have a rotation of that type)
        _calculateMinimumHeight() {
            if (this.matrix.isAxisAligned()) {
                if (this.matrix.isAligned()) {
                    if (this.localMinimumHeight !== null) {
                        return Math.abs(this.transform.transformDeltaY(this.localMinimumHeight));
                    }
                }
                else if (this.localMinimumWidth !== null) {
                    return Math.abs(this.transform.matrix.m10() * this.localMinimumWidth);
                }
            }
            return null;
        }
    });
    // If we're extending into a Node type, include option keys
    if (SizableTrait.prototype._mutatorKeys) {
        const existingKeys = SizableTrait.prototype._mutatorKeys;
        const newKeys = SIZABLE_SELF_OPTION_KEYS;
        const indexOfBoundsBasedOptions = existingKeys.indexOf(REQUIRES_BOUNDS_OPTION_KEYS[0]);
        SizableTrait.prototype._mutatorKeys = [
            ...existingKeys.slice(0, indexOfBoundsBasedOptions),
            ...newKeys,
            ...existingKeys.slice(indexOfBoundsBasedOptions)
        ];
    }
    return SizableTrait;
});
const isSizable = (node) => {
    return node.widthSizable && node.heightSizable;
};
const extendsSizable = (node) => {
    return node.extendsSizable;
};
scenery.register('Sizable', Sizable);
export default Sizable;
export { isSizable, extendsSizable };
//# sourceMappingURL=Sizable.js.map