'use strict';

import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import {isEmpty} from 'lodash';
import Nav from 'react-bootstrap/lib/Nav';
import {KeyCodes} from '../../utils/constants';
import {NavDropdown, TabItem} from '../index';
import {cleanEmptyChildren, isElementHasType} from '../utils/functions';

export const TAB_PANEL_STYLES = {
    TABS: 'tabs',
    PILLS: 'pills',
    MENU: 'menu'
};

export const TAB_PANEL_ITEM_ROLES = {
    menuitem: 'menuitem',
    tab: 'tab'
};

export class TabPanel extends React.Component {
    static propTypes = {
        testAutomationId: PropTypes.string,
        children: PropTypes.node.isRequired,
        centered: PropTypes.bool,
        noBorder: PropTypes.bool,
        topMenu: PropTypes.bool,
        clearRightPadding: PropTypes.bool,
        clearLeftPadding: PropTypes.bool,
        clearLeftWrapperPadding: PropTypes.bool,
        clearRightWrapperPadding: PropTypes.bool,
        fullWidthBorder: PropTypes.bool,
        noPadding: PropTypes.bool,
        preserveBottomBorder: PropTypes.bool,
        className: PropTypes.string,
        activeKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
        bsStyle: PropTypes.oneOf([
            TAB_PANEL_STYLES.TABS,
            TAB_PANEL_STYLES.MENU,
            TAB_PANEL_STYLES.PILLS
        ])
    };

    static defaultProps = {
        testAutomationId: 'tabs',
        centered: false,
        noBorder: false,
        topMenu: false,
        clearRightPadding: false,
        clearLeftPadding: false,
        clearLeftWrapperPadding: false,
        clearRightWrapperPadding: false,
        fullWidthBorder: false,
        preserveBottomBorder: false,
        noPadding: false,
        className: null,
        activeKey: null,
        bsStyle: TAB_PANEL_STYLES.TABS
    };

    static contextTypes = {
        router: PropTypes.object
    };

    static childContextTypes = {
        isStyledAsTab: PropTypes.func
    };

    static getChildIndex(containerNode, childNode) {
        let index = -1;

        let childrenArray = Array.from(containerNode.children);

        while (index === -1 && childNode !== null) {
            index = childrenArray.indexOf(childNode);
            childNode = childNode.parentNode;
        }
        return index;
    }

    static getItemPath(item) {
        if (isEmpty(item) || isEmpty(item.props)) {
            return '';
        }

        let {to} = item.props;

        return (to && (to.pathname || to)) || '';
    }

    static isItemCanBeFocusedWithKeyboard(item) {
        let isAllowedByDefault =
            TabPanel.isItemOfDropdownType(item) || TabPanel.isItemOfTabItemType(item);

        return isAllowedByDefault || item.props.role in TAB_PANEL_ITEM_ROLES;
    }

    static isItemOfDropdownType(item) {
        return item && isElementHasType(item, NavDropdown);
    }

    static isItemOfTabItemType(item) {
        return item && isElementHasType(item, TabItem);
    }

    state = {
        focusedItemIndex: 0,
        isDropdownOpen: false
    };

    getChildContext() {
        return {
            isStyledAsTab: this.isStyledAsTab
        };
    }

    componentDidMount() {
        this.focusActiveItem();
    }

    componentDidUpdate(previousProps, previousState) {
        if (this.isStyledAsTab()) {
            let {focusedItemIndex: previousFocusedItemIndex} = previousState;
            let {focusedItemIndex} = this.state;

            if (previousFocusedItemIndex !== focusedItemIndex) {
                this.focusToItemNodeByIndex(focusedItemIndex);
            }
        }
    }

    focusToItemNodeByIndex(focusedItemIndex) {
        let childNodes = ReactDOM.findDOMNode(this).children;
        let activeNode = childNodes[focusedItemIndex];

        if (activeNode) {
            activeNode.firstChild.focus();
        }
    }

    isStyledAsTab = () => {
        let {bsStyle, activeKey} = this.props;
        return bsStyle === TAB_PANEL_STYLES.TABS || (activeKey || activeKey === 0);
    };

    focusActiveItem() {
        let activeItemIndex = this.getActiveItemIndex();

        this.setFocusedItemByIndex(activeItemIndex);
        this.focusToItemNodeByIndex(activeItemIndex);
    }

    setFocusedItemByIndex = index => {
        let {children: rawChildren} = this.props;
        let children = cleanEmptyChildren(rawChildren);

        if (index !== -1 && !children[index].props.disabled) {
            this.setState({
                focusedItemIndex: index
            });
        }
    };

    getActiveItemIndex() {
        let {activeKey, children: rawChildren} = this.props;
        let children = cleanEmptyChildren(rawChildren);

        let activeItemIndex;
        if (activeKey === null) {
            activeItemIndex = children.findIndex(
                child =>
                    child &&
                    (child.props.active ||
                        (this.context.router &&
                            this.context.router.isActive(TabPanel.getItemPath(child), false)))
            );
        } else {
            activeItemIndex = children.findIndex(
                child => child && child.props.eventKey === activeKey
            );
        }

        return activeItemIndex;
    }

    handleClick = event => {
        let {children: rawChildren} = this.props;
        let children = cleanEmptyChildren(rawChildren);

        let tabPanelNode = ReactDOM.findDOMNode(this);
        let childNode = event.target.parentNode;

        let index = TabPanel.getChildIndex(tabPanelNode, childNode);

        this.setFocusedItemByIndex(index);

        if (!TabPanel.isItemOfDropdownType(children[index])) {
            this.setState({
                isDropdownOpen: false
            });
        }
    };

