<template>
    <div
        :class="
            bem({
                placeholder: placeholderVisible,
                multiline: multiline,
                ...variantsMap,
            })
        "
        ref="input"
        @input="onInput"
        @blur="onBlur"
        @keydown="onKeydown"
        @compositionstart="onCompositionStart"
        @compositionend="onCompositionEnd"
        :contenteditable="enabled ? 'plaintext-only' : false"
        :placeholder="placeholder"
        :disabled="disabled ? 'disabled' : null"
    ></div>
</template>

<script>
import PxBase from '../bases/PxBase.vue';
import PxBaseVariants from '../bases/PxBaseVariants.vue';
import { getCaretPosition, setCaretPosition } from '../utils/caretPosition.js';

export default {
    name: 'px-textbox',
    mixins: [PxBase, PxBaseVariants],
    props: {
        // whether to render as a textbox or textarea, any truthy value signals use textarea and any numeric value will also set the "rows"
        multiline: { type: Boolean, default: false },

        // the value of the textbox
        text: { type: String, default: null },

        // whether to fire an update / v-model event on every character change ('input') or when the textbox loses focus ('change')
        updateMode: {
            type: String,
            default: 'input',
            validator: val => ['input', 'change'].includes(val),
        },

        disabled: { type: Boolean, default: false },

        placeholder: { type: String, default: '' },

        lineBreakReplacement: { type: String, default: ' ' }, // the character to replace line breaks with when multiline is false
    },
    data() {
        return {
            minHeight: 300,
            placeholderVisible: false,
            isComposing: false,
        };
    },
    computed: {
        enabled() {
            return !this.disabled;
        },
    },

    watch: {
        text(nv) {
            this.setValue(nv);
        },
    },
    mounted() {
        this.setValue(this.text);
    },

    methods: {
        setValue(val) {
            if (this.isComposing) {
                return;
            }

            const nbsp = '\u202f';
            const startPos = getCaretPosition(this.$refs.input);
            const prevValue = this.$refs.input.textContent;

            val = val || '';

            // if the first character is nbps, we remove it (assuming we had added it to begin with)
            if (val.startsWith(nbsp)) {
                val = val.slice(1);
            }

            // remove line breaks if we are not in multiline mode
            if (!this.multiline) {
                val = val.replace(/(\r\n|\n|\r)/gm, this.lineBreakReplacement);
            }

            // remove leading and trailing whitespace / new lines
            const trimmed = val.trim().replace(/^\s*\n|\n\s*$/g, '');

            // if the trimmed value is empty, we set it to a single non-breaking space (this eliminates weird UI behaviors when the textbox is empty)
            if (trimmed.length == 0) {
                this.placeholderVisible = true;
                val = nbsp;
            } else {
                this.placeholderVisible = false;
            }

            // set the text content
            this.$refs.input.textContent = val;

            // raise an update event
            this.$emit('update:text', val);

            // update the caret position
            const newPos = startPos + (val.length - prevValue.length);

            if (newPos > 0) {
                setCaretPosition(this.$refs.input, newPos);
            }
        },
        onInput(e) {
            const value = e.target.textContent;
            if (this.updateMode == 'input') {
                this.setValue(value);
            }
            this.$emit('input', value);
        },

        onBlur(e) {
            const value = e.target.textContent;
            if (this.updateMode == 'change') {
                this.setValue(value);
            }
            this.$emit('change', value);
            this.$emit('blue');
        },
        onKeydown(e) {
            if (!this.multiline) {
                if (e.key == 'Enter') {
                    e.preventDefault();
                }
            }
            this.$emit('keydown', e);
        },

        focus(cursorPosition = 'end') {
            if (this.$refs?.input) {
                this.$refs.input.focus();

                if (cursorPosition == 'end') {
                    setCaretPosition(this.$refs.input, this.$refs.input.textContent.length);
                } else {
                    setCaretPosition(this.$refs.input, 0);
                }
            }
        },

        selectAll() {
            this.$el.select();
        },

        onCompositionStart() {
            this.isComposing = true;

            this.placeholderVisible = false;
        },

        onCompositionEnd(e) {
            this.isComposing = false;

            const compositionInput = e.data;

            if (compositionInput) {
                const value = this.text + compositionInput;

                if (this.updateMode == 'input') {
                    this.setValue(value);
                }
                this.$emit('input', value);
            }
        },
    },
};
</script>

<style lang="scss">
@use '../styles/px.scss' as *;
@use '@thinkpixellab-public/px-styles/src/modules/controls' as *;

.px-textbox {
    @include control-reset();

    overflow: hidden;
    white-space: nowrap;
    text-align: left;
    display: inline-flex;

    &--placeholder {
        &:after {
            content: attr(placeholder);
            font-size: inherit;
            font-family: inherit;
            opacity: 0.5;
            white-space: nowrap;
        }
    }

    &--default {
        @include css-map(
            textbox(
                (
                    display: inline-flex,
                    text-align: left,
                    align-items: center,
                    min-width: 10em,
                )
            ),
            $aliases: (disabled: '&[disabled], &[disabled]:hover, &[disabled]:active')
        );
    }

    &--multiline {
        white-space: normal;
        overflow-y: auto;
    }
}
</style>
