import * as React from 'react'
import * as q from '../../../../backend/src/Model'

import { Theme, withStyles } from '@material-ui/core/styles'

import LabelImportant from '@material-ui/icons/LabelImportant'
import TreeNodeSubnodes from './TreeNodeSubnodes'
import TreeNodeTitle from './TreeNodeTitle'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { isElementInViewport } from '../helper/isElementInViewport'
import { treeActions } from '../../actions'
import { AppState } from '../../reducers'

declare var performance: any

const styles = (theme: Theme) => {
  return {
    collapsedSubnodes: {
      color: theme.palette.text.secondary,
    },
    displayBlock: {
      display: 'block',
    },
    node: {
      display: 'block',
      marginLeft: '10px',
    },
    // hover: {
    //   '&:hover': {
    //     backgroundColor: 'rgba(80, 80, 80, 0.35)',
    //   },
    // },
    topicSelect: {
      float: 'right' as 'right',
      opacity: 0,
      cursor: 'pointer',
      marginTop: '-1px',
    },
  }
}

interface Props {
  actions: typeof treeActions
  lastUpdate: number
  animateChages: boolean
  isRoot?: boolean
  treeNode: q.TreeNode
  name?: string | undefined
  collapsed?: boolean | undefined
  performanceCallback?: ((ms: number) => void) | undefined
  autoExpandLimit: number
  classes: any
  className?: string
}

interface State {
  collapsedOverride: boolean | undefined
}

class TreeNode extends React.Component<Props, State> {
  private dirtySubnodes: boolean = true
  private dirtyEdges: boolean = true
  private dirtyMessage: boolean = true
  private animationDirty: boolean = false

  private cssAnimationWasSetAt?: number

  private willUpdateTime: number = performance.now()
  private titleRef?: React.RefObject<HTMLDivElement> = React.createRef<HTMLDivElement>()
  private nodeRef?: React.RefObject<HTMLDivElement> = React.createRef<HTMLDivElement>()
  private topicSelectRef?: React.RefObject<HTMLDivElement> = React.createRef<HTMLDivElement>()

  private subnodesDidchange = () => {
    this.dirtySubnodes = true
  }

  private messageDidChange = () => {
    this.dirtyMessage = true
  }

  private edgesDidChange = () => {
    this.dirtyMessage = true
  }

  constructor(props: Props) {
    super(props)

    this.state = {
      collapsedOverride: props.collapsed,
    }
  }

  public componentDidMount() {
    const { treeNode } = this.props
    this.addSubscriber(treeNode)
  }

  private addSubscriber(treeNode: q.TreeNode) {
    treeNode.onMerge.subscribe(this.subnodesDidchange)
    treeNode.onEdgesChange.subscribe(this.edgesDidChange)
    treeNode.onMessage.subscribe(this.messageDidChange)
  }

  private removeSubscriber(treeNode: q.TreeNode) {
    treeNode.onMerge.unsubscribe(this.subnodesDidchange)
    treeNode.onEdgesChange.unsubscribe(this.edgesDidChange)
    treeNode.onMessage.unsubscribe(this.messageDidChange)
  }

  public componentWillReceiveProps(nextProps: Props) {
    if (nextProps.treeNode !== this.props.treeNode) {
      this.removeSubscriber(this.props.treeNode)
      this.addSubscriber(nextProps.treeNode)
    }
  }

  public componentWillUnmount() {
    const { treeNode } = this.props
    this.removeSubscriber(treeNode)
    this.topicSelectRef = undefined
    this.titleRef = undefined
    this.nodeRef = undefined
  }

  private stateHasChanged(newState: State) {
    return this.state.collapsedOverride !== newState.collapsedOverride
  }

  private propsHasChanged(newProps: Props) {
    return this.props.autoExpandLimit !== newProps.autoExpandLimit
  }

