<template>
  <FlexCol>
    <Toast ref="toast" />
    <DialogRoot ref="dialog" />
    <!-- #ifndef MP -->
    <slot 
      name="uploader" 
      :onClick="onUploadPress"
      :items="currentUpladList"
    >
    <!-- #endif -->
      <FlexView
        v-if="showUpload"
        :direction="props.listType === 'grid' ? 'row' : 'column'"
        wrap 
        :innerStyle="(props.itemListStyle as ViewStyle)"
      >
        <template
          v-for="(item, index) in currentUpladList"
          :key="index"
        >
          <!-- #ifndef MP -->
          <slot 
            name="uploadItem"
            :index="index"
            :item="item"
            :onClick="() => onItemPress(item)"
            :onDeleteClick="() => onItemDeletePress(item)"
            :style="props.itemStyle"
            :imageStyle="props.itemImageStyle"
            :itemMaskStyle="props.itemMaskStyle"
            :itemMaskTextStyle="props.itemMaskTextStyle"
            :itemSize="itemSize"
            :showDelete="showDelete"
            :defaultSource="props.itemDefaultSource"
          >
          <!-- #endif -->
            <UploaderListItem
              :item="item"
              :showDelete="showDelete && !disabled && !readonly"
              :isListStyle="props.listType === 'list'"
              :style="itemStyle"
              :imageStyle="itemImageStyle"
              :itemMaskStyle="itemMaskStyle"
              :itemMaskTextStyle="itemMaskTextStyle"
              :defaultSource="itemDefaultSource"
              :itemSize="itemSize"
              @click="() => onItemPress(item)"
              @delete="() => onItemDeletePress(item)"
            />
          <!-- #ifndef MP -->
          </slot>
          <!-- #endif -->
        </template>
        <slot v-if="currentUpladList.length < maxUploadCount && !disabled && !readonly" name="addButton" :onUploadPress="onUploadPress" :itemSize="itemSize">
          <UploaderListAddItem :itemSize="itemSize" :style="itemStyle" @click="onUploadPress" :isListStyle="props.listType === 'list'" />
        </slot>
        <slot v-if="readonly && currentUpladList.length === 0">
          <Text color="text.second">暂无上传文件</Text>
        </slot>
      </FlexView>
      <slot v-else name="addButton" :onUploadPress="onUploadPress" :itemSize="itemSize">
        <UploaderListAddItem :itemSize="itemSize" :style="itemStyle" @click="onUploadPress" :isListStyle="props.listType === 'list'" />
      </slot>
      
    <!-- #ifndef MP -->
    </slot>
    <!-- #endif -->
  </FlexCol>
</template>

<script setup lang="ts">
import { reactive, ref, watch } from 'vue';
import { propGetThemeVar, useTheme, type TextStyle, type ViewStyle } from '../theme/ThemeDefine';
import type { ToastInstance } from '../feedback/Toast.vue';
import Toast from '../feedback/Toast.vue';
import DialogRoot, { type DialogAlertRoot } from '../dialog/DialogRoot.vue';
import UploaderListAddItem from './UploaderListAddItem.vue';
import UploaderListItem from './UploaderListItem.vue';
import FlexView from '../layout/FlexView.vue';
import FlexCol from '../layout/FlexCol.vue';
import type { UploaderAction, UploaderItem } from './Uploader';
import { Debounce, LogUtils } from '@imengyu/imengyu-utils';
import Text from '../basic/Text.vue';
import { actionSheet } from '../dialog/CommonRoot';

const themeContext = useTheme();
const TAG = 'Uploader';

