import path from 'path'
import pify from 'pify'
import resolve from 'resolve'
import PQueue from 'p-queue'
import { loadModule } from './utils/load-module'

// This queue makes sure node-sass leaves one thread available for executing fs tasks
// See: https://github.com/sass/node-sass/issues/857
const threadPoolSize = process.env.UV_THREADPOOL_SIZE || 4
const workQueue = new PQueue({ concurrency: threadPoolSize - 1 })

const moduleRe = /^~([a-z\d]|@).+/i

const getUrlOfPartial = url => {
  const parsedUrl = path.parse(url)
  return `${parsedUrl.dir}${path.sep}_${parsedUrl.base}`
}

const resolvePromise = pify(resolve)

// List of supported SASS modules in the order of preference
const sassModuleIds = ['sass', 'node-sass']

/* eslint import/no-anonymous-default-export: [2, {"allowObject": true}] */
export default {
  name: 'sass',
  test: /\.(sass|scss)$/,
  process({ code }) {
    return new Promise((resolve, reject) => {
      const sass = loadSassOrThrow()
      const render = pify(sass.render.bind(sass))
      const data = this.options.data || ''
      workQueue.add(() =>
        render({
          ...this.options,
          file: this.id,
          data: data + code,
          indentedSyntax: /\.sass$/.test(this.id),
          sourceMap: this.sourceMap,
          importer: [
            (url, importer, done) => {
              if (!moduleRe.test(url)) return done({ file: url })

              const moduleUrl = url.slice(1)
              const partialUrl = getUrlOfPartial(moduleUrl)

              const options = {
                basedir: path.dirname(importer),
                extensions: ['.scss', '.sass', '.css']
              }
              const finishImport = id => {
                done({
                  // Do not add `.css` extension in order to inline the file
                  file: id.endsWith('.css') ? id.replace(/\.css$/, '') : id
                })
              }

              const next = () => {
                // Catch all resolving errors, return the original file and pass responsibility back to other custom importers
                done({ file: url })
              }

              // Give precedence to importing a partial
              resolvePromise(partialUrl, options)
                .then(finishImport)
                .catch(error => {
                  if (
                    error.code === 'MODULE_NOT_FOUND' ||
                    error.code === 'ENOENT'
                  ) {
                    resolvePromise(moduleUrl, options)
                      .then(finishImport)
                      .catch(next)
                  } else {
                    next()
                  }
                })
            }
          ].concat(this.options.importer || [])
        })
          .then(result => {
            for (const file of result.stats.includedFiles) {
              this.dependencies.add(file)
            }

            resolve({
              code: result.css.toString(),
              map: result.map && result.map.toString()
            })
          })
          .catch(reject)
      )
    })
  }
}

function loadSassOrThrow() {
  // Loading one of the supported modules
  for (const moduleId of sassModuleIds) {
    const module = loadModule(moduleId)
    if (module) {
      return module
    }
  }

  // Throwing exception if module can't be loaded
  throw new Error(
    'You need to install one of the following packages: ' +
    sassModuleIds.map(moduleId => `"${moduleId}"`).join(', ') + ' ' +
    'in order to process SASS files'
  )
}
