/**
 * This class is used to hold validation errors for a record. The results of the record's
 * `{@link Ext.data.Model#validators validators}` are stored as the field values of this
 * record. The first failed validation is all that is stored per field unless the Model
 * class has defined a `validationSeparator` config.
 * @since 5.0.0
 */
Ext.define('Ext.data.Validation', {
    extend: 'Ext.data.Model',
 
    isValidation: true,
 
    /**
     * @property {Number} syncGeneration
     * This is a capture of the `{@link Ext.data.Model#generation}` value from the last
     * time the validation state was refreshed. This is used to determine if this object
     * is potentially out-of-date with its associated `record`.
     * @private
     * @readonly
     * @since 5.0.0
     */
    syncGeneration: 0, // Model generation starts at 1 so we start out-of-sync
 
    /**
     * Attaches this instance to its associated `record`.
     * @param {Ext.data.Model} record The associated record.
     * @private
     * @since 5.0.0
     */
    attach: function(record) {
        /**
         * @property {Ext.data.Model} record
         * The associated record for this validation instance.
         * @readonly
         * @since 5.0.0
         */
        this.record = record;
        this.isBase = record.self === Ext.data.Model;
 
        // We need to remove the id property from our data as that is not meaningful for
        // a Validation pseudo-record.
        delete this.data.id;
    },
 
    getValidation: function() {
        return null;
    },
 
    /**
     * Returns true if the associated record (not this one) is valid.
     * @return {Boolean} 
     */
    isValid: function() {
        var me = this;
 
        if (me.syncGeneration !== me.record.generation) {
            me.refresh();
        }
 
        return !me.dirty;
    },
 
    /**
     * This method updates the state of this record against its associated `record`. This
     * method should not need to be called directly as it is internally called when this
     * record is returned by `{@link Ext.data.Model#getValidation}`.
     * @param {Boolean} [force=false] Pass `true` to force an update of validation state.
     * @private
     * @since 5.0.0
     */
    refresh: function(force) {
        // If it's an Ext.data.Model instance directly, we can't
        // validate it because there can be no fields/validators.
        if (this.isBase) {
            return;
        }
 
        /* eslint-disable-next-line vars-on-top */
        var me = this,
            data = me.data,
            record = me.record,
            fields = record.fields,
            generation = record.generation,
            recordData = record.data,
            sep = record.validationSeparator,
            values = null,
            defaultMessage, currentValue, error, field,
            i, len, msg, val, name;
 
        if (force || me.syncGeneration !== generation) {
            me.syncGeneration = generation;
 
            for (= 0, len = fields.length; i < len; ++i) {
                field = fields[i];
                name = field.name;
                val = recordData[name];
                defaultMessage = field.defaultInvalidMessage;
                error = 0;
 
                if (!(name in data)) {
                    // Now is the cheapest time to populate our data object with "true"
                    // for all validated fields. This ensures that our non-dirty state
                    // equates to isValid.
                    data[name] = currentValue = true; // true === valid
                }
                else {
                    currentValue = data[name];
                }
 
                if (field.validate !== Ext.emptyFn) {
                    msg = field.validate(val, sep, null, record);
                    
                    if (msg !== true) {
                        error = msg || defaultMessage;
                    }
                }
 
                if (!error) {
                    error = true; // valid state is stored as true
                }
                
                if (error !== currentValue) {
                    (values || (values = {}))[name] = error;
                }
            }
 
            if (values) {
                // only need to do this if something changed...
                me.set(values);
            }
        }
    }
});