/**
 * @license
 * Copyright 2018 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const defaultOptions = require('./lib/default-options')
const determineAsValue = require('./lib/determine-as-value')
const doesChunkBelongToHTML = require('./lib/does-chunk-belong-to-html')
const extractChunks = require('./lib/extract-chunks')

class PreloadPlugin {
  constructor (options) {
    this.options = Object.assign({}, defaultOptions, options)
  }

  generateLinks (compilation, htmlPluginData) {
    const options = this.options
    const extractedChunks = extractChunks({
      compilation,
      optionsInclude: options.include
    })

    const htmlChunks = options.include === 'allAssets'
      // Handle all chunks.
      ? extractedChunks
      // Only handle chunks imported by this HtmlWebpackPlugin.
      : extractedChunks.filter((chunk) => doesChunkBelongToHTML({
        chunk,
        compilation,
        htmlAssetsChunks: Object.values(htmlPluginData.assets.chunks)
      }))

    // Flatten the list of files.
    const allFiles = htmlChunks.reduce((accumulated, chunk) => {
      return accumulated.concat(chunk.files)
    }, [])
    const uniqueFiles = new Set(allFiles)
    const filteredFiles = [...uniqueFiles].filter(file => {
      return (
        !this.options.fileWhitelist ||
        this.options.fileWhitelist.some(regex => regex.test(file))
      )
    }).filter(file => {
      return (
        !this.options.fileBlacklist ||
        this.options.fileBlacklist.every(regex => !regex.test(file))
      )
    })
    // Sort to ensure the output is predictable.
    const sortedFilteredFiles = filteredFiles.sort()

    const links = []
    const publicPath = compilation.outputOptions.publicPath || ''
    for (const file of sortedFilteredFiles) {
      const href = `${publicPath}${file}`

      const attributes = {
        href,
        rel: options.rel
      }

      // If we're preloading this resource (as opposed to prefetching),
      // then we need to set the 'as' attribute correctly.
      if (options.rel === 'preload') {
        attributes.as = determineAsValue({
          href,
          optionsAs: options.as
        })

        // On the off chance that we have a cross-origin 'href' attribute,
        // set crossOrigin on the <link> to trigger CORS mode. Non-CORS
        // fonts can't be used.
        if (attributes.as === 'font') {
          attributes.crossorigin = ''
        }
      }

      links.push({
        tagName: 'link',
        attributes
      })
    }

    this.resourceHints = links
    return htmlPluginData
  }

  apply (compiler) {
    const skip = data => {
      const htmlFilename = data.plugin.options.filename
      const exclude = this.options.excludeHtmlNames
      const include = this.options.includeHtmlNames
      return (
        (include && !(include.includes(htmlFilename))) ||
        (exclude && exclude.includes(htmlFilename))
      )
    }

    compiler.hooks.compilation.tap(
      this.constructor.name,
      compilation => {
        compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing.tap(
          this.constructor.name,
          (htmlPluginData) => {
            if (skip(htmlPluginData)) {
              return
            }
            this.generateLinks(compilation, htmlPluginData)
          }
        )

        compilation.hooks.htmlWebpackPluginAlterAssetTags.tap(
          this.constructor.name,
          (htmlPluginData) => {
            if (skip(htmlPluginData)) {
              return
            }
            if (this.resourceHints) {
              htmlPluginData.head = [
                ...this.resourceHints,
                ...htmlPluginData.head
              ]
            }
            return htmlPluginData
          }
        )
      }
    )
  }
}

module.exports = PreloadPlugin
