
//文件上传的状态值
export enum statusCode {
	//待上传
	upload = 0,
	//上传中
	uploading = 1,
	//上传失败
	fail = 2,
	//上传成功
	success = 3,
	//超过大小限制
	max = 4,
}
//文件对象结构
export interface file {
	url: string,//当前显示的图片地址，这是个本地临时地址。待上传前，成功后会替换服务器地址。
	status?: string,//上传状态文本
	progress?: number,//当前文件上传的进度
	uid?: string | number,//文件唯一标识id
	statusCode?: statusCode,//文件状态
	response?: any,//上传成功后的回调数据。
	name?: string,//文件名称
	[propName: string]: any
}

//上传class对象的配置。
export interface fileConfig {
	maxSize?: number,//每一个文件上传的最大尺寸，默认为10mb
	maxFile?: number,//一次选择文件最大数量。
	fileType?: Array<string>,//文件选择的类型。
	hostUrl?: string,//上传文件的服务器地址
	fileList?: Array<file>,//已上传的文件列表。
	autoUpload?: Boolean,
	header?: Object,//头部参数。
	formData?: Object,//额外的表单数据。
	formName?: string,
	statusCode?: number,//服务返回成功的状态码标志，默认200表示成功。
}
export function getUid(length = 3) {
	return Number(Number(Math.random().toString().substr(3, length) + Date.now()).toString(8));
}
/**
 * 上传文件。
 * 作者：tmzdy
 * 时间：2022年4月27日
 * 联系：zhongjihan@sina.com
 * @method {Function} beforeChooesefile -- 选择图片上传前执行的勾子。
 * @method {Function} chooesefile -- 选择图片上传，弹出层进行选择文件 。
 * @method {Function} chooesefileAfter -- 选择图片、视频文件成功后触发。返回选择后的文件。此时还未加入待上传列表，会返回 一个文件列表用于过滤。需要在函数中再返回 一个文件 列表，
 * @method {Function} chooesefileSuccess -- 文件已经加入到了待上传文件列表,需要返回过滤的文件列表
 * @method {Function} beforeAddfile -- 动态加入预计加入文件前执行的勾子，返回true才会正式加入到待上传列表中。
 * @method {Function} addFile -- 动态加入预上传的文件。
 * @method {Function} progress -- 进度。
 * @method {Function} fail -- 失败。服务器出现非200时触发。
 * @method {Function} beforeSuccess -- 服务器返回成功，立即执行的勾子。如果此时返回false,则表示文件 上传失败。
 * @method {Function} success -- 成功。
 * @method {Function} complete -- 单个文件完成。不管上传成功与失败否，都会触发此函数。
 * @method {Function} uploadComplete -- 所有文件上传完时触发
 * @method {Function} beforeStart -- 开始上传。前的校验勾子。如果返回false,将阻止上传
 * @method {Function} start -- 开始上传。
 * @method {Function} stop -- 停止上传。中止上传时触发的函数。
 */