    handleKeyDown = event => {
        let {children: rawChildren} = this.props;
        let children = cleanEmptyChildren(rawChildren);
        let {focusedItemIndex, isDropdownOpen} = this.state;
        let focusedTab = children[focusedItemIndex];
        switch (event.keyCode) {
            case KeyCodes.ARROW_LEFT:
            case KeyCodes.ARROW_UP:
                if (!isDropdownOpen) {
                    let previousTab = this.getPreviousTab();
                    this.setFocusedItemByIndex(previousTab);
                }
                break;
            case KeyCodes.ARROW_RIGHT:
                if (!isDropdownOpen) {
                    let nextTab = this.getNextTab();
                    this.setFocusedItemByIndex(nextTab);
                }
                break;
            case KeyCodes.ARROW_DOWN:
                if (!isDropdownOpen) {
                    if (TabPanel.isItemOfDropdownType(focusedTab)) {
                        this.setState({
                            isDropdownOpen: true
                        });
                    } else {
                        let nextTab = this.getNextTab();
                        this.setFocusedItemByIndex(nextTab);
                        event.preventDefault();
                    }
                }
                break;
            case KeyCodes.TAB:
                this.setState({
                    isDropdownOpen: false
                });
                const activeItem = this.getActiveItemIndex();

                if (
                    (activeItem < focusedItemIndex && event.shiftKey) ||
                    (activeItem > focusedItemIndex && !event.shiftKey)
                ) {
                    this.setFocusedItemByIndex(activeItem);
                    event.preventDefault();
                }
                break;
            case KeyCodes.ENTER:
            case KeyCodes.SPACE:
                if (TabPanel.isItemCanBeFocusedWithKeyboard(children[focusedItemIndex])) {
                    event.preventDefault();
                    this.setState({
                        isDropdownOpen: false
                    });

                    this.setActiveItem(focusedTab, event.target);
                }
                break;
            case KeyCodes.ESC:
                this.setState({
                    isDropdownOpen: false
                });
        }
    };

    setActiveItem = (focusedTab, target) => {
        let focusedItem;

        if (TabPanel.isItemOfTabItemType(focusedTab)) {
            focusedItem = focusedTab;
        } else {
            let navDropdownNode = target.parentNode.parentNode;
            let index = TabPanel.getChildIndex(navDropdownNode, target);

            let children = cleanEmptyChildren(focusedTab.props.children);
            focusedItem = children[index];
        }

        if (isEmpty(focusedItem)) {
            return;
        }

        let activeTabPath = TabPanel.getItemPath(focusedItem);
        this.activatePath(activeTabPath);

        let {eventKey, onClick, onSelect} = focusedItem.props;

        if (onSelect) {
            onSelect(eventKey);
        }

        if (onClick) {
            onClick();
        }
    };

    activatePath = activeTabPath => {
        if (activeTabPath) {
            this.context.router.push(activeTabPath);
        }
    };

    getPreviousTab() {
        let {children: rawChildren} = this.props;
        let children = cleanEmptyChildren(rawChildren);
        let {focusedItemIndex} = this.state;

        let previousItemIndex = focusedItemIndex - 1;
        let isPreviousChildPresent = previousItemIndex >= 0;
        let lastChildIndex = children.length - 1;

        if (
            isPreviousChildPresent &&
            TabPanel.isItemCanBeFocusedWithKeyboard(children[previousItemIndex])
        ) {
            return previousItemIndex;
        }

        return TabPanel.isItemCanBeFocusedWithKeyboard(children[lastChildIndex])
            ? lastChildIndex
            : lastChildIndex - 1;
    }

    getNextTab() {
        let {children: rawChildren} = this.props;
        let children = cleanEmptyChildren(rawChildren);
        let {focusedItemIndex} = this.state;

        let nextItemIndex = focusedItemIndex + 1;
        let isNextChildPresent = nextItemIndex < children.length;

        if (
            isNextChildPresent &&
            TabPanel.isItemCanBeFocusedWithKeyboard(children[nextItemIndex])
        ) {
            return nextItemIndex;
        }

        return 0;
    }

    render() {
        let {
            testAutomationId,
            centered,
            noBorder,
            topMenu,
            clearRightPadding,
            clearLeftPadding,
            clearLeftWrapperPadding,
            clearRightWrapperPadding,
            fullWidthBorder,
            noPadding,
            className,
            children,
            preserveBottomBorder,
            ...props
        } = this.props;

        className = classnames(
            {
                'rc-nav-centered': centered,
                'rc-nav-noHorizontalPadding': noPadding,
                'rc-nav-noBorder': noBorder,
                'rc-nav-fullWidthBorder': !noBorder && fullWidthBorder,
                'rc-nav-topMenu': topMenu,
                'rc-nav-clearRightPadding': clearRightPadding,
                'rc-nav-clearLeftPadding': clearLeftPadding,
                'rc-nav-clearLeftWrapperPadding': clearLeftWrapperPadding,
                'rc-nav-clearRightWrapperPadding': clearRightWrapperPadding,
                'rc-nav-preserveBottomBorder': preserveBottomBorder
            },
            className
        );

        return (
            <Nav
                bsStyle="tabs"
                className={className}
                data-test-automation-id={testAutomationId}
                role="tablist"
                tabIndex={0}
                onClick={this.handleClick}
                onKeyDown={this.handleKeyDown}
                {...props}>
                {children}
            </Nav>
        );
    }
}
