<template>
	<view class="uni-file-picker">
		<view v-if="title" class="uni-file-picker__header">
			<text class="file-title">{{ title }}</text>
			<text class="file-count">{{ filesList.length }}/{{ limitLength }}</text>
		</view>
		<upload-image v-if="fileMediatype === 'image' && showType === 'grid'" :readonly="readonly" :image-styles="imageStyles" :files-list="filesList" :limit="limitLength" :disablePreview="disablePreview" :delIcon="delIcon" @uploadFiles="uploadFiles" @choose="choose" @delFile="delFile">
			<slot>
				<view class="icon-add"></view>
				<view class="icon-add rotate"></view>
			</slot>
		</upload-image>
		<upload-file v-if="fileMediatype !== 'image' || showType !== 'grid'" :readonly="readonly" :list-styles="listStyles" :files-list="filesList" :showType="showType" :delIcon="delIcon" @uploadFiles="uploadFiles" @choose="choose" @delFile="delFile">
			<slot><button type="primary" size="mini">选择文件</button></slot>
		</upload-file>
	</view>
</template>

<script>
	import {
		chooseAndUploadFile,
		uploadCloudFiles
	} from './choose-and-upload-file.js'
	import {
		get_file_ext,
		get_extname,
		get_files_and_is_max,
		get_file_info,
		get_file_data
	} from './utils.js'
	import uploadImage from './upload-image.vue'
	import uploadFile from './upload-file.vue'
	let fileInput = null
	/**
	 * FilePicker 文件选择上传
	 * @description 文件选择上传组件，可以选择图片、视频等任意文件并上传到当前绑定的服务空间
	 * @tutorial https://ext.dcloud.net.cn/plugin?id=4079
	 * @property {Object|Array}	value	组件数据，通常用来回显 ,类型由return-type属性决定
	 * @property {Boolean}	disabled = [true|false]	组件禁用
	 * 	@value true 	禁用
	 * 	@value false 	取消禁用
	 * @property {Boolean}	readonly = [true|false]	组件只读，不可选择，不显示进度，不显示删除按钮
	 * 	@value true 	只读
	 * 	@value false 	取消只读
	 * @property {String}	return-type = [array|object]	限制 value 格式，当为 object 时 ，组件只能单选，且会覆盖
	 * 	@value array	规定 value 属性的类型为数组
	 * 	@value object	规定 value 属性的类型为对象
	 * @property {Boolean}	disable-preview = [true|false]	禁用图片预览，仅 mode:grid 时生效
	 * 	@value true 	禁用图片预览
	 * 	@value false 	取消禁用图片预览
	 * @property {Boolean}	del-icon = [true|false]	是否显示删除按钮
	 * 	@value true 	显示删除按钮
	 * 	@value false 	不显示删除按钮
	 * @property {Boolean}	auto-upload = [true|false]	是否自动上传，值为true则只触发@select,可自行上传
	 * 	@value true 	自动上传
	 * 	@value false 	取消自动上传
	 * @property {Number|String}	limit	最大选择个数 ，h5 会自动忽略多选的部分
	 * @property {String}	title	组件标题，右侧显示上传计数
	 * @property {String}	mode = [list|grid]	选择文件后的文件列表样式
	 * 	@value list 	列表显示
	 * 	@value grid 	宫格显示
	 * @property {String}	file-mediatype = [image|video|all]	选择文件类型
	 * 	@value image	只选择图片
	 * 	@value video	只选择视频
	 * 	@value all		选择所有文件
	 * @property {Array}	file-extname	选择文件后缀，根据 file-mediatype 属性而不同
	 * @property {Object}	list-style	mode:list 时的样式
	 * @property {Object}	image-styles	选择文件后缀，根据 file-mediatype 属性而不同
	 * @event {Function} select 	选择文件后触发
	 * @event {Function} progress 文件上传时触发
	 * @event {Function} success 	上传成功触发
	 * @event {Function} fail 		上传失败触发
	 * @event {Function} delete 	文件从列表移除时触发
	 */
	export default {
		name: 'uniFilePicker',
		components: {
			uploadImage,
			uploadFile
		},
		options: {
			virtualHost: true
		},
		emits: ['select', 'success', 'fail', 'progress', 'delete', 'update:modelValue', 'input'],
		props: {
			modelValue: {
				type: [Array, Object],
				default () {
					return []
				}
			},
			value: {
				type: [Array, Object],
				default () {
					return []
				}
			},
			disabled: {
				type: Boolean,
				default: false
			},
			disablePreview: {
				type: Boolean,
				default: false
			},
			delIcon: {
				type: Boolean,
				default: true
			},
			// 自动上传
			autoUpload: {
				type: Boolean,
				default: true
			},
			// 最大选择个数 ，h5只能限制单选或是多选
			limit: {
				type: [Number, String],
				default: 9
			},
			// 列表样式 grid | list | list-card
			mode: {
				type: String,
				default: 'grid'
			},
			// 选择文件类型  image/video/all
			fileMediatype: {
				type: String,
				default: 'image'
			},
			// 文件类型筛选
			fileExtname: {
				type: [Array, String],
				default () {
					return []
				}
			},
			title: {
				type: String,
				default: ''
			},
			listStyles: {
				type: Object,
				default () {
					return {
						// 是否显示边框
						border: true,
						// 是否显示分隔线
						dividline: true,
						// 线条样式
						borderStyle: {}
					}
				}
			},
			imageStyles: {
				type: Object,
				default () {
					return {
						width: 'auto',
						height: 'auto'
					}
				}
			},
			readonly: {
				type: Boolean,
				default: false
			},
			returnType: {
				type: String,
				default: 'array'
			},
			sizeType: {
				type: Array,
				default () {
					return ['original', 'compressed']
				}
			},
			sourceType: {
				type: Array,
				default () {
					return ['album', 'camera']
				}
			},
			provider: {
				type: String,
				default: '' // 默认上传到 unicloud 内置存储 extStorage 扩展存储
			},
			dir: {
				type: String,
				default: '/'
			}
		},
		data() {
			return {
				files: [],
				localValue: [],
				dirPath: '/'
			}
		},
		watch: {
			value: {
				handler(newVal, oldVal) {
					this.setValue(newVal, oldVal)
				},
				immediate: true
			},
			modelValue: {
				handler(newVal, oldVal) {
					this.setValue(newVal, oldVal)
				},
				immediate: true
			},
			dir: {
				handler(newVal) {
					this.dirPath = newVal
				},
				immediate: true
			},
		},
		computed: {
			filesList() {
				let files = []
				this.files.forEach(v => {
					files.push(v)
				})
				return files
			},
			showType() {
				if (this.fileMediatype === 'image') {
					return this.mode
				}
				return 'list'
			},
			limitLength() {
				if (this.returnType === 'object') {
					return 1
				}
				if (!this.limit) {
					return 1
				}
				if (this.limit >= 9) {
					return 9
				}
				return this.limit
			}
		},
		created() {
			// TODO 兼容不开通服务空间的情况
			if (!(uniCloud.config && uniCloud.config.provider)) {
				this.noSpace = true
				uniCloud.chooseAndUploadFile = chooseAndUploadFile
			}
			this.form = this.getForm('uniForms')
			this.formItem = this.getForm('uniFormsItem')
			if (this.form && this.formItem) {
				if (this.formItem.name) {
					this.rename = this.formItem.name
					this.form.inputChildrens.push(this)
				}
			}
		},
		methods: {
			/**
			 * 公开用户使用，清空文件
			 * @param {Object} index
			 */
			clearFiles(index) {
				if (index !== 0 && !index) {
					this.files = []
					this.$nextTick(() => {
						this.setEmit()
					})
				} else {
					this.files.splice(index, 1)
				}
				this.$nextTick(() => {
					this.setEmit()
				})
			},
			/**
			 * 公开用户使用，继续上传
			 */
			upload() {
				let files = []
				this.files.forEach((v, index) => {
					if (v.status === 'ready' || v.status === 'error') {
						files.push(Object.assign({}, v))
					}
				})
				return this.uploadFiles(files)
			},
			async setValue(newVal, oldVal) {
				const newData = async (v) => {
					const reg = /cloud:\/\/([\w.]+\/?)\S*/
					let url = ''
					if (v.fileID) {
						url = v.fileID
					} else {
						url = v.url
					}
					if (reg.test(url)) {
						v.fileID = url
						v.url = await this.getTempFileURL(url)
					}
					if (v.url) v.path = v.url
					return v
				}
				if (this.returnType === 'object') {
					if (newVal) {
						await newData(newVal)
					} else {
						newVal = {}
					}
				} else {
					if (!newVal) newVal = []
					for (let i = 0; i < newVal.length; i++) {
						let v = newVal[i]
						await newData(v)
					}
				}
				this.localValue = newVal
				if (this.form && this.formItem && !this.is_reset) {
					this.is_reset = false
					this.formItem.setValue(this.localValue)
				}
				let filesData = Object.keys(newVal).length > 0 ? newVal : [];
				this.files = [].concat(filesData)
			},

			/**
			 * 选择文件
			 */
			choose() {
				if (this.disabled) return
				if (this.files.length >= Number(this.limitLength) && this.showType !== 'grid' && this.returnType ===
					'array') {
					uni.showToast({
						title: `您最多选择 ${this.limitLength} 个文件`,
						icon: 'none'
					})
					return
				}
				this.chooseFiles()
			},

			/**
			 * 选择文件并上传
			 */
			chooseFiles() {
				const _extname = get_extname(this.fileExtname)
				// 获取后缀
				uniCloud
					.chooseAndUploadFile({
						type: this.fileMediatype,
						compressed: false,
						sizeType: this.sizeType,
						sourceType: this.sourceType,
						// TODO 如果为空，video 有问题
						extension: _extname.length > 0 ? _extname : undefined,
						count: this.limitLength - this.files.length, //默认9
						onChooseFile: this.chooseFileCallback,
						onUploadProgress: progressEvent => {
							this.setProgress(progressEvent, progressEvent.index)
						}
					})
					.then(result => {
						this.setSuccessAndError(result.tempFiles)
					})
					.catch(err => {
						console.log('选择失败', err)
					})
			},

			/**
			 * 选择文件回调
			 * @param {Object} res
			 */
			async chooseFileCallback(res) {

				const _extname = get_extname(this.fileExtname)

				const is_one = (Number(this.limitLength) === 1 &&
						this.disablePreview &&
						!this.disabled) ||
					this.returnType === 'object'
				// 如果这有一个文件 ，需要清空本地缓存数据
				if (is_one) {
					this.files = []
				}

				let {
					filePaths,
					files
				} = get_files_and_is_max(res, _extname)
				if (!(_extname && _extname.length > 0)) {
					filePaths = res.tempFilePaths
					files = res.tempFiles
				}

				let currentData = []
				for (let i = 0; i < files.length; i++) {
					if (this.limitLength - this.files.length <= 0) break
					files[i].uuid = Date.now()
					let filedata = await get_file_data(files[i], this.fileMediatype)
					filedata.progress = 0
					filedata.status = 'ready'
					// fix by mehaotian ,统一返回，删除也包含file对象
					let fileTempData = {
						...filedata,
						file: files[i]
					}
					this.files.push(fileTempData)
					currentData.push(fileTempData)
				}
				this.$emit('select', {
					tempFiles: currentData,
					tempFilePaths: filePaths
				})
				res.tempFiles = files
				// 停止自动上传
				if (!this.autoUpload || this.noSpace) {
					res.tempFiles = []
				}
				res.tempFiles.map((fileItem, index) => {
					this.provider && (fileItem.provider = this.provider);
					const fileNameSplit = fileItem.name.split('.')
					const ext = fileNameSplit.pop()
					const fileName = fileNameSplit.join('.').replace(/[\s\/\?<>\\:\*\|":]/g, '_')
					// 选择文件目录上传
					let dir = this.dirPath || ''; // 防止用户传入的 dir 不正常
					// 检查最后一个字符是否为 '/'（同时处理空字符串情况）
					if (dir && dir[dir.length - 1] !== '/') {
						dir += '/';
					}

					fileItem.cloudPath = dir + fileName + '_' + Date.now() + '_' + index + '.' + ext
					fileItem.cloudPathAsRealPath = true

					return fileItem
				})
				return res
			},

			/**
			 * 批传
			 * @param {Object} e
			 */
			uploadFiles(files) {
				files = [].concat(files)
				return uploadCloudFiles.call(this, files, 5, res => {
						this.setProgress(res, res.index, true)
					})
					.then(result => {
						this.setSuccessAndError(result)
						return result;
					})
					.catch(err => {
						console.log(err)
					})
			},

			/**
			 * 成功或失败
			 */
			async setSuccessAndError(res, fn) {
				let successData = []
				let errorData = []
				let tempFilePath = []
				let errorTempFilePath = []
				for (let i = 0; i < res.length; i++) {
					const item = res[i]
					const index = item.uuid ? this.files.findIndex(p => p.uuid === item.uuid) : item.index

					if (index === -1 || !this.files) break
					if (item.errMsg === 'request:fail') {
						this.files[index].url = item.path
						this.files[index].status = 'error'
						this.files[index].errMsg = item.errMsg
						// this.files[index].progress = -1
						errorData.push(this.files[index])
						errorTempFilePath.push(this.files[index].url)
					} else {
						this.files[index].errMsg = ''
						this.files[index].fileID = item.url
						const reg = /cloud:\/\/([\w.]+\/?)\S*/
						if (reg.test(item.url)) {
							this.files[index].url = await this.getTempFileURL(item.url)
						} else {
							this.files[index].url = item.url
						}

						this.files[index].status = 'success'
						this.files[index].progress += 1
						successData.push(this.files[index])
						tempFilePath.push(this.files[index].fileID)
					}
				}

				if (successData.length > 0) {
					this.setEmit()
					// 状态改变返回
					this.$emit('success', {
						tempFiles: this.backObject(successData),
						tempFilePaths: tempFilePath
					})
				}

				if (errorData.length > 0) {
					this.$emit('fail', {
						tempFiles: this.backObject(errorData),
						tempFilePaths: errorTempFilePath
					})
				}
			},

			/**
			 * 获取进度
			 * @param {Object} progressEvent
			 * @param {Object} index
			 * @param {Object} type
			 */
			setProgress(progressEvent, index, type) {
				const fileLenth = this.files.length
				const percentNum = (index / fileLenth) * 100
				const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)
				let idx = index
				if (!type) {
					idx = this.files.findIndex(p => p.uuid === progressEvent.tempFile.uuid)
				}
				if (idx === -1 || !this.files[idx]) return
				// fix by mehaotian 100 就会消失，-1 是为了让进度条消失
				this.files[idx].progress = percentCompleted - 1
				// 上传中
				this.$emit('progress', {
					index: idx,
					progress: parseInt(percentCompleted),
					tempFile: this.files[idx]
				})
			},

			/**
			 * 删除文件
			 * @param {Object} index
			 */
			delFile(index) {
				this.$emit('delete', {
					index,
					tempFile: this.files[index],
					tempFilePath: this.files[index].url
				})
				this.files.splice(index, 1)
				this.$nextTick(() => {
					this.setEmit()
				})
			},

			/**
			 * 获取文件名和后缀
			 * @param {Object} name
			 */
			getFileExt(name) {
				const last_len = name.lastIndexOf('.')
				const len = name.length
				return {
					name: name.substring(0, last_len),
					ext: name.substring(last_len + 1, len)
				}
			},

			/**
			 * 处理返回事件
			 */
			setEmit() {
				let data = []
				if (this.returnType === 'object') {
					data = this.backObject(this.files)[0]
					this.localValue = data ? data : null
				} else {
					data = this.backObject(this.files)
					if (!this.localValue) {
						this.localValue = []
					}
					this.localValue = [...data]
				}
				// #ifdef VUE3
				this.$emit('update:modelValue', this.localValue)
				// #endif
				// #ifndef VUE3
				this.$emit('input', this.localValue)
				// #endif
			},

			/**
			 * 处理返回参数
			 * @param {Object} files
			 */
			backObject(files) {
				let newFilesData = []
				files.forEach(v => {
					newFilesData.push({
						extname: v.extname,
						fileType: v.fileType,
						image: v.image,
						name: v.name,
						path: v.path,
						size: v.size,
						fileID: v.fileID,
						url: v.url,
						// 修改删除一个文件后不能再上传的bug, #694
						uuid: v.uuid,
						status: v.status,
						cloudPath: v.cloudPath
					})
				})
				return newFilesData
			},
			async getTempFileURL(fileList) {
				fileList = {
					fileList: [].concat(fileList)
				}
				const urls = await uniCloud.getTempFileURL(fileList)
				return urls.fileList[0].tempFileURL || ''
			},
			/**
			 * 获取父元素实例
			 */
			getForm(name = 'uniForms') {
				let parent = this.$parent;
				let parentName = parent.$options.name;
				while (parentName !== name) {
					parent = parent.$parent;
					if (!parent) return false;
					parentName = parent.$options.name;
				}
				return parent;
			}
		}
	}
</script>

<style>
	.uni-file-picker {
		/* #ifndef APP-NVUE */
		box-sizing: border-box;
		overflow: hidden;
		width: 100%;
		/* #endif */
		flex: 1;
	}

	.uni-file-picker__header {
		padding-top: 5px;
		padding-bottom: 10px;
		/* #ifndef APP-NVUE */
		display: flex;
		/* #endif */
		justify-content: space-between;
	}

	.file-title {
		font-size: 14px;
		color: #333;
	}

	.file-count {
		font-size: 14px;
		color: #999;
	}

	.icon-add {
		width: 50px;
		height: 5px;
		background-color: #f1f1f1;
		border-radius: 2px;
	}

	.rotate {
		position: absolute;
		transform: rotate(90deg);
	}
</style>
