<template>
    <div :class="bem()" :disabled="disabled">
        <div :class="bem('card-list')" v-if="type === 'card'">
            <div v-for="file in fileList" :key="file.id" :class="bem('card')">
                <i :class="bem('remove-btn')" @click="removeFile(file)">
                    <icon name="close" color="#fff" size="small"/>
                </i>
                <img :src="file.src" :class="bem('thumbnail')">
            </div>
            <div
                :class="bem('card', ['input'])"
                @click="onClick"
                v-if="!limit || limit !== fileList.length"
            >
                <slot>
                    <div>
                        <Icon name="upload" size="big"/>
                        <div>上传</div>
                    </div>
                </slot>
            </div>
        </div>
        <div v-else @click="onClick">
            <slot/>
        </div>
        <input
            type="file"
            :webkitdirectory="directory"
            ref="input"
            :multiple="multiple"
            :disabled="disabled"
            :accept="accept"
            @change="onChange"
            style="display: none;"
        >
    </div>
</template>

<script>
import GdFile from './gd-file';
import Icon from '../Icon';
import { componentMixin, createComponentName } from '../_utils/component';
import Oss from '../oss';

const TYPE2CN = {
    image: '图片',
    text: '文本',
    audio: '音频',
    video: '视频',
    application: '二进制'
};

