import { ModelListener } from 'front';


// helper functions

export function formValidateAsPromise(ml, report) {
    return new Promise((resolve) => ml.formValidate(resolve, report));
}

export function isFormValidationTransformed(ml) {
    return ml instanceof ModelListener && ml.hasOwnProperty('formValidate');
}

/**\

    title: Form Validation Transform
    description:
        For react components that have inputs to validate
    properties:
        formValidate[Function(cb)]:
            call this when the form needs to be validated. Accepts a callback
            that will be invoked with truthy/falsy based on valid/not

            will also populate this.model.errors with errors for each field.
            these are automatically cleared on update of input values

            you can access formValidate.settings to configure the formValidate
    usage:
        checker[Function]:
            a function used by the transform to validate fields. It is
            passed the fieldname and a callback.

            if fieldname provided, invoke the cb with an error if the field is
            invalid, null otherwise.

            if no fieldname provided, return an object with keys & properties
            corresponding to the fieldnames & errors. Return null or
            empty object if all fields are valid.

        children[ModelListener]:
            A model listener instance that also has the form validation
            transform. The formValidate method will also be invoked on the
            children when the parent's formValidate is called. In which case,
            the callback will indicate if any errors exist in the descendent
            chain.

\**/
export default function(ml, checker, ...children) {
    let _checker = checker,
        _children = children.filter(isFormValidationTransformed);

    function updateError(key, error) {
        ml.model.errors[key] = error;
        ml.forceUpdate();
    }


    if (!(ml && ml instanceof ModelListener)) {
        throw new Error('Form validation transform error: ' +
            'Can only transform instances of ModelListener');
    }

    if (!checker) {
        throw new Error('Form validation transform error: ' +
            'A \'checker\' function is required');
    }

    if (ml.model.errors) {
        throw new Error('Form validation transform error: ' +
            'The model already has an \'errors\' property defined');
    }

    ml.model.errors = {};

    ml.model.onUpdate('*', (changes) => {
        for (const key in changes) {
            if (!ml.model.errors[key]) {
                continue;
            }

            // if error exists for field, check if error remains
            _checker(key, updateError.bind(this, key));
        }
    });

    ml.formValidate = (cb, report = false) => {
        const checking = _children.map((child) =>
            formValidateAsPromise(child, report)
        );

        Promise.all(checking).then((validSoFar) => {
            const keys = ml.model.keys();
            let valid = true,
                i;

            for (i = 0; i < validSoFar.length; i++) {
                valid = valid && validSoFar[i];
            }

            // if only running report, valid if model.errors is all falsy
            if (report) {
                for (i = 0; i < keys.length; i++) {
                    valid = valid && !ml.model.errors[keys[i]];
                }
                return cb(valid);
            }

            // otherwise, run checker to validate fields
            _checker('', (errors) => {
                valid = valid && (!errors || !Object.keys(errors).length);

                ml.model.errors = errors;
                ml.forceUpdate();

                if (typeof cb === 'function') {
                    cb(valid);
                }
            });
        });
    };

    ml.formValidate.settings = {
        setChecker(newChecker) {
            if (typeof newChecker === 'function') {
                _checker = newChecker;
            }
        },
        addChildren(...toAdd) {
            _children = _children
                .concat(toAdd.filter(isFormValidationTransformed));
        },
        removeChildren(...toRemove) {
            toRemove.forEach((child) => {
                const idx = _children.indexOf(child);

                if (idx !== -1) {
                    _children.splice(idx, 1);
                }
            });
        }
    };
}

/**\
    fieldValidator:
        a function that takes a fieldname and value and returns an error msg
        if it is invalid, null otherwise.
\**/
export function createSyncChecker(ml, fieldValidator) {
    if (!(ml instanceof ModelListener)) {
        return;
    }

    if (typeof fieldValidator !== 'function') {
        return;
    }

    return function(fieldname, cb) {
        let errors = null,
            msg,
            key;

        if (fieldname) {
            errors = fieldValidator(fieldname, ml.model[fieldname]);
        } else {
            errors = {};

            for (key of ml.model.keys()) {
                msg = fieldValidator(key, ml.model[key]);

                if (msg) {
                    errors[key] = msg;
                }
            }
        }

        if (typeof cb === 'function') {
            cb(errors);
        }
    };
}
