<script>
import { mapState } from "vuex";
import MacronRestorationService from "../js/MacronRestorationModule";
import Util, { log } from "../js/Util";

class MacronisationViewFailure {
    /**
     * @param {String} fileName The name of the file for which this failure occured.
     * @param {String} message The reason for this failure.
     */
    constructor(fileName, message) {
        /** @type {String} The UUID of this failure. */
        this.id = Util.generateUuid();

        /** @type {String | null} The name of the file for which this failure occured. */
        this.fileName = fileName;

        /** @type {String} The reason for the failure. */
        this.message = message;
    }
}

const macronService = new MacronRestorationService();

export default {
    name: "FileUpload",
    data() {
        return {
            /** @type {File[]} */
            files: [],
            preserveExistingMacrons: true,
            /** @type {Map<String, MacronisationViewFailure>} */
            failures: new Map(),
            isMacronising: false
        }
    },
    computed: mapState({
        translations: state => state.translations,
        anyFiles() {
            return this.files?.length > 0;
        },
        getFileNameList() {
            let fileNameList = "";

            for (const file of this.files) {
                fileNameList += file.name + ", ";
            }

            return fileNameList.slice(0, fileNameList.length - 2);
        }
    }),
    emits: [ "fileMacronised" ],
    methods: {
        openFilePicker() {
            this.$refs.fileInput.click();
        },
        onFilesChanged() {
            this.files = [];
            const files = this.$refs.fileInput.files;

            if (files?.length !== undefined && files?.length > 0) {
                for (const file of files) {
                    this.files.push(file);
                }
            }
        },
        /**
         * Invoked when a drop event occurs on the file upload panel.
         * @param {DragEvent} ev The drop event.
         */
        async onFilesDropped(ev) {
            ev.preventDefault();

            if (ev.dataTransfer.items) {
                for (let i = 0; i < ev.dataTransfer.items.length; i++) {
                    if (ev.dataTransfer.items[i].kind !== "file") {
                        continue;
                    }

                    const file = ev.dataTransfer.items[i].getAsFile();
                    this.files.push(file);
                }
            }
            else {
                this.files.push(ev.dataTransfer.files);
            }
        },
        /**
         * Invoked when the user clicks a button to remove a file.
         * @param {MouseEvent} ev The mouse event.
         * @param {Number} index The index of the file to remove.
         */
        onFileRemove(ev, index) {
            ev.cancelBubble = true;
            this.files.splice(index, 1);
        },

        async doMacronisation() {
            await this.macronise(this.files);
        },
        /**
         * Attempts to macronise each submitted file.
         * @param {File[]} files The files to macronise.
         */
        async macronise(files) {
            this.isMacronising = true;

            for (const file of files) {
                try {
                    const fileInfo = await macronService.fileMacronisation(file, this.preserveExistingMacrons);
                    this.$store.commit("pushFileInfo", fileInfo);
                }
                catch (ex) {
                    log("Failed to macronise file", "error");
                    log(ex);

                    this.createFailure(ex.fileName, ex.message);
                }
            }

            this.isMacronising = false;
            this.files = [];
        },

        /**
         * Creates a new UI entry indicating that a file failed to be macronised.
         * @param {String} fileName The name of the file that couldn't be macronised.
         * @param {String} reason The reason that the file couldn't be macronised.
         */
        createFailure(fileName, reason) {
            const failure = new MacronisationViewFailure(fileName, reason);
            this.failures.set(failure.id, failure);
        },
        /**
         * Dismisses a failure
         * @param {String} id The UUID of the failure.
         */
        dismissFailure(id) {
            this.failures.delete(id);
        }
    }
}
</script>

<template>
    <div class="audio-file-picker">
        <div class="input-bar">
            <div class="text-input-sl" style="cursor: pointer;"
                @click="openFilePicker" @drop="onFilesDropped" @dragover="$event.preventDefault()">
                <span class="text-placeholder material-icons">attach_file</span>
                <span class="text-placeholder" v-if="!anyFiles">{{ translations.get("FileUpload_SelectFiles") }}</span>

                <div class="uploaded-file-listing" v-for="(file, index) in files" :key="file.name">
                    <span>{{ file.name }}</span>
                    <button type="button" @click="onFileRemove($event, index)" class="btn-pin">
                        <span class="material-icons mdi-s">close</span>
                    </button>
                </div>
            </div>

            <input ref="fileInput" type="file" @input="onFilesChanged"
                accept="application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.oasis.opendocument.text,text/plain,application/vnd.openxmlformats-officedocument.presentationml.presentation,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
                multiple :disabled="isMacronising" />

            <button class="btn-primary" type="submit" :disabled="!anyFiles || isMacronising" @click="doMacronisation">
                <span class="material-icons">history_edu</span>
                <span>{{ translations.get("FileUpload_MacroniseFiles") }}</span>
            </button>
        </div>

        <div v-if="isMacronising" class="progress-bar-container">
            <div class="progress-bar-value progress-bar-indeterminate" />
        </div>

        <ul class="list-view" v-if="failures.size > 0">
            <li v-for="[id, failure] in failures" :key="failure.id" class="list-view__item transcription-error-container">
                <div>
                    {{ translations.get("FileUpload_MacronisationFailed-Message") }} <i v-if="failure.fileName">{{ failure.fileName }}</i><br />
                    <span v-if="failure.message">{{ translations.get("FileUpload_MacronisationFailed-Reason") }}: {{ failure.message }}</span>
                </div>

                <button class="btn-fab theme-flat" @click="dismissFailure(id)">
                    <span class="material-icons">clear</span>
                </button>
            </li>
        </ul>
    </div>
</template>

<style scoped lang="scss">
.audio-file-picker {
    display: flex;
    gap: 0.5em;
    flex-direction: column;

    input {
        display: none;
    }

    .text-container {
        cursor: pointer;
    }
}

.input-bar {
    display: grid;
    align-items: stretch;
    gap: 0.5em;
    grid-template-columns: 1fr auto;
}

.uploaded-file-listing {
    display: flex;
    align-items: center;

    padding: 3px;
    gap: 5px;

    border: 1px solid var(--bg-color);
    border-radius: 3px;
    border-left: 4px solid var(--bg-color);
    background: rgba(var(--bg-color-raw), 0.1);

    margin-right: 0.5em;
    &:last-child {
        margin-right: 0;
    }
}

.transcription-error-container {
    display: grid;
    gap: 0.5em;
    grid-template-columns: 1fr auto;
    align-items: center;

    border-left: 3px solid red;
}
</style>
