const path = require('path')
const hash = require('hash-sum')
const qs = require('querystring')
const plugin = require('./plugin')
const selectBlock = require('./select')
const loaderUtils = require('loader-utils')
const { attrsToQuery } = require('./codegen/utils')
const { parse } = require('@vue/component-compiler-utils')
const genStylesCode = require('./codegen/styleInjection')
const { genHotReloadCode } = require('./codegen/hotReload')
const genCustomBlocksCode = require('./codegen/customBlocks')
const componentNormalizerPath = require.resolve('./runtime/componentNormalizer')
const { NS } = require('./plugin')

let errorEmitted = false
let modules // h5 平台摇树优化时,需要保留编译器原始modules(因为框架内代码不需要modules,开发者代码需要)
function loadTemplateCompiler (loaderContext) {
  try {
    return require('vue-template-compiler')
  } catch (e) {
    if (/version mismatch/.test(e.toString())) {
      loaderContext.emitError(e)
    } else {
      loaderContext.emitError(new Error(
        `[vue-loader] vue-template-compiler must be installed as a peer dependency, ` +
        `or a compatible compiler implementation must be passed via options.`
      ))
    }
  }
}

module.exports = function (source) {
  const loaderContext = this

  if (!errorEmitted && !loaderContext['thread-loader'] && !loaderContext[NS]) {
    loaderContext.emitError(new Error(
      `vue-loader was used without the corresponding plugin. ` +
      `Make sure to include VueLoaderPlugin in your webpack config.`
    ))
    errorEmitted = true
  }

  const stringifyRequest = r => loaderUtils.stringifyRequest(loaderContext, r)

  const {
    target,
    request,
    minimize,
    sourceMap,
    rootContext,
    resourcePath,
    resourceQuery = ''
  } = loaderContext

  const rawQuery = resourceQuery.slice(1)
  const inheritQuery = `&${rawQuery}`
  const incomingQuery = qs.parse(rawQuery)
  const options = loaderUtils.getOptions(loaderContext) || {}

  const isServer = target === 'node'
  const isShadow = !!options.shadowMode
  const isProduction = options.productionMode || minimize || process.env.NODE_ENV === 'production'
  const filename = path.basename(resourcePath)
  const context = rootContext || process.cwd()
  const sourceRoot = path.dirname(path.relative(context, resourcePath))

  const descriptor = parse({
    source,
    compiler: options.compiler || loadTemplateCompiler(loaderContext),
    filename,
    sourceRoot,
    needMap: sourceMap,
    isAppService: options.isAppService,
    isAppView: options.isAppView,
    isAppNVue: options.isAppNVue
  })

  if (options.isH5TreeShaking) { // 摇树优化逻辑(框架组件移除样式,禁用 modules)
    const isWin = /^win/.test(process.platform)
    const normalizePath = path => (isWin ? path.replace(/\\/g, '/') : path)

    if (!options.compilerOptions) {
      options.compilerOptions = {}
    }
    options.compilerOptions.autoComponentResourcePath = normalizePath(resourcePath)

    // fixed by xxxxxx
    if (!modules && options.compilerOptions && options.compilerOptions.modules) {
      modules = options.compilerOptions.modules
    }
    const sourcePath = normalizePath(require('@dcloudio/uni-h5/path').src)
    if (normalizePath(this.resourcePath).indexOf(sourcePath) === 0) {
      descriptor.styles.length = 0
      options.compilerOptions && (delete options.compilerOptions.modules)
    } else if (options.compilerOptions) {
      options.compilerOptions.modules = modules
    }
  }

  // if the query has a type field, this is a language block request
  // e.g. foo.vue?type=template&id=xxxxx
  // and we will return early
  if (incomingQuery.type) {
    return selectBlock(
      descriptor,
      loaderContext,
      incomingQuery,
      !!options.appendExtension
    )
  }

  // module id for scoped CSS & hot-reload
  const rawShortFilePath = path // fixed by xxxxxx
    .relative(process.env.UNI_INPUT_DIR || context, resourcePath)
    .replace(/^(\.\.[\/\\])+/, '')

  const shortFilePath = rawShortFilePath.replace(/\\/g, '/') + resourceQuery

  const id = hash(
    isProduction
      ? shortFilePath + '\n' + source.replace(/\r\n/g, '\n')
      : shortFilePath
  )

  // feature information
  const hasScoped = descriptor.styles.some(s => s.scoped)
  const hasFunctional = descriptor.template && descriptor.template.attrs.functional
  const needsHotReload = (
    !isServer &&
    !isProduction &&
    (descriptor.script || descriptor.template) &&
    options.hotReload !== false
  )

  // template
  // fixed by xxxxxx (recyclable,auto components)
  let recyclable
  let templateImport = `var render, staticRenderFns, recyclableRender, components`
  let templateRequest
  if (descriptor.template) {
    // fixed by xxxxxx
    recyclable = options.isAppNVue && !!(descriptor.template.attrs && descriptor.template.attrs.recyclable)
    const src = descriptor.template.src || resourcePath
    const idQuery = `&id=${id}`
    const scopedQuery = hasScoped ? `&scoped=true` : ``
    const attrsQuery = attrsToQuery(descriptor.template.attrs)
    const query = `?vue&type=template${idQuery}${scopedQuery}${attrsQuery}${inheritQuery}`
    const request = templateRequest = stringifyRequest(src + query)
    // fixed by xxxxxx (auto components)
    templateImport = `import { render, staticRenderFns, recyclableRender, components } from ${request}`
  }

  // script
  let scriptImport = `var script = {}`
  if (descriptor.script) {
    const src = descriptor.script.src || resourcePath
    const attrsQuery = attrsToQuery(descriptor.script.attrs, 'js')
    const query = `?vue&type=script${attrsQuery}${inheritQuery}`
    const request = stringifyRequest(src + query)
    scriptImport = (
      `import script from ${request}\n` +
      `export * from ${request}` // support named exports
    )
  }

  let renderjsImport = `var renderjs`
  if ((options.isAppView || options.isH5) && descriptor.renderjs) {
    const src = descriptor.renderjs.src || resourcePath
    const attrsQuery = attrsToQuery(descriptor.renderjs.attrs, 'js')
    const query = `?vue&type=renderjs${attrsQuery}${inheritQuery}`
    const request = stringifyRequest(src + query)
    renderjsImport = (
      `import renderjs from ${request}\n` +
      `renderjs.__module = '${descriptor.renderjs.attrs.module}'\n` +
      `export * from ${request}` // support named exports
    )
  }


  // styles
  let stylesCode = ``
  // fixed by xxxxxx 仅限 view 层
  if (!options.isAppService && descriptor.styles.length) {
    stylesCode = genStylesCode(
      loaderContext,
      descriptor.styles,
      id,
      resourcePath,
      stringifyRequest,
      needsHotReload,
      isServer || isShadow // needs explicit injection?
    )
  }
  // fixed by xxxxxx (injectStyles,auto components)
  let code = `
${templateImport}
${renderjsImport}
${scriptImport}
${stylesCode}

/* normalize component */
import normalizer from ${stringifyRequest(`!${componentNormalizerPath}`)}
var component = normalizer(
  script,
  render,
  staticRenderFns,
  ${hasFunctional ? `true` : `false`},
  ${options.isAppNVue ? `null` : (/injectStyles/.test(stylesCode) ? `injectStyles` : `null`)},
  ${hasScoped ? JSON.stringify(id) : `null`},
  ${isServer ? JSON.stringify(hash(request)) : `null`},
  ${isShadow ? `true` : `false`},
  components,
  renderjs
)
  `.trim() + `\n`

  if (descriptor.customBlocks && descriptor.customBlocks.length) {
    code += genCustomBlocksCode(
      descriptor.customBlocks,
      resourcePath,
      resourceQuery,
      stringifyRequest
    )
  }

  if (needsHotReload) {
    code += `\n` + genHotReloadCode(id, hasFunctional, templateRequest)
  }
  // fixed by xxxxxx (app-nvue injectStyles)
  if (options.isAppNVue && /injectStyles/.test(stylesCode)) {
    code += `\ninjectStyles.call(component)`
  }
  // Expose filename. This is used by the devtools and Vue runtime warnings.
  if (!isProduction) {
    // Expose the file's full path in development, so that it can be opened
    // from the devtools.
    code += `\ncomponent.options.__file = ${JSON.stringify(rawShortFilePath.replace(/\\/g, '/'))}`
  } else if (options.exposeFilename) {
    // Libraries can opt-in to expose their components' filenames in production builds.
    // For security reasons, only expose the file's basename in production.
    code += `\ncomponent.options.__file = ${JSON.stringify(filename)}`
  }
  if (recyclable) { // fixed by xxxxxx app-plus recyclable
    code += `\nrecyclableRender && (component.options["@render"] = recyclableRender)` // fixed by xxxxxx
  }
  code += `\nexport default component.exports`
  return code
}

module.exports.VueLoaderPlugin = plugin