export interface UploaderProps {
  /**
   * 最大上传数
   * @default 1
   */
  maxUploadCount?: number;
  /**
   * 最大上传文件的大小(B)，为 0 则不限制，超过大小的文件会被自动过滤，这些文件信息可以通过 onOverSize 事件获取。
   * @default 0 不限制
   */
  maxFileSize?: number;
  /**
   * 是否禁用文件上传。
   * @default false
   */
  disabled?: boolean;
  /**
   * 是否只读，只读状态下不会显示上传按钮，也不能删除已上传的文件。
   * @default false
   */
  readonly?: boolean;
  /**
   * 是否显示文件已上传列表。
   * @default true
   */
  showUpload?: boolean;
  /**
   * 是否在已上传列表条目中显示删除按钮。
   * @default true
   */
  showDelete?: boolean;
  /**
   * 上传列表的显示模式
   * * grid：网格模式，显示为图片网格
   * * list：列表模式，显示为文件列表
   * @default 'grid'
   */
  listType?: 'list'|'grid';
  /**
   * 是否在用户添加图片后自动进行上传，否则不会自动上传，你可以手动调用上传进行统一上传。
   * @default true
   */
  uploadWhenAdded?: boolean;
  /**
   * 当用户多选，或者手动统一上传多个条目时，要如何并发上传。
   * * all：同时触发多个上传任务
   * * sequential：按顺序执行上传任务，只有前一个上传完成，后一个才会开始。
   * @default 'all'
   */
  uploadQueueMode?: 'all'|'sequential';
  /**
   * 条目的默认图片
   */
  itemDefaultSource?: string|undefined;
  /**
   * 条目的大小
   * @default { width: 80, height: 80 }
   */
  itemSize?: { width: number, height: number };
  /**
   * 列表自定义外层样式
   */
  itemListStyle?: ViewStyle;
  /**
   * 条目的自定义外层样式
   */
  itemStyle?: ViewStyle;
  /**
   * 条目的自定义图片样式
   */
  itemImageStyle?: ViewStyle;
  /**
   * 条目的自定义信息遮罩样式
   */
  itemMaskTextStyle?: TextStyle;
  /**
   * 条目的自定义信息容器样式
   */
  itemMaskStyle?: ViewStyle;
  /**
   * 初始列表中的条目
   * @default []
   */
  intitalItems?: UploaderItem[];
  /**
   * 选择文件类型
   * * image：图片
   * * video：视频
   * @default 'image'
   */
  chooseType?: 'image'|'video'|'file'|'';
  /**
   * 是否是从消息中选择文件
   * @default true
   */
  formMessage?: boolean;

  /**
   * 上传处理。不提供则无法上传
   * @required true
   */
  upload: (item: UploaderAction) => (() => void);
  /**
   * 自定义选择文件组件，你可以调用自己的文件选择器。默认调用 ImagePicker 选择文件.
   */
  onPickImage?: () => Promise<UploaderItem[]>;
  /**
   * 通过此函数可以在上传前进行校验和处理，Promise.resolve 表示校验通过，Promise.reject 表示校验失败。支持返回一个新的文件对象，可用于自定义处理，例如压缩图片。
   */
  onBeforeAdd?: (item: UploaderItem) => Promise<UploaderItem|undefined>;
  /**
   * 自定义上传失败文件点击的事件。不提供默认是重新上传。
   */
  onRetryClick?: (item: UploaderItem) => void;
  /**
   * 自定义已上传文件点击的事件。不提供默认是调用 ImagePreview 进行预览。
   */
  onPreviewClick?: (item: UploaderItem) => void;
  /**
   * 自定义已上传文件点击删除按钮的事件。Promise.resolve 表示可以删除，Promise.reject 表示不可以删除。
   */
  onDeleteClick?: (item: UploaderItem) => Promise<void>;
  /**
   * 当上传文件超过大小时返回
   */
  onOverSize?: (item: UploaderItem) => void;
  /**
   * 当上传文件超过数量时返回
   */
  onOverCount?: (count: number, max: number) => void;
}
export interface UploaderInstance {

  /**
   * 获取已上传列表数据
   */
  getList: () => UploaderItem[];
  /**
   * 设置已上传列表数据
   */
  setList: (list: UploaderItem[]) => void;
  /**
   * 强制从已上传列表更新某个条目。如果条目在列表中不存在（按文件路径判断），则会添加到末尾。
   */
  updateListItem: (item: UploaderItem) => void;
  /**
   * 强制从已上传列表删除某个条目
   */
  deleteListItem: (item: UploaderItem) => void;
  /**
   * 添加条目到已上传列表，并且自动开始上传。
   * 注：不会限制最大上传数和不限制最大文件大小。
   * * 单选情况下会替换已上传列表中的条目。
   * * 多选情况下会添加到末尾。
   */
  addItemAndUpload: (item: UploaderItem) => void;
  /**
   * 开始手动上传所有条目
   */
  startUploadAll: () => Promise<void>;
  /**
   * 开始手动上传指定条目
   */
  startUpload: (item: UploaderItem) => Promise<void>;
  /**
   * 获取现在是否全部条目处于已上传并且完成状态
   */
  isAllUploadSuccess: () => boolean;
  /**
   * 获取现在是否有任意一个条目正在上传状态
   */
  isAnyUploading: () => boolean;
  /**
   * 获取现在是否有任意一个条目处于失败状态
   */
  isAnyFail: () => boolean;
  /**
   * 调用此函数与用户手动点击添加按钮效果相同
   */
  pick: () => void;
}

const toast = ref<ToastInstance>();
const dialog = ref<DialogAlertRoot>();

const emit = defineEmits([ 'click', 'updateList' ]);
const props = withDefaults(defineProps<UploaderProps>(), {
  disabled: false,
  maxUploadCount: 1,
  maxFileSize: 0,
  showDelete: true,
  showUpload: true,
  uploadWhenAdded: true,
  autoUpdateUploadList: true,
  formMessage: true,
  uploadQueueMode: 'all',
  listType: 'grid',
  chooseType: 'image',
  itemSize: () => propGetThemeVar('UploaderItemSize', { width: 750 / 4 - 15, height: 750 / 4 - 15 }),
});

