'use strict';

import React, {cloneElement} from 'react';
import {findDOMNode} from 'react-dom';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import Modal from 'react-bootstrap/lib/Modal';
import {getScrollbarWidth, closePopup, restorePopupVisibility, popupContainerId} from '../utils';
import {KeyCodes} from '../../../utils/constants';
import tabbable from 'tabbable';
import {Omit} from '../../typings/common';
import lang from '../../../lang/common/index-en_US';

type PopupProps = {
    children: React.ReactNode;
    stackable?: boolean;
    borderedTitle?: boolean;
    noPadding?: boolean;
    noBottomPadding?: boolean;
    noHeaderPadding?: boolean;
    closeButton?: boolean;
    closeLabel?: string;
    title?: string;
    testAutomationId?: string;
    header?: React.ReactNode;
    footer?: React.ReactNode;
    onClose?: Function;
    shouldDeriveDescFromBody?: any;
} & Omit<Modal.ModalProps, 'onHide'> &
    Partial<PopupDefaultProps>;

type PopupDefaultProps = {
    // children: null, // exclude from default to make children required
    stackable: boolean;
    borderedTitle: boolean;
    noPadding: boolean;
    noBottomPadding: boolean;
    noHeaderPadding: boolean;
    closeButton: boolean;
    closeLabel: string;
    title: string;
    testAutomationId: string;
    onHide: Function;
    // header: null;
    // footer: null;
    // onClose: null;
};

export class Popup extends React.Component<PopupProps> {
    static stack: Popup[] = [];
    static styleSheet: any;

    static propTypes = {
        children: PropTypes.node,
        className: PropTypes.string,
        stackable: PropTypes.bool,
        borderedTitle: PropTypes.bool,
        noPadding: PropTypes.bool,
        noBottomPadding: PropTypes.bool,
        noHeaderPadding: PropTypes.bool,
        closeButton: PropTypes.bool,
        closeLabel: PropTypes.string,
        title: PropTypes.string,
        testAutomationId: PropTypes.string,
        header: PropTypes.node,
        footer: PropTypes.node,
        onClose: PropTypes.func,
        'aria-labelledby': PropTypes.string,
        'aria-describedby': PropTypes.string,
        shouldDeriveDescFromBody: PropTypes.bool,
        keyboard: PropTypes.bool
    } as any;

    static defaultProps: PopupDefaultProps = {
        children: null,
        className: null,
        testAutomationId: 'popup',
        borderedTitle: false,
        noPadding: false,
        noBottomPadding: false,
        noHeaderPadding: false,
        title: '',
        closeButton: true,
        get closeLabel() {
            return lang.CLOSE;
        },
        header: null,
        footer: null,
        stackable: false,
        backdrop: 'static',
        onClose: null,
        'aria-labelledby': null,
        'aria-describedby': null,
        shouldDeriveDescFromBody: false,
        keyboard: false
    } as any;

    static close() {
        if (Popup.stack.length) {
            let popup = Popup.stack[Popup.stack.length - 1];
            if (popup.props.onHide) {
                popup.props.onHide();
                return;
            }
        }
        closePopup();
    }

    static addStyleSheet() {
        let width = getScrollbarWidth();
        Popup.styleSheet = document.createElement('style');
        Popup.styleSheet.innerHTML = `
            body.rc-modal-open {
                padding-right: ${width}px !important;
            }
            [aria-hidden="true"] [role="dialog"] {
                padding-right: ${width}px !important;
            }
        `;
        document.head.appendChild(Popup.styleSheet);
    }

    static updateBodyClass() {
        let length = Popup.stack.length;
        let regexp = /\s*rc-modal-open/g;
        if (length) {
            if (!regexp.test(document.body.className)) {
                document.body.className += document.body.className.length
                    ? ' rc-modal-open'
                    : 'rc-modal-open';
            }
        } else {
            document.body.className = document.body.className.replace(regexp, '');
        }
    }

    constructor(props, context) {
        super(props, context);

        if (!Popup.styleSheet) {
            Popup.addStyleSheet();
        }

        Popup.stack.push(this);
        Popup.updateBodyClass();

        this.modalContentRef = React.createRef();

        this.hideModal = this.hideModal.bind(this);
    }

    modalContentRef: any;

