// @flow

import { serializeStyles } from '@emotion/serialize'
import minify from './minify'
import { getLabelFromPath } from './label'
import { getSourceMap } from './source-maps'
import { simplifyObject } from './object-to-string'
import {
  appendStringReturningExpressionToArguments,
  joinStringLiterals
} from './strings'
import createNodeEnvConditional from './create-node-env-conditional'

const CSS_OBJECT_STRINGIFIED_ERROR =
  "You have tried to stringify object returned from `css` function. It isn't supposed to be used directly (e.g. as value of the `className` prop), but rather handed to emotion so it can handle it (e.g. as value of `css` prop)."

export let transformExpressionWithStyles = ({
  babel,
  state,
  path,
  shouldLabel,
  sourceMap = ''
}: {
  babel: *,
  state: *,
  path: *,
  shouldLabel: boolean,
  sourceMap?: string
}): * => {
  const autoLabel = state.opts.autoLabel || 'dev-only'
  let t = babel.types
  if (t.isTaggedTemplateExpression(path)) {
    if (
      !sourceMap &&
      state.emotionSourceMap &&
      path.node.quasi.loc !== undefined
    ) {
      sourceMap = getSourceMap(path.node.quasi.loc.start, state)
    }
    minify(path, t)
  }

  if (t.isCallExpression(path)) {
    const canAppendStrings = path.node.arguments.every(
      arg => arg.type !== 'SpreadElement'
    )

    path.get('arguments').forEach(node => {
      if (t.isObjectExpression(node)) {
        node.replaceWith(simplifyObject(node.node, t))
      }
    })

    path.node.arguments = joinStringLiterals(path.node.arguments, t)

    if (
      !sourceMap &&
      canAppendStrings &&
      state.emotionSourceMap &&
      path.node.loc !== undefined
    ) {
      sourceMap = getSourceMap(path.node.loc.start, state)
    }

    const label =
      shouldLabel && autoLabel !== 'never'
        ? getLabelFromPath(path, state, t)
        : null

    if (
      path.node.arguments.length === 1 &&
      t.isStringLiteral(path.node.arguments[0])
    ) {
      let cssString = path.node.arguments[0].value.replace(/;$/, '')
      let res = serializeStyles([
        `${cssString}${
          label && autoLabel === 'always' ? `;label:${label};` : ''
        }`
      ])
      let prodNode = t.objectExpression([
        t.objectProperty(t.identifier('name'), t.stringLiteral(res.name)),
        t.objectProperty(t.identifier('styles'), t.stringLiteral(res.styles))
      ])

      if (!state.emotionStringifiedCssId) {
        const uid = state.file.scope.generateUidIdentifier(
          '__EMOTION_STRINGIFIED_CSS_ERROR__'
        )
        state.emotionStringifiedCssId = uid
        const cssObjectToString = t.functionDeclaration(
          uid,
          [],
          t.blockStatement([
            t.returnStatement(t.stringLiteral(CSS_OBJECT_STRINGIFIED_ERROR))
          ])
        )
        cssObjectToString._compact = true
        state.file.path.unshiftContainer('body', [cssObjectToString])
      }

      if (label && autoLabel === 'dev-only') {
        res = serializeStyles([`${cssString};label:${label};`])
      }

      let devNode = t.objectExpression(
        [
          t.objectProperty(t.identifier('name'), t.stringLiteral(res.name)),
          t.objectProperty(t.identifier('styles'), t.stringLiteral(res.styles)),
          sourceMap &&
            t.objectProperty(t.identifier('map'), t.stringLiteral(sourceMap)),
          t.objectProperty(
            t.identifier('toString'),
            t.cloneNode(state.emotionStringifiedCssId)
          )
        ].filter(Boolean)
      )

      return createNodeEnvConditional(t, prodNode, devNode)
    }

    if (canAppendStrings && label) {
      const labelString = `;label:${label};`

      switch (autoLabel) {
        case 'dev-only': {
          const labelConditional = createNodeEnvConditional(
            t,
            t.stringLiteral(''),
            t.stringLiteral(labelString)
          )
          appendStringReturningExpressionToArguments(t, path, labelConditional)
          break
        }
        case 'always':
          appendStringReturningExpressionToArguments(t, path, labelString)
          break
      }
    }

    if (sourceMap) {
      let sourceMapConditional = createNodeEnvConditional(
        t,
        t.stringLiteral(''),
        t.stringLiteral(sourceMap)
      )
      appendStringReturningExpressionToArguments(t, path, sourceMapConditional)
    }
  }
}
