<template>
    <div :class="formItemClass">
        <label v-if="label || $slots.label"
               :for="labelFor"
               :class="labelClass"
               :style="labelStyle">
            <slot name="label">{{label + form.labelSuffix}}</slot>
        </label>
        <div :class="`${prefix}-form-item__content`"
             :style="contentStyle">
            <div ref="formContentBox"
                 :class="`${prefix}-form-item__content-box`">
                <div :class="`${prefix}-form-item__content__main`"
                     :style="mainStyle">
                    <slot></slot>
                </div>
                <div v-if="$slots.append"
                     :class="`${prefix}-form-item__content__append`"
                     :style="appendStyle">
                    <slot name="append"></slot>
                </div>
            </div>
            <div v-if="noticeText && !errorMessage"
                 :class="`${prefix}-form-item__notice`"
                 v-html="noticeText"></div>
            <transition name="ani-fade-in-top">
                <slot v-if="errorMessage && showErrorMsg"
                      name="error">
                    <div :class="`${prefix}-form-item__error`">
                        {{errorMessage}}
                    </div>
                </slot>
            </transition>
        </div>
    </div>
</template>

<script>
import schema from 'async-validator';
import { prefix, prefixMixins, getValue } from 'jdd-design/utils';
export default {
    name: `${prefix}-form-item`,
    mixins: [prefixMixins],
    data() {
        return {
            errorState: '',
            errorMessage: '',
            initialValue: null,
            labelStyle: {},
            contentStyle: {},
            mainStyle: {},
            appendStyle: {}
        };
    },
    props: {
        contentWidth: {
            type: Number
        },
        labelWidth: {
            type: Number
        },
        labelSpace: {
            type: Number
        },
        label: String,
        prop: String,
        required: {
            type: Boolean,
            default: undefined
        },
        rules: Array,
        error: String,
        for: String,
        size: String,
        noticeText: String,
        showError: {
            type: Boolean,
            default: true
        }
    },
    provide() {
        return {
            jddFormItem: this
        };
    },
    inject: {
        jddForm: {
            default: null
        }
    },
    watch: {
        error: {
            immediate: true,
            handler(val) {
                if (val) {
                    this.errorMessage = val;
                    this.errorState = 'error';
                }
            }
        },
        'form.labelPosition': function() {
            this.resetLayout();
        }
    },
    computed: {
        formItemClass() {
            let clsNames = [`${prefix}-form-item`];
            clsNames.push(
                `${prefix}-form-item--${this.size || this.form.size}`
            );
            this.errorState && clsNames.push(`is-${this.errorState}`);
            return clsNames;
        },
        labelClass() {
            let clsNames = [`${prefix}-form-item__label`];
            this.isRequired && clsNames.push('is-required');
            clsNames.push(
                `${prefix}-form-item__label--${this.form.labelPosition}`
            );
            this.form.hideRequiredAsterisk && clsNames.push('is-no-asterisk');
            return clsNames;
        },
        form() {
            let parent = this.$parent;
            if (this.jddForm) {
                parent = this.jddForm;
            }
            return parent;
        },
        isRequired() {
            let rules = this.getRules();
            let isRequired = false;

            if (rules && rules.length) {
                rules.every(rule => {
                    if (rule.required) {
                        isRequired = true;
                        return false;
                    }
                    return true;
                });
            }
            return isRequired || this.required;
        },
        showErrorMsg() {
            return this.showError || this.form.showError;
        },
        labelFor() {
            return this.for || '';
        },
        fieldValue() {
            const model = this.form.model;
            if (!model || !this.prop) {
                return;
            }

            return getValue(model, this.prop).value;
        }
    },
    methods: {
        getRules() {
            let formRules = this.form.rules;
            const selfRules = this.rules;
            const requiredRule =
                this.required !== undefined
                    ? { required: !!this.required }
                    : [];

            const prop = getValue(formRules, this.prop || '');
            formRules = formRules
                ? prop.obj[this.prop || ''] || prop.value
                : [];

            return [].concat(selfRules || formRules || []).concat(requiredRule);
        },
        handleBlur() {
            this.validate('blur');
        },
        handleChange() {
            this.validate('change');
        },
        getFilteredRule(trigger) {
            const rules = this.getRules();

            return rules
                .filter(rule => {
                    if (!rule.trigger || trigger === '') return true;
                    if (Array.isArray(rule.trigger)) {
                        return rule.trigger.indexOf(trigger) > -1;
                    } else {
                        return rule.trigger === trigger;
                    }
                })
                .map(rule => {
                    return { ...rule };
                });
        },
        validate(trigger, callback) {
            const rules = this.getFilteredRule(trigger);
            callback = callback || (() => {});
            if ((!rules || rules.length === 0) && this.required === undefined) {
                callback();
                return true;
            }

            this.errorState = 'validating';

            const descriptor = {};
            if (rules && rules.length > 0) {
                rules.forEach(rule => {
                    delete rule.trigger;
                });
            }
            descriptor[this.prop] = rules;

            const validator = new schema(descriptor);
            const model = {};

            model[this.prop] = this.fieldValue;

            validator.validate(
                model,
                { firstFields: true },
                (errors, invalidFields) => {
                    this.errorState = !errors ? 'success' : 'error';
                    this.errorMessage = errors ? errors[0].message : '';

                    callback(this.errorMessage, invalidFields);
                }
            );
        },
        resetField() {
            this.errorState = '';
            this.errorMessage = '';

            let model = this.form.model;
            let path = this.prop;
            if (path) {
                if (path.indexOf(':') !== -1) {
                    path = path.replace(/:/, '.');
                }

                let prop = getValue(model, path);

                prop.obj[prop.key] = this.initialValue;
            }
        },
        clearValidate() {
            this.errorState = '';
            this.errorMessage = '';
        },
        setError(msg) {
            this.errorState = 'error';
            this.errorMessage = msg;
        },
        resetLayout() {
            if (this.form.labelPosition === 'top') {
                this.labelStyle = {};
                this.contentStyle = {};
                return;
            }
            const labelWidth = this.labelWidth || this.form.labelWidth;
            const labelSpace = this.labelSpace || this.form.labelSpace;
            let contentWidth = this.contentWidth || this.form.contentWidth;
            if (labelWidth) {
                this.labelStyle = {
                    width: `${labelWidth}px`,
                    textAlign: 'right'
                };
                this.contentStyle = {
                    marginLeft: `${labelWidth + labelSpace}px`
                };
            }
            if (contentWidth) {
                this.$nextTick(() => {
                    let parentWidth = this.$refs.formContentBox.clientWidth;
                    if (contentWidth > parentWidth) {
                        contentWidth = parentWidth;
                    }
                    this.mainStyle = {
                        width: `${contentWidth}px`
                    };
                    this.appendStyle = {
                        marginLeft: `${contentWidth + 10}px`
                    };
                });
            }
        },
        addValidateEvents() {
            const rules = this.getRules();

            if (rules.length || this.required !== undefined) {
                this.$on('form.blur', this.handleBlur);
                this.$on('form.change', this.handleChange);
            }
        },
        removeValidateEvents() {
            this.$off();
        }
    },
    mounted() {
        if (this.prop) {
            this.initialValue = this.fieldValue;
            if (Array.isArray(this.initialValue)) {
                this.initialValue = [...this.initialValue];
            }
            this.jddForm && this.jddForm.addField(this);
        }
        this.addValidateEvents();
        this.resetLayout();
    },
    beforeDestroy() {
        this.jddForm && this.jddForm.removeField(this);
        this.removeValidateEvents();
    }
};
</script>
