import { Component, createRef } from 'react';
import { CSSTransition } from 'react-transition-group';

import Button from '../button';
import Body from '../body';
import { Theme, type ThemeDark, type ThemeLight } from '../common';
import { DirectionContext } from '../provider/direction';
import withNextPortal from '../withNextPortal/withNextPortal';

const CSS_TRANSITION_DURATION = 400;

export interface SnackbarProps {
  action?: {
    label: string;
    onClick?: React.MouseEventHandler<HTMLButtonElement>;
  };
  text: React.ReactNode;
  /** @deprecated */
  theme?: ThemeLight | ThemeDark;
  timeout: number;
  timestamp: number;
}

interface SnackbarState extends Pick<SnackbarProps, 'action' | 'text' | 'theme'> {
  visible: boolean;
}

export class Snackbar extends Component<SnackbarProps, SnackbarState> {
  bodyRef = createRef<HTMLSpanElement>();
  timeout = 0;
  transitionTimeout = 0;

  constructor(props: SnackbarProps) {
    super(props);
    this.state = {
      visible: false,
      text: '',
    };
  }

  componentWillUnmount() {
    window.clearTimeout(this.timeout);
    window.clearTimeout(this.transitionTimeout);
  }

  shouldComponentUpdate(nextProps: SnackbarProps, nextState: SnackbarState) {
    if (!nextProps.text) {
      return false;
    }
    if (
      nextProps.timestamp === this.props.timestamp &&
      nextState.visible === this.state.visible &&
      nextState.text === this.state.text
    ) {
      return false;
    }

    return true;
  }

  setLeaveTimeout = () => {
    const { timeout } = this.props;

    this.timeout = window.setTimeout(() => {
      this.setState({ visible: false });
    }, timeout);
  };

  componentDidUpdate(previousProps: SnackbarProps) {
    const { action, text, timestamp } = this.props;

    if (!previousProps.text) {
      this.setState({ visible: true, action, text }, () => {
        this.setLeaveTimeout();
      });
    } else if (previousProps.timestamp !== timestamp) {
      window.clearTimeout(this.timeout);

      if (this.state.visible) {
        this.setState({ visible: false }, () => {
          this.transitionTimeout = window.setTimeout(() => {
            this.setState({ visible: true, action, text });
            this.setLeaveTimeout();
          }, CSS_TRANSITION_DURATION);
        });
      } else {
        this.setState({ visible: true, action, text });
        this.setLeaveTimeout();
      }
    }
  }

  render() {
    const { action, text, visible } = this.state;
    const { timeout } = this.props;

    return (
      <div className="snackbar" aria-live="assertive" role="alert">
        <CSSTransition
          nodeRef={this.bodyRef}
          in={visible}
          classNames="snackbar__text-container"
          timeout={{
            appear: 0,
            enter: timeout,
            exit: CSS_TRANSITION_DURATION,
          }}
          unmountOnExit
        >
          <Body ref={this.bodyRef} as="span" className="snackbar__text">
            {text}
            {action ? (
              <Button className="snackbar__text__action" v2 size="sm" onClick={action.onClick}>
                {action.label}
              </Button>
            ) : null}
          </Body>
        </CSSTransition>
      </div>
    );
  }
}

Snackbar.contextType = DirectionContext;

export default withNextPortal(Snackbar);
