import _ from 'lodash';
import '../../../../../assert/js/assert.js';
import '../../../../../globals.js';
// Copyright 2024, University of Colorado Boulder
/**
 * This doc assumes you have read the doc in GroupSelectModel. Read that first as it explains the "group select
 * interaction" more generally.
 *
 * This type adds sorting on top of the GroupSelectView
 * in the interaction for (keyboard).
 *
 * @author Michael Kauzmann (PhET Interactive Simulations)
 * @author Marla Schulz (PhET Interactive Simulations)
 */
import Multilink from '../../../../../axon/js/Multilink.js';
import optionize from '../../../../../phet-core/js/optionize.js';
import { KeyboardListener } from '../../../../../scenery/js/imports.js';
import sceneryPhet from '../../../sceneryPhet.js';
import SceneryPhetStrings from '../../../SceneryPhetStrings.js';
import GroupSelectView from './GroupSelectView.js';
const navigableStringProperty = SceneryPhetStrings.a11y.groupSort.navigableStringProperty;
const sortableStringProperty = SceneryPhetStrings.a11y.groupSort.sortableStringProperty;
// A list of all keys that are listened to, except those covered by the numberKeyMapper
const sortingKeys = [
    'd', 'arrowRight', 'a', 'arrowLeft', 'arrowUp', 'arrowDown', 'w', 's', // default-step sort
    'shift+d', 'shift+arrowRight', 'shift+a', 'shift+arrowLeft', 'shift+arrowUp', 'shift+arrowDown', 'shift+w', 'shift+s', // shift-step sort
    'pageUp', 'pageDown', // page-step sort
    'home', 'end' // min/max
];
export default class GroupSortInteractionView extends GroupSelectView {
    constructor(model, primaryFocusedNode, // Client is responsible for setting accessibleName and nothing else!
    providedOptions) {
        const options = optionize()({
            numberKeyMapper: null,
            onSort: _.noop,
            sortStep: 1,
            shiftSortStep: 2,
            pageSortStep: Math.ceil(providedOptions.sortingRangeProperty.value.getLength() / 5),
            sortingRangeListener: (newRange) => {
                const selectedGroupItem = model.selectedGroupItemProperty.value;
                if (selectedGroupItem) {
                    const currentValue = model.getGroupItemValue(selectedGroupItem);
                    if (currentValue && !newRange.contains(currentValue)) {
                        model.selectedGroupItemProperty.value = providedOptions.getGroupItemToSelect();
                    }
                }
            }
        }, providedOptions);
        super(model, primaryFocusedNode, options);
        this.sortGroupItem = options.sortGroupItem;
        this.onSort = options.onSort;
        this.sortingRangeProperty = options.sortingRangeProperty;
        this.sortStep = options.sortStep;
        this.shiftSortStep = options.shiftSortStep;
        this.pageSortStep = options.pageSortStep;
        const selectedGroupItemProperty = this.model.selectedGroupItemProperty;
        const isGroupItemKeyboardGrabbedProperty = this.model.isGroupItemKeyboardGrabbedProperty;
        // If the new range doesn't include the current selection, reset back to the default heuristic.
        options.sortingRangeProperty.lazyLink(options.sortingRangeListener);
        this.disposeEmitter.addListener(() => {
            options.sortingRangeProperty.unlink(options.sortingRangeListener);
        });
        const deltaKeyboardListener = new KeyboardListener({
            fireOnHold: true,
            keys: sortingKeys,
            fire: (event, keysPressed) => {
                if (selectedGroupItemProperty.value !== null) {
                    const groupItem = selectedGroupItemProperty.value;
                    const oldValue = this.model.getGroupItemValue(groupItem);
                    assert && assert(oldValue !== null, 'We should have a group item when responding to input?');
                    // Sorting an item
                    if (isGroupItemKeyboardGrabbedProperty.value) {
                        // Don't do any sorting when disabled
                        // For these keys, the item will move by a particular delta
                        if (this.model.enabled && sortingKeys.includes(keysPressed)) {
                            const delta = this.getDeltaForKey(keysPressed);
                            assert && assert(delta !== null, 'should be a supported key');
                            const newValue = oldValue + delta;
                            this.onSortedValue(groupItem, newValue, oldValue);
                        }
                    }
                    else {
                        // Selecting an item
                        const unclampedDelta = this.getDeltaForKey(keysPressed);
                        if (unclampedDelta !== null) {
                            this.model.hasKeyboardSelectedGroupItemProperty.value = true;
                            const clampedDelta = this.sortingRangeProperty.value.clampDelta(oldValue, unclampedDelta);
                            selectedGroupItemProperty.value = options.getNextSelectedGroupItem(clampedDelta, groupItem);
                        }
                    }
                    this.onGroupItemChange(groupItem);
                }
            }
        });
        if (options.numberKeyMapper) {
            const numbersKeyboardListener = new KeyboardListener({
                fireOnHold: true,
                keys: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'],
                fire: (event, keysPressed) => {
                    if (selectedGroupItemProperty.value !== null && isGroupItemKeyboardGrabbedProperty.value &&
                        isSingleDigit(keysPressed)) {
                        const groupItem = selectedGroupItemProperty.value;
                        const oldValue = this.model.getGroupItemValue(groupItem);
                        assert && assert(oldValue !== null, 'We should have a group item when responding to input?');
                        assert && assert(isSingleDigit(keysPressed), 'sanity check on numbers for keyboard listener');
                        const mappedValue = options.numberKeyMapper(keysPressed);
                        if (mappedValue) {
                            this.onSortedValue(groupItem, mappedValue, oldValue);
                            this.onGroupItemChange(groupItem);
                        }
                    }
                }
            });
            primaryFocusedNode.addInputListener(numbersKeyboardListener);
            this.disposeEmitter.addListener(() => {
                primaryFocusedNode.removeInputListener(numbersKeyboardListener);
                numbersKeyboardListener.dispose();
            });
        }
        primaryFocusedNode.addInputListener(deltaKeyboardListener);
        Multilink.multilink([
            model.isGroupItemKeyboardGrabbedProperty
        ], isGrabbed => {
            primaryFocusedNode.setPDOMAttribute('aria-roledescription', isGrabbed ? sortableStringProperty : navigableStringProperty);
        });
        this.disposeEmitter.addListener(() => {
            primaryFocusedNode.removeInputListener(deltaKeyboardListener);
            deltaKeyboardListener.dispose();
        });
    }
    // Conduct the sorting of a value
    onSortedValue(groupItem, value, oldValue) {
        assert && assert(value !== null, 'We should have a value for the group item by the end of the listener.');
        this.sortGroupItem(groupItem, this.sortingRangeProperty.value.constrainValue(value));
        this.onSort(groupItem, oldValue);
        this.model.hasKeyboardSortedGroupItemProperty.value = true;
    }
    /**
     * Get the delta to change the value given what key was pressed. The returned delta may not result in a value in range,
     * please constrain value from range or provide your own defensive measures to this delta.
     */
    getDeltaForKey(key) {
        const fullRange = this.sortingRangeProperty.value.getLength();
        return key === 'home' ? -fullRange :
            key === 'end' ? fullRange :
                key === 'pageDown' ? -this.pageSortStep :
                    key === 'pageUp' ? this.pageSortStep :
                        ['arrowLeft', 'a', 'arrowDown', 's'].includes(key) ? -this.sortStep :
                            ['arrowRight', 'd', 'arrowUp', 'w'].includes(key) ? this.sortStep :
                                ['shift+arrowLeft', 'shift+a', 'shift+arrowDown', 'shift+s'].includes(key) ? -this.shiftSortStep :
                                    ['shift+arrowRight', 'shift+d', 'shift+arrowUp', 'shift+w'].includes(key) ? this.shiftSortStep :
                                        null;
    }
    dispose() {
        this.groupSortGroupFocusHighlightPath.dispose();
        this.grabReleaseCueNode.dispose();
        this.positionSortCueNodeEmitter.dispose();
        super.dispose();
    }
    /**
     * Creator factory, similar to PhetioObject.create(). This is most useful if you don't need to keep the instance of
     * your GroupSortInteractionView.
     */
    static create(model, primaryFocusedNode, providedOptions) {
        return new GroupSortInteractionView(model, primaryFocusedNode, providedOptions);
    }
}
function isSingleDigit(key) { return /^\d$/.test(key); }
sceneryPhet.register('GroupSortInteractionView', GroupSortInteractionView);
//# sourceMappingURL=GroupSortInteractionView.js.map