/**
 * @private
 */
Ext.define('Ext.event.publisher.Focus', {
    extend: 'Ext.event.publisher.Dom',
 
    type: 'focus',
 
    handledEvents: ['focusenter', 'focusleave'],
 
    handledDomEvents: ['focusin', 'focusout'],
 
    doDelegatedEvent: function(e, invokeAfter) {
        var me = this,
            relatedTarget;
 
        e = me.callParent([e, false]);
 
        if (e) {
            if (e.type === 'focusout') {
                // If focus is departing to the document, there will be no forthcoming focusin event 
                // to trigger a focusleave, to fire a focusleave now. 
                if (e.relatedTarget == null) {
                    me.processFocusIn(e, e.target, document.body, invokeAfter);
                }
            }
            else {
                relatedTarget = e.relatedTarget;
 
                // IE reports relatedTarget as either an inaccessible object which coercively 
                // equates to null, or just a blank object in the case of focusing from nowhere. 
                if (relatedTarget == null || !relatedTarget.tagName) {
                    relatedTarget = document.body;
                }
                
                me.processFocusIn(e, relatedTarget, e.target, invokeAfter);
            }
        }
    },
 
    processFocusIn: function(e, fromElement, toElement, invokeAfter) {
        var me = this,
            commonAncestor = Ext.Element.getCommonAncestor(toElement, fromElement, true),
            node, targets = [],
            event;
 
        // Gather targets for focusleave event from the fromElement to the parentNode (not inclusive) 
        for (node = fromElement; node && node !== commonAncestor; node = node.parentNode) {
            targets.push(node);
        }
 
        // Publish the focusleave event for the bubble hierarchy 
        if (targets.length) {
            event = me.createSyntheticEvent('focusleave', e, fromElement, toElement);
            me.publish('focusleave', targets, event);
            targets.length = 0;
            if (event.isStopped) {
                return;
            }
        }
 
        // Gather targets for focusenter event from the focus targetElement to the parentNode (not inclusive) 
        for (node = toElement; node && node !== commonAncestor; node = node.parentNode) {
            targets.push(node);
        }
 
        // Publish the focusleave event for the bubble hierarchy 
        event = me.createSyntheticEvent('focusenter', e, toElement, fromElement);
        me.publish('focusenter', targets, event);
        if (event.isStopped) {
            return;
        }
 
        if (invokeAfter) {
            me.afterEvent(e);
        }
 
        Ext.GlobalEvents.fireEvent('focus', {
            event: event,
            toElement: toElement,
            fromElement: fromElement
        });
    },
    
    createSyntheticEvent: function(eventName, browserEvent, target, relatedTarget) {
        var event = new Ext.event.Event(browserEvent);
    
        event.type = eventName;
 
        event.relatedTarget = relatedTarget;
        event.target = target;
    
        return event;
    }
},
 
function(Focus) {
    var focusTimeout;
 
    // At this point only Firefox does not support focusin/focusout, see this bug: 
    // https://bugzilla.mozilla.org/show_bug.cgi?id=687787 
    if (!Ext.supports.FocusinFocusoutEvents) {
        // When focusin/focusout are not available we capture focus event instead, 
        // and fire both focusenter *and* focusleave in the focus handler. 
        this.override({
            handledDomEvents: ['focus', 'blur'],
            
            doDelegatedEvent: function(e, invokeAfter) {
                var me = this,
                    targetIsElement;
                
                e = me.callSuper([e, false]);
                
                if (e) {
                    // We need to know if event target was an element or (window || document) 
                    targetIsElement = e.target !== window && e.target !== document;
                    
                    // There might be an upcoming focus event, but if none happens 
                    // within a minimal timeout, then we treat this as a focus of the body 
                    if (e.type === 'blur') {
                        if (!targetIsElement) {
                            // Apparently when focus goes outside of the document, Firefox 
                            // will fire blur on the currently focused element, then on the document, 
                            // then on the window. Interestingly enough, both follow-up blur events 
                            // will have explicitOriginalTarget pointing at the previously focused 
                            // element; when that happens we can be reasonably sure that focus 
                            // indeed goes out the window. 
                            if (e.explicitOriginalTarget === Focus.previousActiveElement) {
                                // But we want that to fire only once, so process window blur 
                                // which happens last. 
                                if (e.target === window) {
                                    clearTimeout(focusTimeout);
                                    focusTimeout = 0;
                                    me.processFocusIn(e, Focus.previousActiveElement, document.body, invokeAfter);
                                    Focus.previousActiveElement = null;
                                }
                            }
                        }
                        else {
                            // If event target is a valid element, blur could have been caused 
                            // by removing previously focused element from the DOM, or some 
                            // other happening that doesn't involve <strike>Elvis</strike>focus 
                            // completely leaving the building. 
                            focusTimeout = setTimeout(function() {
                                focusTimeout = 0;
                                me.processFocusIn(e, e.target, document.body, invokeAfter);
                                Focus.previousActiveElement = null;
                            }, 0);
                        }
                        
                        Focus.previousActiveElement = targetIsElement ? e.target : null;
                    }
                    else {
                        clearTimeout(focusTimeout);
                        focusTimeout = 0;
                        
                        me.processFocusIn(
                            e,
                            Focus.previousActiveElement || document.body,
                            targetIsElement ? e.target : document.body,
                            invokeAfter
                        );
                    }
                }
            }
        });
    }
 
    Focus.instance = new Focus();
});