var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import { h, defineComponent, computed, provide, toRef, ref } from 'vue';
import { createId } from 'seemly';
import { useConfig, useTheme } from '../../_mixins';
import { getFirstSlotVNode, warn, call } from '../../_utils';
import { NFadeInExpandTransition } from '../../_internal';
import { uploadLight } from '../styles';
import NUploadFile from './UploadFile';
import style from './styles/index.cssr';
import { uploadInjectionKey } from './interface';
import { useMergedState } from 'vooks';
import { uploadDraggerKey } from './UploadDragger';
/**
 * fils status ['pending', 'uploading', 'finished', 'removed', 'error']
 */
function createXhrHandlers(inst, file, XHR) {
    const { doChange, XhrMap } = inst;
    let percentage = 0;
    return {
        handleXHRLoad(e) {
            var _a;
            let fileAfterChange = Object.assign({}, file, {
                status: 'finished',
                percentage,
                file: null
            });
            XhrMap.delete(file.id);
            fileAfterChange =
                ((_a = inst.onFinish) === null || _a === void 0 ? void 0 : _a.call(inst, { file: fileAfterChange, event: e })) || fileAfterChange;
            doChange(fileAfterChange, e);
        },
        handleXHRAbort(e) {
            const fileAfterChange = Object.assign({}, file, {
                status: 'removed',
                file: null,
                percentage
            });
            XhrMap.delete(file.id);
            doChange(fileAfterChange, e);
        },
        handleXHRError(e) {
            const fileAfterChange = Object.assign({}, file, {
                status: 'error',
                percentage,
                file: null
            });
            XhrMap.delete(file.id);
            doChange(fileAfterChange, e);
        },
        handleXHRProgress(e) {
            const fileAfterChange = Object.assign({}, file, {
                status: 'uploading'
            });
            if (e.lengthComputable) {
                const progress = Math.ceil((e.loaded / e.total) * 100);
                fileAfterChange.percentage = progress;
                percentage = progress;
            }
            doChange(fileAfterChange, e);
        }
    };
}
function registerHandler(inst, file, request) {
    const handlers = createXhrHandlers(inst, file, request);
    request.onabort = handlers.handleXHRAbort;
    request.onerror = handlers.handleXHRError;
    request.onload = handlers.handleXHRLoad;
    if (request.upload) {
        request.upload.onprogress = handlers.handleXHRProgress;
    }
}
function unwrapFunctionValue(data, file) {
    if (typeof data === 'function') {
        return data({ file });
    }
    if (data)
        return data;
    return {};
}
function setHeaders(request, headers, file) {
    const headersObject = unwrapFunctionValue(headers, file);
    if (!headersObject)
        return;
    Object.keys(headersObject).forEach((key) => {
        request.setRequestHeader(key, headersObject[key]);
    });
}
function appendData(formData, data, file) {
    const dataObject = unwrapFunctionValue(data, file);
    if (!dataObject)
        return;
    Object.keys(dataObject).forEach((key) => {
        formData.append(key, dataObject[key]);
    });
}
function submitImpl(inst, file, formData, { method, action, withCredentials, headers, data }) {
    const request = new XMLHttpRequest();
    inst.XhrMap.set(file.id, request);
    request.withCredentials = withCredentials;
    appendData(formData, data, file);
    registerHandler(inst, file, request);
    if (action !== undefined) {
        request.open(method.toUpperCase(), action);
        setHeaders(request, headers, file);
        request.send(formData);
        const fileAfterChange = Object.assign({}, file, {
            status: 'uploading'
        });
        inst.doChange(fileAfterChange);
    }
}
const uploadProps = Object.assign(Object.assign({}, useTheme.props), { name: {
        type: String,
        default: 'file'
    }, accept: String, action: String, 
    // to be impl
    // directory: {
    //   type: Boolean,
    //   default: false
    // },
    method: {
        type: String,
        default: 'POST'
    }, multiple: Boolean, showFileList: {
        type: Boolean,
        default: true
    }, data: [Object, Function], headers: [Object, Function], withCredentials: Boolean, disabled: Boolean, onChange: Function, onRemove: Function, onFinish: Function, onBeforeUpload: Function, 
    /** currently of no usage */
    onDownload: Function, defaultUpload: {
        type: Boolean,
        default: true
    }, fileList: Array, 'onUpdate:fileList': [Function, Array], onUpdateFileList: [Function, Array], fileListStyle: [String, Object], defaultFileList: {
        type: Array,
        default: () => []
    }, showCancelButton: {
        type: Boolean,
        default: true
    }, showRemoveButton: {
        type: Boolean,
        default: true
    }, showDownloadButton: {
        type: Boolean,
        default: false
    }, showRetryButton: {
        type: Boolean,
        default: true
    } });
