/**
 * This class handles navigation inside a list that is linked to an input field. The keyboard
 * events are taken from the input field, and the list is never focused. Focus remains in
 * the input field.
 *
 * @private
 * @since 6.5.0
 */
Ext.define('Ext.dataview.BoundListNavigationModel', {
    extend: 'Ext.dataview.NavigationModel',
    alias: 'navmodel.boundlist',
    requires: [
        'Ext.dataview.BoundListLocation'
    ],
 
    config: {
        navigateOnSpace: true
    },
 
    locationClass: 'Ext.dataview.BoundListLocation',
 
    privates: {
        getKeyNavCfg: function(view) {
            var me = this,
                eventEl;
 
            if (me.keyboard !== false) {
                // Drive the KeyNav off the BoundList's ownerField's focusEl if possible.
                // If there's no ownerField, try the view's focusEl. If that is not focusable
                // then we are not keyboard navigable.
                eventEl = (view.ownerField || view).getFocusEl();
 
                // If we are not linked
                if (eventEl) {
                    return {
                        target: eventEl,
                        eventName: 'keydown',
                        defaultEventAction: 'stopEvent',
                        esc: me.onKeyEsc,
                        up: me.onKeyUp,
                        down: me.onKeyDown,
                        right: me.onKeyRight,
                        left: me.onKeyLeft,
                        pageDown: me.onKeyPageDown,
                        pageUp: me.onKeyPageUp,
                        home: me.onKeyHome,
                        end: me.onKeyEnd,
                        tab: me.onKeyTab,
                        space: me.onKeySpace,
                        enter: me.onKeyEnter,
                        A: {
                            ctrl: true,
                            // Need a separate function because we don't want the key
                            // events passed on to selectAll (causes event suppression).
                            handler: me.onSelectAllKeyPress
                        },
                        // This object has to get its key processing in first.
                        // Specifically, before any Editor's key handling.
                        priority: 1001,
                        scope: me
                    };
                }
            }
        },
 
        getViewListeners: function(view) {
            var result = this.callParent([view]);
 
            result.childtouchstart = 'onChildTouchStart';
            result.childTap = 'onChildTap';
 
            return result;
        },
 
        // Focus never moves during BoundList navigation.
        // If expanded using a touch gesture, the body keeps focus to avoid
        // the virtual keyboard. On second tap, the field is focused.
        // Navigation never changes the focused element.
        doFocus: Ext.privateFn,
 
        handleLocationChange: function(location, options) {
            var target = location.sourceElement,
                ownerField = this.getView().ownerField;
 
            if (target && ownerField) {
                ownerField.inputElement.dom.setAttribute('aria-activedescendant', target.id);
            }
 
            this.callParent([location, options]);
        },
 
        onChildTouchStart: function(view, location) {
            var e = location.event;
 
            // Mousedown/pointerdown events move focus by default.
            // BoundLists do not accept focus so prevent any focus movement.
            if (e.pointerType !== 'touch') {
                e.preventDefault();
            }
        },
 
        onChildTap: function(view, location) {
            var me = this,
                e = location.event,
                newLocation;
 
            if (!view.destroyed) {
                // Touch tap events move focus by default.
                // BoundLists do not accept focus so prevent any focus movement.
                if (e.pointerType === 'touch') {
                    e.preventDefault();
                }
 
                // Must create a new location based around the *item*, not the clicked
                // element because it's the location's sourceElement that is scrolled into
                // view (for example input fields and other focusables take priority)
                // but we want the whole list item to be the measured, scrolled element.
                newLocation = me.createLocation(location.item);
 
                // We're already here. For example SelectField setting the location on
                // downarrow expand
                if (me.location && me.location.equals(newLocation)) {
                    me.onNavigate(e);
                }
                else {
                    // Because there is going to be no focus event, we must explicitly
                    // set the position here and select it.
                    me.setLocation(newLocation, {
                        event: location.event,
                        animation: true
                    });
                }
            }
 
            // Touch taps go "through" the list and focus the field below it.
            if (e.pointerType === 'touch') {
                e.stopEvent();
            }
        },
 
        onChildTrigger: Ext.privateFn,
 
        onKeyLeft: function() {
            return true;
        },
 
        onKeyRight: function() {
            return true;
        },
 
        onKeySpace: function(e) {
            if (this.getNavigateOnSpace()) {
                e.preventDefault();
                this.onNavigate(e);
            }
 
            // Allow to propagate to field
            return true;
        },
 
        onKeyEsc: function() {
            var view = this.getView(),
                field = view.ownerField;
 
            // ESC collapsed an expanded list.
            if (field && view.isVisible()) {
                field.collapse();
            }
            // Allow ESC to propagate if it's not being used
            // to do a collapse.
            else {
                return true;
            }
        },
 
        onKeyTab: function(e) {
            var view = this.getView(),
                field = view.ownerField;
 
            if (view.isVisible()) {
                // SelectOnTab selects the value of the field
                // So that it's propagated into the input field
                // prior to blur so that post blur processing
                // asserts a true value.
                if (field.getSelectOnTab()) {
                    this.selectHighlighted(e);
                }
 
                if (field.collapse) {
                    field.collapse();
                }
            }
 
            // Tab key event is allowed to propagate to field
            return true;
        },
 
        // ENTER emulates an childtap event at the View level
        onKeyEnter: function(e) {
            var view = this.getView(),
                selectable = view.getSelectable(),
                field = view.ownerField;
 
            // Stop the keydown event so that an ENTER keyup does not get delivered to
            // any element which focus is transferred to in a select handler.
            e.stopEvent();
 
            // Handle the case where the highlighted item is already selected
            // In this case, the change event won't fire, so just collapse
            if (!(field.getMultiSelect && field.getMultiSelect()) &&
                selectable.isSelected(this.location.record) && field.collapse) {
                field.collapse();
            }
            else {
                this.selectHighlighted(e);
            }
 
            // Stop propagation of the ENTER keydown event so that any Editor which owns the
            // field does not completeEdit, but we also need to still fire the specialkey
            // event for ENTER, so lets add fromBoundList to the event, and this will be
            // handled by CellEditor#onSpecialKey.
            e.fromBoundList = true;
            field.fireEvent('specialkey', field, e);
 
            return false;
        },
 
        onNavigate: function(event) {
            var doNavigate = event && (
                    event.pointerType ||
                    (this.getNavigateOnSpace() && event.keyCode === event.SPACE)
                ),
                view = this.getView(),
                field = view.getRefOwner();
 
            // We dnly select on pointer gestures or (SPACE if configured).
            // The ENTER key is handled above.
            if (doNavigate) {
                this.callParent([event]);
                
                if (field && field.maybeCollapse) {
                    field.maybeCollapse(event);
                }
            }
        },
 
        /**
         * Triggers selection of the currently highlighted item according to the behavior of
         * the configured SelectionModel.
         */
        selectHighlighted: function(e) {
            var me = this,
                view = me.getView(),
                store = view.getStore(),
                selectable = view.getSelectable(),
                location = me.location,
                highlightedRec, index;
 
            // If there is no currently highlighted item or all options have been filtered out,
            // then do NOT add select the currently highlighted item.
            if (location && view.getViewItems().length) {
                highlightedRec = location.record;
 
                if (highlightedRec) {
                    // Select if not already selected.
                    // If already selected, selecting with no CTRL flag will deselect the record.
                    if (e.getKey() === e.ENTER || !selectable.isSelected(highlightedRec)) {
                        selectable.selectWithEvent(highlightedRec, e);
 
                        // If the result of that selection is that the record is removed or
                        // filtered out, jump to the next one.
                        if (!view.getStore().contains(highlightedRec)) {
                            index = Math.min(location.recordIndex, store.getCount() - 1);
                            me.setLocation(store.getAt(index));
                        }
                    }
                }
            }
        }
    }
});