text: /* @flow */
  
  // The SSR codegen is essentially extending the default codegen to handle
  // SSR-optimizable nodes and turn them into string render fns. In cases where
  // a node is not optimizable it simply falls back to the default codegen.
  
  import {
    genIf,
    genFor,
    genData,
    genText,
    genElement,
    genChildren,
    CodegenState
  } from 'compiler/codegen/index'
  
  import {
    genAttrSegments,
    genDOMPropSegments,
    genClassSegments,
    genStyleSegments,
    applyModelTransform
  } from './modules'
  
  import { escape } from 'web/server/util'
  import { optimizability } from './optimizer'
  import type { CodegenResult } from 'compiler/codegen/index'
  
  export type StringSegment = {
    type: number;
    value: string;
  };
  
  // segment types
  export const RAW = 0
  export const INTERPOLATION = 1
  export const EXPRESSION = 2
  
  export function generate (
    ast: ASTElement | void,
    options: CompilerOptions
  ): CodegenResult {
    const state = new CodegenState(options)
    const code = ast ? genSSRElement(ast, state) : '_c("div")'
    return {
      render: `with(this){return ${code}}`,
      staticRenderFns: state.staticRenderFns
    }
  }
  
  function genSSRElement (el: ASTElement, state: CodegenState): string {
    if (el.for && !el.forProcessed) {
      return genFor(el, state, genSSRElement)
    } else if (el.if && !el.ifProcessed) {
      return genIf(el, state, genSSRElement)
    } else if (el.tag === 'template' && !el.slotTarget) {
      return el.ssrOptimizability === optimizability.FULL
        ? genChildrenAsStringNode(el, state)
        : genSSRChildren(el, state) || 'void 0'
    }
  
    switch (el.ssrOptimizability) {
      case optimizability.FULL:
        // stringify whole tree
        return genStringElement(el, state)
      case optimizability.SELF:
        // stringify self and check children
        return genStringElementWithChildren(el, state)
      case optimizability.CHILDREN:
        // generate self as VNode and stringify children
        return genNormalElement(el, state, true)
      case optimizability.PARTIAL:
        // generate self as VNode and check children
        return genNormalElement(el, state, false)
      default:
        // bail whole tree
        return genElement(el, state)
    }
  }
  
  function genNormalElement (el, state, stringifyChildren) {
    const data = el.plain ? undefined : genData(el, state)
    const children = stringifyChildren
      ? `[${genChildrenAsStringNode(el, state)}]`
      : genSSRChildren(el, state, true)
    return `_c('${el.tag}'${
      data ? `,${data}` : ''
    }${
      children ? `,${children}` : ''
    })`
  }
  
  function genSSRChildren (el, state, checkSkip) {
    return genChildren(el, state, checkSkip, genSSRElement, genSSRNode)
  }
  
  function genSSRNode (el, state) {
    return el.type === 1
      ? genSSRElement(el, state)
      : genText(el)
  }
  
  function genChildrenAsStringNode (el, state) {
    return el.children.length
      ? `_ssrNode(${flattenSegments(childrenToSegments(el, state))})`
      : ''
  }
  
  function genStringElement (el, state) {
    return `_ssrNode(${elementToString(el, state)})`
  }
  
  function genStringElementWithChildren (el, state) {
    const children = genSSRChildren(el, state, true)
    return `_ssrNode(${
      flattenSegments(elementToOpenTagSegments(el, state))
    },"</${el.tag}>"${
      children ? `,${children}` : ''
    })`
  }
  
  function elementToString (el, state) {
    return `(${flattenSegments(elementToSegments(el, state))})`
  }
  
  function elementToSegments (el, state): Array<StringSegment> {
    // v-for / v-if
    if (el.for && !el.forProcessed) {
      el.forProcessed = true
      return [{
        type: EXPRESSION,
        value: genFor(el, state, elementToString, '_ssrList')
      }]
    } else if (el.if && !el.ifProcessed) {
      el.ifProcessed = true
      return [{
        type: EXPRESSION,
        value: genIf(el, state, elementToString, '"<!---->"')
      }]
    } else if (el.tag === 'template') {
      return childrenToSegments(el, state)
    }
  
    const openSegments = elementToOpenTagSegments(el, state)
    const childrenSegments = childrenToSegments(el, state)
    const { isUnaryTag } = state.options
    const close = (isUnaryTag && isUnaryTag(el.tag))
      ? []
      : [{ type: RAW, value: `</${el.tag}>` }]
    return openSegments.concat(childrenSegments, close)
  }
  
  function elementToOpenTagSegments (el, state): Array<StringSegment> {
    applyModelTransform(el, state)
    let binding
    const segments = [{ type: RAW, value: `<${el.tag}` }]
    // attrs
    if (el.attrs) {
      segments.push.apply(segments, genAttrSegments(el.attrs))
    }
    // domProps
    if (el.props) {
      segments.push.apply(segments, genDOMPropSegments(el.props, el.attrs))
    }
    // v-bind="object"
    if ((binding = el.attrsMap['v-bind'])) {
      segments.push({ type: EXPRESSION, value: `_ssrAttrs(${binding})` })
    }
    // v-bind.prop="object"
    if ((binding = el.attrsMap['v-bind.prop'])) {
      segments.push({ type: EXPRESSION, value: `_ssrDOMProps(${binding})` })
    }
    // class
    if (el.staticClass || el.classBinding) {
      segments.push.apply(
        segments,
        genClassSegments(el.staticClass, el.classBinding)
      )
    }
    // style & v-show
    if (el.staticStyle || el.styleBinding || el.attrsMap['v-show']) {
      segments.push.apply(
        segments,
        genStyleSegments(
          el.attrsMap.style,
          el.staticStyle,
          el.styleBinding,
          el.attrsMap['v-show']
        )
      )
    }
    // _scopedId
    if (state.options.scopeId) {
      segments.push({ type: RAW, value: ` ${state.options.scopeId}` })
    }
    segments.push({ type: RAW, value: `>` })
    return segments
  }
  
  function childrenToSegments (el, state): Array<StringSegment> {
    let binding
    if ((binding = el.attrsMap['v-html'])) {
      return [{ type: EXPRESSION, value: `_s(${binding})` }]
    }
    if ((binding = el.attrsMap['v-text'])) {
      return [{ type: INTERPOLATION, value: `_s(${binding})` }]
    }
    if (el.tag === 'textarea' && (binding = el.attrsMap['v-model'])) {
      return [{ type: INTERPOLATION, value: `_s(${binding})` }]
    }
    return el.children
      ? nodesToSegments(el.children, state)
      : []
  }
  
  function nodesToSegments (
    children: Array<ASTNode>,
    state: CodegenState
  ): Array<StringSegment> {
    const segments = []
    for (let i = 0; i < children.length; i++) {
      const c = children[i]
      if (c.type === 1) {
        segments.push.apply(segments, elementToSegments(c, state))
      } else if (c.type === 2) {
        segments.push({ type: INTERPOLATION, value: c.expression })
      } else if (c.type === 3) {
        let text = escape(c.text)
        if (c.isComment) {
          text = '<!--' + text + '-->'
        }
        segments.push({ type: RAW, value: text })
      }
    }
    return segments
  }
  
  function flattenSegments (segments: Array<StringSegment>): string {
    const mergedSegments = []
    let textBuffer = ''
  
    const pushBuffer = () => {
      if (textBuffer) {
        mergedSegments.push(JSON.stringify(textBuffer))
        textBuffer = ''
      }
    }
  
    for (let i = 0; i < segments.length; i++) {
      const s = segments[i]
      if (s.type === RAW) {
        textBuffer += s.value
      } else if (s.type === INTERPOLATION) {
        pushBuffer()
        mergedSegments.push(`_ssrEscape(${s.value})`)
      } else if (s.type === EXPRESSION) {
        pushBuffer()
        mergedSegments.push(`(${s.value})`)
      }
    }
    pushBuffer()
  
    return mergedSegments.join('+')
  }
  