import * as React from 'react'
import * as cx from 'classnames'

import { connect } from 'react-redux'

import { throttle } from 'lodash'

import { StylableComponent } from '../theme'
import { ScrollListScrollState } from './reducers'
import { ScrollListProps, ScrollListComponentProps } from './interfaces'
import { getScrollListById } from './selectors'
import { scrollListScroll, scrollListUpdate } from './actions'

import { childrenChanged, forceArray } from '../../utils'

const styles = require('../../../src/components/scroll-list/styles.scss')

class Component extends StylableComponent<ScrollListComponentProps> {
    private list: HTMLDivElement
    private getHeight = throttle(() => this.list.getBoundingClientRect().height, 200)
    private jumping = false

    public componentDidMount() {
        this.update()
    }

    public componentDidUpdate(prevProps: ScrollListComponentProps) {
        const { scrollList } = this.props
        if (childrenChanged(prevProps.children, this.props.children)) {
            this.update()
        }
        if (scrollList.jumped !== false) {
            this.jumping = true
            this.list.scrollTop = scrollList.scroll.scrollTop
        }
    }

    public render() {
        const { height, style } = this.props
        const skip = this.getSkip()
        const limit = this.getLimit(skip)
        return (
            <div
                className={cx(this.props.className, styles.container)}
                onScroll={this.handleScroll}
                style={{
                    ...this.props.style,
                    height: this.props.height,
                }}
                ref={this.setListRef}>
                {forceArray(this.props.children).slice(skip, limit)}
            </div>
        )
    }

    private getSkip() {
        const { scrollList } = this.props
        return scrollList
            ? scrollList.scroll.skip
            : 0
    }

    private getLimit(skip: number) {
        const { maxItemsToRender } = this.props
        return skip + maxItemsToRender
    }

    private setListRef = (e: any) => this.list = e

    private maxScrollTop(): number {
        const { scrollList, children } = this.props
        if (!scrollList) {
            return 0
        }
        const { childHeight, childrenVisible } = scrollList

        return (childHeight * forceArray(children).length) - (childrenVisible * childHeight)

    }

    private getScrollState(): ScrollListScrollState {
        const { scrollList, maxItemsToRender, children } = this.props
        if (!scrollList) {
            return { scrollTop: 0, idx: 0, skip: 0 }
        }
        const { childHeight, childrenVisible, scroll } = scrollList

        const scrollTop = Math.min(this.list.scrollTop, this.maxScrollTop())

        const idx = Math.max(
            Math.min(
                scroll.skip + Math.floor(scrollTop / childHeight),
                forceArray(children).length - childrenVisible,
            ),
            0,
        )

        let skip = scroll.skip

        if (scroll.idx + (childrenVisible * 2) > this.getLimit(this.getSkip())) {
            skip = Math.min(
                scroll.skip + (maxItemsToRender - childrenVisible),
                forceArray(this.props.children).length - childrenVisible,
            )
        } else if (idx - childrenVisible < scroll.skip) {
            skip = Math.max(scroll.skip - childrenVisible, 0)
        }

        return {
            scrollTop,
            skip: Math.max(0, Math.min(skip, idx - childrenVisible)),
            idx,
        }
    }

    private handleScroll = (e: React.SyntheticEvent<any>): void => {
        if (this.jumping) {
            this.jumping = false
            this.update()
        } else {
            this.props.scrollListScroll(this.props.id, this.getScrollState())
        }
    }

    private update() {
        const height = this.getHeight()
        const childHeight = this.props.childHeight

        const childrenVisible = Math.ceil(height / childHeight)

        if (this.props.maxItemsToRender < childrenVisible * 4) {
            console.error('maxItemsToRender should be no less than the amount of items visible * 4')
        }

        const scroll = this.getScrollState()

        this.props.scrollListUpdate(this.props.id, {
            height,
            childHeight,
            scroll,
            childrenVisible: Math.ceil(height / childHeight),
            jumped: this.maxScrollTop() < scroll.scrollTop ? scroll.idx : false,
        })
    }
}

const mapStateToProps = (state: any, ownProps: ScrollListProps) => ({
    ...ownProps,
    scrollList: getScrollListById(state, ownProps.id),
})

const mapDispatchToProps = {
    scrollListScroll,
    scrollListUpdate,
}

export const ScrollList = connect(mapStateToProps, mapDispatchToProps)(Component)