import React, { useEffect, useState } from 'react';
import type { FC, ChangeEventHandler } from 'react';
import cx from 'classnames';
import { Tooltip } from '../tooltip';
import {
    absoluteToIndexValue,
    absoluteToPercentageValue,
    defaultFormatter,
    findZeroPoint,
} from './utils';
import type { Props, SliderRange } from './types';

export const CLASSNAME = 'Slider';

/**
 * Slider is an interactive UI element that allows users to select a single value or a range of values from a continuum by moving one or several indicators on a horizontal line.
 *
 * @type molecule
 * @status live
 * @tags form-elements
 */
export const Slider: FC<Props> = ({
    className,
    style,
    testId = CLASSNAME,
    min = 0,
    max = 100,
    step = 1,
    value: controlledValue,
    initialValue,
    variant = 'range',
    gap = variant === 'range' ? step : 0,
    tooltipFormatter = defaultFormatter,
    labelFormatter = defaultFormatter,
    onChange,
    onInput,
}) => {
    const [container, setContainer] = useState<HTMLDivElement | null>(null);
    const [activeSlider, setActiveSlider] = useState<'min' | 'max'>(
        variant === 'range' ? 'min' : variant
    );
    const [hoveredSlider, setHoveredSlider] = useState<'min' | 'max' | undefined>();
    const [value, setValue] = useState<SliderRange>(
        controlledValue ?? initialValue ?? { min, max }
    );
    const { min: minValue, max: maxValue } = value;

    useEffect(() => controlledValue && setValue(controlledValue), [controlledValue]);

    const handleMinChange: ChangeEventHandler<HTMLInputElement> = (e) => {
        e.preventDefault();
        const currentMinValue = e.currentTarget.valueAsNumber;
        if (currentMinValue + gap > maxValue) return;
        setValue((val) => ({ ...val, min: currentMinValue }));
        onInput?.(
            { min: currentMinValue, max: maxValue },
            {
                min: absoluteToIndexValue(min, currentMinValue, step),
                max: absoluteToIndexValue(min, maxValue, step),
            }
        );
    };

    const handleMaxChange: ChangeEventHandler<HTMLInputElement> = (e) => {
        e.preventDefault();
        const currentMaxValue = e.currentTarget.valueAsNumber;
        if (currentMaxValue - gap < minValue) return;
        setValue((val) => ({ ...val, max: currentMaxValue }));
        onInput?.(
            { min: minValue, max: currentMaxValue },
            {
                min: absoluteToIndexValue(min, minValue, step),
                max: absoluteToIndexValue(min, currentMaxValue, step),
            }
        );
    };

    const handleChange = () => {
        onChange?.(value, {
            min: absoluteToIndexValue(min, minValue, step),
            max: absoluteToIndexValue(min, maxValue, step),
        });
    };

    const handleEnter = (slider: 'min' | 'max') => {
        setActiveSlider(slider);
        setHoveredSlider(slider);
    };
    const handleLeave = () => setHoveredSlider(undefined);

    const minPercentage = absoluteToPercentageValue(min, max, minValue);
    const maxPercentage = absoluteToPercentageValue(min, max, maxValue);
    const zeroPercentage = findZeroPoint(min, max);
    const isMinInputVisible = variant !== 'max';
    const isMaxInputVisible = variant !== 'min';

    const popperConfig = {
        modifiers: [
            {
                name: 'offset',
                options: {
                    offset: [0, 3],
                },
            },
            {
                name: 'preventOverflow',
                options: {
                    padding: 0,
                },
            },
        ],
    };

    return (
        <div
            className={cx(CLASSNAME, className, {
                [`${CLASSNAME}-min-active`]: activeSlider === 'min',
                [`${CLASSNAME}-max-active`]: activeSlider === 'max',
                [`${CLASSNAME}-${hoveredSlider}-hovered`]: Boolean(hoveredSlider),
            })}
            style={style}
            data-testid={testId}
        >
            <div className={`${CLASSNAME}-tooltips`} ref={(el) => setContainer(el)}>
                <div className={`${CLASSNAME}-tooltips-track`}>
                    {isMinInputVisible && container && (
                        <Tooltip
                            id={`${CLASSNAME}-min`}
                            className={`${CLASSNAME}-tooltip`}
                            overlayClassName={hoveredSlider === 'min' ? 'zidx-2' : undefined}
                            message={tooltipFormatter(minValue, minPercentage)}
                            style={{ left: `${minPercentage}%` }}
                            popperUpdateTrigger={minPercentage}
                            popperConfig={popperConfig}
                            container={container}
                            show
                        >
                            <div
                                className={cx(`${CLASSNAME}-thumb`, `${CLASSNAME}-thumb-min`)}
                                data-testid={`${CLASSNAME}-thumb-min`}
                                onPointerEnter={() => handleEnter('min')}
                            />
                        </Tooltip>
                    )}
                    {isMaxInputVisible && container && (
                        <Tooltip
                            id={`${CLASSNAME}-max`}
                            className={cx(`${CLASSNAME}-tooltip`, `${CLASSNAME}-tooltip-max`)}
                            message={tooltipFormatter(maxValue, maxPercentage)}
                            style={{ left: `${maxPercentage}%` }}
                            popperUpdateTrigger={maxPercentage}
                            container={container}
                            popperConfig={popperConfig}
                            show
                        >
                            <div
                                className={cx(`${CLASSNAME}-thumb`, `${CLASSNAME}-thumb-max`)}
                                data-testid={`${CLASSNAME}-thumb-max`}
                                onPointerEnter={() => handleEnter('max')}
                            />
                        </Tooltip>
                    )}
                </div>
            </div>
            <div className={`${CLASSNAME}-inputs`}>
                {isMinInputVisible && (
                    <input
                        className={cx(`${CLASSNAME}-input`, `${CLASSNAME}-input-min`)}
                        type="range"
                        value={minValue}
                        min={min}
                        max={max}
                        step={step}
                        onChange={handleMinChange}
                        onPointerCancel={handleChange}
                        onPointerUp={handleChange}
                        onPointerEnter={() => handleEnter('min')}
                        onPointerLeave={handleLeave}
                    />
                )}
                {isMaxInputVisible && (
                    <input
                        className={cx(`${CLASSNAME}-input`, `${CLASSNAME}-input-max`)}
                        type="range"
                        value={maxValue}
                        min={min}
                        max={max}
                        step={step}
                        onChange={handleMaxChange}
                        onPointerCancel={handleChange}
                        onPointerUp={handleChange}
                        onPointerEnter={() => handleEnter('max')}
                        onPointerLeave={handleLeave}
                    />
                )}
                <div className={`${CLASSNAME}-controls`}>
                    <div className={`${CLASSNAME}-track`} />
                    <div
                        className={`${CLASSNAME}-range`}
                        style={{
                            left: `${minPercentage}%`,
                            right: `${100 - maxPercentage}%`,
                        }}
                    />
                    <div
                        className={cx(`${CLASSNAME}-tick`, `${CLASSNAME}-tick-min`, {
                            active: !isMinInputVisible,
                        })}
                    />
                    <div
                        className={cx(`${CLASSNAME}-tick`, `${CLASSNAME}-tick-max`, {
                            active: !isMaxInputVisible,
                        })}
                    />
                    {zeroPercentage !== null && (
                        <div
                            className={cx(`${CLASSNAME}-tick`, `${CLASSNAME}-tick-zero`, {
                                active:
                                    minPercentage < zeroPercentage &&
                                    maxPercentage > zeroPercentage,
                            })}
                            style={{ left: `${zeroPercentage}%` }}
                        />
                    )}
                </div>
            </div>
            <div className={`${CLASSNAME}-labels`}>
                <div className={`${CLASSNAME}-labels-min`}>
                    {labelFormatter(min, minPercentage)}
                </div>
                {zeroPercentage !== null && (
                    <div
                        className={`${CLASSNAME}-labels-zero`}
                        style={{ left: `${zeroPercentage}%` }}
                    >
                        {labelFormatter(0, zeroPercentage)}
                    </div>
                )}
                <div className={`${CLASSNAME}-labels-max`}>
                    {labelFormatter(max, maxPercentage)}
                </div>
            </div>
        </div>
    );
};
