import React, {
  Children, forwardRef, useEffect, useRef,
} from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { useTabContext } from './TabsProvider';
import { modifierCSS } from '../../../helpers';

const propTypes = {
  /**
   * Children to set inside the TabList
   *
   * @see Tab
   */
  children: PropTypes.node,
  /**
  * The default class for the TabList.
  */
  defaultClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
  /**
   * Callback function trigerred on tab change. The object given is of form
   * { index, name }.
   */
  onChangeTab: PropTypes.func,
  /**
   * Callback function trigerred on tab click. The event is given as
   * parameter.
   */
  onClick: PropTypes.func,
  /**
   * Callback function trigerred on panel focus. This is called on ArrowKeyDown
   * press.
   */
  onFocusPanel: PropTypes.func,
  /**
   * Callback function trigerred on key down. The event is given as a parameter.
   */
  onKeyDown: PropTypes.func,
  /**
   * Size of the tab.
   */
  size: PropTypes.oneOf(['', 'sm', 'lg']),
  /**
   * The custom tag for the root of the TabList
   */
  tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
};

const defaultProps = {
  children: '',
  defaultClassName: 'sui-m-tabs__wrapper',
  onChangeTab: () => {},
  onClick: () => {},
  onFocusPanel: () => {},
  onKeyDown: () => {},
  size: '',
  tag: 'div',
};

export const TabList = forwardRef(({
  children,
  defaultClassName,
  onChangeTab,
  onClick,
  onFocusPanel,
  onKeyDown,
  size,
  tag: Tag,
  ...attributes
}, ref) => {
  const {
    selectedName,
    handleChangeTab,
  } = useTabContext();

  const focusableElements = Children.map(
    children,
    (child, index) => (child.props.isDisabled ? null : { index, name: child.props.name })
  ).filter((element) => element !== null);

  const focusedElementIndexRef = useRef(0);

  useEffect(() => {
    focusedElementIndexRef.current = focusableElements
      .map((element) => element.name)
      .indexOf(selectedName);
  }, [selectedName]);

  useEffect(() => {
    React.Children.forEach(children, (child, index) => {
      if (selectedName === '' && index === 0) {
        if (child.props && child.props.name) {
          handleChangeTab(child.props.name);
        }
      }
    });
  }, []);

  const allNodesRef = useRef([]);

  const classes = classnames(
    defaultClassName,
    modifierCSS(size),
  );

  const count = focusableElements.length;

  const updateIndex = (index) => {
    const child = focusableElements[index];

    allNodesRef.current[child.index].node.focus();
    focusedElementIndexRef.current = index;
    if (onChangeTab) {
      onChangeTab(child);
    }
  };

  const handleKeyDown = (event) => {
    if (event.key === 'ArrowRight') {
      event.preventDefault();
      const nextIndex = (focusedElementIndexRef.current + 1) % count;

      updateIndex(nextIndex);
    }

    if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {
      event.preventDefault();
      const nextIndex = (focusedElementIndexRef.current - 1 + count) % count;
      updateIndex(nextIndex);
    }

    if (event.key === 'Home') {
      event.preventDefault();
      updateIndex(0);
    }

    if (event.key === 'End') {
      event.preventDefault();
      updateIndex(count - 1);
    }

    if (event.key === 'ArrowDown') {
      event.preventDefault();
      if (onFocusPanel) {
        onFocusPanel();
      }
    }

    if (onKeyDown) {
      onKeyDown(event);
    }
  };

  const clones = React.Children.map(children, (child, index) => {
    const isSelected = child.props.name === selectedName;

    const handleClick = (event) => {
      // Hack for Safari. Buttons don't receive focus on click on Safari
      // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus
      allNodesRef.current[index].node.focus();

      handleChangeTab(child.props.name);

      if (child.props.onClick) {
        child.props.onClick(event);
      }

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

    return React.cloneElement(child, {
      ref: (node) => {
        allNodesRef.current[index] = { node, child };
        return allNodesRef.current[index].node;
      },
      isSelected,
      onClick: handleClick,
    });
  });

  return (
    <Tag
      className={classes}
      ref={ref}
      role="tablist"
      onKeyDown={handleKeyDown}
      {...attributes}
    >
      {clones}
    </Tag>
  );
});

TabList.propTypes = propTypes;
TabList.defaultProps = defaultProps;