const currentUpladList = ref<UploaderItem[]>(props.intitalItems?.concat() || []);

//上传按钮点击
function onUploadPress() {
  if (props.disabled || props.readonly)
    return;
  if (props.maxUploadCount > 1 && props.maxUploadCount - currentUpladList.value.length <= 0) {
    props.onOverCount ?
      props.onOverCount(props.maxUploadCount, currentUpladList.value.length) :
      toast.value?.text(`最多上传 ${props.maxUploadCount} 个文件哦！`);
    return;
  }
  const items = props.onPickImage ?
    props.onPickImage() :
    new Promise<UploaderItem[]>((resolve, reject) => {
      function handleFiles(res: {
        path: string;
        size: number;
      }[]) {
        resolve(res.map((item) => {
          let isImage = typeof (item as any).type === 'string' ? (item as any).type.startsWith('image/') : false;
          if (!isImage) {
            isImage = isImagePath(item.path);
          }
          return {
            filePath: item.path,
            previewPath: item.path,
            size: item.size,
            state: 'notstart',
            isImage,
            message: '',
            progress: 0,
          } as UploaderItem;
        }))
      }
      //#ifdef MP
      if (props.formMessage) {
        actionSheet({
          title: '选择上传方式',
          actions: [
            {
              name: '从相册选择',
              subname: '从相机立即拍摄或者相册中选择照片/视频',
            },
            {
              name: '从微信聊天中选择',
              subname: '可在录音机或者WPS文档分享给文件传输助手\n然后选择任意文档/文件',
            },
          ],
          showCancel: true,
          onSelect(index, name) {
          },
        }).then((index) => {
          if (index === 0) {
            chooseLocal();
          } else if (index === 1) {
            uni.chooseMessageFile({
              type: props.chooseType || 'all',
              count: props.maxUploadCount - currentUpladList.value.length,
              success: (res) => {
                LogUtils.printLog(TAG, 'info', 'chooseMessageFile', res);
                handleFiles(res.tempFiles as { path: string; size: number; }[])
              },
              fail: (e) => {
                LogUtils.printLog(TAG, 'error', 'chooseMessageFile', e);
                reject(e);
              }
            });
          }
        });
      } else {
        chooseLocal();
      }
      //#endif
      //#ifndef MP
      chooseLocal();
      //#endif

      function chooseLocal() {
        switch (props.chooseType) {
          case 'video':
            uni.chooseVideo().then((res) => handleFiles([
              {
                path: res.tempFilePath,
                size: res.size,
              }
            ])).catch(reject);
            break;
          case 'file':
            uni.chooseFile().then((res) => handleFiles(res.tempFiles as { path: string; size: number; }[])).catch(reject);
            break;
          default:
          case 'image':
            uni.chooseImage({
              count: props.maxUploadCount - currentUpladList.value.length,
            }).then((res) => handleFiles(res.tempFiles as { path: string; size: number; }[])).catch(reject);
            break;
        }
      }
    });

  items
    .then((res) => { 
      if (props.maxFileSize > 0) {
        res = res.filter((item) => {
          if (item.size && item.size > props.maxFileSize) {
            props.onOverSize?.(item);
            return false;
          }
          return true;
        });
        if (res.length === 0) {
          toast.value?.text('您选择的文件过大，请重新选择！');
          return;
        }
      }

      res = res.map((item) => {
        item.state = 'notstart';
        item.message = '';
        item.progress = 0;
        return reactive(item);
      });

      //添加条目
      currentUpladList.value = props.maxUploadCount > 1 ? currentUpladList.value.concat(res) : res;
      //自动上传
      if (props.uploadWhenAdded)
        startUploadMulitItem(currentUpladList.value);
    })
    .catch((e) => console.warn('PickImage failed', e));
}
//条目点击
function onItemPress(item: UploaderItem) {
  if (props.readonly) {
    props.onPreviewClick ?
      props.onPreviewClick(item) :
      onItemPreview(item); //默认预览
    return;
  }

  if (item.state === 'fail') {
    props.onRetryClick ?
      props.onRetryClick(item) :
      //重试上传
      startUploadItem(item);
  } else {
    props.onPreviewClick ?
      props.onPreviewClick(item) :
      onItemPreview(item); //默认预览
  }
}
//条目预览
function onItemPreview(item: UploaderItem) {
  //判断后缀是不是图片
  const previewPath = item.previewPath || item.uploadedPath || item.filePath;
  if (item.isImage || isImagePath(previewPath)) {
    uni.previewImage({
      urls: [ 
        previewPath 
      ],
    });
  } else {
    uni.openDocument({
      filePath: previewPath,
    });
  }
}
//条目删除点击
function onItemDeletePress(item: UploaderItem) {
  if (props.disabled || props.readonly)
    return;
  props.onDeleteClick ?
    props.onDeleteClick(item).then(() => {
      deleteListItem(item);
    }).catch(() => {}) :
    dialog.value?.confirm({
      title: '提示',
      content: '是否确认删除此文件？',
    }).then((confirm) => {
      if (confirm)
        deleteListItem(item);
    });
}
//更新列表条目
function updateListItem(item: UploaderItem) {
  emit('updateList', currentUpladList.value);
}
//删除列表条目
function deleteListItem(item: UploaderItem) {
  currentUpladList.value = currentUpladList.value.filter((k) => k.filePath !== item.filePath);

  //如果正在上传，先取消上传
  if (item.state === 'uploading') {
    item.cancelUpload?.();
    item.state = 'fail';
  }
}

