import {Component, createElement} from 'react'

import filterProps from './utils/filterProps'
import composeClasses from './utils/composeClasses'
import generateTagName from './utils/generateTagName'
import getSeparatedStyles from './utils/getSeparatedStyles'

import type {
  JssSheet,
  ComponentStyleType,
  StyledElementPropsType
} from './types'

type StyledArgs = {
  tagName: string,
  elementStyle: ComponentStyleType,
  mountSheet: Function
}

const styled = ({tagName, elementStyle, mountSheet}: StyledArgs) => {
  const {dynamicStyle, staticStyle} = getSeparatedStyles(elementStyle)
  const staticTagName = staticStyle && generateTagName(tagName)

  const availableDynamicTagNames = []
  const classMap = {}

  return class StyledElement extends Component {
    static tagName: string = tagName
    static style: ComponentStyleType = elementStyle

    props: StyledElementPropsType

    dynamicTagName = ''

    sheet: JssSheet

    constructor(props: StyledElementPropsType) {
      super(props)
      if (!this.dynamicTagName && dynamicStyle) {
        this.dynamicTagName = availableDynamicTagNames.pop() || generateTagName(tagName)
      }
    }

    componentWillMount() {
      this.sheet = this.sheet || mountSheet()
      const rulesIndex = this.sheet.rules.index
      const rulesTotal = rulesIndex.length

      if (staticStyle && !this.sheet.getRule(staticTagName)) {
        this.sheet.addRule(staticTagName, staticStyle)
      }

      if (!dynamicStyle) return

      if (!this.sheet.getRule(this.dynamicTagName)) {
        this.sheet.addRule(this.dynamicTagName, dynamicStyle)
      }

      classMap[this.dynamicTagName] = rulesIndex.slice(rulesTotal)
      this.updateSheet(this.props)
    }

    componentWillReceiveProps(nextProps: StyledElementPropsType) {
      if (dynamicStyle) this.updateSheet(nextProps)
    }

    componentWillUnmount() {
      availableDynamicTagNames.push(this.dynamicTagName)
    }

    updateSheet(props: StyledElementPropsType) {
      let rule
      let ruleIndex = 0
      // nested styles become to flatten rules, so we need to update each nested rule
      for (ruleIndex; ruleIndex < classMap[this.dynamicTagName].length; ruleIndex++) {
        rule = classMap[this.dynamicTagName][ruleIndex]
        this.sheet.update(rule.name, props)
      }
    }

    render() {
      const {children, className, ...attrs} = this.props

      const props = filterProps(attrs)
      const tagClass = composeClasses([
        this.sheet.classes[staticTagName],
        this.sheet.classes[this.dynamicTagName],
        className
      ])

      return createElement(tagName, {...props, className: tagClass}, children)
    }
  }
}

export default styled

@@ -1642,16 +1642,49 @@
 gName%5D =
+ classMap%5Bthis.dynamicTagName%5D %7C%7C
  rulesIn

Fix #26