import { from, of, defer, EMPTY } from 'rxjs';
import { catchError, map, concatAll, filter, takeWhile } from 'rxjs/operators';
import { isFieldSetModel } from './models';
import { finalizeWithLast } from './finalize-with-last';
import isNil from '../../utils/isNil';
export var ASYNC_VALIDATOR = Symbol('AsyncValidator');
export function isAsyncValidator(validator) {
    if (validator[ASYNC_VALIDATOR]) {
        return true;
    }
    return false;
}
export function createAsyncValidator(validator) {
    var _a;
    return _a = {},
        _a[ASYNC_VALIDATOR] = true,
        _a.validator = validator,
        _a;
}
export var ValidateOption;
(function (ValidateOption) {
    ValidateOption[ValidateOption["Empty"] = 0] = "Empty";
    ValidateOption[ValidateOption["IncludeAsync"] = 2] = "IncludeAsync";
    ValidateOption[ValidateOption["IncludeUntouched"] = 4] = "IncludeUntouched";
    ValidateOption[ValidateOption["IncludeChildrenRecursively"] = 8] = "IncludeChildrenRecursively";
    ValidateOption[ValidateOption["ExcludePristine"] = 16] = "ExcludePristine";
    ValidateOption[ValidateOption["StopPropagation"] = 32] = "StopPropagation";
    ValidateOption[ValidateOption["Default"] = 0] = "Default";
})(ValidateOption || (ValidateOption = {}));
var ErrorSubscriber = (function () {
    function ErrorSubscriber(model) {
        this.model = model;
    }
    ErrorSubscriber.prototype.next = function (error) {
        this.model.error = error;
    };
    return ErrorSubscriber;
}());
export { ErrorSubscriber };
var ValidatorContext = (function () {
    function ValidatorContext(model) {
        this.model = model;
    }
    ValidatorContext.prototype.getSection = function () {
        return this.model.owner;
    };
    ValidatorContext.prototype.getSectionValue = function () {
        var names = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            names[_i] = arguments[_i];
        }
        if (!this.model.owner || !isFieldSetModel(this.model.owner)) {
            return null;
        }
        if (names.length === 0) {
            return this.model.owner.getRawValue();
        }
        var data = {};
        for (var i = 0; i < names.length; i += 1) {
            var name_1 = names[i];
            var model = this.model.owner.get(name_1);
            if (model) {
                data[name_1] = model.getRawValue();
            }
        }
        return data;
    };
    ValidatorContext.prototype.getFormValue = function () {
        var _a;
        return (_a = this.model.form) === null || _a === void 0 ? void 0 : _a.getRawValue();
    };
    return ValidatorContext;
}());
export { ValidatorContext };
function runValidator(validator, _a, value, ctx) {
    var reject = _a.reject;
    try {
        if (isAsyncValidator(validator)) {
            var ret = validator.validator(value, ctx);
            if (ret === null) {
                return of(null);
            }
            return from(ret);
        }
        else {
            return of(validator(value, ctx));
        }
    }
    catch (error) {
        reject(error);
        return EMPTY;
    }
}
var ValidatorExecutor = (function () {
    function ValidatorExecutor(model) {
        this.model = model;
        this.ctx = new ValidatorContext(model);
    }
    ValidatorExecutor.prototype.call = function (validation) {
        var _this = this;
        var option = validation.option, reject = validation.reject, resolve = validation.resolve;
        if (!this.model.touched() && !(option & ValidateOption.IncludeUntouched)) {
            resolve();
            return of(null);
        }
        if (option & ValidateOption.ExcludePristine && this.model.pristine()) {
            resolve();
            return of(null);
        }
        var value = this.model.getRawValue();
        var skipAsync = (option & ValidateOption.IncludeAsync) === 0;
        return from(this.model.validators).pipe(filter(function (validator) { return (skipAsync ? !isAsyncValidator(validator) : true); }), map(function (validator) {
            return defer(function () { return runValidator(validator, validation, value, _this.ctx); });
        }), concatAll(), takeWhile(isNil, true), catchError(function (error) {
            reject(error);
            return EMPTY;
        }), finalizeWithLast(resolve, null));
    };
    return ValidatorExecutor;
}());
export function validate(model) {
    var executor = new ValidatorExecutor(model);
    return function (validation) { return executor.call(validation); };
}
