Newer
Older
kotlin-css-generator / src / commonMain / kotlin / nl / astraeus / css / style / Style.kt
rnentjes on 9 Nov 2021 38 KB v. 1.0.4, add vh,vw measurements
package nl.astraeus.css.style

import nl.astraeus.css.properties.*

typealias Css = Style.() -> Unit

typealias ConditionalCss = ConditionalStyle.() -> Unit

@DslMarker
annotation class CssTagMarker

private fun prp(vararg css: CssValue): List<CssProperty> {
  val result = mutableListOf<CssProperty>()

  for (c in css) {
    result.add(CssProperty(c.css()))
  }

  return result
}

private fun prp(vararg css: String): List<CssProperty> {
  val result = mutableListOf<CssProperty>()

  for (c in css) {
    result.add(CssProperty(c))
  }

  return result
}

abstract class CssGenerator {
  val definitions: MutableMap<String, MutableList<Css>> = LinkedHashMap()
  val props: MutableMap<String, List<CssProperty>> = mutableMapOf()

  abstract fun getValidator(name: String): List<Validator>?

  private fun propertyCss(
    indent: String,
    name: String,
    minified: Boolean,
    props: List<CssProperty>
  ): String {
    val builder = StringBuilder()

    getValidator(name)?.forEach {
      if (!it.validate(props)) {
        println("Validate error '$name' - ${it.getMessage(name)}")
      }
    }

    for (prop in props) {
      if (builder.isNotEmpty()) {
        builder.append(" ")
      }
      builder.append(prop.css())
    }

    val paddedName = StringBuilder()
    if (!minified) {
      paddedName.append(indent)
    }
    paddedName.append(name)
    paddedName.append(":")
    if (!minified) {
      while (paddedName.length < 32) {
        paddedName.append(' ')
      }
    }
    return "$paddedName$builder;"
  }

  fun generatePropertyCss(
    indent: String,
    sortProperties: Boolean,
    minified: Boolean
  ): String {
    val builder = StringBuilder()

    if (sortProperties) {
      for (name in props.keys.sorted()) {
        val prop = props[name] ?: error("$name not found in properties after sorting!")

        builder.append(propertyCss(indent, name, minified, prop))
        if (!minified) {
          builder.append("\n")
        }
      }
    } else {
      for ((name, prop) in props) {
        builder.append(propertyCss(indent, name, minified, prop))
        if (!minified) {
          builder.append("\n")
        }
      }
    }

    return builder.toString()
  }

  open fun generateCss(
    indent: String = "",
    minified: Boolean = false,
    warnOnRedeclaration: Boolean = true,
    allowCommaInSelector: Boolean = false,
    combineEqualBlocks: Boolean = false,
    sortProperties: Boolean = false
  ): String {
    val blocks = generateCssBlocks(
      indent = indent,
      minified = minified,
      warnOnRedeclaration = warnOnRedeclaration,
      allowCommaInSelector = allowCommaInSelector,
      sortProperties = sortProperties
    )

    val builder = StringBuilder()

    fun StringBuilder.generateBlock(
      indent: String,
      selectors: List<String>,
      block: CssBlock?
    ) {
      if (selectors.isNotEmpty() && block != null) {
        append(indent)
        append(selectors.joinToString(if (minified) { "," } else { ",\n" }))
        if (!minified) {
          append(" ")
        }
        append("{")
        if (!minified) {
          append("\n")
        }
        append(block.content)
        append(indent)
        append("}")
        if (!minified) {
          append("\n\n")
        }
      }
    }

    if (!combineEqualBlocks) {
      var first = true
      val selectors = mutableListOf<String>()
      var lastBlock: CssBlock? = null

      for (block in blocks) {
        if (first) {
          first = false
          selectors.add(block.selector)
          lastBlock = block
        } else {
          lastBlock = if (lastBlock != null && lastBlock.content == block.content) {
            selectors.add(block.selector)
            block
          } else {
            builder.generateBlock(indent, selectors, lastBlock)

            selectors.clear()
            selectors.add(block.selector)
            block
          }
        }
      }

      builder.generateBlock(indent, selectors, lastBlock)
    } else {
      val blockHashes: MutableMap<Int, MutableList<CssBlock>> = mutableMapOf()

      for (block in blocks) {
        blockHashes.getOrPut(block.content.hashCode()) {
          mutableListOf()
        }.add(block)
      }
      val done = mutableSetOf<Int>()

      for(block in blocks) {
        val hashCode = block.content.hashCode()

        if (!done.contains(hashCode)) {
          blockHashes[hashCode]?.let {
            val slctrs = it.map { blk ->
              blk.selector
            }
            builder.generateBlock(indent, slctrs, block)
            done.add(hashCode)
          }
        }
      }
    }

    return builder.toString()
  }

