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

import { dasherize, flatten, eventValue } from '../utils'

import {
    Breakpoint,
    FlexProps,
    FlexConfig,
    FlexConfigKey,
    FlexShortcutProp,
    FlexShortcutProps,
    FlexShortcutPropsKey,
} from '../interfaces'

import {
    BREAKPOINTS,
    FLEX_WIDTH_CONFIG,
    FLEX_SHORTCUT_PROPS,
} from '../constants'

const styles = require('../../src/styles/components/flex.scss')

export abstract class Flex<T>
    extends React.Component<T, any> {

    public static defaultProps: FlexProps = {}

    /**
     * Basically we are normalising class for comparison of values so we don't get overlaps
     */
    private static classAndNormalised(key: string, width: Breakpoint, value: string): string[] {
        return [
            styles[`${dasherize(key)}-${width}-${value}`],
            `${dasherize(key)}-${width}`,
        ]
    }

    protected defaultClasses: string[] = []
    protected shortcutProps: FlexShortcutProps = FLEX_SHORTCUT_PROPS

    private flexClassesCache: string[]

    public componentWillMount() {
        this.flexClassesCache = this.flexClasses(this.props)
    }

    public componentWillUpdate(nextProps: any) {
        this.updateFlexClasses(nextProps)
    }

    public render(): JSX.Element {
        return (
            <div {...this.baseProps()}>
                {this.props.children}
            </div>
        )
    }

    protected baseProps(): object {
        const {
            id,
            children,
            style,
            onClick,
            tooltip,
            tooltipPos,
        } = (this.props as FlexProps)

        return {
            id,
            onClick,
            style,
            'className': this.classNames(),
            'data-tooltip': tooltip,
            'data-tooltip-pos': tooltipPos,
        }
    }

    protected classNames(): string {
        const props = (this.props as FlexProps)

        // Use classnames to build up a string of classnames
        return cx(
            this.defaultClasses,
            styles.container,
            ...this.flexClassesCache,
            {
                [styles.fullWidth]: props.fullWidth,
                [styles.expandLeft]: props.expand === 'left',
                [styles.expandRight]: props.expand === 'right',
            },
            props.className,
        )
    }

    private updateFlexClasses(nextProps: FlexProps) {
        const props = (this.props as FlexProps)
        const shortcutPropsUpdated = Object.keys(FLEX_SHORTCUT_PROPS).reduce(
            (acc, key: keyof FlexProps) => acc || nextProps[key] !== props[key],
            false,
        )
        const breakpointsUpdated = Object.keys(BREAKPOINTS).reduce(
            (acc, key: keyof FlexProps) => acc || nextProps[key] !== props[key],
            false,
        )

        if (shortcutPropsUpdated || breakpointsUpdated) {
            this.flexClassesCache = this.flexClasses(nextProps)
        }
    }

    private flexClasses(props: FlexProps): string[] {
        // Build a map of classes keyed by their normal name
        const shortcutClasses = flatten(Object.keys(this.shortcutProps)
            .filter((prop: FlexShortcutPropsKey) => props[prop] !== undefined)
            .map((prop: FlexShortcutPropsKey) =>
                this.classesFromProp(prop, (props[prop] as string)),
        ),
        )
            .reduce(
            (dst, [cls, nrmCls]) => ({ ...dst, [nrmCls]: cls }),
            {},
        )

        // Reduce override classes into the initial map, overriding based on normalised name
        const classes = flatten(BREAKPOINTS.map(width => FLEX_WIDTH_CONFIG
            .filter((key: FlexConfigKey) => props[width] !== undefined
                && (props[width] as any)[key] !== undefined)
            .map((key: FlexConfigKey) =>
                this.classFromOverride(props, width, key)),
        ))
            .reduce(
            (dst, [cls, nrmCls]) => ({ ...dst, [nrmCls]: cls }),
            shortcutClasses,
        )

        return Object.values(classes)
    }

    private classFromOverride(props: FlexProps, width: Breakpoint, key: FlexConfigKey): string[] {
        return Flex.classAndNormalised(key, width, ((props[width] as FlexConfig)[key] as any))
    }

    private classesFromProp(prop: FlexShortcutPropsKey, value: string): string[][] {
        const { key, propIsValue } = this.shortcutProps[prop]

        return propIsValue
            ? [Flex.classAndNormalised(key, (value as Breakpoint), dasherize(prop))]
            : [Flex.classAndNormalised(key, BREAKPOINTS[0], value)]
    }

}
