import React, { forwardRef, useMemo } from 'react';

import { theme, styled } from '../../stitches.config';
import { Icon } from '../Icon';

import { useValue } from '../../hooks';

const StyledPagination = styled('div', {
    display: 'flex',
    alignItems: 'center',
    gap: theme.space['1'],
    padding: `${theme.space['2']} 0`,
});

const StyledPageButton = styled('button', {
    display: 'inline-flex',
    alignItems: 'center',
    justifyContent: 'center',
    height: theme.space['8'],
    minWidth: theme.space['8'],
    padding: `${theme.space['0']} ${theme.space['1']}`,
    color: theme.colors['grey-1'],
    background: 'transparent',
    border: 'none',
    borderRadius: theme.radii.default,
    fontSize: theme.fontSizes.sm,
    cursor: 'pointer',
    outline: 'none',
    '&:hover': {
        background: theme.colors['primary-5'],
    },
    '&:disabled': {
        backgroundColor: theme.colors['grey-4'],
        borderColor: theme.colors['grey-4'],
        cursor: 'default',
        pointerEvents: 'none',
    },
    '&:focus': {
        outline: `2px solid ${theme.colors['primary-1']}`,
        outlineOffset: '2px',
    },
    variants: {
        active: {
            true: {
                fontWeight: theme.fontWeights['semibold'],
                background: theme.colors['primary-5'],
            },
        },
        action: {
            true: {
                fontWeight: theme.fontWeights['semibold'],
                padding: `${theme.space['0']} ${theme.space['1']}`,
            },
        },
    },
});

const StyledSeparator = styled('div', {
    display: 'inline-flex',
    alignItems: 'end',
    justifyContent: 'center',
    height: theme.space['8'],
    width: theme.space['8'],
    padding: `${theme.space['0']} ${theme.space['1']}`,
    color: theme.colors['grey-1'],
    background: 'transparent',
    border: 'none',
    borderRadius: theme.radii.default,
    outline: 'none',
});

// TODO: Need to figure out why @mdi/js isn't tree shaking
const mdiChevronLeft =
    'M15.41,16.58L10.83,12L15.41,7.41L14,6L8,12L14,18L15.41,16.58Z';
const mdiDotsHorizontal =
    'M16,12A2,2 0 0,1 18,10A2,2 0 0,1 20,12A2,2 0 0,1 18,14A2,2 0 0,1 16,12M10,12A2,2 0 0,1 12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12M4,12A2,2 0 0,1 6,10A2,2 0 0,1 8,12A2,2 0 0,1 6,14A2,2 0 0,1 4,12Z';
const mdiChevronRight =
    'M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z';

export interface PaginationProps
    extends Omit<
        React.ComponentPropsWithRef<'div'>,
        'value' | 'defaultValue' | 'onChange'
    > {
    /** Value of the selected page. */
    value: number;

    /** Default selected page. */
    defaultValue?: number;

    /** Callback that is triggered when the selected page changes */
    onChange?: (value: number) => void;

    /** Mark the pagination in a disabled/enabled state */
    disabled?: boolean;

    /** Total number of pages */
    total: number;

    /** Number of siblings when in the middle of the range */
    sibling?: number;
}

const ELLIPSES = 'ELLIPSES' as const;
const START = 1 as const;

/**
 * Generate the range based on a start and end number
 * @param start - start of the range to generate
 * @param end - end of the range to generate
 */
const generateRange = (start: number, end: number) => {
    const range: number[] = [];
    for (let i = start; i <= end; i++) {
        range.push(i);
    }
    return range;
};

export const Pagination = forwardRef<HTMLDivElement, PaginationProps>(
    (props, ref): JSX.Element => {
        const {
            value,
            defaultValue,
            onChange = () => null,
            disabled = false,
            total,
            sibling = 1,
            ...otherProps
        } = props;

        // hold the value
        const [internalValue, setInternalValue] = useValue<number>({
            initialValue: START,
            value: value,
            defaultValue: defaultValue,
            onChange: onChange,
        });

        // calculate the range of the pagination items
        const range: (number | typeof ELLIPSES)[] = useMemo(() => {
            // 2 * sibling + value, first, last, both ellipsis,
            const totalPageNumbers = 2 * sibling + 5;

            // check if it can fit in the range
            const canFit = total - START + 1 > totalPageNumbers;

            if (!canFit) {
                return generateRange(START, total);
            }

            //Calculate left and right sibling index and make sure they are within the start and end
            const leftSiblingIndex = Math.max(internalValue - sibling, START),
                rightSiblingIndex = Math.min(internalValue + sibling, total);

            // if it fits, show the dots if there is more than 2 (include start and end)
            const shouldShowLeftDots = leftSiblingIndex - START > 2;
            const shouldShowRightDots = total - rightSiblingIndex > 2;

            if (shouldShowLeftDots && shouldShowRightDots) {
                return [
                    START,
                    ELLIPSES,
                    ...generateRange(leftSiblingIndex, rightSiblingIndex),
                    ELLIPSES,
                    total,
                ];
            } else if (shouldShowLeftDots) {
                return [
                    START,
                    ELLIPSES,
                    ...generateRange(total - (totalPageNumbers - 2 - 1), total),
                ];
            } else if (shouldShowRightDots) {
                return [
                    ...generateRange(START, START + totalPageNumbers - 2 - 1),
                    ELLIPSES,
                    total,
                ];
            } else {
                return [];
            }
        }, [total, sibling, internalValue]);

        return (
            <StyledPagination ref={ref} {...otherProps}>
                {/*Left Arrow*/}
                <StyledPageButton
                    action={true}
                    disabled={disabled || undefined}
                    onClick={() => {
                        const updated = Math.max(internalValue - 1, START);
                        setInternalValue(updated);
                    }}
                    type={'button'}
                >
                    <Icon path={mdiChevronLeft} />
                </StyledPageButton>

                {range.map((page, idx) => {
                    if (page === ELLIPSES) {
                        return (
                            <StyledSeparator key={idx}>
                                <Icon path={mdiDotsHorizontal} />
                            </StyledSeparator>
                        );
                    }

                    return (
                        <StyledPageButton
                            disabled={disabled || undefined}
                            onClick={() => setInternalValue(page)}
                            key={idx}
                            active={internalValue == page ? true : false}
                            type={'button'}
                        >
                            {page}
                        </StyledPageButton>
                    );
                })}

                {/*Right Arrow*/}
                <StyledPageButton
                    action={true}
                    disabled={disabled || undefined}
                    onClick={() => {
                        const updated = Math.min(internalValue + 1, total);
                        setInternalValue(updated);
                    }}
                    type={'button'}
                >
                    <Icon path={mdiChevronRight} />
                </StyledPageButton>
            </StyledPagination>
        );
    },
);

Pagination.displayName = 'Pagination';
