/**
 * Modal window
 *
 * @author: Ilya Braujer
 * @date: 2019-12-26
 */
import * as React from 'react';

import {Overlay} from './core/overlay/Overlay';
import {joinClassNames} from '../../utils/joinClassNames';
import {filterProps} from '../../utils/filterProps';
import {getSizeThemeKey} from '../../utils/getSizeThemeKey';

import {Icon, SIZE} from '../../index';

import {DefaultProps, IProps, ModalAnimation, PropsWithDefault, SizeClassNames} from './Modal.types';

import * as theme from './modal.m.scss';

const DEFAULT_BODY_PADDING = 24; // Default padding for the modal body is 24px (see model.m.scss)

interface IState {
    isOpenModal: boolean;
}

export {
    IProps as IModalProps,
    ModalAnimation
};

export class Modal extends React.PureComponent<IProps, IState> {

    constructor (props: IProps) {
        super(props);
        this.updateIsOpenModalAfterAnimation = this.updateIsOpenModalAfterAnimation.bind(this);
    }

    override state: IState = {
        isOpenModal: false
    };

    static defaultProps: DefaultProps = {
        shouldCloseOnClickOutside: true,
        shouldCloseOnEsc: true,
        hasCloseIcon: true,
        'data-qaid': 'modal',
        animation: ModalAnimation.Fade

    };

    static excludingProps: Array<keyof IProps> = [
        'isOpen',
        'size',
        'header',
        'footer',
        'noBodyPadding',
        'shouldCloseOnClickOutside',
        'shouldCloseOnEsc',
        'hasCloseIcon',
        'onClose',
        'styles',
        'animation'
    ];

    headerRef = React.createRef<HTMLDivElement>();
    bodyRef = React.createRef<HTMLDivElement>();

    override componentDidMount (): void {
        this.updateIsOpenModal();
        this.setHeaderWidth();
    }

    override componentDidUpdate (prevProps: Readonly<IProps>, prevState: Readonly<{}>, snapshot?: any): void {
        this.updateIsOpenModal();
        this.setHeaderWidth();
    }

    setHeaderWidth = () => {
        if (this.bodyRef.current && this.headerRef.current) {
            const bodyChildren = this.bodyRef.current.children.item(0);
            const headerElement = this.headerRef.current;

            if (bodyChildren) {
                const bodyWidth = bodyChildren.clientWidth;
                const bodyPadding = this.props.noBodyPadding === true ? 0 : DEFAULT_BODY_PADDING;

                if (bodyWidth !== headerElement.offsetWidth) {
                    headerElement.style.width = `${bodyWidth + (bodyPadding * 2)}px`;
                }
            }
        }
    };

    updateIsOpenModal () {
        if (this.props.animation !== ModalAnimation.None) {
            if (this.props.isOpen !== this.state.isOpenModal && !this.state.isOpenModal) {
                this.setState({
                    isOpenModal: true
                });
            }
        } else {
            this.setState({
                isOpenModal: this.props.isOpen
            });
        }
    }

    updateIsOpenModalAfterAnimation () {
        if (this.props.animation !== ModalAnimation.None &&
            this.props.isOpen !== this.state.isOpenModal &&
            this.state.isOpenModal
        ) {
            this.setState({
                isOpenModal: false
            });
        }
    }

    override render () {
        const {
            isOpen, header, size, styles, shouldCloseOnClickOutside, shouldCloseOnEsc, hasCloseIcon, onClose, animation
        } = this.props as PropsWithDefault;

        const style = styles && styles.modal;
        const headerElement = this.renderHeader();
        const footerElement = this.renderFooter();

        let sizeClassName;

        if (size) {
            const sizeThemeKey = getSizeThemeKey<SizeClassNames>('modal', size);

            sizeClassName = theme[sizeThemeKey];
        }

        const className = joinClassNames(
            theme.modal,
            sizeClassName,
            [theme.modalHasHeaderContent, Boolean(header)],
            [theme.modalHasCloseIcon, Boolean(hasCloseIcon)],
            [theme.modalHasHeader, Boolean(headerElement)],
            [theme.modalHasFooter, Boolean(footerElement)],
        );

        const htmlProps = {
            ...filterProps<IProps, React.HTMLAttributes<HTMLDivElement>>(this.props, Modal.excludingProps),
            style,
            className
        };

        const containerClass = joinClassNames(
            theme.container,
            [theme.animationFlipTopOpen, (animation === ModalAnimation.FlipTop && isOpen)],
            [theme.animationFlipTopClose, (animation === ModalAnimation.FlipTop && !isOpen)],
            [theme.animationSlitOpen, (animation === ModalAnimation.Slit && isOpen)],
            [theme.animationSlitClose, (animation === ModalAnimation.Slit && !isOpen)],
            [theme.animationOpen, animation === ModalAnimation.Fade && isOpen],
            [theme.animationClose, animation === ModalAnimation.Fade && !isOpen]
        );

        return (
            <Overlay
                isOpen={this.state.isOpenModal}
                hasBackdrop={true}
                shouldCloseOnClickOutside={shouldCloseOnClickOutside}
                shouldCloseOnEsc={shouldCloseOnEsc}
                onClose={onClose}
            >
                <div {...htmlProps} data-qaid={this.props['data-qaid']}>
                    <div className={containerClass} onAnimationEnd={this.updateIsOpenModalAfterAnimation}>
                        {headerElement}
                        {this.renderBody()}
                        {footerElement}
                    </div>
                </div>
            </Overlay>
        );
    }

    private renderCloseIcon () {
        if (!this.props.hasCloseIcon) {
            return null;
        }

        const {onClose, styles} = this.props;
        const iconElement: React.ReactNode = <Icon name={'cross'} className={theme.closeIcon} size={SIZE.EXTRA_LARGE}/>;
        const style = styles && styles.closeIconContainer;

        return (
            <div
                data-qaid={'tools-close'}
                style={style}
                className={theme.closeIconContainer}
                onClick={onClose}
            >
                {iconElement}
            </div>
        );
    }

    private renderHeader () {
        const {header, styles} = this.props;

        let headerContentElement = null;

        if (header) {
            const className = joinClassNames(theme.headerContent, [
                theme.headerContentHasText,
                typeof header === 'string'
            ]);

            const style = styles && styles.headerContent;

            headerContentElement = (
                <div style={style} className={className}>
                    {header}
                </div>
            );
        }

        const closeIconElement = this.renderCloseIcon();

        if (headerContentElement || closeIconElement) {
            const style = styles && styles.header;

            return (
                <div style={style} className={theme.header} ref={this.headerRef}>
                    {headerContentElement}
                    {closeIconElement}
                </div>
            );
        }

        return null;
    }

    private renderBody () {
        const {styles, noBodyPadding} = this.props;
        const style = styles && styles.body;

        const className = joinClassNames(
            theme.body, [theme.noPadding, Boolean(noBodyPadding)]
        );

        return (
            <div style={style} className={className} ref={this.bodyRef}>
                {this.props.children}
            </div>
        );
    }

    private renderFooter () {
        const {styles, footer} = this.props;

        if (footer) {
            const style = styles && styles.footer;

            return (
                <div style={style} className={theme.footer}>
                    {footer}
                </div>
            );
        }

        return null;
    }
}