  open fun generateCssBlocks(
    namespace: String = "",
    indent: String = "",
    minified: Boolean = false,
    warnOnRedeclaration: Boolean = true,
    allowCommaInSelector: Boolean = false,
    sortProperties: Boolean = false
  ): List<CssBlock> {
    val blocks = mutableListOf<CssBlock>()

    for (name in definitions.keys) {
      val props = definitions[name]!!
      val css = StringBuilder()

      if (warnOnRedeclaration && props.size > 1) {
        css.append("  $indent/* style '$name' is defined ${props.size} times! */\n")
      }

      val finalStyle = Style()

      for (prop in props) {
        prop(finalStyle)
      }

      css.append(finalStyle.generatePropertyCss("  $indent", sortProperties, minified))

      if (css.isNotBlank()) {
        val builder = StringBuilder()

        check (allowCommaInSelector || !name.contains(',')) {
          "Comma is not allowed in selector (option is set in generateCss call)"
        }

        //builder.append("\n$namespace$name".trim())

        //builder.append("  $indent")
        //builder.append(" {\n")

        finalStyle.fontFace?.let { ff ->
          if (!minified) {
            builder.append("  $indent")
          }
          builder.append("@font-face")
          if (!minified) {
            builder.append(" ")
          }
          builder.append("{")
          if (!minified) {
            builder.append("\n")
          }
          builder.append(ff.generatePropertyCss("    $indent", sortProperties, minified))
          if (!minified) {
            builder.append("  $indent")
          }
          builder.append("}")
          if (!minified) {
            builder.append("\n")
          }
        }

        finalStyle.keyFrames.let { kf ->
          kf.keys.sorted().forEach { frameName ->
            val css = kf[frameName]

            if (!minified) {
              builder.append("  $indent")
            }
            builder.append("@keyframes ")
            builder.append(frameName)
            if (!minified) {
              builder.append(" ")
            }
            builder.append("{")
            if (!minified) {
              builder.append("\n")
            }
            css?.let { css ->
              for ((nr, style) in css.frames) {
                if (!minified) {
                  builder.append("    $indent")
                }
                builder.append("${nr}% ")
                if (!minified) {
                  builder.append("    $indent")
                }
                builder.append("{")
                if (!minified) {
                  builder.append("\n")
                }

                val finalStyle = Style()

                style(finalStyle)

                builder.append(finalStyle.generatePropertyCss("      $indent", sortProperties, minified))

                if (!minified) {
                  builder.append("    $indent")
                }
                builder.append("}")
                if (!minified) {
                  builder.append("\n")
                }
              }

              if (!minified) {
                builder.append("  $indent")
              }
              builder.append("}")
              if (!minified) {
                builder.append("\n")
              }
            }
          }
        }

        builder.append(css)
        //builder.append("}\n\n")

        blocks.add(CssBlock(
          "$namespace$name".trim(),
          builder.toString()
        ))
      }

      blocks.addAll(finalStyle.generateCssBlocks(
        "$namespace$name".trim(),
        indent,
        minified = minified,
        allowCommaInSelector = allowCommaInSelector,
        warnOnRedeclaration = warnOnRedeclaration,
        sortProperties = sortProperties
      ))
    }

    if (this is ConditionalStyle) {
      this.media.let { mq ->
        mq.keys.sorted().forEach { mediaName ->
          val css = mq[mediaName]

          css?.let { css ->
            val mediaStyle = ConditionalStyle()

            css(mediaStyle)

            blocks.add(CssBlock(
              "$indent@media $mediaName".trim(),
              mediaStyle.generateCss(
                "  $indent",
                minified = minified,
                allowCommaInSelector = allowCommaInSelector,
                warnOnRedeclaration = warnOnRedeclaration,
                sortProperties = sortProperties
              )
            ))
          }
        }
      }

      this.supports.let { mq ->
        mq.keys.sorted().forEach { mediaName ->
          val css = mq[mediaName]

          css?.let { css ->
            val mediaStyle = ConditionalStyle()

            css(mediaStyle)

            blocks.add(CssBlock(
              "$indent@supports $mediaName".trim(),
              mediaStyle.generateCss(
                "  $indent",
                minified = minified,
                allowCommaInSelector = allowCommaInSelector,
                warnOnRedeclaration = warnOnRedeclaration,
                sortProperties = sortProperties
              )
            ))
          }
        }
      }
    }

    return blocks
  }
}

interface DescriptionProvider {
  fun description(): String
}

class ValueDescriptionProvider(
  val value: String
) : DescriptionProvider {

  override fun description() = value

}

fun txt(name: String): DescriptionProvider = ValueDescriptionProvider(name)
fun id(name: String): DescriptionProvider = ValueDescriptionProvider("#$name")
fun cls(name: String): DescriptionProvider = ValueDescriptionProvider(".$name")
fun attr(name: String): DescriptionProvider = ValueDescriptionProvider("[$name]")
fun attrEquals(name: String, value: String): DescriptionProvider = ValueDescriptionProvider("[$name=$value]")
fun attrContains(name: String, value: String): DescriptionProvider = ValueDescriptionProvider("[$name*=$value]")
fun attrEntriesContain(name: String, value: String): DescriptionProvider = ValueDescriptionProvider("[$name~=$value]")
fun attrEndsWith(name: String, value: String): DescriptionProvider = ValueDescriptionProvider("[$name$=$value]")
fun attrStartsWith(name: String, value: String): DescriptionProvider = ValueDescriptionProvider("[$name^=$value]")

fun id(name: DescriptionProvider) = ValueDescriptionProvider("#${name.description()}")
fun cls(name: DescriptionProvider): DescriptionProvider = ValueDescriptionProvider(".${name.description()}")
fun attr(name: DescriptionProvider): DescriptionProvider = ValueDescriptionProvider("[${name.description()}]")
fun attrEquals(name: DescriptionProvider, value: String): DescriptionProvider = ValueDescriptionProvider("[${name.description()}=$value]")
fun attrContains(name: DescriptionProvider, value: String): DescriptionProvider =
  ValueDescriptionProvider("[${name.description()}*=$value]")

