<!--
    #VUE3 Done
-->
<template>
    <component
        :is="calcTag"
        class="px-balanced-text"
        :style="{ opacity: elementOpacity, color: debugColor, textAlign, width: '100%' }"
    >
        <!-- html -->
        <component
            v-if="hasHtml"
            :class="[bem('inner', { html: true }), innerClass]"
            :style="calcInnerStyle"
            :is="calcInnerTag"
            ref="text"
            v-html="html"
        />

        <!-- slot -->
        <component
            v-else
            :class="[bem('inner'), innerClass]"
            :style="calcInnerStyle"
            :is="calcInnerTag"
            ref="text"
        >
            <slot>
                {{ text }}
            </slot>
        </component>

        <!-- after -->
        <slot name="after"></slot>
    </component>
</template>

<script>
import PxBaseResize from '../bases/PxBaseResize.vue';
import balanceText from '../utils/balanceText.js';
import isServer from '../utils/isServer.js';

export default {
    name: 'px-balanced-text',
    mixins: [PxBaseResize],
    props: {
        /**
         * The outer tag element
         */
        tag: { type: String, default: null },

        /**
         * The inner tag element
         */
        innerTag: { type: String, default: null },

        /**
         * Class applied to the inner element.
         */
        innerClass: { type: [Array, String], default: null },

        /**
         * Style applied to the inner element.
         */
        innerStyle: { type: [String, Object], default: null },

        /**
         * Html that gets set on the inner tag element
         */
        html: { type: String, default: null },

        // text that gets set on the inner tag element
        text: { type: String, default: null },

        // set to false to disable the balancing alorithm
        enabled: { type: Boolean, default: true },

        /**
         * Set to center, start, or end to align the text
         */
        textAlign: { type: String, default: null },

        /**
         * When true opacity will be set to 0 if balancing is enabled but text is unbalanced (as a precaution, we also show if balancing fails for some reason)
         */
        hideUnbalanced: { type: Boolean, default: true },

        /**
         * Adjusts how much "give" there is in the line length. You can adjust this to make the
         * balancing more forigiving or strict.
         */
        lineAdjust: { type: Number, default: 1.15 },

        // enable to get debug color (blue: balanced html, green: balance slot, red: disabled)
        debug: { type: Boolean, default: false },
    },
    data() {
        return {
            balanced: false,
            balanceFailed: false,
        };
    },
    computed: {
        calcTag() {
            const defaultTag = this.hasHtml ? 'div' : 'p';
            return this.tag || defaultTag;
        },
        calcInnerTag() {
            const defaultTag = this.hasHtml ? 'div' : 'span';
            return this.innerTag || defaultTag;
        },

        calcInnerStyle() {
            const style = this.innerStyle || {};
            if (typeof style === 'string') {
                return style + ' display: inline-block;';
            }

            style.display = 'inline-block';
            return style;
        },

        hasHtml() {
            return !!this.htmlValue && !this.text;
        },

        htmlValue() {
            return this.html?.trim();
        },

        elementOpacity() {
            if (this.enabled && this.hideUnbalanced && !this.balanceFailed && !this.balanced) {
                return 0;
            }
            return null;
        },
        debugColor() {
            if (this.debug) {
                if (!this.enabled) {
                    return 'red';
                }

                if (this.balanced && this.hasHtml) {
                    return 'blue';
                }

                if (this.balanced) {
                    return 'green';
                }
            }
            return null;
        },
        isSafari() {
            if (isServer()) {
                return false;
            }
            return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
        },
        isHeadless() {
            // TODO: replace with isUnderTest
            if (isServer()) {
                return false;
            }
            return /HeadlessChrome/i.test(navigator.userAgent);
        },
    },
    watch: {
        enabled() {
            if (isServer()) {
                return;
            }
            this.balanced = false;
            this.$nextTick(() => {
                this.resize();
            });
        },
        html() {
            this.balance();
        },
    },

    methods: {
        resize() {
            this.balance();
        },
        balance() {
            if (isServer()) {
                return;
            }

            if (this.isSafari || this.isHeadless) {
                this.balanced = true;
                return;
            }

            if (this.enabled) {
                try {
                    this.$emit('start');
                    this.balanceFailed = false;
                    if (this.$refs.text && this.$refs.text.offsetWidth > 0) {
                        balanceText(this.$refs.text, this.lineAdjust);
                    }
                    this.balanced = true;
                    this.$emit('complete');
                } catch (error) {
                    console.error('balanceText failed');
                    console.error(error);
                    this.balanceFailed = true;
                }
            }
        },
        getInnerTextRef() {
            return this.$refs.text;
        },
    },
};
</script>
