# frozen_string_literal: true

require 'fileutils'

module Tasks
  module Gitlab
    module Assets
      FOSS_ASSET_FOLDERS = %w[app/assets fixtures/emojis vendor/assets].freeze
      EE_ASSET_FOLDERS = %w[ee/app/assets].freeze
      JH_ASSET_FOLDERS = %w[jh/app/assets].freeze
      # In the new caching strategy, we check the assets hash sum *before* compiling
      # the app/assets/javascripts/locale/**/app.js files. That means the hash sum
      # must depend on locale/**/gitlab.po.
      JS_ASSET_PATTERNS = %w[*.js config/**/*.js locale/**/gitlab.po].freeze
      JS_ASSET_FILES = %w[
        package.json
        yarn.lock
        babel.config.js
        config/webpack.config.js
      ].freeze
      EXCLUDE_PATTERNS = %w[
        app/assets/javascripts/locale/**/app.js
      ].freeze
      PUBLIC_ASSETS_DIR = 'public/assets'
      HEAD_ASSETS_SHA256_HASH_ENV = 'GITLAB_ASSETS_HASH'
      CACHED_ASSETS_SHA256_HASH_FILE = 'cached-assets-hash.txt'

      def self.master_assets_sha256
        @master_assets_sha256 ||=
          if File.exist?(Tasks::Gitlab::Assets::CACHED_ASSETS_SHA256_HASH_FILE)
            File.read(Tasks::Gitlab::Assets::CACHED_ASSETS_SHA256_HASH_FILE)
          else
            'missing!'
          end
      end

      def self.head_assets_sha256
        @head_assets_sha256 ||= ENV.fetch(Tasks::Gitlab::Assets::HEAD_ASSETS_SHA256_HASH_ENV) do
          Tasks::Gitlab::Assets.sha256_of_assets_impacting_compilation(verbose: false)
        end
      end

      def self.sha256_of_assets_impacting_compilation(verbose: true)
        start_time = Time.now
        asset_files = assets_impacting_compilation
        puts "Generating the SHA256 hash for #{asset_files.size} Webpack-related assets..." if verbose

        assets_sha256 = asset_files.map { |asset_file| Digest::SHA256.file(asset_file).hexdigest }.join

        Digest::SHA256.hexdigest(assets_sha256).tap { |sha256| puts "=> SHA256 generated in #{Time.now - start_time}: #{sha256}" if verbose }
      end

      def self.assets_impacting_compilation
        assets_folders = FOSS_ASSET_FOLDERS
        assets_folders += EE_ASSET_FOLDERS if ::Gitlab.ee?
        assets_folders += JH_ASSET_FOLDERS if ::Gitlab.jh?

        asset_files = Dir.glob(JS_ASSET_PATTERNS)
        asset_files += JS_ASSET_FILES

        assets_folders.each do |folder|
          asset_files.concat(Dir.glob(["#{folder}/**/*.*"]))
        end

        asset_files - Dir.glob(EXCLUDE_PATTERNS)
      end
      private_class_method :assets_impacting_compilation
    end
  end
end

namespace :gitlab do
  namespace :assets do
    desc 'GitLab | Assets | Return the hash sum of all frontend assets'
    task :hash_sum do
      print Tasks::Gitlab::Assets.sha256_of_assets_impacting_compilation(verbose: false)
    end

    desc 'GitLab | Assets | Compile all frontend assets'
    task :compile do
      require_dependency 'gitlab/task_helpers'

      puts "Assets SHA256 for `master`: #{Tasks::Gitlab::Assets.master_assets_sha256.inspect}"
      puts "Assets SHA256 for `HEAD`: #{Tasks::Gitlab::Assets.head_assets_sha256.inspect}"

      if Tasks::Gitlab::Assets.head_assets_sha256 != Tasks::Gitlab::Assets.master_assets_sha256
        FileUtils.rm_rf([Tasks::Gitlab::Assets::PUBLIC_ASSETS_DIR] + Dir.glob('app/assets/javascripts/locale/**/app.js'))

        # gettext:po_to_json needs to run before rake:assets:precompile because
        # app/assets/javascripts/locale/**/app.js are pre-compiled by Sprockets
        Gitlab::TaskHelpers.invoke_and_time_task('gettext:po_to_json')
        Gitlab::TaskHelpers.invoke_and_time_task('rake:assets:precompile')

        log_path = ENV['WEBPACK_COMPILE_LOG_PATH']

        cmd = 'yarn webpack'
        cmd += " > #{log_path}" if log_path

        unless system(cmd)
          abort 'Error: Unable to compile webpack production bundle.'.color(:red)
        end

        puts "Written webpack stdout log to #{log_path}" if log_path
        puts "You can inspect the webpack log here: #{ENV['CI_JOB_URL']}/artifacts/file/#{log_path}" if log_path && ENV['CI_JOB_URL']

        Gitlab::TaskHelpers.invoke_and_time_task('gitlab:assets:fix_urls')
        Gitlab::TaskHelpers.invoke_and_time_task('gitlab:assets:check_page_bundle_mixins_css_for_sideeffects')
      end
    end

    desc 'GitLab | Assets | Clean up old compiled frontend assets'
    task clean: ['rake:assets:clean']

    desc 'GitLab | Assets | Remove all compiled frontend assets'
    task purge: ['rake:assets:clobber']

    desc 'GitLab | Assets | Uninstall frontend dependencies'
    task purge_modules: ['yarn:clobber']

    desc 'GitLab | Assets | Fix all absolute url references in CSS'
    task :fix_urls do
      css_files = Dir['public/assets/*.css']
      css_files.each do |file|
        # replace url(/assets/*) with url(./*)
        puts "Fixing #{file}"
        system "sed", "-i", "-e", 's/url(\([\"\']\?\)\/assets\//url(\1.\//g', file

        # rewrite the corresponding gzip file (if it exists)
        gzip = "#{file}.gz"
        next unless File.exist?(gzip)

        puts "Fixing #{gzip}"

        FileUtils.rm(gzip)
        mtime = File.stat(file).mtime

        File.open(gzip, 'wb+') do |f|
          gz = Zlib::GzipWriter.new(f, Zlib::BEST_COMPRESSION)
          gz.mtime = mtime
          gz.write IO.binread(file)
          gz.close

          File.utime(mtime, mtime, f.path)
        end
      end
    end

    desc 'GitLab | Assets | Compile vendor assets'
    task :vendor do
      unless system('yarn webpack-vendor')
        abort 'Error: Unable to compile webpack DLL.'.color(:red)
      end
    end

    desc 'GitLab | Assets | Check that scss mixins do not introduce any sideffects'
    task :check_page_bundle_mixins_css_for_sideeffects do
      system('./scripts/frontend/check_page_bundle_mixins_css_for_sideeffects.js')
    end
  end
end