import _ from 'lodash';
import '../../assert/js/assert.js';
import '../../globals.js';
// Copyright 2013-2024, University of Colorado Boulder
/**
 * Control panel around a content node.
 * Dynamically adjusts its size to fit its contents.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Chris Malley (PixelZoom, Inc.)
 * @author John Blanco (PhET Interactive Simulations)
 */
import { optionize3 } from '../../phet-core/js/optionize.js';
import { assertNoAdditionalChildren, LayoutConstraint, Node, Rectangle, Sizable } from '../../scenery/js/imports.js';
import sun from './sun.js';
// valid values for options.align
const ALIGN_VALUES = ['left', 'center', 'right'];
const DEFAULT_OPTIONS = {
    fill: 'white',
    stroke: 'black',
    lineWidth: 1,
    lineDash: [],
    xMargin: 5,
    yMargin: 5,
    cornerRadius: 10,
    resize: true,
    backgroundPickable: true,
    excludeInvisibleChildrenFromBounds: true,
    align: 'left',
    minWidth: 0,
    minHeight: 0
};
assert && Object.freeze(DEFAULT_OPTIONS);
class Panel extends Sizable(Node) {
    constructor(content, providedOptions) {
        const options = optionize3()({}, DEFAULT_OPTIONS, providedOptions);
        assert && assert(_.includes(ALIGN_VALUES, options.align), `invalid align: ${options.align}`);
        super();
        this._content = content;
        this._backgroundContainer = new Node();
        // correct size will be set by layout
        this._background = new Rectangle(0, 0, 1, 1, {
            lineWidth: options.lineWidth,
            pickable: options.backgroundPickable,
            lineDash: options.lineDash,
            cornerRadius: options.cornerRadius
        });
        // update the fill and stroke (before layout)
        this.setStroke(options.stroke);
        this.setFill(options.fill);
        this.addChild(this._backgroundContainer);
        this.addChild(content);
        // Only do this AFTER adding as a child
        this.constraint = new PanelConstraint(this, options);
        this.constraint.updateLayout();
        // Don't update automatically if resize:false
        this.constraint.enabled = options.resize;
        // Apply options after the layout is done, so that options that use the bounds will work properly.
        this.mutate(options);
        // Decorating with additional content is an anti-pattern, see https://github.com/phetsims/sun/issues/860
        assert && assertNoAdditionalChildren(this);
    }
    /**
     * Get the background rectangle's stroke (can be overridden)
     */
    getStroke() {
        return this._background.stroke;
    }
    get stroke() { return this.getStroke(); }
    set stroke(value) { this.setStroke(value); }
    /**
     * Change the background rectangle's stroke (can be overridden)
     */
    setStroke(stroke) {
        this._background.stroke = stroke;
        // Since it depends on the stroke (if it's null, our minimum bounds get reduced)
        this.constraint && this.constraint.updateLayoutAutomatically();
    }
    /**
     * Get the background rectangle's fill (can be overridden)
     */
    getFill() {
        return this._background.fill;
    }
    get fill() { return this.getFill(); }
    set fill(value) { this.setFill(value); }
    /**
     * Change the background rectangle's fill (can be overridden)
     */
    setFill(fill) {
        this._background.fill = fill;
    }
    setExcludeInvisibleChildrenFromBounds(excludeInvisibleChildrenFromBounds) {
        super.setExcludeInvisibleChildrenFromBounds(excludeInvisibleChildrenFromBounds);
        this.constraint.updateLayoutAutomatically();
    }
}
Panel.DEFAULT_PANEL_OPTIONS = DEFAULT_OPTIONS;
export default Panel;
class PanelConstraint extends LayoutConstraint {
    constructor(panel, options) {
        super(panel);
        this.panel = panel;
        assert && assert(typeof options.minWidth === 'number', 'Panel minWidth should be a number');
        assert && assert(typeof options.xMargin === 'number', 'Panel xMargin should be a number');
        assert && assert(typeof options.yMargin === 'number', 'Panel yMargin should be a number');
        assert && assert(typeof options.lineWidth === 'number', 'Panel lineWidth should be a number');
        assert && assert(ALIGN_VALUES.includes(options.align), `Panel align should be one of ${ALIGN_VALUES}`);
        this.minWidth = options.minWidth;
        this.minHeight = options.minHeight;
        this.xMargin = options.xMargin;
        this.yMargin = options.yMargin;
        this.lineWidth = options.lineWidth;
        this.align = options.align;
        this.panel.localPreferredWidthProperty.lazyLink(this._updateLayoutListener);
        this.panel.localPreferredHeightProperty.lazyLink(this._updateLayoutListener);
        this.addNode(panel._content);
    }
    layout() {
        super.layout();
        const panel = this.panel;
        const content = panel._content;
        const background = panel._background;
        const hasValidContent = panel.isChildIncludedInLayout(content);
        // Bail out (and make the background invisible) if our bounds are invalid
        panel._backgroundContainer.children = hasValidContent ? [background] : [];
        if (!hasValidContent) {
            panel.localMinimumWidth = null;
            panel.localMinimumHeight = null;
            return;
        }
        const contentProxy = this.createLayoutProxy(content);
        // Should only happen when we are disconnected during disposal
        if (!contentProxy) {
            return;
        }
        // We only have to account for the lineWidth in our layout if we have a stroke
        const lineWidth = panel.stroke === null ? 0 : this.lineWidth;
        const minimumContentWidth = contentProxy.minimumWidth;
        const minimumContentHeight = contentProxy.minimumHeight;
        // Our minimum dimensions are directly determined by the content, margins and lineWidth
        // NOTE: options.minWidth does NOT include the stroke (e.g. lineWidth), left for backward compatibility.
        const minimumWidth = Math.max(this.minWidth, minimumContentWidth + (2 * this.xMargin)) + lineWidth;
        const minimumHeight = Math.max(this.minHeight, minimumContentHeight + (2 * this.yMargin)) + lineWidth;
        // Our resulting sizes (allow setting preferred width/height on the panel)
        const preferredWidth = Math.max(minimumWidth, panel.localPreferredWidth || 0);
        const preferredHeight = Math.max(minimumHeight, panel.localPreferredHeight || 0);
        // Determine the size available to our content
        contentProxy.preferredWidth = preferredWidth - lineWidth - 2 * this.xMargin;
        contentProxy.preferredHeight = preferredHeight - lineWidth - 2 * this.yMargin;
        background.setRect(0, 0, preferredWidth - lineWidth, preferredHeight - lineWidth);
        // Align the content within the background. If the content width >= minWidth, then all alignments are equivalent.
        if (this.align === 'center') {
            contentProxy.center = background.center;
        }
        else if (this.align === 'left') {
            // Use background.rectWidth instead of background.width because they differ by the background lineWidth
            contentProxy.left = background.centerX - background.rectWidth / 2 + this.xMargin;
            contentProxy.centerY = background.centerY;
        }
        else { /* right */
            // Use background.rectWidth instead of background.width because they differ by the background lineWidth
            contentProxy.right = background.centerX + background.rectWidth / 2 - this.xMargin;
            contentProxy.centerY = background.centerY;
        }
        // Set minimums at the end
        panel.localMinimumWidth = minimumWidth;
        panel.localMinimumHeight = minimumHeight;
        contentProxy.dispose();
    }
    dispose() {
        this.panel.localPreferredWidthProperty.unlink(this._updateLayoutListener);
        this.panel.localPreferredHeightProperty.unlink(this._updateLayoutListener);
        super.dispose();
    }
}
sun.register('Panel', Panel);
//# sourceMappingURL=Panel.js.map