<script>
import UploadList from './upload-list';
import Upload from './upload';
import ElProgress from 'element-ui/packages/progress';
import Migrating from 'element-ui/src/mixins/migrating';
import Message from 'packages/message';

function noop() {}

const defaultSelectErrorHandler = (unsupportedFiles, selectedFiles, existingFiles, errorType) => {
  if (errorType === 'limit') {
    Message.warning('File limit exceeded. File(s) not selected.');
  } else if (errorType === 'size') {
    Message.warning('File too big. File(s) not selected.');
  } else if (errorType === 'format') {
    Message.warning('File format not supported. File(s) not selected.');
  }
};

const defaultUploadErrorHandler = () => {
  Message.error('File upload failed.');
};

const changeType = {
  SELECT: 'select',
  REMOVE: 'remove'
};

export default {
  name: 'ElUpload',

  mixins: [Migrating],

  components: {
    ElProgress,
    UploadList,
    Upload
  },

  provide() {
    return {
      uploader: this
    };
  },

  inject: {
    elForm: {
      default: ''
    }
  },

  props: {
    action: {
      type: String,
      required: true
    },
    method: {
      type: String,
      default: 'POST'
    },
    headers: {
      type: Object,
      default() {
        return {};
      }
    },
    data: Object,
    multiple: {
      type: Boolean,
      default: false
    },
    fileKey: {
      type: String,
      default: 'file'
    },
    name: {
      type: String,
      default: 'file'
    },
    drag: Boolean,
    withCredentials: Boolean,
    showFileList: {
      type: Boolean,
      default: true
    },
    accept: String,
    type: {
      type: String,
      default: 'select'
    },
    onBeforeRequest: {
      type: Function,
      default: noop
    },
    onBeforeUpload: Function,
    onBeforeRemove: Function,
    onChange: {
      type: Function,
      default: noop
    },
    onPreview: {
      type: Function
    },
    onSuccess: {
      type: Function,
      default: noop
    },
    onProgress: {
      type: Function,
      default: noop
    },
    onError: {
      type: Function,
      default: defaultUploadErrorHandler
    },
    fileList: {
      type: Array,
      default() {
        return [];
      }
    },
    listPlacement: {
      type: String,
      default: 'default',
      validator: val => ['above', 'inside', 'below', 'default'].includes(val)
    },
    autoUpload: {
      type: Boolean,
      default: true
    },
    listType: {
      type: String,
      default: 'text' // text,picture,picture-card
    },
    httpRequest: Function,
    disabled: Boolean,
    limit: Number,
    maxFileSize: { // in kilobytes
      type: Number,
      default: 1024 * 10 // 10MB
    },
    onSelectError: {
      type: Function,
      default: defaultSelectErrorHandler
    },
    expanded: {
      type: Boolean,
      default: false
    },
    dropzoneStyle: {
      type: Object,
      default: () => {}
    },
    dropzoneClass: {
      type: String,
      default: ''
    }
  },

  data() {
    return {
      uploadFiles: [],
      dragOver: false,
      draging: false,
      tempIndex: 1
    };
  },

  computed: {
    uploadDisabled() {
      return this.disabled || (this.elForm || {}).disabled;
    }
  },

  watch: {
    fileList: {
      immediate: true,
      handler(fileList) {
        if (!this.multiple && fileList.length > 1) {
          throw Error('`file-list` prop can only be passed an array with 1 item if `multiple` is not set');
        }
        this.uploadFiles = fileList.map(item => {
          item.uid = item.uid || (Date.now() + this.tempIndex++);
          item.status = item.status || 'success';
          return item;
        });
      }
    }
  },

  methods: {
    handleStart(rawFile) {
      rawFile.uid = Date.now() + this.tempIndex++;
      let file = {
        status: 'ready',
        name: rawFile.name,
        size: rawFile.size,
        percentage: 0,
        uid: rawFile.uid,
        raw: rawFile
      };

      try {
        file.url = URL.createObjectURL(rawFile);
      } catch (err) {
        console.error(err);
        return;
      }

      if (this.multiple) this.uploadFiles.push(file);
      else this.uploadFiles = [file];

      this.onChange(file, this.uploadFiles, changeType.SELECT);
    },
    handleProgress(ev, rawFile) {
      const file = this.getFile(rawFile);
      this.onProgress(ev, file, this.uploadFiles);
      file.status = 'uploading';
      file.percentage = ev.percent || 0;
    },
    handleSuccess(res, rawFile) {
      const file = this.getFile(rawFile);

      if (file) {
        file.status = 'success';
        file.response = res;

        this.onSuccess(res, file, this.uploadFiles);
      }
    },
    handleError(err, rawFile) {
      const file = this.getFile(rawFile);
      const fileList = this.uploadFiles;

      file.status = 'fail';

      fileList.splice(fileList.indexOf(file), 1);

      this.onError(err, file, this.uploadFiles);
    },
    handleRemove(file, raw) {
      if (raw) {
        file = this.getFile(raw);
      }
      let doRemove = () => {
        this.abort(file);
        let fileList = this.uploadFiles;
        fileList.splice(fileList.indexOf(file), 1);
        this.onChange(file, fileList, changeType.REMOVE);
      };

      if (!this.onBeforeRemove) {
        doRemove();
      } else if (typeof this.onBeforeRemove === 'function') {
        const before = this.onBeforeRemove(file, this.uploadFiles);
        if (before && before.then) {
          before.then(() => {
            doRemove();
          }, noop);
        } else if (before !== false) {
          doRemove();
        }
      }
    },
    getFile(rawFile) {
      let fileList = this.uploadFiles;
      let target;
      fileList.every(item => {
        target = rawFile.uid === item.uid ? item : null;
        return !target;
      });
      return target;
    },
    abort(file) {
      this.$refs['upload-inner'].abort(file);
    },

    // utility functions for the consumer
    getFiles() {
      return this.uploadFiles;
    },
    clearFiles() {
      this.uploadFiles = [];
    },
    submit() {
      this.uploadFiles
        .filter(file => file.status === 'ready')
        .forEach(file => {
          this.$refs['upload-inner'].upload(file.raw);
        });
    },
    removeFile(file) {
      this.handleRemove(file);
    }
  },

  render(h) {
    let uploadList;

    if (this.showFileList) {
      const defaultUploadList = (
        <UploadList
          disabled={this.uploadDisabled}
          listType={this.listType}
          files={this.uploadFiles}
          on-remove={this.handleRemove}
          handlePreview={this.onPreview}>
        </UploadList>
      );
      const customUploadList = this.$scopedSlots['upload-list'];

      uploadList = customUploadList ? customUploadList({ files: this.uploadFiles, removeFile: this.removeFile }) : defaultUploadList;
    }

    const uploadData = {
      props: {
        type: this.type,
        drag: this.drag,
        action: this.action,
        method: this.method,
        multiple: this.multiple,
        'before-upload': this.onBeforeUpload,
        'with-credentials': this.withCredentials,
        headers: this.headers,
        fileKey: this.fileKey,
        name: this.name,
        data: this.data,
        accept: this.accept,
        fileList: this.uploadFiles,
        autoUpload: this.autoUpload,
        listType: this.listType,
        listPlacement: this.listPlacement,
        disabled: this.uploadDisabled,
        limit: this.limit,
        maxFileSize: this.maxFileSize,
        beforeRequest: this.onBeforeRequest,
        'on-select-error': this.onSelectError,
        'on-start': this.handleStart,
        'on-progress': this.handleProgress,
        'on-success': this.handleSuccess,
        'on-error': this.handleError,
        'on-preview': this.onPreview,
        'on-remove': this.handleRemove,
        'http-request': this.httpRequest,
        expanded: this.expanded,
        dropzoneStyle: this.dropzoneStyle,
        dropzoneClass: this.dropzoneClass
      },
      ref: 'upload-inner'
    };

    const calculatedListPlacement = (() => {
      const { listType, listPlacement } = this;
      if (listPlacement === 'default') return listType === 'picture-card' ? 'above' : 'below';
      return listPlacement;
    })();

    const trigger = this.$slots.trigger || this.$slots.default;
    const uploadComponent = (
      <upload {...uploadData}>
        {trigger}
        { calculatedListPlacement === 'inside' ? uploadList : ''}
      </upload>
    );

    return (
      <div>
        { calculatedListPlacement === 'above' ? uploadList : ''}
        {
          this.$slots.trigger
            ? [uploadComponent, this.$slots.default]
            : uploadComponent
        }
        {this.$slots.tip}
        { calculatedListPlacement === 'below' ? uploadList : ''}
      </div>
    );
  }
};
</script>