function isImagePath(path: string) {
  return path.match(/\.(jpg|jpeg|png|gif|webp)$/) !== null;
}


//开始上传条目
function startUploadItem(item: UploaderItem) {
  if (item.state === 'uploading')
    return;
  return new Promise<void>((resolve, reject) => {
    if (item.state === 'success') {
      resolve();
      return;
    }
    LogUtils.printLog(TAG, 'message', `调用上传文件 ${item.filePath}`);

    const updateProgressDebounce = new Debounce<number>(400, (precent) => {
      item.state = 'uploading';
      item.message = precent ? `上传中 ${precent}%` : '上传中...';
      item.progress = precent;
      updateListItem(item);
    });

    item.cancelUpload = props.upload({
      item,
      onError(error) {
        item.state = 'fail';
        item.message = ('' + error) || '上传失败';
        updateListItem(item);
        reject(error);
        LogUtils.printLog(TAG, 'error', `上传文件 ${item.filePath} 失败，错误信息：${error}`);
      },
      onFinish(result, message) {
        updateProgressDebounce.cancel();
        item.state = 'success';
        item.message = message || '上传完成';
        item.progress = 100;
        item.uploadedPath = result.uploadedUrl;
        if (result.previewUrl)
          item.previewPath = result.previewUrl;
        updateListItem(item);
        resolve();
        LogUtils.printLog(TAG, 'success', `上传文件 ${item.filePath} 成功，上传路径：${result.uploadedUrl}`);
      },
      onProgress(precent) {
        updateProgressDebounce.executeWithDelay(200, precent);
      },
      onStart(message) {
        item.state = 'uploading';
        item.message = message || '上传中...';
        item.progress = 0;
        updateListItem(item);
        LogUtils.printLog(TAG, 'message', `上传文件 ${item.filePath} 开始，信息：${item.message}`);
      },
    });
  });
}
//开始上传条目
function startUploadMulitItem(items: UploaderItem[]) : Promise<void> {
  if (props.uploadQueueMode === 'sequential')
    return items.reduce((promiseChain, currentItem) =>
      promiseChain.then(() => startUploadItem(currentItem)).catch(() => startUploadItem(currentItem)),
      Promise.resolve()
    );
  else
    return Promise.all(items.map(item => startUploadItem(item))) as unknown as Promise<void>;
}

defineExpose<UploaderInstance>({
  startUploadAll() {
    return startUploadMulitItem(currentUpladList.value);
  },
  async startUpload(item) {
    return await startUploadItem(item);
  },
  setList(list) {
    const needRemoveItems = [] as string[];
    for (const item of currentUpladList.value) {
      if (list.findIndex(k => k.filePath === item.filePath) === -1)
        needRemoveItems.push(item.filePath);
    }
    for (const filePath of needRemoveItems)
      currentUpladList.value.splice(currentUpladList.value.findIndex(k => k.filePath === filePath), 1);
    list.forEach(item => {
      if (currentUpladList.value.findIndex(k => k.filePath === item.filePath) === -1)
        currentUpladList.value.push(item);
    });
  },
  getList() {
    return currentUpladList.value;
  },
  deleteListItem(item) {
    deleteListItem(item);
  },
  updateListItem(item) {
    updateListItem(item);
  },
  addItemAndUpload(item) {
    //如果是单选模式，且已存在上传项，则替换已存在项
    if (props.maxUploadCount === 1 && currentUpladList.value.length > 0)
      deleteListItem(currentUpladList.value[0]);
    currentUpladList.value.push(item);
    startUploadItem(item);
  },
  pick() {
    onUploadPress();
  },
  isAllUploadSuccess: () => {
    return currentUpladList.value.every(k => k.state === 'success');
  },
  isAnyUploading: () => {
    return currentUpladList.value.find(k => k.state === 'uploading') !== undefined;
  },
  isAnyFail: () => {
    return currentUpladList.value.find(k => k.state === 'fail') !== undefined;
  },
});
</script>