/**
 * This class is the root stub for managing a `ViewModel`.
 * @private
 */
Ext.define('Ext.app.bind.RootStub', {
    extend: 'Ext.app.bind.AbstractStub',
    requires: [
        'Ext.app.bind.LinkStub',
        'Ext.app.bind.Stub'
    ],
 
    isRootStub: true,
 
    depth: 0,
 
    createRootChild: function(name, direct) {
        var me = this,
            owner = me.owner,
            ownerData = owner.getData(),
            children = me.children,
            previous = children && children[name],
            parentStub = previous ? null : me,
            parentVM, stub;
 
        if (direct || ownerData.hasOwnProperty(name) || !(parentVM = owner.getParent())) {
            stub = new Ext.app.bind.Stub(owner, name, parentStub);
        }
        else {
            stub = new Ext.app.bind.LinkStub(owner, name, parentStub);
            stub.link('{' + name + '}', parentVM);
        }
 
        if (previous) {
            previous.graft(stub);
        }
 
        return stub;
    },
 
    createStubChild: function(name) {
        return this.createRootChild(name, true);
    },
 
    descend: function(path, index) {
        var me = this,
            children = me.children,
            pos = index || 0,
            name = path[pos++],
            ret = (children && children[name]) || me.createRootChild(name);
 
        if (pos < path.length) {
            ret = ret.descend(path, pos);
        }
 
        return ret;
    },
 
    getFullName: function() {
        return this.fullName || (this.fullName = this.owner.id + ':');
    },
 
    // The root Stub is associated with the owner's "data" object
 
    getDataObject: function() {
        return this.owner.data;
    },
 
    getRawValue: function() {
        return this.owner.data;
    },
 
    getValue: function() {
        return this.owner.data;
    },
 
    isDescendantOf: function() {
        return false;
    },
 
    set: function(value, preventClimb) {
        //<debug>
        if (!value || value.constructor !== Object) {
            Ext.raise('Only an object can be set at the root');
        }
        //</debug>
 
        /* eslint-disable-next-line vars-on-top */
        var me = this,
            children = me.children || (me.children = {}),
            owner = me.owner,
            data = owner.data,
            parentVM = owner.getParent(),
            stub, v, key, setSelf, created;
 
        for (key in value) {
            //<debug>
            if (key.indexOf('.') >= 0) {
                Ext.raise('Value names cannot contain dots');
            }
            //</debug>
 
            // Setting the value.
            // Ensure the Stub exists for the name, and set its value.
            v = value[key];
 
            if (!== undefined) {
                stub = children[key];
                setSelf = preventClimb || !me.shouldClimb(key);
 
                if (!stub) {
                    stub = me.createRootChild(key, setSelf);
                    created = true;
                }
                else if (setSelf && stub.isLinkStub && !stub.getLinkFormulaStub()) {
                    stub = me.insertChild(key);
                }
 
                if (!created || !data.hasOwnProperty(value)) {
                    owner.invalidateChildLinks(key);
                }
 
                stub.set(v, setSelf);
            }
            // Clearing the value. Delete the data item
            // Invalidate the Stub if it exists.
            else if (data.hasOwnProperty(key)) {
                delete data[key];
 
                stub = children[key];
 
                if (stub) {
                    if (!stub.isLinkStub && parentVM) {
                        stub = me.createRootChild(key);
                    }
 
                    owner.invalidateChildLinks(key, true);
                    stub.invalidate(true);
                }
            }
        }
    },
 
    schedule: Ext.emptyFn,
 
    unschedule: Ext.emptyFn,
 
    privates: {
        checkAvailability: function() {
            // Always available
            return true;
        },
 
        insertChild: function(name) {
            return this.createRootChild(name, true);
        },
 
        invalidateChildLink: function(name, clear) {
            var children = this.children,
                stub = children && children[name];
 
            if (stub && stub.isLinkStub && !stub.getLinkFormulaStub()) {
                stub = this.createRootChild(name);
 
                if (clear) {
                    stub.invalidate(true);
                }
 
                this.owner.invalidateChildLinks(name, clear);
            }
        },
 
        shouldClimb: function(name) {
            var parent = this.owner.getParent();
 
            while (parent) {
                if (parent.getData().hasOwnProperty(name)) {
                    return true;
                }
 
                parent = parent.getParent();
            }
 
            return false;
        }
    }
});