export default defineComponent({
    name: 'Upload',
    props: uploadProps,
    setup(props) {
        const { mergedClsPrefixRef } = useConfig(props);
        const themeRef = useTheme('Upload', 'Upload', style, uploadLight, props, mergedClsPrefixRef);
        const uncontrolledFileListRef = ref(props.defaultFileList);
        const controlledFileListRef = toRef(props, 'fileList');
        const inputElRef = ref(null);
        const draggerInsideRef = {
            value: false
        };
        const dragOverRef = ref(false);
        const XhrMap = new Map();
        const mergedFileListRef = useMergedState(controlledFileListRef, uncontrolledFileListRef);
        function openFileDialog() {
            var _a;
            (_a = inputElRef.value) === null || _a === void 0 ? void 0 : _a.click();
        }
        function handleTriggerClick() {
            if (props.disabled)
                return;
            openFileDialog();
        }
        function handleTriggerDragOver(e) {
            e.preventDefault();
            dragOverRef.value = true;
        }
        function handleTriggerDragEnter(e) {
            e.preventDefault();
            dragOverRef.value = true;
        }
        function handleTriggerDragLeave(e) {
            e.preventDefault();
            dragOverRef.value = false;
        }
        function handleTriggerDrop(e) {
            e.preventDefault();
            if (!draggerInsideRef.value || props.disabled)
                return;
            const dataTransfer = e.dataTransfer;
            const files = dataTransfer === null || dataTransfer === void 0 ? void 0 : dataTransfer.files;
            if (files) {
                handleFileAddition(files);
            }
            dragOverRef.value = false;
        }
        function handleFileInputChange(e) {
            const target = e.target;
            handleFileAddition(target.files, e);
            // May have bug! set to null?
            target.value = '';
        }
        function doUpdateFileList(files) {
            const { 'onUpdate:fileList': _onUpdateFileList, onUpdateFileList } = props;
            if (_onUpdateFileList)
                call(_onUpdateFileList, files);
            if (onUpdateFileList)
                call(onUpdateFileList, files);
            uncontrolledFileListRef.value = files;
        }
        function handleFileAddition(files, e) {
            if (!files || files.length === 0)
                return;
            const { onBeforeUpload } = props;
            const filesAsArray = props.multiple ? Array.from(files) : [files[0]];
            void Promise.all(filesAsArray.map((file) => __awaiter(this, void 0, void 0, function* () {
                const fileInfo = {
                    id: createId(),
                    name: file.name,
                    status: 'pending',
                    percentage: 0,
                    file: file,
                    url: null
                };
                if (!onBeforeUpload ||
                    (yield onBeforeUpload({
                        file: fileInfo,
                        fileList: mergedFileListRef.value
                    })) !== false) {
                    doChange(fileInfo, e, {
                        append: true
                    });
                }
            }))).then(() => {
                if (props.defaultUpload) {
                    submit();
                }
            });
        }
        function submit(fileId) {
            const { method, action, withCredentials, headers, data, name: fieldName } = props;
            const filesToUpload = fileId !== undefined
                ? mergedFileListRef.value.filter((file) => file.id === fileId)
                : mergedFileListRef.value;
            filesToUpload.forEach((file) => {
                const { status } = file;
                if (status === 'pending' || status === 'error') {
                    const formData = new FormData();
                    formData.append(fieldName, file.file);
                    submitImpl({
                        doChange,
                        XhrMap,
                        onFinish: props.onFinish
                    }, file, formData, {
                        method,
                        action,
                        withCredentials,
                        headers,
                        data
                    });
                }
            });
        }
        const doChange = (fileAfterChange, event, options = {
            append: false,
            remove: false
        }) => {
            const { append, remove } = options;
            const fileListAfterChange = Array.from(mergedFileListRef.value);
            const fileIndex = fileListAfterChange.findIndex((file) => file.id === fileAfterChange.id);
            if (append || remove || ~fileIndex) {
                if (append) {
                    fileListAfterChange.push(fileAfterChange);
                }
                else if (remove) {
                    fileListAfterChange.splice(fileIndex, 1);
                }
                else {
                    fileListAfterChange.splice(fileIndex, 1, fileAfterChange);
                }
                const { onChange } = props;
                if (onChange) {
                    onChange({
                        file: fileAfterChange,
                        fileList: fileListAfterChange,
                        event
                    });
                }
                doUpdateFileList(fileListAfterChange);
            }
            else if (process.env.NODE_ENV !== 'production') {
                warn('upload', 'File has no corresponding id in current file list.');
            }
        };
        provide(uploadInjectionKey, {
            mergedClsPrefixRef,
            mergedThemeRef: themeRef,
            disabledRef: toRef(props, 'disabled'),
            showCancelButtonRef: toRef(props, 'showCancelButton'),
            showDownloadButtonRef: toRef(props, 'showDownloadButton'),
            showRemoveButtonRef: toRef(props, 'showRemoveButton'),
            showRetryButtonRef: toRef(props, 'showRetryButton'),
            onRemoveRef: toRef(props, 'onRemove'),
            onDownloadRef: toRef(props, 'onDownload'),
            mergedFileListRef: mergedFileListRef,
            XhrMap,
            submit,
            doChange
        });
        return {
            mergedClsPrefix: mergedClsPrefixRef,
            draggerInsideRef,
            inputElRef,
            mergedFileList: mergedFileListRef,
            mergedTheme: themeRef,
            dragOver: dragOverRef,
            handleTriggerDrop,
            handleTriggerDragLeave,
            handleTriggerDragEnter,
            handleTriggerDragOver,
            handleTriggerClick,
            handleFileInputChange,
            submit,
            openFileDialog,
            cssVars: computed(() => {
                const { common: { cubicBezierEaseInOut }, self: { draggerColor, draggerBorder, draggerBorderHover, itemColorHover, itemColorHoverError, itemTextColorError, itemTextColorSuccess, itemTextColor, itemIconColor, itemDisabledOpacity, lineHeight, borderRadius, fontSize } } = themeRef.value;
                return {
                    '--bezier': cubicBezierEaseInOut,
                    '--border-radius': borderRadius,
                    '--dragger-border': draggerBorder,
                    '--dragger-border-hover': draggerBorderHover,
                    '--dragger-color': draggerColor,
                    '--font-size': fontSize,
                    '--item-color-hover': itemColorHover,
                    '--item-color-hover-error': itemColorHoverError,
                    '--item-disabled-opacity': itemDisabledOpacity,
                    '--item-icon-color': itemIconColor,
                    '--item-text-color': itemTextColor,
                    '--item-text-color-error': itemTextColorError,
                    '--item-text-color-success': itemTextColorSuccess,
                    '--line-height': lineHeight
                };
            })
        };
    },
    render() {
        var _a;
        const { draggerInsideRef, mergedClsPrefix, $slots } = this;
        if ($slots.default) {
            const firstChild = getFirstSlotVNode($slots, 'default');
            // @ts-expect-error
            if ((_a = firstChild === null || firstChild === void 0 ? void 0 : firstChild.type) === null || _a === void 0 ? void 0 : _a[uploadDraggerKey]) {
                draggerInsideRef.value = true;
            }
        }
        return (h("div", { class: [
                `${mergedClsPrefix}-upload`,
                {
                    [`${mergedClsPrefix}-upload--dragger-inside`]: draggerInsideRef.value,
                    [`${mergedClsPrefix}-upload--drag-over`]: this.dragOver,
                    [`${mergedClsPrefix}-upload--disabled`]: this.disabled
                }
            ], style: this.cssVars },
            h("input", { ref: "inputElRef", type: "file", class: `${mergedClsPrefix}-upload__file-input`, accept: this.accept, multiple: this.multiple, onChange: this.handleFileInputChange }),
            h("div", { class: `${mergedClsPrefix}-upload__trigger`, onClick: this.handleTriggerClick, onDrop: this.handleTriggerDrop, onDragover: this.handleTriggerDragOver, onDragenter: this.handleTriggerDragEnter, onDragleave: this.handleTriggerDragLeave }, this.$slots),
            this.showFileList && (h("div", { class: `${mergedClsPrefix}-upload-file-list`, style: this.fileListStyle },
                h(NFadeInExpandTransition, { group: true }, {
                    default: () => this.mergedFileList.map((file) => (h(NUploadFile, { clsPrefix: mergedClsPrefix, key: file.id, file: file })))
                })))));
    }
});
