/**
 * This plugin provides a way to map actions to swipe gesture on all list items.
 */
Ext.define('Ext.dataview.listswiper.ListSwiper', {
    extend: 'Ext.plugin.Abstract',
    alias: 'plugin.listswiper',
 
    requires: [
        'Ext.util.DelayedTask'
    ],
 
    /**
     * @event itemaction
     * Fires whenever a swipe action has been triggered from a list item.
     * @param {Ext.dataview.List} this 
     * @param {Number} index The index of the swipped item.
     * @param {Ext.data.Model} record The record associated to the item.
     * @param {String} action The triggered action key.
     * @member Ext.dataview.List
     */
 
    config: {
        left: [],
        right: [],
 
        /**
         * @cfg {Boolean} dismissOnTap
         * If `true`, actions in the undo state will be committed when the item is tapped.
         * Any open menus will be closed.
         */
        dismissOnTap: true,
 
        /**
         * @cfg {Boolean} dismissOnScroll
         * If `true`, actions in the undo state will be committed as soon as the list is scrolled.
         * Any open menus will be closed
         */
        dismissOnScroll: true,
 
        /**
         * @cfg {Number} commitDelay
         * Number of milliseconds before actions in the undo state are automatically committed 
         * (`0` to disable this behavior). Only applicable for {@link #actions} with 
         * `undoable: true`.
         */
        commitDelay: 0,
 
        /**
         * @cfg {Object/Ext.dataview.plugin.ListSwiperStepper} widget
         * The config object for a {@link Ext.dataview.plugin.ListSwiperStepper}.
         * @cfg {String} widget.xtype (required) The type of component or widget to create.
         */
        widget: {
            xtype: 'listswiperaccordion'
        },
 
        swipeMax: {
            single: 50,
            multiple: 90
        },
 
        directionLock: true,
 
        /**
         * @cfg {'inner'/'outer'} [target='inner']
         * The section of the list item that is swipable.  Supports the following values:
         *
         * - `'inner'` - the default value. the body of the list item, which includes any
         *   tools is swipable, and any docked items remain fixed in place while swiping.
         * - `'outer'` - the entire list item including the docked items is swipable
         */
        target: null
    },
 
    shadowCls: Ext.baseCSSPrefix + 'listswiper-shadow',
 
    init: function(list) {
        var me = this,
            scrollable = list.getScrollable();
 
        // Contains items being swiped or pending
        me.items = [];
 
        list.on({
            scope: this,
            add: 'onItemAdd'
        });
 
        list.el.on({
            scope: this,
            dragstart: 'onDragStart',
            drag: 'onDragMove',
            dragend: 'onDragEnd'
        });
 
        if (scrollable) {
            scrollable.setX(false);
        }
 
        me.dismissAllTask = new Ext.util.DelayedTask(me.dismissAll, me);
        me.updateDismissOnScroll(me.getDismissOnScroll());
    },
 
    destroy: function() {
        var list = this.cmp;
 
        list.un({
            scope: this,
            add: 'onItemAdd'
        });
 
        list.el.un({
            scope: this,
            dragstart: 'onDragStart',
            drag: 'onDragMove',
            dragend: 'onDragEnd'
        });
 
        this.callParent();
    },
 
    createWidget: function(config) {
        var me = this,
            leftItems = me.getLeft(),
            rightItems = me.getRight();
 
        return Ext.apply({
            owner: me,
            defaults: me.defaults,
            leftActions: leftItems,
            rightActions: rightItems
        }, config);
    },
 
    onScrollStart: function() {
        if (this.getDismissOnScroll()) {
            this.dismissAll();
        }
    },
 
    onItemAdd: function(list, item) {
        item.setTouchAction({
            panX: false
        });
    },
 
    onItemUpdateData: function(item) {
        // In order to migrate contexts in case of one or more records have been inserted
        // or removed at a lower index, resyncing needs to be differed until all records
        // have been reassigned to their associated item.
        Ext.asap(this.resyncItem, this, [item]);
    },
 
    onDragStart: function(evt) {
        var me = this,
            list = me.cmp,
            record = list.mapToRecord(evt),
            target, translationTarget, renderTarget, item, widget;
 
        if (!me.hasActions() || (evt.absDeltaX < evt.absDeltaY)) {
            return;
        }
 
        if (record) {
            item = list.mapToItem(record);
 
            if (item) {
                widget = item.$swiperWidget;
 
                if (!widget) {
                    widget = me.createWidget(me.getWidget());
                    widget.ownerCmp = item;
                    target = me.getTarget();
 
                    if (item.isGridRow || (target === 'outer')) {
                        renderTarget = item.el;
                        // the element that gets translated could be either the body or the
                        // dock wrapper depending on whether or not there are docked items
                        translationTarget = item.el.first();
                    }
                    else {
                        renderTarget = item.bodyElement;
                        translationTarget = item.hasToolZones
                            ? renderTarget.child('.' + Ext.baseCSSPrefix + 'tool-dock')
                            : item.innerElement;
                    }
 
                    translationTarget.addCls(me.shadowCls);
 
                    widget.translationTarget = translationTarget;
                    renderTarget = translationTarget.parent();
                    item.$swiperWidget = widget = Ext.create(widget);
                    renderTarget.insertFirst(widget.el);
                    widget.setRendered(true);
 
                    if (list.infinite) {
                        list.stickItem(item, true);
                    }
 
                    this.items.push(item);
                }
 
                widget.onDragStart(evt);
            }
        }
 
    },
 
    onDragMove: function(evt) {
        var me = this,
            list = me.cmp,
            item = list.mapToItem(evt),
            swiperItem;
 
        if (item) {
            swiperItem = item.$swiperWidget;
 
            if (!me.hasActions() || !swiperItem) {
                return;
            }
 
            swiperItem.onDragMove(evt);
        }
    },
 
    onDragEnd: function(evt) {
        var me = this,
            list = me.cmp,
            item = list.mapToItem(evt),
            swiperItem;
 
        if (item) {
            swiperItem = item.$swiperWidget;
 
            if (!me.hasActions() || !swiperItem) {
                return;
            }
 
            swiperItem.onDragEnd(evt);
        }
    },
 
    updateDismissOnScroll: function(value) {
        var list = this.getCmp(),
            scrollable, listeners;
 
        if (this.isConfiguring || !list) {
            return;
        }
 
        scrollable = list.getScrollable();
 
        if (!scrollable) {
            return;
        }
 
        listeners = {
            scrollstart: 'onScrollStart',
            scope: this
        };
 
        if (value === true) {
            scrollable.on(listeners);
        }
        else {
            scrollable.un(listeners);
        }
    },
 
    //<debug>
    updateTarget: function(target, oldTarget) {
        if (target != null && target !== 'inner' && target !== 'outer') {
            Ext.raise('Invalid target for ListSwiper: ' + target);
        }
    },
    //</debug>
 
    hasActions: function() {
        return this.getLeft() || this.getRight();
    },
 
    privates: {
        destroyItem: function(item) {
            var me = this,
                list = me.cmp,
                swiperWidget = item.$swiperWidget,
                i = me.items.indexOf(item);
 
            if (!== -1) {
                me.items.splice(i, 1);
            }
 
            if (swiperWidget) {
                swiperWidget.destroy();
            }
 
            item.$swiperWidget = null;
 
            if (list.infinite && !item.destroyed) {
                list.stickItem(item, null);
            }
        },
 
        dismissAll: function() {
            var me = this;
 
            me.items.map(function(item) {
                return item.$swiperWidget;
            }).forEach(function(swiperItem) {
                swiperItem.dismiss();
            });
        }
    }
});