'use strict';

var index = require('./index-feA4skrY.js');
var tslib_es6 = require('./tslib.es6-DrfCPgT9.js');
var aria = require('./aria-BzkqcV7W.js');
var array = require('./array-Db63Hhl1.js');
var form = require('./form-BSlIiqcR.js');
var formInput = require('./form-input-BwFB4Rej.js');
var log = require('./log-CXDMiw7o.js');
var _commonjsHelpers = require('./_commonjsHelpers-BJu3ubxk.js');
var bem = require('./bem-p0PWVN8n.js');
var balFileUpload_type = require('./bal-file-upload.type-BzNkF3YQ.js');
require('./index-BZ67Dcnb.js');
require('./index-BobQkZjn.js');
require('./helpers-HuaiWXrB.js');
require('./browser-BwmkTmm8.js');
require('./config.utils-Civnz2jT.js');

var lib = {};

var spec = {};

var hasRequiredSpec;

function requireSpec () {
	if (hasRequiredSpec) return spec;
	hasRequiredSpec = 1;
	Object.defineProperty(spec, "__esModule", { value: true });
	var si = { radix: 1e3, unit: ['b', 'kb', 'Mb', 'Gb', 'Tb', 'Pb', 'Eb', 'Zb', 'Yb'] };
	var iec = { radix: 1024, unit: ['b', 'Kib', 'Mib', 'Gib', 'Tib', 'Pib', 'Eib', 'Zib', 'Yib'] };
	var jedec = { radix: 1024, unit: ['b', 'Kb', 'Mb', 'Gb', 'Tb', 'Pb', 'Eb', 'Zb', 'Yb'] };
	spec.SPECS = {
	    si: si,
	    iec: iec,
	    jedec: jedec,
	};
	
	return spec;
}

var hasRequiredLib;

function requireLib () {
	if (hasRequiredLib) return lib;
	hasRequiredLib = 1;
	Object.defineProperty(lib, "__esModule", { value: true });
	var spec_1 = requireSpec();
	/**
	 * file size
	 * @param bytes
	 * @param fixed
	 * @param spec
	 */
	function default_1(bytes, fixed, spec) {
	    if (fixed === void 0) { fixed = 1; }
	    bytes = Math.abs(bytes);
	    var _a = spec_1.SPECS[spec] || spec_1.SPECS['jedec'], radix = _a.radix, unit = _a.unit;
	    var loop = 0;
	    // calculate
	    while (bytes >= radix) {
	        bytes /= radix;
	        ++loop;
	    }
	    return bytes.toFixed(fixed) + " " + unit[loop];
	}
	lib.default = default_1;
	
	return lib;
}

var libExports = requireLib();
var fileSize = /*@__PURE__*/_commonjsHelpers.getDefaultExportFromCjs(libExports);

const bemListEl = bem.BEM.block('file-upload').element('list');
const FileListComponent = ({ files, disabled, subTitle, onRemoveFile, }) => {
    return (index.h("bal-card", { class: Object.assign({}, bemListEl.class()), flat: true }, index.h("bal-list", { disabled: disabled, border: true, size: "large" }, files.map((file, index$1) => (index.h("bal-list-item", { key: file.name, disabled: disabled }, index.h("bal-list-item-icon", null, index.h("bal-icon", { name: "document" })), index.h("bal-list-item-content", null, index.h("bal-list-item-title", null, file.name), index.h("bal-list-item-subtitle", null, subTitle ? subTitle(file) : fileSize(file.size))), index.h("bal-list-item-icon", { right: true, class: {
            'file-remove': true,
        } }, index.h("button", { class: "button is-square is-danger", disabled: disabled, onClick: event => onRemoveFile(event, index$1) }, index.h("bal-icon", { name: "trash", color: disabled ? 'grey' : 'danger' })))))))));
};

/**
 * FileList Utils
 * -------------------------------
 * Utils to work with the FileList object. Unfortunately, jsdom does not
 * support the DataTransfer API, so we were not able to write any
 * unit test for it.
 */
const toFileArray = (list) => {
    return Array.from(list ? list : []);
};
const toFileList = (files) => {
    const dataTransfer = new DataTransfer();
    if (files && files.length > 0) {
        files.forEach(file => dataTransfer.items.add(file));
    }
    return dataTransfer.files;
};

