/**
 * This component is copied from Twilight at commit 6728b90f106e68140c414282a527163c0bd1ecf0
 *
 * The changes were the mininal set to allow integration:
 * - subbing in our unique id generator
 * - adding the tslint typedef line, as twilight relies on type inference
 * - calling e.currentTarget.blur() in the handle button event
 * - removed check for whether the child is actually a balloon. (see 584a8aa03c62df7d965782f6c6ed58c06e650629)
 * - added capture of onClick handler for button component in render()
 * - added call of original onClick handler if present in handleButtonClick()
 */

// tslint:disable:typedef

import * as React from 'react';
import { uniqueIDGenerator } from 'mweb/common/utils/uniqueIDs';
import { Display, Layout, Position } from 'twitch-core-ui';
import { BalloonProps } from 'twitch-core-ui/src/components/balloon';

export const MOUSE_ENTER_DETECTOR_SELECTOR =
  'toggle-balloon-wrapper__mouse-enter-detector';

export interface PublicProps {
  /** specifies that the balloon's content should always exist in the DOM, regardless of whether or not it is visible.
   * needed for current channel-header resizing implementation.
   */
  alwaysMountBalloonContent?: boolean;
  display?: Display;
  /** specifies whether balloon starts in shown state when initially rendered. */
  openByDefault?: boolean;
  onToggle?: (isClosed: boolean) => void;
}

interface State {
  /** Prop passed to underlying Balloon to indicidate whether it should hide or show via CSS. */
  showBalloon: boolean;
  /** 'Sticky' state begins false; can only be set to true by mouseenter, click, or programmatic toggle via public toggleBallon method */
  hasInteracted: boolean;
}

type Props = PublicProps;

/**
 * Container for a click-to-toggle balloon that will close when
 * the user clicks outside. It should contain exactly two child elements:
 * - A button or link first, its onClick prop will be overwritten
 * - A core-ui Balloon element second, its show and toggle props will be over-written.
 * Doesn't mount the child Balloon unless alwaysMountBalloonContent prop is true, or user has
 * moused-over or clicked the corresponding Button.
 */
export class ToggleBalloonWrapper extends React.Component<Props, State> {
  public state = {
    showBalloon: !!this.props.openByDefault,
    hasInteracted: !!this.props.openByDefault,
  };

  private toggleBalloonId = uniqueIDGenerator();

  private originalButtonOnClick: Function;

  public componentDidMount() {
    if (this.props.openByDefault) {
      document.addEventListener('click', this.handleGlobalClick);
    }
  }

  public componentWillUpdate(_nextProps: {}, nextState: State) {
    if (this.state.showBalloon !== nextState.showBalloon) {
      if (nextState.showBalloon) {
        document.addEventListener('click', this.handleGlobalClick);
      } else {
        document.removeEventListener('click', this.handleGlobalClick);
      }
    }
  }

  public componentWillUnmount() {
    document.removeEventListener('click', this.handleGlobalClick);
  }

  public render() {
    // tslint:disable-next-line:no-any
    const children = React.Children.toArray(
      this.props.children,
    ) as React.ReactElement<any>[];
    if (children.length !== 2) {
      throw new Error(
        'ToggleBalloonWrapper should only be given two children: a clickable and a Balloon',
      );
    }

    const [originalButton, originalBalloon] = children;

    const button = React.cloneElement(originalButton, {
      onClick: this.handleButtonClick,
    });

    this.originalButtonOnClick = originalButton.props.onClick;

    let balloon: React.ReactElement<BalloonProps> | null = null;

    if (this.state.hasInteracted || this.props.alwaysMountBalloonContent) {
      balloon = React.cloneElement(originalBalloon, {
        show: this.state.showBalloon,
      });
    }

    return (
      <Layout
        data-toggle-balloon-id={this.toggleBalloonId}
        display={this.props.display}
        position={Position.Relative}
      >
        <div
          style={{ display: 'inherit' }}
          onMouseEnter={this.onMouseEnter}
          data-test-selector={MOUSE_ENTER_DETECTOR_SELECTOR}
        >
          {button}
        </div>
        {balloon}
      </Layout>
    );
  }

  public toggleBalloon(newValue?: boolean) {
    if (newValue === undefined) {
      newValue = !this.state.showBalloon;
    }

    this.setState({
      showBalloon: newValue,
      hasInteracted: true,
    });
  }

  private handleButtonClick = (e: React.MouseEvent<HTMLButtonElement>) => {
    if (e && e.currentTarget) {
      e.currentTarget.blur();
    }

    if (this.originalButtonOnClick) {
      this.originalButtonOnClick(e);
    }

    this.setState(
      prevState => ({
        showBalloon: !prevState.showBalloon,
        hasInteracted: true,
      }),
      () => {
        if (this.props.onToggle) {
          this.props.onToggle(!this.state.showBalloon);
        }
      },
    );
  };

  private handleGlobalClick = (e: MouseEvent) => {
    const target = e.target as Element;
    if (
      !target.matches(`[data-toggle-balloon-id="${this.toggleBalloonId}"] *`)
    ) {
      this.setState(
        {
          showBalloon: false,
        },
        () => {
          if (this.props.onToggle) {
            this.props.onToggle(!this.state.showBalloon);
          }
        },
      );
    }
  };

  private onMouseEnter = () => {
    if (!this.state.hasInteracted) {
      this.setState({ hasInteracted: true });
    }
  };
}
