import '../../assert/js/assert.js';
import '../../globals.js';
// Copyright 2020-2023, University of Colorado Boulder
/**
 * Shows a set of tick labels within or next to a chart.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
import Bounds2 from '../../dot/js/Bounds2.js';
import Utils from '../../dot/js/Utils.js';
import Orientation from '../../phet-core/js/Orientation.js';
import { Path, Text } from '../../scenery/js/imports.js';
import bamboo from './bamboo.js';
import TickMarkSet from './TickMarkSet.js';
import optionize from '../../phet-core/js/optionize.js';
class TickLabelSet extends Path {
    /**
     * @param chartTransform
     * @param axisOrientation - the progression of the ticks.  For instance HORIZONTAL has ticks at x=0,1,2, etc.
     * @param spacing - in model coordinates
     * @param [providedOptions]
     */
    constructor(chartTransform, axisOrientation, spacing, providedOptions) {
        const options = optionize()({
            // SelfOptions
            value: 0, // appear on the axis by default
            edge: null,
            origin: 0,
            skipCoordinates: [],
            extent: TickMarkSet.DEFAULT_EXTENT,
            clippingType: 'strict',
            createLabel: (value) => new Text(Utils.toFixed(value, 1), { fontSize: 12 }),
            positionLabel: (label, tickBounds, axisOrientation) => {
                if (axisOrientation === Orientation.HORIZONTAL) {
                    // ticks flow horizontally, so tick labels should be below
                    label.centerTop = tickBounds.centerBottom.plusXY(0, 1);
                }
                else {
                    label.rightCenter = tickBounds.leftCenter.plusXY(-1, 0);
                }
                return label;
            }
        }, providedOptions);
        if (options.edge) {
            assert && assert(options.value === 0, 'value and edge are mutually exclusive');
        }
        super(null, options);
        this.chartTransform = chartTransform;
        this.axisOrientation = axisOrientation;
        this.spacing = spacing;
        this.origin = options.origin;
        this.skipCoordinates = options.skipCoordinates;
        this.extent = options.extent;
        this.value = options.value;
        this.clippingType = options.clippingType;
        this.edge = options.edge;
        this.createLabel = options.createLabel;
        this.positionLabel = options.positionLabel;
        this.labelMap = new Map();
        // Initialize
        this.update();
        // Update when the transform changes.
        const changedListener = () => this.update();
        chartTransform.changedEmitter.addListener(changedListener);
        this.disposeTickLabelSet = () => chartTransform.changedEmitter.removeListener(changedListener);
    }
    setSpacing(spacing) {
        if (this.spacing !== spacing) {
            this.spacing = spacing;
            this.update();
        }
    }
    getSpacing() {
        return this.spacing;
    }
    // Updates the labels when range or spacing has changed.
    update() {
        const children = [];
        const used = new Set();
        this.chartTransform.forEachSpacing(this.axisOrientation, this.spacing, this.origin, this.clippingType, (modelCoordinate, viewCoordinate) => {
            if (!this.skipCoordinates.includes(modelCoordinate)) {
                const tickBounds = new Bounds2(0, 0, 0, 0);
                if (this.axisOrientation === Orientation.HORIZONTAL) {
                    const viewY = this.edge === 'min' ? this.chartTransform.viewHeight :
                        this.edge === 'max' ? 0 :
                            this.chartTransform.modelToView(this.axisOrientation.opposite, this.value);
                    tickBounds.setMinMax(viewCoordinate, viewY - this.extent / 2, viewCoordinate, viewY + this.extent / 2);
                }
                else {
                    const viewX = this.edge === 'min' ? 0 :
                        this.edge === 'max' ? this.chartTransform.viewWidth :
                            this.chartTransform.modelToView(this.axisOrientation.opposite, this.value);
                    tickBounds.setMinMax(viewX - this.extent / 2, viewCoordinate, viewX + this.extent / 2, viewCoordinate);
                }
                const label = this.labelMap.has(modelCoordinate) ? this.labelMap.get(modelCoordinate) :
                    this.createLabel ? this.createLabel(modelCoordinate) :
                        null;
                this.labelMap.set(modelCoordinate, label);
                label && this.positionLabel(label, tickBounds, this.axisOrientation);
                label && children.push(label);
                used.add(modelCoordinate);
            }
        });
        // empty cache of unused values
        const toRemove = [];
        for (const key of this.labelMap.keys()) {
            if (!used.has(key)) {
                toRemove.push(key);
            }
        }
        toRemove.forEach(t => {
            this.labelMap.delete(t);
        });
        this.children = children;
    }
    /**
     * Clears the cache and updates the label set. Use this if you need to have new labels for values that are in
     * the cache. For example, if your createLabel function had logic to switch between numeric (e.g. 2) and
     * symbolic labels (e.g. '2L').
     */
    invalidateTickLabelSet() {
        this.labelMap.clear();
        this.update();
    }
    setCreateLabel(createLabel) {
        this.createLabel = createLabel;
        this.invalidateTickLabelSet();
    }
    dispose() {
        this.disposeTickLabelSet();
        super.dispose();
    }
    getSpacingBorders() {
        return this.chartTransform.getSpacingBorders(this.axisOrientation, this.spacing, this.origin, this.clippingType);
    }
}
bamboo.register('TickLabelSet', TickLabelSet);
export default TickLabelSet;
//# sourceMappingURL=TickLabelSet.js.map