<template>
    <span
        :class="cx('root')"
        :data-orientation="orientation"
        :data-disabled="disabled ? '' : undefined"
        :data-readonly="readonly ? '' : undefined"
        v-bind="ptmi('root')"
        @pointerenter="onPointerEnter"
        @pointermove="onPointerMove"
        @pointerleave="onPointerLeave"
        @focusin="onFocusIn"
        @focusout="onFocusOut"
    >
        <span
            v-for="value in stars"
            :key="value"
            :ref="(el) => optionRef(el, value - 1)"
            :class="cx('option')"
            :data-index="value - 1"
            :data-highlighted="isHighlighted(value) ? '' : undefined"
            :data-half="isHalf(value) ? '' : undefined"
            :data-checked="isChecked(value) ? '' : undefined"
            v-bind="getPTOptions('option', value)"
        >
            <input
                v-if="allowHalf"
                type="radio"
                class="p-hidden-accessible"
                :name="inputName"
                :value="value - 0.5"
                :checked="d_value === value - 0.5"
                :disabled="disabled"
                :readonly="readonly"
                :aria-readonly="readonly || undefined"
                :aria-label="starAriaLabel(value - 0.5)"
                @change="onInputChange($event, value - 0.5)"
                :data-p-hidden-accessible="true"
                v-bind="ptm('halfInput')"
            />
            <input
                type="radio"
                class="p-hidden-accessible"
                :name="inputName"
                :value="value"
                :checked="d_value === value"
                :disabled="disabled"
                :readonly="readonly"
                :aria-readonly="readonly || undefined"
                :aria-label="starAriaLabel(value)"
                @change="onInputChange($event, value)"
                :data-p-hidden-accessible="true"
                v-bind="ptm('fullInput')"
            />
            <span :class="cx('onIcon')" :data-orientation="orientation" @pointermove="onIconPointerMove($event, value - 1)" @click="onIconClick($event, value - 1)" @mousedown.prevent v-bind="ptm('onIcon')">
                <slot name="onicon" :value="value">
                    <component :is="onIcon ? 'span' : 'StarFill'" :class="onIcon" />
                </slot>
            </span>
            <span :class="cx('offIcon')" @pointermove="onIconPointerMove($event, value - 1)" @click="onIconClick($event, value - 1)" @mousedown.prevent v-bind="ptm('offIcon')">
                <slot name="officon" :value="value">
                    <component :is="offIcon ? 'span' : 'Star'" :class="offIcon" />
                </slot>
            </span>
        </span>
    </span>
</template>

<script>
import Star from '@primeicons/vue/star';
import StarFill from '@primeicons/vue/star-fill';
import { uuid } from '@primeuix/utils';
import BaseRating from './BaseRating.vue';

export default {
    name: 'Rating',
    extends: BaseRating,
    inheritAttrs: false,
    emits: ['change', 'focus', 'blur'],
    optionEls: null,
    data() {
        return {
            hovering: false,
            hoveringValue: 0
        };
    },
    beforeUpdate() {
        this.optionEls = null;
    },
    methods: {
        getPTOptions(key, value) {
            return this.ptm(key, {
                context: {
                    highlighted: this.isHighlighted(value),
                    half: this.isHalf(value),
                    checked: this.isChecked(value)
                }
            });
        },
        optionRef(el, index) {
            if (!this.optionEls) this.optionEls = [];
            this.optionEls[index] = el;
        },
        startHover(value = this.d_value ?? 0) {
            if (!this.disabled && !this.readonly) {
                this.hoveringValue = value;
                this.hovering = true;
            }
        },
        stopHover() {
            if (!this.disabled && !this.readonly) {
                this.hovering = false;
                this.hoveringValue = 0;
            }
        },
        resolvePointerValue(event, optionEl, index) {
            if (this.allowHalf && optionEl) {
                const rect = optionEl.getBoundingClientRect();
                const isHalf = this.orientation === 'vertical' ? event.clientY < rect.top + rect.height / 2 : event.clientX < rect.left + rect.width / 2;

                return isHalf ? index + 0.5 : index + 1;
            }

            return index + 1;
        },
        resolveSelectedValue(value) {
            return this.d_value === value ? null : value;
        },
        updateValue(event, value) {
            this.writeValue(value, event);
            this.$emit('change', { originalEvent: event, value });
        },
        onPointerEnter() {
            if (this.disabled || this.readonly) return;
            this.startHover();
        },
        onPointerMove() {
            if (!this.disabled && !this.readonly && !this.hovering) this.startHover();
        },
        onPointerLeave() {
            if (this.disabled || this.readonly) return;

            this.stopHover();
        },
        onFocusIn(event) {
            if (this.disabled || this.readonly) return;

            const target = event.target;

            if (target && target.type === 'radio' && target.name === this.inputName) {
                this.startHover(parseFloat(target.value));
                this.$emit('focus', event);
            }
        },
        onFocusOut(event) {
            if (this.disabled || this.readonly) return;

            if (!event.currentTarget.contains(event.relatedTarget)) {
                this.stopHover();
                this.formField.onBlur?.();
                this.$emit('blur', event);
            }
        },
        onIconPointerMove(event, index) {
            if (this.disabled || this.readonly) return;
            this.startHover(this.resolvePointerValue(event, this.optionEls?.[index], index));
        },
        onIconClick(event, index) {
            if (this.disabled || this.readonly) return;
            this.updateValue(event, this.resolveSelectedValue(this.resolvePointerValue(event, this.optionEls?.[index], index)));
        },
        onInputChange(event, value) {
            if (this.disabled || this.readonly) return;
            this.updateValue(event, this.resolveSelectedValue(value));
        },
        starAriaLabel(value) {
            const locale = this.$primevue?.config?.locale?.aria;

            if (!locale) return String(value);
            if (value === 1) return locale.star;

            return locale.stars.replace(/{star}/g, value);
        }
    },
    computed: {
        inputName() {
            return this.name || uuid('rating-');
        },
        activeValue() {
            return this.hovering ? this.hoveringValue : (this.d_value ?? 0);
        },
        isHighlighted() {
            return (value) => {
                const idx = value - 1;

                return idx >= 0 && this.activeValue > idx;
            };
        },
        isHalf() {
            return (value) => {
                const idx = value - 1;

                return this.allowHalf && idx >= 0 && this.activeValue > idx && this.activeValue === idx + 0.5;
            };
        },
        isChecked() {
            return (value) => {
                const idx = value - 1;

                return idx >= 0 && (this.activeValue === idx + 1 || (this.allowHalf && this.activeValue === idx + 0.5));
            };
        }
    },
    components: {
        StarFill,
        Star
    }
};
</script>