fun attrEntriesContain(name: DescriptionProvider, value: String): DescriptionProvider =
  ValueDescriptionProvider("[${name.description()}~=$value]")

fun attrEndsWith(name: DescriptionProvider, value: String): DescriptionProvider =
  ValueDescriptionProvider("[${name.description()}$=$value]")

fun attrStartsWith(name: DescriptionProvider, value: String): DescriptionProvider =
  ValueDescriptionProvider("[${name.description()}^=$value]")

@CssTagMarker
open class InlineStyle : CssGenerator() {

  private val validators = mapOf<String, List<Validator>>(
    "background-position" to listOf(InitialInheritSingleValue()),
    "background-size" to listOf(MaxCountValidator(2)),
    "border-radius" to listOf(
      MaxCountValidator(4),
      InitialInheritSingleValue()
    ),

    "animation-iteration-mode" to listOf(MaxCountValidator(4)),
    "animation-timing-function" to listOf(MaxCountValidator(4)),
    "border-image-repeat" to listOf(MaxCountValidator(2)),
    "border-image-slice" to listOf(MaxCountValidator(4)),
    "border-spacing" to listOf(MaxCountValidator(4)),
    "border-style" to listOf(MaxCountValidator(4))
  )

  override fun getValidator(name: String) = validators[name]

  private fun addStyle(selector: String, style: Css) {
    definitions[selector] = definitions[selector] ?: mutableListOf()
    definitions[selector]?.add(style)
  }

  fun active(style: Css) {
    addStyle(":active", style)
  }

  fun focus(style: Css) {
    addStyle(":focus", style)
  }

  fun focusWithin(style: Css) {
    addStyle(":focus-within", style)
  }

  fun hover(style: Css) {
    addStyle(":hover", style)
  }

  fun visited(style: Css) {
    addStyle(":visited", style)
  }

  fun plain(name: String, value: String) {
    props[name] = prp(value)
  }

  fun plain(name: String, value: CssProperty) {
    props[name] = prp(value)
  }

  fun alignContent(value: AlignContent) {
    props["align-content"] = prp(value)
  }

  fun alignItems(alignItems: AlignItems) {
    props["align-items"] = prp(alignItems)
  }

  fun all(all: All) {
    props["all"] = prp(all)
  }

  fun alignSelf(alignSelf: AlignSelf) {
    props["align-self"] = prp(alignSelf)
  }

  fun animation(text: String) {
    props["animation"] = prp(text)
  }

  fun animationDelay(delay: DelayDuration) {
    props["animation-delay"] = prp(delay)
  }

  fun animationDirection(direction: AnimationDirection) {
    props["animation-direction"] = prp(direction)
  }

  fun animationDuration(duration: DelayDuration) {
    props["animation-duration"] = prp(duration)
  }

  fun animationFillMode(fillMode: AnimationFillMode) {
    props["animation-fill-mode"] = prp(fillMode)
  }

  fun animationIterationMode(vararg iterationMode: Count) {
    props["animation-iteration-mode"] = prp(*iterationMode)
  }

  fun animationFrame(frame: AnimationFrame) {
    props["animation-frame"] = prp(frame)
  }

  fun animationName(vararg name: String) {
    props["animation-name"] = prp(*name)
  }

  fun animationPlayState(vararg state: AnimationPlayState) {
    props["animation-play-state"] = prp(*state)
  }

  fun animationTimingFunction(vararg timingFunction: TimingFunction) {
    props["animation-timing-function"] = prp(*timingFunction)
  }

  fun backfaceVisibility(backfaceVisibility: BackfaceVisibility) {
    props[""] = prp(backfaceVisibility)
  }

  fun background(background: String) {
    props["background"] = prp(background)
  }

  fun backgroundAttachment(attachment: BackgroundAttachment) {
    props["background-attachment"] = prp(attachment)
  }

  fun backgroundBlendMode(blendMode: BackgroundBlendMode) {
    props["background-blend-mode"] = prp(blendMode)
  }

  fun backgroundClip(clip: ClipOrigin) {
    props["background-clip"] = prp(clip)
  }

  fun backgroundColor(color: Color) {
    props["background-color"] = prp(color)
  }

  fun backgroundImage(image: Image) {
    props["background-image"] = prp(image)
  }

  fun backgroundImage(value: String) {
    props["background-image"] = prp(value)
  }

  fun backgroundOrigin(origin: ClipOrigin) {
    props["background-origin"] = prp(origin)
  }

  fun backgroundPosition(position: BackgroundPosition) {
    props["background-position"] = prp(position)
  }

  fun backgroundRepeat(repeat: BackgroundRepeat) {
    props["background-repeat"] = prp(repeat)
  }

  fun backgroundSize(vararg size: BackgroundSize) {
    props["background-size"] = prp(*size)
  }

  fun border(border: String) {
    props["border"] = prp(border)
  }

  fun border(
    width: Measurement,
    style: BorderStyle,
    color: Color
  ) {
    props["border"] = prp(width, style, color)
  }

  fun borderBottom(borderBottom: String) {
    props["border-bottom"] = prp(borderBottom)
  }

  fun borderBottomColor(color: Color) {
    props["border-bottom-color"] = prp(color)
  }