export class uploadfile {
	//文件列表。
	filelist: Array<file> = [];
	isStop = false;
	index = 0;
	config: fileConfig = {};
	uploadobj: UniNamespace.UploadTask | null = null;
	constructor(config: fileConfig) {
		let cf: fileConfig = { maxSize: 10 * 1024 * 1024, maxFile: 9, fileType: ['album', 'camera'], fileList: [], autoUpload: true, header: {}, formData: {}, formName: 'file' }
		cf = { ...cf, ...arguments[0] ?? {} };
		//配置{name: 'file', // 上传时的文件key名。默认file,header: {}, // 上传的头部参数。}
		this.config = cf;
		this.addFile(cf.fileList);
		delete this.config.fileList;
	}
	async beforeChooesefile() {
		return true;
	}
	async chooesefileAfter(fileList: Array<file>) {

		return fileList;
	}
	async chooesefileSuccess(fileList: Array<file>) {

		return fileList;
	}
	delete(item: file) {
		let index = this.filelist.findIndex(el => el.uid == item.uid);
		if (index > -1) {
			let p = [...this.filelist]
			p.splice(index, 1)
			this.filelist = [...p];
		}

		return this.filelist;
	}
	async clear() {
		/** 清清前要选暂停所有正在上传的文件 */
		this.stop();
		this.filelist = [];
	}
	setFileStatus(item: file) {
		let index = this.filelist.findIndex(el => el.uid == item.uid);
		if (index > -1) {
			let p = [...this.filelist]
			p.splice(index, 1, item)
			this.filelist = [...p];
		}
	}
	/**
	 * 成功后返回选择后的图片列表。
	 */
	async chooesefile(): Promise<Array<file>> {
		let t = this;

		return new Promise(async (rs, rj) => {
			let isready = await t.beforeChooesefile();
			if (!isready) {
				rs([])
				return;
			}
			uni.chooseImage({
				count: t.config.maxFile,
				sourceType: t.config.fileType,
				fail: (e) => {
					rj("取消选择");
				},
				success: async (res) => {

					if (res.tempFilePaths.length == 0) {
						rj("未选择")
						return;
					}

					let imgarray = res.tempFilePaths;
					let fielist = res.tempFiles;
					let jgsk: Array<file> = [];
					//0待上传，1上传中，2上传失败，3上传成功。4超过大小限制
					imgarray.forEach((item: string, index: number) => {
						let isMaxsize = fielist[index].size > t.config.maxSize ? true : false;
						jgsk.push({
							url: item,
							status: isMaxsize ? '超过大小' : "待上传",
							progress: isMaxsize ? 100 : 0,
							uid: getUid(),
							statusCode: isMaxsize ? statusCode.max : statusCode.upload,
							response: null,
							name: fielist[index].name ?? ""
						})
					})

					let isreadyChoose = await t.chooesefileAfter(jgsk);

					if (!Array.isArray(isreadyChoose) || typeof isreadyChoose != 'object') {
						rj("chooesefileAfter:函数过滤，没有返回文件列表。")
						return;
					}

					t.filelist.push(...isreadyChoose)

					t.chooesefileSuccess(isreadyChoose);
					rs(isreadyChoose)
					if (t.config.autoUpload) {
						setTimeout(function () {
							t.start();
						}, 500);
					}


				}
			})
		})
	}
	async chooseMPH5weixinFile() {
		let t = this;
		return new Promise((rs, rj) => {
			var fs = uni.chooseFile;
			// #ifdef MP-WEIXIN || MP-QQ
			fs = uni.chooseMessageFile;
			// #endif
			var config = {
				count: t.config.maxfile,
				type: t.config.type,
				extension: t.config.extension,
			}
			if (!t.config.extension || !Array.isArray(t.config.extension) || t.config.extension?.length == 0) {
				delete config.extension
			}
			fs({
				...config,
				fail: (e) => {
					console.error(e);
					uni.$tm.toast("已取消选择");
					rj(e);
				},
				success: (res) => {
					if (res.tempFiles.length == 0) {
						uni.$tm.toast("未选择");
						return;
					}
					let fielist = res.tempFiles;
					let jgsk = [];
					//0待上传，1上传中，2上传失败，3上传成功。4超过大小限制
					fielist.forEach((item, index) => {
						let isMaxsize = fielist[index].size > t.config.maxsize ? true : false;
						let ftype = item.name || ""
						if (ftype) {
							ftype = ftype.substr(ftype.lastIndexOf(".") + 1).toLocaleLowerCase();
						}
						jgsk.push({
							url: item.path,
							name: item.name || '默认文件名称',
							type: ftype,
							status: isMaxsize ? '超过大小' : "待上传",
							progress: isMaxsize ? 100 : 0,
							fileId: guid(),
							statusCode: isMaxsize ? 4 : 0,
							data: null,//上传成功后的回调数据。
						})
					})
					t.filelist.push(...jgsk)

					t.selected(t.filelist);
					if (t.config.isAuto) {
						t.start();
					}

					rs(t.filelist)
				}
			})

		})
	}
	setConfig(config: fileConfig) {
		this.config = { ...this.config, ...config ?? {} }
	}
	/**
	 * 动态加入文件
	 * @param {Object} filelist
	 */
	addFile(filelist: Array<file> = []) {
		if (typeof filelist !== 'object' && !Array.isArray(filelist)) return;
		let total_uid = new Set(this.filelist.map(e => e.uid))
		let total_url = new Set(this.filelist.map(e => e.url))
		let cfilelist = filelist.map(el => {
			return {
				...el,
				status: el?.status ?? "待上传",
				statusCode: el?.statusCode ?? statusCode.upload,
				uid: el?.uid ?? getUid(),
				progress: el?.progress ?? 0,
				name: el?.name ?? "",
				response: el?.response ?? null,
				url: el?.url ?? ""
			}
		})
		let filterFIle = cfilelist.filter(item => !total_uid.has(item.uid) && !total_url.has(item.url))
		this.filelist.push(...filterFIle)

	}
	beforeSuccess(item: file) {
		return Promise.resolve(true);
	}
	beforeStart(item: file) {
		return Promise.resolve(true);
	}
	// 进度。
	progress(item: file,index:number) { }
	// 失败
	fail(item: file) { }
	// 成功
	success(item: file, fileList: Array<file>) { }
	// 完成。
	complete(filelist: file) { }
	uploadComplete(filelist: Array<file>) { }
	awaitTime() {
		return new Promise((resolve) => {
			setTimeout(() => {
				resolve(true)
			}, 20)
		})
	}
	// 开始上传。
	async start() {

		if (this.filelist.length <= 0) {
			console.error("未选择图片,已取消上传")
			return;
		}
		let t = this;
		// t重新开始上传从头开始。
		this.index = 0;
		this.isStop = false;
		async function startupload() {
			if (t.isStop) return;

			let item = t.filelist[t.index];

			if (!item || typeof item === 'undefined') {
				// 文件不存在。直接结束。
				t.uploadComplete(t.filelist);
				return;
			}

			let canbleStart = await t.beforeStart(item)
			if (!canbleStart) {
				item.statusCode = statusCode.fail;
				item.status = "不允许上传"
				t.filelist.splice(t.index, 1, item)
				t.index++;
				t.setFileStatus(item)
				t.fail(item)
				t.complete(item);
				startupload();
				return;
			}

			if (item.statusCode == 3 || item.statusCode == 1 || item.statusCode == 4 || item.statusCode == 2) {
				// 直接跳过。至下一个文件。
				t.index++;
				startupload();
				return;
			}

			item.statusCode = statusCode.uploading;
			item.status = "上传中..."
			t.setFileStatus(item)
			const upObj = t.uploadobj = uni.uploadFile({
				url: String(t.config.hostUrl),
				name: t.config?.formName ?? 'file',
				header: t.config?.header ?? {},
				filePath: item.url,
				formData: { name: item.name, ...t.config.formData },
				success: async (res) => {
					if (t.isStop) return
					item.response = res.data;
					let isOksuccess = await t.beforeSuccess(item);
					const statusCode_reonese = t.config?.statusCode ?? 200
					if (res.statusCode != statusCode_reonese || !isOksuccess) {
						item.statusCode = statusCode.fail;
						item.status = "上传失败";
						t.fail(item)
						t.setFileStatus(item)
						t.index++;
						return;
					}

					// 上传成功。
					item.statusCode = statusCode.success;
					item.status = "上传成功";
					t.setFileStatus(item)
					t.success(item, t.filelist)
					t.index++;
				},
				fail: (res) => {
					if (t.isStop) return
					item.statusCode = statusCode.fail;
					item.status = "上传失败";
					t.setFileStatus(item)
					t.fail(item)
					t.index++;
				},
				complete: async (res) => {
					if (t.isStop) return
					await t.awaitTime();
					t.complete(item);
					// 直接下一个文件。
					startupload();
				}
			})
			if (upObj) {
				let item = t.filelist[t.index];
				upObj.onProgressUpdate(async (res) => {
					if (t.isStop) return
					item.progress = res.progress;
					item.statusCode = statusCode.uploading;
					item.status = "...";
					t.setFileStatus(item)
					t.progress(item,t.index)

				})
			}

		}
		await startupload();
	}
	// 停止上传
	stop() {
		this.isStop = true;
		if (this.uploadobj != null) {
			this.uploadobj.abort()
		}
	}

}
