require "requirejs/error"
require "requirejs/rails/view"

module RequirejsHelper
  def self.included(clazz)
    clazz.class_eval do
      extend Forwardable

      # Delegate all JavaScript path queries to the specially modified internal view.
      def_delegators :view, :javascript_path
    end
  end

  # EXPERIMENTAL: Additional priority settings appended to
  # any user-specified priority setting by requirejs_include_tag.
  # Used for JS test suite integration.
  mattr_accessor :_priority
  @@_priority = []

  def requirejs_include_tag(name = nil, &block)
    requirejs = Rails.application.config.requirejs

    if requirejs.loader == :almond
      name = requirejs.module_name_for(requirejs.build_config['modules'][0])
      return almond_include_tag(name, &block)
    end

    html = ""

    once_guard do
      rjs_attributes = {
          src: [host_url, javascript_path("require")].join
      }

      rjs_attributes = rjs_attributes.merge(Hash[block.call(controller).map do |key, value|
        ["data-#{key}", value]
      end]) \
        if block

      html.concat(content_tag(:script, "", rjs_attributes))

      unless requirejs.run_config.empty?
        run_config = requirejs.run_config.dup

        unless _priority.empty?
          run_config = run_config.dup
          run_config[:priority] ||= []
          run_config[:priority].concat _priority
        end

        if Rails.application.config.assets.digest
          assets_precompiled = !Rails.application.config.assets.compile
          modules = requirejs.build_config["modules"].map {|m| requirejs.module_name_for m}
          user_paths = requirejs.build_config["paths"] || {}

          # Generate digestified paths from the modules spec
          paths = {}

          modules.each do |module_name|
            script_path = if !assets_precompiled
              # If modules haven't been precompiled, search for them based on their user-defined paths before using the
              # module name.
              user_paths[module_name] || module_name
            else
              # If modules have been precompiled, the script path is just the module name.
              module_name
            end

            normalized_script_path = javascript_path(script_path).gsub(/\.js$/, "")

            if !host_url
              paths[module_name] = normalized_script_path
            else
              paths[module_name] = [host_url, normalized_script_path].join
            end
          end

          if run_config.has_key? "paths"
            # Add paths for assets specified by full URL (on a CDN)
            run_config["paths"].each do |k, v|
              paths[k] = v if v.is_a?(Array) || v =~ /^(https?:)?\/\//
            end
          end

          # Override user paths, whose mappings are only relevant in dev mode
          # and in the build_config.
          run_config["paths"] = paths
        end

        run_config["baseUrl"] = [host_url, Rails.application.config.assets.prefix].join

        html.concat(content_tag(:script) do
          script = "require.config(#{run_config.to_json});"

          # Pass an array to `require`, since it's a top-level module about to be loaded asynchronously (see
          # `http://requirejs.org/docs/errors.html#notloaded`).
          script.concat(" require([#{name.dump}]);") \
            if name

          script.html_safe
        end)
      end

      html.html_safe
    end
  end

  private

  def once_guard
    if defined?(controller) && controller.requirejs_included
      raise Requirejs::MultipleIncludeError, "Only one requirejs_include_tag allowed per page."
    end

    retval = yield

    controller.requirejs_included = true if defined?(controller)
    retval
  end

  def almond_include_tag(name, &block)
    content_tag(:script, "", src: [host_url, javascript_path(name)].join)
  end

  # Retrieve where the assets are hosted. Assume the root path if `nil`.
  def host_url
    config = Rails.application.config
    action_controller_config = config.action_controller
    asset_host = action_controller_config.asset_host

    @host_url ||= if !asset_host
      action_controller_config.relative_url_root
    else
      asset_host
    end
  end

  def view
    @view ||= Requirejs::Rails::View.new
  end
end