  public shouldComponentUpdate(nextProps: Props, nextState: State) {
    const shouldRenderToRemoveCssAnimation = this.cssAnimationWasSetAt !== undefined
    return this.stateHasChanged(nextState)
      || this.propsHasChanged(nextProps)
      || this.dirtyEdges
      || this.dirtyMessage
      || this.dirtySubnodes
      || this.animationDirty
      || shouldRenderToRemoveCssAnimation
  }

  public componentDidUpdate() {
    if (this.props.performanceCallback) {
      const renderTime = performance.now() - this.willUpdateTime
      this.props.performanceCallback(renderTime)
    }
  }

  public componentWillUpdate() {
    if (this.props.performanceCallback) {
      this.willUpdateTime = performance.now()
    }
  }

  private toggle() {
    this.setState({ collapsedOverride: !this.collapsed() })
  }

  private collapsed() {
    if (this.state.collapsedOverride !== undefined) {
      return this.state.collapsedOverride
    }

    return this.props.treeNode.edgeCount() > this.props.autoExpandLimit
  }

  public render() {
    const { classes } = this.props
    const isDirty = this.dirtyEdges || this.dirtyMessage || this.dirtySubnodes
    this.dirtyEdges = this.dirtyMessage = this.dirtySubnodes = false

    const shouldStartAnimation = (isDirty && !this.animationDirty) && !this.props.isRoot
    const animation = shouldStartAnimation ? { willChange: 'auto', translateZ: 0, animation: 'example 0.5s' } : {}
    this.animationDirty = shouldStartAnimation

    return (
      <div
        key={this.props.treeNode.hash()}
        className={`${classes.node} ${this.props.className}`}
        onClick={this.didClickNode}
        onMouseOver={this.mouseOver}
        onMouseOut={this.mouseOut}
        ref={this.nodeRef}
      >
        <span ref={this.titleRef} style={animation}>
          <TreeNodeTitle
            collapsed={this.collapsed()}
            treeNode={this.props.treeNode}
            name={this.props.name}
            lastUpdate={this.props.treeNode.lastUpdate}
          />
        </span>
        {this.renderNodes()}
      </div>
    )
  }

  private mouseOver = (event: React.MouseEvent) => {
    event.stopPropagation()
    if (this.nodeRef && this.nodeRef.current) {
      this.nodeRef.current.style.backgroundColor = 'rgba(100, 100, 100, 0.55)'
    }
    if (this.topicSelectRef && this.topicSelectRef.current) {
      this.topicSelectRef.current.style.opacity = '1'
    }
  }
  private mouseOut = (event: React.MouseEvent) => {
    event.stopPropagation()
    if (this.nodeRef && this.nodeRef.current) {
      this.nodeRef.current.style.backgroundColor = 'inherit'
    }
    if (this.topicSelectRef && this.topicSelectRef.current) {
      this.topicSelectRef.current.style.opacity = '0'
    }
  }

  private didSelectNode = (event: React.MouseEvent) => {
    event.stopPropagation()
    if (this.topicSelectRef && this.topicSelectRef.current) {
      this.topicSelectRef.current.style.opacity = '1'
    }
    this.props.actions.selectTopic(this.props.treeNode)
  }

  private didClickNode = (event: React.MouseEvent) => {
    event.stopPropagation()
    this.toggle()
    this.props.actions.selectTopic(this.props.treeNode)
  }

  private renderNodes() {
    return (
      <TreeNodeSubnodes
        animateChanges={this.props.animateChages}
        collapsed={this.collapsed()}
        autoExpandLimit={this.props.autoExpandLimit}
        treeNode={this.props.treeNode}
        lastUpdate={this.props.treeNode.lastUpdate}
      />
    )
  }
}

const mapDispatchToProps = (dispatch: any) => {
  return {
    actions: bindActionCreators(treeActions, dispatch),
  }
}

const mapStateToProps = (state: AppState) => {
  return {
    autoExpandLimit: state.settings.autoExpandLimit,
  }
}

export default withStyles(styles)(connect(mapStateToProps, mapDispatchToProps)(TreeNode))