    componentWillUnmount() {
        const index = Popup.stack.indexOf(this);

        if (index !== -1) {
            Popup.stack.splice(index, 1);
            Popup.updateBodyClass();

            setImmediate(restorePopupVisibility);
        }
    }

    handleHeaderRef = ref => {
        if (!ref) {
            return;
        }

        const $modalHeader = findDOMNode(ref) as any;

        if (!$modalHeader) {
            return;
        }

        const $closeButton = $modalHeader.querySelector("button[class = 'close']");

        if ($closeButton) {
            $closeButton.setAttribute('data-test-automation-id', 'closeButton');
        }
    };

    trapFocus = event => {
        if (event.keyCode === KeyCodes.TAB) {
            const activeElement = document.activeElement as any;
            const modalContentNode = this.modalContentRef.current;
            const tabbableNodes = tabbable(modalContentNode);

            event.stopPropagation();

            if (tabbableNodes.length === 0) {
                return event.preventDefault();
            }

            if (event.shiftKey) {
                const isDispatchedOnModalWrapper = activeElement.contains(modalContentNode);
                if (isDispatchedOnModalWrapper || tabbableNodes[0] === activeElement) {
                    event.preventDefault();
                    tabbableNodes[tabbableNodes.length - 1].focus();
                }
            } else {
                if (tabbableNodes[tabbableNodes.length - 1] === activeElement) {
                    event.preventDefault();
                    tabbableNodes[0].focus();
                }
            }
        }
    };

    hideModal = () => {
        if (this.props.onClose) {
            this.props.onClose();
        } else {
            Popup.close();
        }
    };

    render() {
        // Extract own props to make it safe to pass Modal props down
        let {
            testAutomationId,
            stackable,
            noPadding,
            noBottomPadding,
            noHeaderPadding,
            borderedTitle,
            closeButton,
            keyboard,
            closeLabel,
            className,
            header,
            footer,
            title,
            onClose,
            children,
            'aria-labelledby': ariaLabelledBy,
            'aria-describedby': ariaDescribedBy,
            shouldDeriveDescFromBody,
            ...props
        } = this.props;

        const titleId = `dlg-title-${testAutomationId}`;
        const contentId = `dlg-content-${testAutomationId}`;

        if (!ariaLabelledBy) {
            ariaLabelledBy = titleId;
        }

        if (!ariaDescribedBy && shouldDeriveDescFromBody) {
            ariaDescribedBy = contentId;
        }

        if (this.props.footer) {
            footer = <Modal.Footer data-test-automation-id="popupFooter">{footer}</Modal.Footer>;
        }

        if (title) {
            title = (
                <Modal.Title
                    role="presentation"
                    data-test-automation-id="title"
                    className={classnames({'bordered-modal-title': borderedTitle})}>
                    <span id={titleId}>{title}</span>
                </Modal.Title>
            ) as any;
        }

        if (!title && header) {
            header = cloneElement(header as any, {id: titleId});
        }

        const supportEscClose = keyboard || closeButton;

        return (
            <Modal
                keyboard={supportEscClose}
                onKeyDown={this.trapFocus}
                show={true}
                data-test-automation-id={testAutomationId}
                container={document.getElementById(popupContainerId)}
                {...props}
                aria-labelledby={ariaLabelledBy}
                aria-describedby={ariaDescribedBy}
                role="document"
                enforceFocus={false}
                onHide={this.hideModal}
                className={classnames(
                    'static-modal',
                    {
                        'rc-popup-noPadding': noPadding,
                        'rc-popup-noBottomPadding': noBottomPadding,
                        'rc-popup-noHeaderPadding': noHeaderPadding,

                        // we have a technical difficulties for close button manipulations during closeButton prop changes
                        // Modal calls componentDidUpdate in incorrect order, so we have to render it always and manage
                        // visibility via CSS
                        'rc-popup-noCloseButton': !closeButton
                    },
                    className
                )}>
                <div ref={this.modalContentRef}>
                    <Modal.Header
                        ref={this.handleHeaderRef}
                        data-test-automation-id="popupHeader"
                        closeButton
                        closeLabel={closeLabel}>
                        {title}
                        {header}
                    </Modal.Header>
                    <Modal.Body id={contentId} data-test-automation-id="popupContent">
                        {children}
                    </Modal.Body>
                    {footer}
                </div>
            </Modal>
        );
    }
}
