<template>
<div>
    <div class="editor-controls">
        <audio-time-bar v-model.number="currentPlaybackTime" :audio-length="playbackState.length" :isDisabled="playbackState.id != transcription.id" />

        <toggle-button v-model="enableEditing">
            <span class="material-icons .mdi-m">edit</span>
        </toggle-button>
    </div>

    <div class="text-container words-container" :hidden="enableEditing">
        <ul class="list-view" v-if="!enableEditing">
            <li v-for="word in words" :key="word.id" class="word-container" @click="playAudio(word.startTime)">
                <span class="word-highlight word" :class="{ 'word-highlight-applied': word.shouldHighlight }">
                    {{ word.word }}
                </span>
            </li>
        </ul>

        <ul class="list-view" v-if="enableEditing">
            <li v-for="(word, index) in words" :key="word.id" class="word-container">
                <input :size="word.word.length <= 1 ? 1 : word.word.length - 1" :ref="word.id"
                    class="editor-word" v-model="word.word" type="text"
                    @input="onEditorInput($event, index)" @focusout="onEditorFocusOut(index)" @keyup="onEditorKeyUp($event, index)" />
                <span>&nbsp;</span>
            </li>
        </ul>
    </div>
</div>
</template>

<style scoped lang="scss">
.word {
    padding: 0.25em;
}

.editor-controls {
    display: grid;
    align-items: center;
    grid-template-columns: 1fr auto;
    margin: 0.5em 0 0.3em 0;
    gap: 1em;
}

.words-container {
    transition-duration: var(--transition-duration);

    &[hidden] {
        background-color: inherit;
    }
}

.word-container {
    font: var(--monospace-font);
    display: inline-block;
    line-height: 2em;
}

.word-highlight-applied {
    background-color: rgba(255, 255, 0, 0.4);
}

.word-highlight {
    &:hover {
        @extend .word-highlight-applied;
    }
}

.editor-word {
    border: 1px solid black;
    border-radius: 2px;
    padding: 2px;
    font-family: inherit;
    font-size: 1rem;
}
</style>

<script>
import { mapState } from "vuex";
import { TranscriptionViewModel, PlaybackState } from "../main";
import AudioTimeBar from "./AudioTimeBar.vue"
import Util from "../js/Util";

export class Word {
    /**
     * Initialises a new instance of the Word class.
     * @param {String} word The word.
     * @param {Number} startTime The time within the audio that this word starts being spoken.
     * @param {Number} endTime The time within the audio that this word finishes being spoken.
     */
    constructor(word, startTime, endTime) {
        /** @type {String} The ID of this word. */
        this.id = Util.generateUuid();

        /** @type {String} The word. */
        this.word = word;

        /** @type {Number} The time within the audio that this word starts being spoken. */
        this.startTime = startTime;

        /** @type {Number} The time within the audio that this word finishes being spoken. */
        this.endTime = endTime;

        /** @type {Boolean} A value indicating whether this word should be highlighted. */
        this.shouldHighlight = false;

        /** @type {Number} The number of times the user has tried to delete this word. */
        this.deletionAttempts = 0;
    }
}