const validateFileArray = (state, files, fileOptions) => {
    const validFiles = [];
    const newState = [...state];
    const invalidFiles = [];
    for (let index = 0; index < files.length; index++) {
        const file = files[index];
        if (file) {
            const reasons = [];
            if (fileOptions.accept && fileOptions.accept.split(' ').join('').split(',').indexOf(file.type) === -1) {
                reasons.push(balFileUpload_type.FileUploadRejectionReason.BAD_EXTENSION);
            }
            if (fileOptions.maxFileSize !== undefined && file.size > fileOptions.maxFileSize) {
                reasons.push(balFileUpload_type.FileUploadRejectionReason.FILE_TOO_BIG);
            }
            const stateBundleSize = newState.map(f => f.size).reduce((a, b) => a + b, 0);
            const bundleSize = stateBundleSize + file.size;
            if (fileOptions.maxBundleSize !== undefined && bundleSize > fileOptions.maxBundleSize) {
                reasons.push(balFileUpload_type.FileUploadRejectionReason.FILE_SIZE_SUM_TOO_BIG);
            }
            const amountOfFiles = newState.length + 1;
            if (fileOptions.maxFiles !== undefined && amountOfFiles > fileOptions.maxFiles) {
                reasons.push(balFileUpload_type.FileUploadRejectionReason.TOO_MANY_FILES);
            }
            const areFilesEqual = (fileA, fileB) => fileA.size === fileB.size &&
                fileA.name === fileB.name &&
                fileA.type === fileB.type &&
                fileA.lastModified === fileB.lastModified;
            const duplicatedFiles = state.filter(f => areFilesEqual(f, file));
            if (duplicatedFiles.length > 0) {
                reasons.push(balFileUpload_type.FileUploadRejectionReason.DUPLICATED_FILE);
            }
            if (reasons.length > 0) {
                invalidFiles.push({ file, reasons });
            }
            else {
                newState.push(file);
                validFiles.push(file);
            }
        }
    }
    return {
        validFiles: validFiles,
        invalidFiles: invalidFiles,
    };
};

const balFileUploadCss = ".bal-file-upload{display:block;position:static;width:100%}.bal-file-upload .file-label{word-break:break-word;white-space:normal}.bal-file-upload .bal-heading{word-break:break-all}.bal-file-upload__card{margin-top:var(--bal-space-normal)}.bal-file-upload__list{padding:0 !important}";

