<template>
  <FlexCol>
    <Toast ref="toast" />
    <DialogRoot ref="dialog" />
    <slot 
      name="uploader" 
      :onClick="onUploadPress"
      :items="currentUpladList"
    >
      <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"
        >
          <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"
              
          >
            <UploaderListItem
              :item="item"
              :showDelete="showDelete"
              :isListStyle="props.listType === 'list'"
              :style="itemStyle"
              :imageStyle="itemImageStyle"
              :itemMaskStyle="itemMaskStyle"
              :itemMaskTextStyle="itemMaskTextStyle"
              :defaultSource="itemDefaultSource"
              :itemSize="itemSize"
              @click="() => onItemPress(item)"
              @delete="() => onItemDeletePress(item)"
            />
          </slot>
        </template>
        <slot v-if="currentUpladList.length < maxUploadCount" name="addButton" :onUploadPress="onUploadPress" :itemSize="itemSize">
          <UploaderListAddItem :itemSize="itemSize" :style="itemStyle" @click="onUploadPress" :isListStyle="props.listType === 'list'" />
        </slot>
      </FlexView>
      <slot v-else name="addButton" :onUploadPress="onUploadPress" :itemSize="itemSize">
        <UploaderListAddItem :itemSize="itemSize" :style="itemStyle" @click="onUploadPress" :isListStyle="props.listType === 'list'" />
      </slot>
    </slot>
  </FlexCol>
</template>

<script setup lang="ts">
import { ref } 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 { LogUtils } from '@imengyu/imengyu-utils';

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 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';
  /**
   * 上传处理。不提供则无法上传
   * @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;
  /**
   * 开始手动上传所有条目
   */
  startUploadAll: () => Promise<void>;
  /**
   * 开始手动上传指定条目
   */
  startUpload: (item: UploaderItem) => Promise<void>;
  /**
   * 获取现在是否全部条目处于已上传并且完成状态
   */
  isAllUploadSuccess: () => boolean;
  /**
   * 获取现在是否有任意一个条目正在上传状态
   */
  isAnyUploading: () => boolean;
  /**
   * 获取现在是否有任意一个条目处于失败状态
   */
  isAnyFail: () => boolean;
  /**
   * 调用此函数与用户手动点击添加按钮效果相同
   */
  pick: () => void;
}

const isImageExt = [
  '.png',
  '.jpg',
  '.jpeg',
  '.bmp',
  '.webp',
];

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,
  uploadQueueMode: 'all',
  listType: 'grid',
  chooseType: 'image',
  itemSize: () => propGetThemeVar('UploaderItemSize', { width: 750 / 4 - 15, height: 750 / 4 - 15 }),
});

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

//上传按钮点击
function onUploadPress() {
  if (props.disabled) {
    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;
          for (const ext of isImageExt) {
            if (item.path.endsWith(ext)) {
              isImage = true;
              break;
            }
          }
          return {
            filePath: item.path,
            previewPath: item.path,
            size: item.size,
            state: 'notstart',
            isImage,
          } as UploaderItem
        }))
      }

      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;
        });
      //添加条目
      currentUpladList.value = props.maxUploadCount > 1 ? currentUpladList.value.concat(res) : res;
      //自动上传
      if (props.uploadWhenAdded)
        startUploadMulitItem(res);
    })
    .catch((e) => console.warn('PickImage failed', e));
}
//条目点击
function onItemPress(item: UploaderItem) {
  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) {
    uni.previewImage({
      urls: [ 
        previewPath 
      ],
    });
  } else {
    uni.openDocument({
      filePath: previewPath,
    });
  }
}
//条目删除点击
function onItemDeletePress(item: UploaderItem) {
  props.onDeleteClick ?
    props.onDeleteClick(item).then(() => {
      deleteListItem(item);
    }).catch(() => {}) :
    dialog.value?.confirm({
      title: '提示',
      content: '是否确认删除此文件？',
    }).then((confirm) => {
      if (confirm)
        deleteListItem(item);
    });
}
//更新列表条目
function updateListItem(item: UploaderItem) {
  currentUpladList.value = ((prev) => {
    const newList = prev.concat();
    const index = prev.findIndex((k) => k.filePath === item.filePath);
    index >= 0 ? newList[index] = { ...item } : newList.push(item);
    return newList;
  })(currentUpladList.value);
  emit('updateList', currentUpladList.value);
}
//删除列表条目
function deleteListItem(item: UploaderItem) {
  currentUpladList.value = currentUpladList.value.filter((k) => k.filePath !== item.filePath);
}
//开始上传条目
function startUploadItem(item: UploaderItem) {
  return new Promise<void>((resolve, reject) => {
    if (item.state === 'success') {
      resolve();
      return;
    }
    LogUtils.printLog(TAG, 'message', `调用上传文件 ${item.filePath}`);

    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) {
        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) {
        item.state = 'uploading';
        item.message = precent ? `${precent}%` : '上传中...';
        item.progress = precent;
        updateListItem(item);
      },
      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);
  },
  startUpload(item) {
    return startUploadItem(item);
  },
  setList(list) {
    currentUpladList.value = list;
  },
  getList() {
    return currentUpladList.value;
  },
  deleteListItem(item) {
    deleteListItem(item);
  },
  updateListItem(item) {
    updateListItem(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>