export default {
    name: "TranscriptionItemEditor",
    components: {
        AudioTimeBar
    },
    props: {
        transcription: TranscriptionViewModel
    },
    data() {
        return {
            enableEditing: false,
            words: [],
            lastHighlightedWord: 0
        }
    },
    computed: {
        currentPlaybackTime: {
            get() {
                return this.$store.state.playbackState.currentTime;
            },
            set(value) {
                this.$store.commit("setCurrentPlaybackTime", value);
            }
        },
        ...mapState({
            playbackState: state => state.playbackState,
            translations: state => state.translations
        })
    },
    methods: {
        playAudio(startTime) {
            const pState = new PlaybackState(this.transcription.id, true, startTime);
            this.$store.commit("setPlaybackState", pState);
        },
        /**
         * Invoked when the value of a word changes.
         * @param {Number} index The index of the word that has changed.
         */
        onEditorInput(event, index) {
            /** @type {Word} */
            const word = this.words[index];

            if (event.inputType === "insertText") {
                // Insert a new word if whitespace is entered on an existing word
                if (event.data === " " && word.word !== " ") {
                    // TODO: Proper timing metadata
                    const newWord = new Word("", word.startTime, word.endTime);
                    this.words.splice(index + 1, 0, newWord);

                    // We have to give the element some time to render, even though the ref is registered immediately
                    setTimeout(() => this.$refs[newWord.id].focus(), 50);
                }

                word.word = word.word.replace(" ", "");
            }
        },
        onEditorFocusOut(index) {
            // Remove empty words when they lose focus
            if (this.words[index].word === "") {
                this.words.splice(index, 1);
            }
        },
        onEditorKeyUp(event, index) {
            /** @type {Word} */
            const word = this.words[index];

            // Remove the word if the user is repetitively hitting backspace/delete on an empty cell
            if (event.code === "Backspace" || event.code === "Delete") {
                if (word.word.length < 1) {
                    if (word.deletionAttempts === 1) {
                        this.words.splice(index, 1);

                        setFocus(event.code === "Backspace", this);
                    }

                    console.log(word.deletionAttempts);
                    word.deletionAttempts++;
                }
            }
            else {
                word.deletionAttempts = 0;
            }

            // Focus on the next word if the user is moving right with their caret at the word's end
            // if (event.code === "ArrowRight") {
            //     const inputElement = this.$refs[word.id];

            //     if (inputElement.selectionStart === word.word.length) {
            //         setFocus(false, this);
            //     }
            // }

            // Focus on the previous word if the user is moving left with their caret at the word's start
            // if (event.code === "ArrowLeft") {
            //     const inputElement = this.$refs[word.id];

            //     if (inputElement.selectionStart === 0) {
            //         setFocus(true, this);
            //     }
            // }

            /**
             * Gets the index of an adjacent word to focus on.
             * @param {Boolean} focusLeft Whether to focus on the word to the left or right of the current index.
             * @param vm The view model.
             */
            function getFocusIndex(focusLeft, vm) {
                let focusIndex = 0;

                if (focusLeft) {
                    focusIndex = index === 0 ? 0 : index - 1;
                }
                else {
                    focusIndex = index === vm.words.length - 1 ? vm.words.length - 1 : index + 1;
                }

                return focusIndex;
            }

            /**
             * Sets the focus to an adjacent word.
             * @param {Boolean} focusLeft Whether to focus on the word to the left or right of the current index.
             * @param vm The view model.
             */
            function setFocus(focusLeft, vm) {
                const focusIndex = getFocusIndex(focusLeft, vm);
                const focusId = vm.words[focusIndex].id;
                vm.$refs[focusId].focus();

                if (!focusLeft) {
                    vm.$refs[focusId].setSelectionRange(-1, 0);
                }
            }
        }
    },
    watch: {
        currentPlaybackTime(newValue) {
            this.words[this.lastHighlightedWord].shouldHighlight = false;

            if (!this.playbackState.isPlaying || this.playbackState.id !== this.transcription.id) {
                return;
            }

            for (const word of this.words) {
                word.shouldHighlight = word.startTime < newValue && word.endTime > newValue;
            }
        }
    },
    beforeMount() {
        this.words = getWords(this.transcription);
    }
}

/**
 * Gets the words in a transcription.
 * @param {TranscriptionViewModel} transcription The transcription.
 * @returns {Word[]}
 */
export function getWords(transcription) {
    /** @type {Word[]} */
    const words = [];

    let lastWord = "";
    let currStartTime = 0;

    for (const metadata of transcription.metadata) {
        if (metadata.char === " ") {
            words.push(new Word(lastWord, currStartTime, metadata.start_time));

            lastWord = "";
            currStartTime = metadata.start_time;
        }
        else {
            lastWord += metadata.char;
        }
    }

    // Push the last word, as most transcriptions will not end in a space (hence breaking the above algorithm)
    if (lastWord.length > 0) {
        const newWord = new Word(lastWord, currStartTime, transcription.metadata[transcription.metadata.length - 1].start_time)
        words.push(newWord);
    }

    return words;
}
</script>
