import {
    HTMLAttributes,
    FunctionComponent,
    createContext,
    useContext,
    useState,
    useRef,
    useLayoutEffect,
    useCallback,
    ReactNode
} from 'react';
import cls from 'classnames';
import {renderForeground, useOnKey, useSize} from '@core0/utils';
import {RoundButton} from '@core0/round-button';
import {Tooltip} from '@core0/tooltip';
import {Transition} from '@core0/transition';
import {RectButton} from '@core0/rect-button';
import {Icon, IconType} from '@core0/icon';
import {Backdrop} from '@core0/backdrop';
import cn from './sideTray.module.styl';

type MyContext = [SideTrayContextProps, React.Dispatch<React.SetStateAction<SideTrayContextProps>>];

const SideTrayContext = createContext<MyContext | null>(null);

interface SideTrayContextProps {
    transition: boolean;
    hasScrollRemaining: boolean;
    hasScrolled: boolean;
}

export interface SideTrayBackdropProps {
    show: boolean;
    setShow: (v: boolean) => void;
    className?: string;
    transition?: boolean;
    peek?: boolean;
    position?: 'cover' | 'global';
    children?: ReactNode;
}

export interface SideTrayProps {
    show?: boolean;
    setShow?: (v: boolean) => void;
    className?: string;
    onBack?: () => void;
    transition?: boolean;
    peek?: boolean;
    position?: 'cover' | 'global';
    children?: ReactNode;
}

export interface SideTrayContentProps {
    children: React.ReactNode;
    className?: string;
}

export interface SideTrayBodyProps {
    children: React.ReactNode;
    className?: string;
    transition?: boolean;
}

export interface SideTrayTitleProps extends Pick<HTMLAttributes<HTMLElement>, 'onClick' | 'className'> {
    icon?: IconType;
    title: string;
    subtitle?: string;
    className?: string;
}

export interface SideTrayFooterProps {
    children: React.ReactNode;
    className?: string;
    transition?: boolean;
}

export const SideTrayBackdrop: FunctionComponent<SideTrayBackdropProps> = (props) => {
    const {show, setShow, peek, className, position, children, transition = true} = props;

    const onClose = useCallback(() => (setShow ? setShow(false) : undefined), [setShow]);
    const closeIfOpen = useCallback(() => show && onClose(), [show, onClose]);

    useOnKey('Escape', onClose);

    function blockClick() {
        closeIfOpen()
    }

    return renderForeground(
        <div>
            {show && <Backdrop onClick={blockClick}/>}
            <SideTray show={show} setShow={setShow} position={position} peek={peek} className={className} transition={transition}>
                {children}
            </SideTray>
        </div>
    );
};

export const SideTray: FunctionComponent<SideTrayProps> = (props) => {
    const {
        show = true,
        position = 'global',
        setShow,
        onBack,
        className,
        children,
        transition = true,
        peek = false
    } = props;
    const close = useCallback(() => (setShow ? setShow(false) : undefined), [setShow]);
    const open = useCallback(() => (setShow ? setShow(true) : undefined), [setShow]);
    const myContext = useState(() => ({transition, hasScrollRemaining: false, hasScrolled: false}));
    const ctx = myContext[0];

    const classNames = cls(
        {
            [cn.sideTray]: true,
            [cn.cover]: position === 'cover',
            [cn.global]: position === 'global',
            [cn.tray]: true,
            [cn.peek]: peek,
            [cn.show]: show,
            [cn.hasScrollRemaining]: ctx.hasScrollRemaining,
            [cn.hasScrolled]: ctx.hasScrolled
        },
        className
    );

    return (
        <SideTrayContext.Provider value={myContext}>
            <div className={classNames}>
                {onBack && (
                    <RectButton
                        className={cn.backButton}
                        inverse
                        flat={!ctx.hasScrolled}
                        onClick={onBack}
                        icon="previous"
                        label="Back"
                    />
                )}

                {setShow && (
                    <RoundButton className={cn.closeButton} inverse onClick={close}>
                        <Icon type="next" />
                        <Tooltip>Close Side Tray</Tooltip>
                    </RoundButton>
                )}
                <div className={cn.backSplashContainer}>
                    <div className={cn.backSplash} onClick={open}>
                        <Icon type="left" color="primary" className={cn.openArrow} />
                    </div>
                </div>
                <SideTrayContent>{children}</SideTrayContent>
            </div>
        </SideTrayContext.Provider>
    );
};

export const SideTrayTitle: FunctionComponent<SideTrayTitleProps> = (props) => {
    const {icon, title, subtitle, className} = props;

    return (
        <div className={cls(cn.sideTrayHeader, className)}>
            <h1 className={cn.title}>
                {icon && <Icon type={icon} color="primary" className={cn.icon} />}
                <strong>{title}</strong>
            </h1>
            {subtitle && <h2 className={cn.subTitle}>{subtitle}</h2>}
        </div>
    );
};

export function SideTrayContent(props: SideTrayContentProps) {
    const {children, className} = props;

    const ctx = useContext(SideTrayContext);

    if (ctx === null) {
        throw new Error('SideContent must be within SideTray');
    }
    const [context, setContext] = ctx;

    const target = useRef<HTMLDivElement>(null);
    const size = useSize(target);

    function onScroll() {
        processScroll(target.current);
    }

    function processScroll(element: HTMLDivElement | null) {
        if (element === null) return;
        const hasScrollRemaining = element.scrollHeight > element.clientHeight + element.scrollTop + 10;
        const hasScrolled = element.scrollTop > 10;
        setContext({...context, hasScrollRemaining, hasScrolled});
    }

    useLayoutEffect(() => {
        processScroll(target.current);
    }, [size, children]);

    return <div className={cls(cn.content, className)} ref={target} onScroll={onScroll}>{children}</div>;
}

export function SideTrayBodyContent(props: SideTrayContentProps) {
    const {children, className} = props;
    return <div className={cls(cn.bodyContent, className)}>{children}</div>;
}

export function SideTrayBody(props: SideTrayBodyProps) {
    const {children, className, transition} = props;
    const ctx = useContext(SideTrayContext);

    if (ctx === null) {
        throw new Error('SideTrayBody must be within SideTray');
    }
    const [context, setContext] = ctx;

    const enableTransition = transition ?? context.transition ?? true;

    return (
        <div className={cls(cn.body)}>
            <Transition disabled={!enableTransition}>
                <div className={className}>{children}</div>
            </Transition>
        </div>
    );
}

export function SideTrayFooter(props: SideTrayFooterProps) {
    const {children, className, transition} = props;
    const ctx = useContext(SideTrayContext);

    if (ctx === null) {
        throw new Error('SideTrayBody must be within SideTray');
    }
    const [context] = ctx;

    const enableTransition = transition ?? context.transition ?? true;

    return (
        <footer className={cls(cn.footer)}>
            <Transition disabled={!enableTransition}>
                <div className={cls(cn.footerContent, className)}>{children}</div>
            </Transition>
        </footer>
    );
}