  fun borderBottomLeftRadius(vararg radius: Measurement) {
    props["border-bottom-left-radius"] = prp(*radius)
  }

  fun borderBottomRightRadius(vararg radius: Measurement) {
    props["border-bottom-right-radius"] = prp(*radius)
  }

  fun borderBottomStyle(style: BorderStyle) {
    props["border-bottom-style"] = prp(style)
  }

  fun borderBottomWidth(width: BorderWidth) {
    props["border-bottom-width"] = prp(width)
  }

  fun borderBottomWidth(width: Measurement) {
    props["border-bottom-width"] = prp(width)
  }

  fun borderCollapse(collapse: BorderCollapse) {
    props["border-collapse"] = prp(collapse)
  }

  fun borderColor(vararg color: Color) {
    props["border-color"] = prp(*color)
  }

  fun borderImage(image: String) {
    props["border-image"] = prp(image)
  }

  fun borderImageOutset(imageOutset: Length) {
    props["border-image-outset"] = prp(imageOutset)
  }

  fun borderImageRepeat(vararg repeat: ImageRepeat) {
    props["border-image-repeat"] = prp(*repeat)
  }

  fun borderImageSlice(vararg slice: ImageSlice) {
    props["border-image-slice"] = prp(*slice)
  }

  fun borderImageSource(vararg source: ImageSource) {
    props["border-image-source"] = prp(*source)
  }

  fun borderImageWidth(vararg width: BorderImageWidth) {
    props["border-image-width"] = prp(*width)
  }

  fun borderLeft(left: String) {
    props["border-left"] = prp(left)
  }

  fun borderLeftColor(color: Color) {
    props["border-left-color"] = prp(color)
  }

  fun borderLeftStyle(style: BorderStyle) {
    props["border-left-style"] = prp(style)
  }

  fun borderLeftWidth(width: BorderWidth) {
    props["border-left-width"] = prp(width)
  }

  fun borderLeftWidth(width: Measurement) {
    props["border-left-width"] = prp(width)
  }

  fun borderRadius(radius: Measurement) {
    props["border-radius"] = prp(radius)
  }

  fun borderRadius(
    topLeftBottomRight: Measurement,
    topRightBottomLeft: Measurement
  ) {
    props["border-radius"] = listOf(
      topLeftBottomRight, topRightBottomLeft
    )
  }

  fun borderRadius(
    topLeft: Measurement,
    topRightBottomLeft: Measurement,
    bottomRight: Measurement
  ) {
    props["border-radius"] = listOf(
      topLeft, topRightBottomLeft, bottomRight
    )
  }

  fun borderRadius(
    topLeft: Measurement,
    topRight: Measurement,
    bottomRight: Measurement,
    bottomLeft: Measurement
  ) {
    props["border-radius"] = listOf(
      topLeft, topRight, bottomRight, bottomLeft
    )
  }

  fun borderRight(border: String) {
    props["border-right"] = prp(border)
  }

  fun borderRightColor(color: Color) {
    props["border-right-color"] = prp(color)
  }

  fun borderRightStyle(style: BorderStyle) {
    props["border-right-style"] = prp(style)
  }

  fun borderRightWidth(width: BorderWidth) {
    props["border-right-width"] = prp(width)
  }

  fun borderRightWidth(width: Measurement) {
    props["border-right-width"] = prp(width)
  }

  fun borderSpacing(vararg spacing: BorderSpacing) {
    props["border-spacing"] = prp(*spacing)
  }

  fun borderStyle(vararg style: BorderStyle) {
    props["border-style"] = prp(*style)
  }

  fun borderTop(border: String) {
    props["border-top"] = prp(border)
  }

  fun borderTopColor(color: Color) {
    props["border-top-color"] = prp(color)
  }

  fun borderTopLeftRadius(radius: Measurement) {
    props["border-top-left-radius"] = prp(radius)
  }

  fun borderTopRightRadius(radius: Measurement) {
    props["border-top-right-radius"] = prp(radius)
  }

  fun borderTopStyle(style: BorderStyle) {
    props["border-top-style"] = prp(style)
  }

  fun borderTopWidth(width: BorderWidth) {
    props["border-top-width"] = prp(width)
  }

  fun borderTopWidth(width: Measurement) {
    props["border-top-width"] = prp(width)
  }

  fun borderWidth(width: Measurement) {
    props["border-width"] = prp(width)
  }

  fun borderWidth(width: BorderWidth) {
    props["border-width"] = prp(width)
  }

  fun bottom(measurement: Measurement) {
    props["bottom"] = prp(measurement)
  }

  fun boxDecorationBreak(brk: BoxDecorationBreak) {
    props["box-decoration-break"] = prp(brk)
  }

  fun boxShadow(shadow: BoxShadow) {
    props["box-shadow"] = prp(shadow)
  }

  fun boxSizing(sizing: BoxSizing) {
    props["box-sizing"] = prp(sizing)
  }

  fun breakAfter(brk: Break) {
    props["break-after"] = prp(brk)
  }

  fun breakBefore(brk: Break) {
    props["break-before"] = prp(brk)
  }

  fun breakInside(brk: Break) {
    props["break-inside"] = prp(brk)
  }

  fun captionSide(side: CaptionSide) {
    props["caption-side"] = prp(side)
  }

  fun caretColor(color: Color) {
    props["caret-color"] = prp(color)
  }

