import _ from 'lodash';
import '../../../../assert/js/assert.js';
import '../../../../globals.js';
// Copyright 2021-2024, University of Colorado Boulder
/**
 * A vertical/horizontal flow-based layout container.
 *
 * See https://phetsims.github.io/scenery/doc/layout#FlowBox for details
 *
 * FlowBox-only options:
 *   - resize (see https://phetsims.github.io/scenery/doc/layout#FlowBox-resize)
 *   - orientation (see https://phetsims.github.io/scenery/doc/layout#FlowBox-orientation)
 *   - spacing (see https://phetsims.github.io/scenery/doc/layout#FlowBox-spacing)
 *   - lineSpacing (see https://phetsims.github.io/scenery/doc/layout#FlowBox-lineSpacing)
 *   - justify (see https://phetsims.github.io/scenery/doc/layout#FlowBox-justify)
 *   - justifyLines (see https://phetsims.github.io/scenery/doc/layout#FlowBox-justifyLines)
 *   - wrap (see https://phetsims.github.io/scenery/doc/layout#FlowBox-wrap)
 *   - layoutOrigin (see https://phetsims.github.io/scenery/doc/layout#layoutOrigin)
 *
 * FlowBox and layoutOptions options (can be set either in the FlowBox itself, or within its child nodes' layoutOptions):
 *   - align (see https://phetsims.github.io/scenery/doc/layout#FlowBox-align)
 *   - stretch (see https://phetsims.github.io/scenery/doc/layout#FlowBox-stretch)
 *   - grow (see https://phetsims.github.io/scenery/doc/layout#FlowBox-grow)
 *   - cellAlign (see https://phetsims.github.io/scenery/doc/layout#FlowBox-cellAlign)
 *   - margin (see https://phetsims.github.io/scenery/doc/layout#FlowBox-margins)
 *   - xMargin (see https://phetsims.github.io/scenery/doc/layout#FlowBox-margins)
 *   - yMargin (see https://phetsims.github.io/scenery/doc/layout#FlowBox-margins)
 *   - leftMargin (see https://phetsims.github.io/scenery/doc/layout#FlowBox-margins)
 *   - rightMargin (see https://phetsims.github.io/scenery/doc/layout#FlowBox-margins)
 *   - topMargin (see https://phetsims.github.io/scenery/doc/layout#FlowBox-margins)
 *   - bottomMargin (see https://phetsims.github.io/scenery/doc/layout#FlowBox-margins)
 *   - minContentWidth (see https://phetsims.github.io/scenery/doc/layout#FlowBox-minContent)
 *   - minContentHeight (see https://phetsims.github.io/scenery/doc/layout#FlowBox-minContent)
 *   - maxContentWidth (see https://phetsims.github.io/scenery/doc/layout#FlowBox-maxContent)
 *   - maxContentHeight (see https://phetsims.github.io/scenery/doc/layout#FlowBox-maxContent)
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */
import optionize from '../../../../phet-core/js/optionize.js';
import Orientation from '../../../../phet-core/js/Orientation.js';
import { FLOW_CONSTRAINT_OPTION_KEYS, FlowCell, FlowConstraint, LAYOUT_NODE_OPTION_KEYS, LayoutAlign, LayoutNode, MarginLayoutCell, Node, REQUIRES_BOUNDS_OPTION_KEYS, scenery, SceneryConstants, SIZABLE_OPTION_KEYS } from '../../imports.js';
// FlowBox-specific options that can be passed in the constructor or mutate() call.
const FLOWBOX_OPTION_KEYS = [
    ...LAYOUT_NODE_OPTION_KEYS,
    ...FLOW_CONSTRAINT_OPTION_KEYS.filter(key => key !== 'excludeInvisible')
];
const DEFAULT_OPTIONS = {
    orientation: 'horizontal',
    spacing: 0,
    align: 'center',
    stretch: false
};
class FlowBox extends LayoutNode {
    constructor(providedOptions) {
        const options = optionize()({
            // Allow dynamic layout by default, see https://github.com/phetsims/joist/issues/608
            excludeInvisibleChildrenFromBounds: true,
            resize: true,
            // For LayoutBox compatibility
            disabledOpacity: SceneryConstants.DISABLED_OPACITY
        }, providedOptions);
        super();
        // Track the connection between Nodes and cells
        this._cellMap = new Map();
        this._constraint = new FlowConstraint(this, {
            preferredWidthProperty: this.localPreferredWidthProperty,
            preferredHeightProperty: this.localPreferredHeightProperty,
            minimumWidthProperty: this.localMinimumWidthProperty,
            minimumHeightProperty: this.localMinimumHeightProperty,
            layoutOriginProperty: this.layoutOriginProperty,
            orientation: DEFAULT_OPTIONS.orientation,
            spacing: DEFAULT_OPTIONS.spacing,
            align: DEFAULT_OPTIONS.align,
            stretch: DEFAULT_OPTIONS.stretch,
            excludeInvisible: false // Should be handled by the options mutate below
        });
        this.onChildInserted = this.onFlowBoxChildInserted.bind(this);
        this.onChildRemoved = this.onFlowBoxChildRemoved.bind(this);
        this.onChildrenReordered = this.onFlowBoxChildrenReordered.bind(this);
        this.onChildrenChanged = this.onFlowBoxChildrenChanged.bind(this);
        this.childInsertedEmitter.addListener(this.onChildInserted);
        this.childRemovedEmitter.addListener(this.onChildRemoved);
        this.childrenReorderedEmitter.addListener(this.onChildrenReordered);
        this.childrenChangedEmitter.addListener(this.onChildrenChanged);
        const nonBoundsOptions = _.omit(options, REQUIRES_BOUNDS_OPTION_KEYS);
        const boundsOptions = _.pick(options, REQUIRES_BOUNDS_OPTION_KEYS);
        // Before we do layout, do non-bounds-related changes (in case we have resize:false), and prevent layout for
        // performance gains.
        this._constraint.lock();
        this.mutate(nonBoundsOptions);
        this._constraint.unlock();
        // Update the layout (so that it is done once if we have resize:false)
        this._constraint.updateLayout();
        // After we have our localBounds complete, now we can mutate things that rely on it.
        this.mutate(boundsOptions);
        this.linkLayoutBounds();
    }
    /**
     * Called when a child is inserted.
     */
    onFlowBoxChildInserted(node, index) {
        const cell = new FlowCell(this._constraint, node, this._constraint.createLayoutProxy(node));
        this._cellMap.set(node, cell);
        this._constraint.insertCell(index, cell);
    }
    /**
     * Called when a child is removed.
     */
    onFlowBoxChildRemoved(node) {
        const cell = this._cellMap.get(node);
        assert && assert(cell);
        this._cellMap.delete(node);
        this._constraint.removeCell(cell);
        cell.dispose();
    }
    /**
     * Called when children are rearranged
     */
    onFlowBoxChildrenReordered(minChangeIndex, maxChangeIndex) {
        this._constraint.reorderCells(this._children.slice(minChangeIndex, maxChangeIndex + 1).map(node => this._cellMap.get(node)), minChangeIndex, maxChangeIndex);
    }
    /**
     * Called on change of children (child added, removed, order changed, etc.)
     */
    onFlowBoxChildrenChanged() {
        this._constraint.updateLayoutAutomatically();
    }
    getCell(node) {
        const result = this._cellMap.get(node);
        assert && assert(result);
        return result;
    }
    get orientation() {
        return this._constraint.orientation;
    }
    set orientation(value) {
        this._constraint.orientation = value;
    }
    get spacing() {
        return this._constraint.spacing;
    }
    set spacing(value) {
        this._constraint.spacing = value;
    }
    get lineSpacing() {
        return this._constraint.lineSpacing;
    }
    set lineSpacing(value) {
        this._constraint.lineSpacing = value;
    }
    get justify() {
        return this._constraint.justify;
    }
    set justify(value) {
        this._constraint.justify = value;
    }
    get justifyLines() {
        return this._constraint.justifyLines;
    }
    set justifyLines(value) {
        this._constraint.justifyLines = value;
    }
    get wrap() {
        return this._constraint.wrap;
    }
    set wrap(value) {
        this._constraint.wrap = value;
    }
    get align() {
        assert && assert(typeof this._constraint.align === 'string');
        return this._constraint.align;
    }
    set align(value) {
        this._constraint.align = value;
    }
    get stretch() {
        assert && assert(typeof this._constraint.stretch === 'boolean');
        return this._constraint.stretch;
    }
    set stretch(value) {
        this._constraint.stretch = value;
    }
    get grow() {
        return this._constraint.grow;
    }
    set grow(value) {
        this._constraint.grow = value;
    }
    get margin() {
        return this._constraint.margin;
    }
    set margin(value) {
        this._constraint.margin = value;
    }
    get xMargin() {
        return this._constraint.xMargin;
    }
    set xMargin(value) {
        this._constraint.xMargin = value;
    }
    get yMargin() {
        return this._constraint.yMargin;
    }
    set yMargin(value) {
        this._constraint.yMargin = value;
    }
    get leftMargin() {
        return this._constraint.leftMargin;
    }
    set leftMargin(value) {
        this._constraint.leftMargin = value;
    }
    get rightMargin() {
        return this._constraint.rightMargin;
    }
    set rightMargin(value) {
        this._constraint.rightMargin = value;
    }
    get topMargin() {
        return this._constraint.topMargin;
    }
    set topMargin(value) {
        this._constraint.topMargin = value;
    }
    get bottomMargin() {
        return this._constraint.bottomMargin;
    }
    set bottomMargin(value) {
        this._constraint.bottomMargin = value;
    }
    get minContentWidth() {
        return this._constraint.minContentWidth;
    }
    set minContentWidth(value) {
        this._constraint.minContentWidth = value;
    }
    get minContentHeight() {
        return this._constraint.minContentHeight;
    }
    set minContentHeight(value) {
        this._constraint.minContentHeight = value;
    }
    get maxContentWidth() {
        return this._constraint.maxContentWidth;
    }
    set maxContentWidth(value) {
        this._constraint.maxContentWidth = value;
    }
    get maxContentHeight() {
        return this._constraint.maxContentHeight;
    }
    set maxContentHeight(value) {
        this._constraint.maxContentHeight = value;
    }
    /**
     * Releases references
     */
    dispose() {
        // Lock our layout forever
        this._constraint.lock();
        this.childInsertedEmitter.removeListener(this.onChildInserted);
        this.childRemovedEmitter.removeListener(this.onChildRemoved);
        this.childrenReorderedEmitter.removeListener(this.onChildrenReordered);
        this.childrenChangedEmitter.removeListener(this.onChildrenChanged);
        // Dispose our cells here. We won't be getting the children-removed listeners fired (we removed them above)
        for (const cell of this._cellMap.values()) {
            cell.dispose();
        }
        super.dispose();
    }
    // LayoutBox Compatibility (see the ES5 setters/getters, or the options doc)
    setOrientation(orientation) {
        this.orientation = orientation;
        return this;
    }
    getOrientation() { return this.orientation; }
    setSpacing(spacing) {
        this.spacing = spacing;
        return this;
    }
    getSpacing() { return this.spacing; }
    setAlign(align) {
        this.align = align;
        return this;
    }
    getAlign() { return this.align; }
    setResize(resize) {
        this.resize = resize;
        return this;
    }
    isResize() { return this.resize; }
    getHelperNode() {
        const marginsNode = MarginLayoutCell.createHelperNode(this.constraint.displayedCells, this.constraint.layoutBoundsProperty.value, cell => {
            let str = '';
            const internalOrientation = Orientation.fromLayoutOrientation(cell.orientation);
            str += `align: ${LayoutAlign.internalToAlign(internalOrientation, cell.effectiveAlign)}\n`;
            str += `stretch: ${cell.effectiveStretch}\n`;
            str += `grow: ${cell.effectiveGrow}\n`;
            return str;
        });
        return marginsNode;
    }
    mutate(options) {
        return super.mutate(options);
    }
}
FlowBox.DEFAULT_FLOW_BOX_OPTIONS = DEFAULT_OPTIONS;
export default FlowBox;
/**
 * {Array.<string>} - String keys for all of the allowed options that will be set by node.mutate( options ), in the
 * order they will be evaluated in.
 *
 * NOTE: See Node's _mutatorKeys documentation for more information on how this operates, and potential special
 *       cases that may apply.
 */
FlowBox.prototype._mutatorKeys = [...SIZABLE_OPTION_KEYS, ...FLOWBOX_OPTION_KEYS, ...Node.prototype._mutatorKeys];
scenery.register('FlowBox', FlowBox);
//# sourceMappingURL=FlowBox.js.map