import {TAG_LOGIC_PROPERTY} from '../../constants'
import addLinesOffset from '../../utils/add-lines-offset'
import generateAST from '../../utils/generate-ast'
import getPreprocessorTypeByAttribute from '../../utils/get-preprocessor-type-by-attribute'
import isEmptySourcemap from '../../utils/is-empty-sourcemap'
import {isExportDefaultStatement} from '../../utils/ast-nodes-checks'
import preprocess from '../../utils/preprocess-node'
import sourcemapToJSON from '../../utils/sourcemap-as-json'
import {types} from '../../utils/build-types'

/**
 * Find the export default statement
 * @param   { Array } body - tree structure containing the program code
 * @returns { Object } node containing only the code of the export default statement
 */
function findExportDefaultStatement(body) {
  return body.find(isExportDefaultStatement)
}

/**
 * Find all the code in an ast program except for the export default statements
 * @param   { Array } body - tree structure containing the program code
 * @returns { Array } array containing all the program code except the export default expressions
 */
function filterNonExportDefaultStatements(body) {
  return body.filter(node => !isExportDefaultStatement(node))
}

/**
 * Get the body of the AST structure
 * @param   { Object } ast - ast object generated by recast
 * @returns { Array } array containing the program code
 */
function getProgramBody(ast) {
  return ast.body || ast.program.body
}

/**
 * Extend the AST adding the new tag method containing our tag sourcecode
 * @param   { Object } ast - current output ast
 * @param   { Object } exportDefaultNode - tag export default node
 * @returns { Object } the output ast having the "tag" key extended with the content of the export default
 */
function extendTagProperty(ast, exportDefaultNode) {
  types.visit(ast, {
    visitProperty(path) {
      if (path.value.key.value === TAG_LOGIC_PROPERTY) {
        path.value.value = exportDefaultNode.declaration
        return false
      }

      this.traverse(path)
    }
  })

  return ast
}

/**
 * Generate the component javascript logic
 * @param   { Object } sourceNode - node generated by the riot compiler
 * @param   { string } source - original component source code
 * @param   { Object } meta - compilation meta information
 * @param   { AST } ast - current AST output
 * @returns { AST } the AST generated
 */
export default function javascript(sourceNode, source, meta, ast) {
  const preprocessorName = getPreprocessorTypeByAttribute(sourceNode)
  const javascriptNode = addLinesOffset(sourceNode.text.text, source, sourceNode)
  const { options } = meta
  const preprocessorOutput = preprocess('javascript', preprocessorName, meta, {
    ...sourceNode,
    text: javascriptNode
  })
  const inputSourceMap = sourcemapToJSON(preprocessorOutput.map)
  const generatedAst = generateAST(preprocessorOutput.code, {
    sourceFileName: options.file,
    inputSourceMap: isEmptySourcemap(inputSourceMap) ? null : inputSourceMap
  })
  const generatedAstBody = getProgramBody(generatedAst)
  const bodyWithoutExportDefault = filterNonExportDefaultStatements(generatedAstBody)
  const exportDefaultNode = findExportDefaultStatement(generatedAstBody)
  const outputBody = getProgramBody(ast)

  // add to the ast the "private" javascript content of our tag script node
  outputBody.unshift(...bodyWithoutExportDefault)

  // convert the export default adding its content to the "tag" property exported
  if (exportDefaultNode) extendTagProperty(ast, exportDefaultNode)

  return ast
}