  fun clear(clear: Clear) {
    props["clear"] = prp(clear)
  }

  fun clip(clip: Clip) {
    props["clip"] = prp(clip)
  }

  fun clipPath(path: ClipPath) {
    props["clip-path"] = prp(path)
  }

  fun color(color: Color) {
    props["color"] = listOf(color)
  }

  fun columnCount(count: Count) {
    props["column-count"] = prp(count)
  }

  fun columnFill(fill: Fill) {
    props["column-fill"] = prp(fill)
  }

  fun columnGap(gap: Measurement) {
    props["column-gap"] = prp(gap)
  }

  fun columnRule(rule: String) {
    props["column-rule"] = prp(rule)
  }

  fun columnRuleColor(color: Color) {
    props["column-rule-color"] = prp(color)
  }

  fun columnRuleStyle(style: BorderStyle) {
    props["column-rule-style"] = prp(style)
  }

  fun columnRuleWidth(width: Measurement) {
    props["column-rule-width"] = prp(width)
  }

  fun columnSpan(span: Span) {
    props["column-span"] = prp(span)
  }

  fun columnWidth(width: Measurement) {
    props["column-width"] = prp(width)
  }

  fun column(column: String) {
    props["column"] = prp(column)
  }

  fun content(content: Content) {
    props["content"] = prp(content)
  }

  fun counterIncrement(increment: String) {
    props["counter-increment"] = prp(increment)
  }

  fun counterReset(reset: String) {
    props["counter-reset"] = prp(reset)
  }

  fun cursor(cursor: String) {
    props["cursor"] = prp(cursor)
  }

  fun direction(direction: Direction) {
    props["direction"] = prp(direction)
  }

  fun display(display: Display) {
    props["display"] = prp(display)
  }

  fun emptyCells(cells: EmptyCells) {
    props["empty-cells"] = prp(cells)
  }

  fun filter(filter: String) {
    props["filter"] = prp(filter)
  }

  fun flex(flex: String) {
    props["flex"] = prp(flex)
  }

  fun flexBasis(basis: Measurement) {
    props["flex-basis"] = prp(basis)
  }

  fun flexDirection(direction: FlexDirection) {
    props["flex-direction"] = prp(direction)
  }

  fun flexFlow(flow: String) {
    props["flex-flow"] = prp(flow)
  }

  fun flexGrow(grow: FlexGrowShrink) {
    props["flex-grow"] = prp(grow)
  }

  fun flexShrink(shrink: FlexGrowShrink) {
    props["flex-shrink"] = prp(shrink)
  }

  fun flexWrap(wrap: FlexWrap) {
    props["flex-wrap"] = prp(wrap)
  }

  fun float(cssFloat: CssFloat) {
    props["float"] = prp(cssFloat)
  }

  fun font(font: String) {
    props["font"] = prp(font)
  }

  fun fontFamily(font: String) {
    props["font-family"] = prp(font)
  }

  fun fontFeatureSettings(vararg setting: String) {
    props["font-feature-settings"] = prp(*setting)
  }

  fun fontKerning(kerning: FontKerning) {
    props["font-kerking"] = prp(kerning)
  }

  fun fontSize(size: FontSize) {
    props["font-size"] = prp(size)
  }

  fun fontSize(size: Measurement) {
    props["font-size"] = prp(size)
  }

  fun fontSizeAdjust(number: Double) {
    props["font-size-adjust"] = prp(CssProperty("$number"))
  }

  fun fontSizeAdjust(adjust: FontSizeAdjust) {
    props["font-size-adjust"] = prp(adjust)
  }

  fun fontStretch(stretch: FontStretch) {
    props["font-stretch"] = prp(stretch)
  }

  fun fontStyle(style: FontStyle) {
    props["font-style"] = prp(style)
  }

  fun fontVariant(variant: FontVariant) {
    props["font-variant"] = prp(variant)
  }

  fun fontVariantCaps(caps: FontVariantCaps) {
    props["font-variant-caps"] = prp(caps)
  }

  fun fontWeight(weight: FontWeight) {
    props["font-weight"] = prp(weight)
  }

  fun grid(grid: String) {
    props["grid"] = prp(grid)
  }

  fun gridArea(area: String) {
    props["grid-area"] = prp(area)
  }

  fun gridAutoColumns(columns: GridAuto) {
    props["grid-auto-columns"] = prp(columns)
  }

  fun gridAutoFlow(flow: GridFlow) {
    props["grid-auto-flow"] = prp(flow)
  }

  fun gridAutoRows(autoRows: GridAuto) {
    props["grid-auto-rows"] = prp(autoRows)
  }

  fun gridAutoRows(size: Measurement) {
    props["grid-auto-rows"] = prp(size)
  }

  fun gridColumn(start: GridValue, end: GridValue) {
    props["grid-column"] = prp(CssProperty("${start.css()}/${end.css()}"))
  }

  fun gridColumnEnd(end: GridValue) {
    props["grid-column-end"] = prp(end)
  }

  fun gridColumnGap(gap: GridValue) {
    props["grid-column-gap"] = prp(gap)
  }

  fun gridColumnStart(start: GridValue) {
    props["grid-column-start"] = prp(start)
  }

  fun gridGap(
    rowGap: Measurement = 0.px,
    columnGap: Measurement = 0.px
  ) {
    props["grid-gap"] = prp(rowGap, columnGap)
  }