const FileUpload = class {
    constructor(hostRef) {
        index.registerInstance(this, hostRef);
        this.balChange = index.createEvent(this, "balChange");
        this.balFilesAdded = index.createEvent(this, "balFilesAdded");
        this.balFilesRemoved = index.createEvent(this, "balFilesRemoved");
        this.balRejectedFile = index.createEvent(this, "balRejectedFile");
        this.balInputClick = index.createEvent(this, "balInputClick");
        this.balBlur = index.createEvent(this, "balBlur");
        this.balFocus = index.createEvent(this, "balFocus");
        this.fileUploadId = `bal-file-upload-${FileUploadIds++}`;
        this.files = [];
        this.focused = false;
        this.ariaForm = form.defaultBalAriaForm;
        /**
         * PUBLIC PROPERTY API
         * ------------------------------------------------------
         */
        /**
         * The name of the control, which is submitted with the form data.
         */
        this.name = this.fileUploadId;
        /**
         * Input value.
         */
        this.value = [];
        this.initialValue = this.value || [];
        /**
         * Label of the drop area.
         */
        this.label = 'Choose or drop a file...';
        /**
         * If `true` multiple file upload is possible.
         */
        this.multiple = true;
        /**
         * If `true`, the element is not mutable, focusable, or even submitted with the form. The user can neither edit nor focus on the control, nor its form control descendants.
         */
        this.disabled = false;
        /**
         * If `true` the element can not mutated, meaning the user can not edit the control.
         */
        this.readonly = false;
        /**
         * If `true` the file upload is disabled and shows a spinner
         */
        this.loading = false;
        /**
         * If `true`, the user must fill in a value before submitting a form.
         */
        this.required = false;
        /**
         * If `true` below the drop-down area it generates a file list.
         */
        this.hasFileList = true;
        /**
         * If `true` the component gets a invalid style.
         */
        this.invalid = false;
        /**
         * If `true`, in Angular reactive forms the control will not be set invalid
         */
        this.autoInvalidOff = false;
        /**
         * PRIVATE METHODS
         * ------------------------------------------------------
         */
        this.addEventListenerDragAndDrop = () => {
            if (this.labelEl) {
                this.labelEl.addEventListener('dragenter', this.onDragenter, false);
                this.labelEl.addEventListener('dragover', this.onDragover, false);
                this.labelEl.addEventListener('drop', this.onDrop, false);
            }
        };
        this.removeEventListenerDragAndDrop = () => {
            if (this.labelEl) {
                this.labelEl.removeEventListener('dragenter', this.onDragenter, false);
                this.labelEl.removeEventListener('dragover', this.onDragover, false);
                this.labelEl.removeEventListener('drop', this.onDrop, false);
            }
        };
        this.handleFiles = (fileList) => {
            const files = toFileArray(fileList);
            const validatedFiles = validateFileArray(this.files, files, {
                accept: this.accept,
                maxFileSize: this.maxFileSize,
                maxBundleSize: this.maxBundleSize,
                maxFiles: this.maxFiles,
            });
            if (validatedFiles.invalidFiles.length > 0) {
                this.balRejectedFile.emit(validatedFiles.invalidFiles[0]);
            }
            if (validatedFiles.validFiles.length > 0) {
                this.files = [...this.files, ...validatedFiles.validFiles];
                this.balChange.emit(this.files);
                this.balFilesAdded.emit(validatedFiles.validFiles);
            }
            this.updateFileInput();
        };
        this.updateFileInput = () => {
            var _a;
            if ((_a = this.nativeInput) === null || _a === void 0 ? void 0 : _a.files) {
                this.nativeInput.files = toFileList(this.files);
            }
        };
        /**
         * HANDLERS
         * ------------------------------------------------------
         */
        this.onDragenter = (ev) => {
            formInput.stopEventBubbling(ev);
        };
        this.onDragover = (ev) => {
            formInput.stopEventBubbling(ev);
        };
        this.onDrop = (ev) => {
            formInput.stopEventBubbling(ev);
            if (!this.disabled && !this.readonly && !this.loading) {
                const dataTransfer = ev.dataTransfer;
                if (dataTransfer) {
                    this.handleFiles(dataTransfer.files);
                }
            }
        };
        this.onInputChange = () => {
            var _a;
            if (!this.disabled && !this.readonly && !this.loading) {
                if ((_a = this.nativeInput) === null || _a === void 0 ? void 0 : _a.files) {
                    this.handleFiles(this.nativeInput.files);
                }
            }
        };
        this.onRemoveFile = (ev, index) => {
            formInput.stopEventBubbling(ev);
            if (index >= 0 && index < this.files.length) {
                const files = this.files;
                const removedFiles = files.splice(index, 1);
                this.balFilesRemoved.emit(removedFiles);
                this.files = [...files];
                this.balChange.emit(this.files);
                this.updateFileInput();
            }
        };
        this.onHostClick = (ev) => formInput.inputHandleHostClick(this, ev);
        this.onInputFocus = (ev) => formInput.inputHandleFocus(this, ev);
        this.onInputBlur = (ev) => formInput.inputHandleBlur(this, ev);
        this.onInputClick = (ev) => {
            if (this.nativeInput && !this.disabled && !this.readonly && !this.loading) {
                this.nativeInput.value = '';
            }
            this.balInputClick.emit(ev);
        };
    }
    createLogger(log) {
        this.log = log;
    }
    onValueChange() {
        if (!array.areArraysEqual(this.files, this.value)) {
            this.files = this.value;
        }
    }
    /**
     * LIFECYCLE
     * ------------------------------------------------------
     */
    componentWillLoad() {
        this.onValueChange();
    }
    connectedCallback() {
        this.initialValue = this.value || [];
        this.addEventListenerDragAndDrop();
    }
    componentDidLoad() {
        this.addEventListenerDragAndDrop();
    }
    disconnectedCallback() {
        this.removeEventListenerDragAndDrop();
    }
    resetHandler(ev) {
        const formElement = ev.target;
        if (formElement === null || formElement === void 0 ? void 0 : formElement.contains(this.el)) {
            this.files = [...this.initialValue];
            clearTimeout(this.resetHandlerTimer);
            this.resetHandlerTimer = setTimeout(() => {
                if (this.nativeInput) {
                    this.nativeInput.files = toFileList(this.initialValue);
                }
            });
        }
    }
    /**
     * PUBLIC METHODS
     * ------------------------------------------------------
     */
    /**
     * Sets the file list to an empty list
     */
    async clear() {
        this.files = [];
        this.updateFileInput();
    }
    /**
     * Sets focus on the native `input`. Use this method instead of the global
     * `input.focus()`.
     */
    async setFocus() {
        formInput.inputSetFocus(this);
    }
    /**
     * Sets blur on the native `input`. Use this method instead of the global
     * `input.blur()`.
     * @internal
     */
    async setBlur() {
        formInput.inputSetBlur(this);
    }
    /**
     * Returns the native `<input>` element used under the hood.
     */
    getInputElement() {
        return Promise.resolve(this.nativeInput);
    }
    /**
     * @internal
     */
    async setAriaForm(ariaForm) {
        this.ariaForm = Object.assign({}, ariaForm);
    }
    /**
     * RENDER
     * ------------------------------------------------------
     */
    render() {
        const id = this.ariaForm.controlId || this.fileUploadId;
        return (index.h(index.Host, { key: '438044d74c8a20cee269a65e7492a19c800c8f60', onClick: this.onHostClick, "aria-disabled": aria.ariaBooleanToString(this.disabled), class: {
                'bal-file-upload': true,
            } }, index.h("div", { key: 'ea6316e2f4765d3385c0fc38d1dd6e9b55d711d0', class: {
                'file': true,
                'is-disabled': this.disabled || this.readonly || this.loading,
                'is-danger': this.invalid,
            } }, index.h("label", { key: 'a75f2f2f116c6c9f7672ddbbfe2d4636039d2c64', htmlFor: id, ref: el => (this.labelEl = el), class: {
                'file-label': true,
                'is-disabled': this.disabled || this.loading || this.readonly,
            } }, index.h("input", { key: '2ab53c147961166e12b4cab0fa0e0862557a325f', class: "file-input", type: "file", id: id, "aria-labelledby": this.ariaForm.labelId, "aria-describedby": this.ariaForm.messageId, "aria-invalid": this.invalid === true ? 'true' : 'false', "aria-disabled": aria.ariaBooleanToString(this.disabled), name: this.name, multiple: this.multiple, disabled: this.disabled || this.loading || this.readonly, readonly: this.readonly, required: this.required, accept: this.accept, onClick: this.onInputClick, onChange: this.onInputChange, onFocus: this.onInputFocus, onBlur: this.onInputBlur, ref: el => (this.nativeInput = el), "data-testid": "bal-file-upload-input" }), this.loading ? (index.h("span", { class: "file-cta" }, index.h("bal-spinner", null))) : (index.h("span", { class: "file-cta" }, index.h("span", { class: "file-icon" }, index.h("bal-icon", { name: "upload", size: "medium", color: this.disabled || this.loading || this.readonly ? 'grey-light' : this.invalid ? 'danger' : 'blue' })), index.h("span", { class: "file-label", "data-testid": "bal-file-upload-label" }, this.label))))), this.hasFileList && this.files.length > 0 ? (index.h(FileListComponent, { files: this.files, disabled: this.disabled || this.readonly || this.loading, subTitle: this.subTitle, onRemoveFile: this.onRemoveFile })) : ('')));
    }
    get el() { return index.getElement(this); }
    static get watchers() { return {
        "value": ["onValueChange"]
    }; }
};
tslib_es6.__decorate([
    log.Logger('bal-file-upload'),
    tslib_es6.__metadata("design:type", Function),
    tslib_es6.__metadata("design:paramtypes", [Function]),
    tslib_es6.__metadata("design:returntype", void 0)
], FileUpload.prototype, "createLogger", null);
let FileUploadIds = 0;
FileUpload.style = balFileUploadCss;

exports.bal_file_upload = FileUpload;
