<template>
    <component v-if="!asChild" :is="as" :class="cx('root')" v-bind="attrs">
        <slot :value="d_value" :isDragging="isDragging" />
    </component>
    <slot v-else :class="cx('root')" :a11yAttrs="a11yAttrs" :value="d_value" :isDragging="isDragging" />
</template>

<script>
import { isRTL } from '@primeuix/utils/dom';
import { mergeProps } from 'vue';
import BaseCompare from './BaseCompare.vue';

export default {
    name: 'Compare',
    extends: BaseCompare,
    inheritAttrs: false,
    emits: ['update:modelValue', 'value-change-end', 'focus', 'blur'],
    data() {
        return {
            d_value: this.normalizeValue(this.modelValue ?? (this.min + this.max) / 2),
            isDragging: false,
            isHandlePointerDown: false,
            dragOffsetPx: 0
        };
    },
    watch: {
        modelValue(newValue) {
            if (newValue !== undefined) {
                this.d_value = this.normalizeValue(newValue);
            }
        },
        min() {
            this.updateValue(this.d_value);
        },
        max() {
            this.updateValue(this.d_value);
        },
        step() {
            this.updateValue(this.d_value);
        }
    },
    methods: {
        clamp(value, minValue, maxValue) {
            return Math.min(Math.max(value, minValue), maxValue);
        },
        getPrecision(stepValue) {
            const stepString = String(stepValue);

            if (stepString.includes('e-')) {
                return Number(stepString.split('e-')[1] || 0);
            }

            const dotIndex = stepString.indexOf('.');

            return dotIndex >= 0 ? stepString.length - dotIndex - 1 : 0;
        },
        roundToStep(value, stepValue, minValue) {
            if (!stepValue) return value;

            const precision = this.getPrecision(stepValue);
            const rounded = Math.round((value - minValue) / stepValue) * stepValue + minValue;

            return Number(rounded.toFixed(precision));
        },
        normalizeValue(value) {
            return this.clamp(this.roundToStep(Number(value ?? this.min), this.step, this.min), this.min, this.max);
        },
        getValuePercent(value) {
            const range = this.max - this.min;

            if (!range) return 0;

            return this.clamp(((value - this.min) / range) * 100, 0, 100);
        },
        getValueFromPointer(event, offsetPx = 0) {
            const rootElement = event.currentTarget;
            const rect = rootElement.getBoundingClientRect();
            const size = this.isHorizontal ? rect.width : rect.height;

            if (!size) return this.min;

            const pointerPosition = this.isHorizontal ? event.clientX - rect.left : event.clientY - rect.top;
            const position = (pointerPosition - offsetPx) / size;
            const clampedPosition = this.clamp(position, 0, 1);
            const orientedPosition = this.isHorizontal ? (isRTL(rootElement) ? 1 - clampedPosition : clampedPosition) : 1 - clampedPosition;
            const value = this.min + orientedPosition * (this.max - this.min);

            return this.normalizeValue(value);
        },
        updateValue(nextValue) {
            const value = this.normalizeValue(nextValue);

            if (value === this.d_value) {
                return value;
            }

            this.d_value = value;
            this.$emit('update:modelValue', value);

            return value;
        },
        updateValueFromPointer(event) {
            this.dragOffsetPx = 0;
            this.updateValue(this.getValueFromPointer(event, 0));
        },
        resetPointerState() {
            this.isDragging = false;
            this.isHandlePointerDown = false;
            this.dragOffsetPx = 0;
        },
        onPointerDown(event) {
            if (this.disabled || this.readonly) return;
            if (event.pointerType === 'mouse' && event.button !== 0) return;

            event.preventDefault();
            event.currentTarget.setPointerCapture?.(event.pointerId);
            this.isDragging = true;

            if (this.isHandlePointerDown) {
                this.isHandlePointerDown = false;

                return;
            }

            this.dragOffsetPx = 0;
            this.updateValueFromPointer(event);
        },
        onPointerMove(event) {
            if (this.disabled || this.readonly) return;

            if (this.slideOnHover) {
                this.updateValueFromPointer(event);

                return;
            }

            if (!this.isDragging) return;

            event.preventDefault();
            this.updateValue(this.getValueFromPointer(event, this.dragOffsetPx));
        },
        onPointerUp(event) {
            if (this.disabled || this.readonly) return;
            if (!this.isDragging) return;

            event.preventDefault();

            if (event.currentTarget.hasPointerCapture?.(event.pointerId)) {
                event.currentTarget.releasePointerCapture(event.pointerId);
            }

            this.resetPointerState();
            this.$emit('value-change-end', { originalEvent: event, value: this.d_value });
        },
        onPointerCancel(event) {
            if (this.isDragging) {
                this.$emit('value-change-end', { originalEvent: event, value: this.d_value });
            }

            this.resetPointerState();
        },
        onLostPointerCapture(event) {
            if (this.isDragging) {
                this.$emit('value-change-end', { originalEvent: event, value: this.d_value });
            }

            this.resetPointerState();
        },
        onHandlePointerDown(event) {
            if (this.disabled || this.readonly) return;
            if (event.pointerType === 'mouse' && event.button !== 0) return;

            event.preventDefault();

            const handle = event.currentTarget;

            if (handle) {
                const handleRect = handle.getBoundingClientRect();
                const handleCenter = this.isHorizontal ? handleRect.left + handleRect.width / 2 : handleRect.top + handleRect.height / 2;
                const pointerAxis = this.isHorizontal ? event.clientX : event.clientY;

                this.dragOffsetPx = pointerAxis - handleCenter;
            } else {
                this.dragOffsetPx = 0;
            }

            this.isHandlePointerDown = true;
        },
        onInputInput(event) {
            if (this.disabled || this.readonly) return;

            this.updateValue(Number(event.target.value));
        },
        onInputChange(event) {
            if (this.disabled || this.readonly) return;

            const value = this.updateValue(Number(event.target.value));

            this.$emit('value-change-end', { originalEvent: event, value });
        },
        onInputFocus(event) {
            if (this.disabled || this.readonly) return;

            this.$emit('focus', event);
        },
        onInputBlur(event) {
            if (this.disabled || this.readonly) return;

            this.$emit('blur', event);
        }
    },
    computed: {
        isHorizontal() {
            return this.orientation === 'horizontal';
        },
        attrs() {
            return mergeProps(this.a11yAttrs, this.ptmi('root'));
        },
        a11yAttrs() {
            return {
                'data-pc-section': 'root',
                'data-orientation': this.orientation,
                'data-disabled': this.disabled ? '' : undefined,
                'data-invalid': this.invalid ? '' : undefined,
                'data-dragging': this.isDragging ? '' : undefined,
                onPointerdown: this.onPointerDown,
                onPointermove: this.onPointerMove,
                onPointerup: this.onPointerUp,
                onPointercancel: this.onPointerCancel,
                onLostpointercapture: this.onLostPointerCapture
            };
        }
    }
};
</script>