  fun gridRow(start: GridValue, end: GridValue) {
    props["grid-row"] = prp(CssProperty("${start.css()}/${end.css()}"))
  }

  fun gridRowEnd(end: GridValue) {
    props["grid-row-end"] = prp(end)
  }

  fun gridRowGap(gap: GridValue) {
    props["grid-row-end"] = prp(gap)
  }

  fun gridRowStart(start: GridValue) {
    props["grid-row-start"] = prp(start)
  }

  fun gridTemplate(template: String) {
    props["grid-template"] = prp(template)
  }

  fun gridTemplateAreas(template: String) {
    props["grid-template-areas"] = prp(template)
  }

  @Deprecated(
    "Fixed type, use gridTemplateColumns instead",
    ReplaceWith("gridTemplateColumns(columns)")
  )
  fun gridTemplateColumn(vararg columns: TemplateRowColumn) {
    props["grid-template-columns"] = prp(*columns)
  }

  fun gridTemplateColumns(vararg columns: TemplateRowColumn) {
    props["grid-template-columns"] = prp(*columns)
  }

  fun gridTemplateColumns(vararg columns: Measurement) {
    props["grid-template-columns"] = prp(*columns)
  }

  fun gridTemplateRows(vararg rows: TemplateRowColumn) {
    props["grid-template-rows"] = prp(*rows)
  }

  fun gridTemplateRows(vararg rows: Measurement) {
    props["grid-template-rows"] = prp(*rows)
  }

  fun hangingPunctuation(punctuation: Isolation) {
    props["hanging-punctuation"] = prp(punctuation)
  }

  fun height(height: Measurement) {
    props["height"] = prp(height)
  }

  fun hyphens(hyphens: Hyphens) {
    props["hyphens"] = prp(hyphens)
  }

  fun isolation(isolation: Isolation) {
    props["isolation"] = prp(isolation)
  }

  fun justifyContent(content: JustifyContent) {
    props["justify-content"] = prp(content)
  }

  fun left(left: Measurement) {
    props["left"] = prp(left)
  }

  fun letterSpacing(length: Measurement) {
    props["letter-spacing"] = prp(length)
  }

  fun letterSpacing(spacing: LetterSpacing) {
    props["letter-spacing"] = prp(spacing)
  }

  fun lineHeight(number: Double) {
    props["line-height"] = prp("$number")
  }

  fun lineHeight(measurement: Measurement) {
    props["line-height"] = prp(measurement)
  }

  fun lineHeight(height: LineHeight) {
    props["line-height"] = prp(height)
  }

  fun listStyle(style: String) {
    props["list-style"] = prp(style)
  }

  fun listStyleImage(url: String) {
    props["list-style-image"] = prp("url('$url')")
  }

  fun listStylePosition(position: ListStylePosition) {
    props["list-style-position"] = prp(position)
  }

  fun listStyleType(position: ListStyleType) {
    props["list-style-type"] = prp(position)
  }

  fun margin(all: Measurement) {
    props["margin"] = prp(all)
  }

  fun margin(
    topBottom: Measurement,
    leftRight: Measurement
  ) {
    props["margin"] = prp(topBottom, leftRight)
  }

  fun margin(top: Measurement, right: Measurement, bottom: Measurement, left: Measurement) {
    props["margin"] = prp(top, right, bottom, left)
  }

  fun marginBottom(bottom: Measurement) {
    props["margin-bottom"] = prp(bottom)
  }

  fun marginLeft(left: Measurement) {
    props["margin-left"] = prp(left)
  }

  fun marginRight(right: Measurement) {
    props["margin-right"] = prp(right)
  }

  fun marginTop(top: Measurement) {
    props["margin-top"] = prp(top)
  }

  fun maxHeight(height: Measurement) {
    props["max-height"] = prp(height)
  }

  fun maxWidth(width: Measurement) {
    props["max-width"] = prp(width)
  }

  fun minHeight(height: Measurement) {
    props["min-height"] = prp(height)
  }

  fun minWidth(width: Measurement) {
    props["min-width"] = prp(width)
  }

  fun mixBlendMode(blendMode: MixBlendMode) {
    props["mix-blend-mode"] = prp(blendMode)
  }

  fun objectFit(fit: ObjectFit) {
    props["object-fit"] = prp(fit)
  }

  fun objectPosition(position: String) {
    props["object-position"] = prp(position)
  }

  fun opacity(opacity: Double) {
    props["opacity"] = prp(opacity.toString())
  }

  fun opacity(opacity: InitialInherit) {
    props["opacity"] = prp(opacity.toString())
  }

  fun order(order: Int) {
    props["order"] = prp(order.toString())
  }

  fun order(order: InitialInherit) {
    props["order"] = prp(order)
  }

  fun outline(outline: String) {
    props["outline"] = prp(outline)
  }

  fun outlineColor(color: Color) {
    props["outline-color"] = prp(color)
  }

  fun outlineOffset(offset: Measurement) {
    props["outline-offset"] = prp(offset)
  }

  fun outlineStyle(style: BorderStyle) {
    props["outline-style"] = prp(style)
  }

  fun outlineWidth(width: OutlineWidth) {
    props["outline-width"] = prp(width)
  }

  fun outlineWidth(width: Measurement) {
    props["outline-width"] = prp(width)
  }

  fun overflow(overflow: Overflow) {
    props["overflow"] = prp(overflow)
  }

