import {
    forwardRef,
    ForwardedRef,
    ComponentPropsWithRef,
    useState,
} from 'react';
import { styled, theme } from '../../stitches.config';

import { Root, Track, Range, Thumb } from '@radix-ui/react-slider';
import { Tooltip } from '../Tooltip/';

import { InputOptions, trimNumber } from '../../utility';
import { useValue } from '../../hooks';

const StyledSlider = styled(Root, {
    display: 'flex',
    alignItems: 'center',
    userSlider: 'none',
    touchAction: 'none',
    width: theme.space['full'],
    position: 'relative',
    height: theme.space[5],
});

const StyledTrack = styled(Track, {
    backgroundColor: theme.colors['grey-4'],
    position: 'relative',
    flexGrow: 1,
    borderRadius: theme.radii['full'],
    height: theme.space[1],
    '&[data-disabled]': {
        backgroundColor: theme.colors['grey-5'],
    },
});

const StyledRange = styled(Range, {
    position: 'absolute',
    borderRadius: theme.radii['full'],
    height: '100%',
    '&[data-disabled]': {
        backgroundColor: theme.colors['grey-4'],
    },
    variants: {
        valid: {
            true: {
                backgroundColor: theme.colors['primary-1'],
            },
            false: {
                backgroundColor: theme.colors['error-3'],
            },
        },
    },
});

const StyledThumb = styled(Thumb, {
    all: 'unset',
    display: 'block',
    width: theme.space[5],
    height: theme.space[5],
    borderRadius: theme.radii['full'],
    cursor: 'pointer',
    '&:focus': {
        outline: `2px solid ${theme.colors['primary-1']}`,
        outlineOffset: '2px',
    },
    '&[data-disabled]': {
        cursor: 'default',
        backgroundColor: theme.colors['grey-4'],
        '&:hover': {
            backgroundColor: theme.colors['grey-4'],
        },
    },
    variants: {
        valid: {
            true: {
                backgroundColor: theme.colors['primary-1'],
                '&:hover': {
                    backgroundColor: theme.colors['primary-2'],
                },
            },
            false: {
                backgroundColor: theme.colors['error-1'],
                '&:hover': {
                    backgroundColor: theme.colors['error-2'],
                },
            },
        },
    },
    compoundVariants: [],
});

const StyledFooter = styled('div', {
    display: 'flex',
    justifyContent: 'space-between',
    overflow: 'hidden',
});

const StyledLabel = styled('span', {
    fontSize: theme.fontSizes.xs,
    color: theme.colors['grey-1'],
    whiteSpace: 'nowrap',
    overflow: 'hidden',
    textOverflow: 'ellipsis',
});

interface BaseSlider<V>
    extends InputOptions<V>,
        Omit<
            ComponentPropsWithRef<'span'>,
            'value' | 'defaultValue' | 'defaultChecked' | 'onChange' | 'dir'
        > {
    /** Convert the slider to allow multiple options */
    multiple?: boolean;

    /** Minimum thumb value */
    min?: number;

    /** Maximum thumb value */
    max?: number;

    /** Size of the thumb's step */
    step?: number;

    /** Show the footer with the labels */
    footer?: boolean;
}

type SingleSlider = BaseSlider<number>;

type MultipleSlider = BaseSlider<[number, number]>;

export type SliderProps<multiple extends boolean> = multiple extends true
    ? MultipleSlider
    : SingleSlider;

const _Slider = <multiple extends boolean>(
    props: SliderProps<multiple>,
    ref: ForwardedRef<HTMLSpanElement>,
): JSX.Element => {
    const {
        id,
        value,
        defaultValue,
        min = 0,
        max = 100,
        step = 1,
        multiple = false,
        onChange = () => null,
        disabled = false,
        valid = true,
        footer = true,
        ...otherProps
    } = props;

    const [thumbOneEntered, setThumbOneEntered] = useState(false);
    const [thumbOneFocused, setThumbOneFocused] = useState(false);
    const [thumbTwoEntered, setThumbTwoEntered] = useState(false);
    const [thumbTwoFocused, setThumbTwoFocused] = useState(false);

    /**
     * time the passed in value
     * @param value - value to validate
     * @returns a valid value
     */
    const trimValue = (
        value: MultipleSlider['value'] | SingleSlider['value'],
    ): number[] | undefined => {
        if (typeof value === 'undefined') {
            return value;
        }

        if (multiple) {
            value = value as NonNullable<MultipleSlider['value']>;

            return [
                trimNumber(value[0], min, max),
                trimNumber(trimNumber(value[1], min, max), min, max),
            ];
        } else {
            value = value as NonNullable<SingleSlider['value']>;

            return [trimNumber(value, min, max)];
        }
    };

    // manage the internal value
    const [internalValue, setInternalValue] = useValue<number[]>({
        initialValue: multiple
            ? [((max - min) * 1) / 4, ((max - min) * 3) / 4]
            : [((max - min) * 1) / 2],
        value: trimValue(value),
        defaultValue: trimValue(defaultValue),
        onChange: (value) => {
            if (multiple) {
                (onChange as NonNullable<MultipleSlider['onChange']>)([
                    value[0],
                    value[1],
                ]);
            } else {
                (onChange as NonNullable<SingleSlider['onChange']>)(value[0]);
            }
        },
    });

    return (
        <>
            <StyledSlider
                ref={ref}
                aria-label={id}
                min={min}
                max={max}
                step={step}
                value={internalValue}
                onValueChange={(value) => {
                    setInternalValue(value);
                }}
                disabled={disabled}
                {...otherProps}
            >
                <StyledTrack data-aria-disabled={disabled}>
                    <StyledRange valid={valid} data-aria-disabled={disabled} />
                </StyledTrack>

                <Tooltip
                    open={thumbOneEntered || thumbOneFocused}
                    contentKey={internalValue[0]}
                    content={internalValue[0]}
                    side={'top'}
                    offset={8}
                >
                    <StyledThumb
                        valid={valid}
                        onMouseEnter={() => {
                            setThumbOneEntered(true);
                        }}
                        onMouseLeave={() => {
                            setThumbOneEntered(false);
                        }}
                        onFocus={() => {
                            setThumbOneFocused(true);
                        }}
                        onBlur={() => {
                            setThumbOneFocused(false);
                        }}
                    ></StyledThumb>
                </Tooltip>

                {multiple && (
                    <Tooltip
                        open={thumbTwoEntered || thumbTwoFocused}
                        contentKey={internalValue[1]}
                        content={internalValue[1]}
                        side={'top'}
                        offset={8}
                    >
                        <StyledThumb
                            valid={valid}
                            onMouseEnter={() => setThumbTwoEntered(true)}
                            onMouseLeave={() => setThumbTwoEntered(false)}
                            onFocus={() => {
                                setThumbTwoFocused(true);
                            }}
                            onBlur={() => {
                                setThumbTwoFocused(false);
                            }}
                        ></StyledThumb>
                    </Tooltip>
                )}
            </StyledSlider>

            {footer && (
                <StyledFooter>
                    <StyledLabel title={`Minimum Value:${min}`}>
                        {min}
                    </StyledLabel>
                    <StyledLabel title={`Maximum Value:${max}`}>
                        {max}
                    </StyledLabel>
                </StyledFooter>
            )}
        </>
    );
};

export const Slider = forwardRef(_Slider) as <multiple extends boolean>(
    props: SliderProps<multiple> & {
        ref?: ForwardedRef<HTMLSpanElement>;
    },
) => ReturnType<typeof _Slider>;