export default {
    name: createComponentName('upload'),
    components: {
        Icon
    },
    mixins: [componentMixin],
    // TODO: fileList 支持 v-model
    // model: {
    //     prop: 'fileList',
    //     event: 'update',
    // },
    props: {
    // 接受上传的文件类型
        accept: {
            type: String,
            default: 'image/jpeg, image/png, image/gif'
        },
        /**
     * 上传前处理钩子
     * @args { GdFile }
     * @return { Promise | boolean | File | Blob | GdFile } 如果返回 false 将会终止上传并删除文件
     */
        beforeUpload: {
            // `(gdFile): Promise | boolean | File | Blob | GdFile`
            type: Function
        },
        action: {
            type: Function
        },
        // 上传文件夹, IE 不支持
        directory: {
            type: Boolean,
            default: false
        },
        // 是否支持多选文件
        multiple: {
            type: Boolean,
            default: false
        },
        // 选择文件立即上传
        autoUpload: {
            type: Boolean,
            default: true
        },
        // 是否禁用
        disabled: {
            type: Boolean,
            default: false
        },
        // 最大上传数目
        limit: {
            type: Number,
            // 0 表示 不限制
            default: 0
        },
        concurrent: {
            type: Number,
            default: 1
        },
        type: {
            // `card` `normal`
            type: String,
            default: 'normal'
        }
    },
    data() {
        return {
            fileList: []
        };
    },
    created() {
        this.willUploadFileds = [];
    },
    methods: {
        onClick() {
            if (!this.disabled && this.$refs.input) this.$refs.input.click();
        },
        verifyAccept(type) {
            if (this.accept && this.accept !== '*') {
                const reg = new RegExp(
                    this.accept
                        .split(',')
                        .map(type => type.trim())
                        .join('|')
                        .toLowerCase()
                );
                return reg.test(type.toLowerCase());
            }
            return true;
        },
        accepterrorText() {
            if (this.accept) {
                const types = this.accept
                    .replace(/\s+/g, '')
                    .split(',')
                    .map(MIME => {
                        const [type, subtype] = MIME.toLowerCase().split('/');
                        if (subtype === '*') {
                            return TYPE2CN[type] || type;
                        }
                        return subtype.toUpperCase();
                    })
                    .join('/');
                return `仅支持${types}文件`;
            }
            return '无效的文件格式';
        },
        onError(error, gdFile) {
            this.removeFile(gdFile);

            if (error.message === 'stop') {
                gdFile.status = 'cancel';
                return;
            }

            gdFile.error = error;
            gdFile.status = 'error';
            this.$emit('error', error, gdFile);
        },
        onChange(e) {
            const files = [];

            // 如果使用 [...e.target.files] 编译到 lib 会转成 e.target.files.concat() 导致报错
            for (const file of e.target.files) {
                files.push(file);
            }
            if (this.limit && files.length + this.fileList.length > this.limit) {
                // 超出限上线触发
                this.$emit('exceed', files, this.fileList);
                return;
            }

            const gdFiles = files
                .map(file => {
                    const gdFile = new GdFile(file);
                    if (!this.verifyAccept(gdFile.type)) {
                        const error = new Error(this.accepterrorText());
                        this.onError(error, gdFile);
                        return false;
                    }

                    return gdFile;
                })
                .filter(f => f);

            this.$refs.input.value = null;
            this.$emit('change', gdFiles);

            gdFiles.forEach(gdFile => {
                this.fileList.push(gdFile);

                if (this.autoUpload) {
                    this.uplaodFile(gdFile);
                }

                if (this.type === 'card') {
                    gdFile.src = gdFile.toURL();
                }
            });
        },
        async uplaodFile(gdFile) {
            /**
       * TODO: 支持其他格式上传
       */
            if (!/image\//.test(gdFile.type)) {
                return this.onError(new Error('目前只支持上传图片文件'), gdFile);
            }

            if (this.beforeUpload) {
                let res;
                try {
                    let res = await this.beforeUpload(gdFile);
                    if (!res) return this.removeFile(gdFile);
                }
                catch (error) {
                    return this.removeFile(gdFile);
                }

                const type = Object.prototype.toString.call(res);
                if (type === '[object Blob]') {
                    gdFile = new File([res], gdFile.name, { type: gdFile.type });
                    gdFile.originFile = gdFile;
                }
                else if (type === '[object File]') {
                    for (const key in res) {
                        if (res.hasOwnProperty(key)) {
                            gdFile[key] = res[key];
                            gdFile.originFile = gdFile;
                        }
                    }
                }
            }

            if (
                this.concurrent &&
        this.fileList.filter(f => f.status === 'uploading').length >=
          this.concurrent
            ) {
                return this.willUploadFileds.push(gdFile);
            }

            await this.post(gdFile);

            const num =
        this.concurrent -
        this.fileList.filter(f => f.status === 'uploading').length;
            if (this.concurrent && num) {
                const files = this.willUploadFileds.splice(0, num);
                files.forEach(file => this.uplaodFile(file));
            }
        },

        removeFile(gdFile) {
            gdFile.status = 'error';
            const index = this.fileList.indexOf(gdFile);
            if (index > -1) {
                this.$emit('remove', gdFile, this.fileList);
                this.fileList.splice(index, 1);
            }
        },

        post(gdFile) {
            gdFile.status = 'uploading';
            const oss = new Oss();
            gdFile.cancel = () => oss.cancel('stop');
            return oss
                .uploadFile(gdFile.originFile, {
                    onProgress: e => {
                        gdFile.percent = e.percent;
                        this.$emit('progress', e, gdFile, this.fileList);
                    }
                })
                .then(src => {
                    gdFile.src = src;
                    gdFile.status = 'done';
                    gdFile.cancel = null;
                    this.$emit('success', gdFile, this.fileList);
                })
                .catch(error => {
                    this.onError(error, gdFile);
                });
        },
        /**
     * @vuese
     * 上传所有 status: 'ready' 的文件列表
     */
        async uploadFiles() {
            await Promise.all(
                this.fileList
                    .filter(f => f.status === 'ready')
                    .map(f => this.uplaodFile(f))
            );
            return this.fileList;
        },
        cancel(gdFile) {
            const index = this.willUploadFileds.indexOf(gdFile);
            if (index > -1) {
                this.willUploadFileds.splice(index, 1);
            }

            if (gdFile.cancel) {
                gdFile.cancel();
                gdFile.cancel = null;
                gdFile.status = 'ready';
            }
        },
        cancelAll() {
            this.fileList.forEach(f => this.cancel(f));
        },
        /**
     * @vuese
     * 清空文件列表
     */
        clearFiles() {
            this.fileList = [];
        }
    }
};
</script>

<style lang="less">
@import "../_styles/base.less";

@b: ~"@{prefix}-upload";

.@{b} {
  &__card-list {
    display: flex;
    flex-wrap: wrap;
  }

  &__card {
    position: relative;
    overflow: hidden;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 122px;
    width: 122px;
    background: #fff;
    border: 1px solid #d6d8df;
    border-radius: 1px;
    font-size: 14px;
    text-align: center;
    margin-right: 12px;

    &:last-child {
      margin-right: 0px;
    }

    &--input {
      cursor: pointer;
      &:hover {
        border-color: @color-primary;
      }
    }
  }

  &__remove-btn {
    background: #d1d1d1;
    border-radius: 50%;
    cursor: pointer;
    margin: -26px -25px 0 0;
    height: 50px;
    width: 50px;
    position: absolute;
    right: 0;
    top: 0;

    .gd-icon {
      position: absolute;
      left: 10px;
      top: 30px;
      width: 10px;
      height: 10px;
    }

    &:hover {
      background: #bbb;
    }
  }

  &__thumbnail {
    max-width: 100%;
    max-height: 100%;
  }
}
</style>