  fun overflowX(overflow: Overflow) {
    props["overflow-x"] = prp(overflow)
  }

  fun overflowY(overflow: Overflow) {
    props["overflow-y"] = prp(overflow)
  }

  fun padding(padding: Measurement) {
    props["padding"] = prp(padding)
  }

  fun padding(vertical: Measurement, horizontal: Measurement) {
    props["padding"] = prp(vertical, horizontal)
  }

  fun padding(top: Measurement, right: Measurement, bottom: Measurement, left: Measurement) {
    props["padding"] = prp(top, right, bottom, left)
  }

  fun padding(padding: InitialInherit) {
    props["padding"] = prp(padding)
  }

  fun paddingBottom(padding: Measurement) {
    props["padding-bottom"] = prp(padding)
  }

  fun paddingBottom(padding: InitialInherit) {
    props["padding-bottom"] = prp(padding)
  }

  fun paddingLeft(padding: Measurement) {
    props["padding-left"] = prp(padding)
  }

  fun paddingLeft(padding: InitialInherit) {
    props["padding-left"] = prp(padding)
  }

  fun paddingRight(padding: Measurement) {
    props["padding-right"] = prp(padding)
  }

  fun paddingRight(padding: InitialInherit) {
    props["padding-right"] = prp(padding)
  }

  fun paddingTop(padding: Measurement) {
    props["padding-top"] = prp(padding)
  }

  fun paddingTop(padding: InitialInherit) {
    props["padding-top"] = prp(padding)
  }

  fun pageBreakAfter(pageBreak: PageBreak) {
    props["page-break-after"] = prp(pageBreak)
  }

  fun pageBreakBefore(pageBreak: PageBreak) {
    props["page-break-before"] = prp(pageBreak)
  }

  fun pageBreakInside(pageBreak: PageBreak) {
    props["page-break-inside"] = prp(pageBreak)
  }

  fun perspective(length: Measurement) {
    props["perspective"] = prp(length)
  }

  fun perspective(perspective: Perspective) {
    props["perspective"] = prp(perspective)
  }

  fun perspectiveOrigin(po: String) {
    props["perspective-origin"] = prp(po)
  }

  fun pointerEvents(pe: PointerEvents) {
    props["pointer-events"] = prp(pe)
  }

  fun position(poition: Position) {
    props["position"] = prp(poition)
  }

  fun quotes(value: String) {
    props["quotes"] = prp(value)
  }

  fun resize(resize: Resize) {
    props["resize"] = prp(resize)
  }

  fun right(right: Measurement) {
    props["right"] = prp(right)
  }

  fun scrollBehavior(sb: ScrollBehavior) {
    props["scroll-behavior"] = prp(sb)
  }

  fun tabSize(number: Int) {
    props["tab-size"] = prp(number.toString())
  }

  fun tabSize(length: Measurement) {
    props["tab-size"] = prp(length)
  }

  fun tabSize(ts: InitialInherit) {
    props["tab-size"] = prp(ts)
  }

  fun tableLayout(tl: TableLayout) {
    props["table-layout"] = prp(tl)
  }

  fun textAlign(ta: TextAlign) {
    props["text-align"] = prp(ta)
  }

  fun textAlignLast(tal: TextAlignLast) {
    props["text-align-last"] = prp(tal)
  }

  fun textDecoration(decoration: String) {
    props["text-decoration"] = prp(decoration)
  }

  fun textDecorationColor(color: Color) {
    props["text-decoration-color"] = prp(color)
  }

  fun textDecorationLine(tdc: TextDecorationLine) {
    props["text-decoration-line"] = prp(tdc)
  }

  fun textDecorationStyle(tds: TextDecorationStyle) {
    props["text-decoration-style"] = prp(tds)
  }

  fun textIndent(length: Measurement) {
    props["text-indent"] = prp(length)
  }

  fun textIndent(indent: InitialInherit) {
    props["text-indent"] = prp(indent)
  }

  fun textJustify(tj: TextJustify) {
    props["text-justify"] = prp(tj)
  }

  fun textOverflow(to: String) {
    props["text-overflow"] = prp(to)
  }

  fun textShadow(ts: String) {
    props["text-shadow"] = prp(ts)
  }

  fun textTransform(tt: TextTransform) {
    props["text-transform"] = prp(tt)
  }

  fun top(top: Measurement) {
    props["top"] = prp(top)
  }

  fun transform(transform: Transform) {
    props["transform"] = prp(transform)
  }

  fun transformOrigin(origin: String) {
    props["transform-origin"] = prp(origin)
  }

  fun transformStyle(style: TransformStyle) {
    props["transform-style"] = prp(style)
  }

  fun transition(transition: String) {
    props["transition"] = prp(transition)
  }

  fun transitionDelay(timeInSeconds: Double) {
    props["transition-delay"] = prp("${timeInSeconds}s")
  }

  fun transitionDelay(timeInMillis: Int) {
    props["transition-delay"] = prp("${timeInMillis}ms")
  }

  fun transitionDelay(delay: DelayDuration) {
    props["transition-delay"] = prp(delay)
  }

  fun transitionDuration(timeInSeconds: Double) {
    props["transition-duration"] = prp("${timeInSeconds}s")
  }

  fun transitionDuration(timeInMillis: Int) {
    props["transition-duration"] = prp("${timeInMillis}ms")
  }

  fun transitionDuration(td: DelayDuration) {
    props["transition-duration"] = prp(td)
  }

  fun transitionProperty(property: String) {
    props["transition-property"] = prp(property)
  }

  fun transitionTimingFunction(function: TimingFunction) {
    props["transition-timing-function"] = prp(function)
  }

  fun unicodeBidi(ub: UnicodeBidi) {
    props["unicode-bidi"] = prp(ub)
  }

  fun userSelect(us: UserSelect) {
    props["user-select"] = prp(us)
  }

  fun verticalAlign(length: Measurement) {
    props["vertical-align"] = prp(length)
  }

  fun verticalAlign(va: VerticalAlign) {
    props["vertical-align"] = prp(va)
  }

  fun visibility(visibility: Visibility) {
    props["visibility"] = prp(visibility)
  }

  fun whiteSpace(whiteSpace: WhiteSpace) {
    props["white-space"] = prp(whiteSpace)
  }

  fun width(width: Measurement) {
    props["width"] = prp(width)
  }

  fun wordBreak(wordBreak: WordBreak) {
    props["word-break"] = prp(wordBreak)
  }

  fun wordSpacing(wordSpacing: Measurement) {
    props["word-spacing"] = prp(wordSpacing)
  }

  fun wordSpacing(wordSpacing: WordSpacing) {
    props["word-spacing"] = prp(wordSpacing)
  }

  fun wordWrap(wordWrap: WordWrap) {
    props["word-wrap"] = prp(wordWrap)
  }

  fun writingMode(writingMode: WritingMode) {
    props["writing-mode"] = prp(writingMode)
  }

  fun zIndex(zIndex: Int) {
    props["z-index"] = prp(zIndex.toString())
  }

  fun zIndex(zIndex: ZIndex) {
    props["z-index"] = prp(zIndex)
  }
}

@CssTagMarker
open class Style : InlineStyle() {
  var fontFace: FontFace? = null
  var keyFrames: MutableMap<String, KeyFrames> = mutableMapOf()

  private fun addStyle(selector: String, style: Css) {
    definitions[selector] = definitions[selector] ?: mutableListOf()
    definitions[selector]?.add(style)
  }

  /**
   * like the scss &
   */
  fun and(vararg selectors: DescriptionProvider, style: Css) {
    for (selector in selectors) {
      addStyle(selector.description(), style)
    }
  }

  fun and(vararg selectors: String, style: Css) {
    for (selector in selectors) {
      addStyle(selector, style)
    }
  }

  fun select(vararg selectors: DescriptionProvider, style: Css) {
    for (selector in selectors) {
      addStyle(" ${selector.description()}", style)
    }
  }

  fun select(vararg selectors: String, style: Css) {
    for (selector in selectors) {
      addStyle(" $selector", style)
    }
  }

  fun descendant(vararg descNames: DescriptionProvider, style: Css) {
    for (descName in descNames) {
      addStyle(" ${descName.description()}", style)
    }
  }

  fun descendant(vararg descNames: String, style: Css) {
    for (descName in descNames) {
      addStyle(" $descName", style)
    }
  }

  fun child(vararg childNames: DescriptionProvider, style: Css) {
    for (childName in childNames) {
      addStyle(" > ${childName.description()}", style)
    }
  }

  fun child(vararg childNames: String, style: Css) {
    for (childName in childNames) {
      addStyle(" > $childName", style)
    }
  }

  fun sibling(vararg childNames: DescriptionProvider, style: Css) {
    for (childName in childNames) {
      addStyle(" ~ ${childName.description()}", style)
    }
  }

  fun sibling(vararg childNames: String, style: Css) {
    for (childName in childNames) {
      addStyle(" ~ $childName", style)
    }
  }

  fun adjSibling(vararg childNames: DescriptionProvider, style: Css) {
    for (childName in childNames) {
      addStyle(" + ${childName.description()}", style)
    }
  }

  fun adjSibling(vararg childNames: String, style: Css) {
    for (childName in childNames) {
      addStyle(" + $childName", style)
    }
  }

  fun firstChild(style: Css) {
    addStyle(":first-child", style)
  }

  fun lastChild(style: Css) {
    addStyle(":last-child", style)
  }

  fun pseudoElement(selector: DescriptionProvider, style: Css) {
    addStyle(":${selector.description()}", style)
  }

  fun pseudoChild(selector: DescriptionProvider, style: Css) {
    addStyle("::${selector.description()}", style)
  }

  fun not(selector: DescriptionProvider, style: Css) {
    addStyle(":not(${selector.description()})", style)
  }

  fun fontFace(face: FontFace.() -> Unit) {
    fontFace = FontFace()

    face.invoke(fontFace!!)
  }

  fun import(style: Css) {
    style(this)
  }

  fun keyFrames(animationName: String, frames: KeyFrames.() -> Unit) {
    val frameCss = KeyFrames()

    frames.invoke(frameCss)

    keyFrames[animationName] = frameCss
  }
}

@CssTagMarker
open class ConditionalStyle : Style() {
  var media: MutableMap<String, ConditionalCss> = mutableMapOf()
  var supports: MutableMap<String, ConditionalCss> = mutableMapOf()

  fun media(definition: String, style: ConditionalCss) {
    media[definition] = style
  }

  fun supports(query: String, style: ConditionalCss) {
    supports[query] = style
  }
}