require 'pathname'

module Sprockets
  # `AssetAttributes` is a wrapper similar to `Pathname` that provides
  # some helper accessors.
  #
  # These methods should be considered internalish.
  class AssetAttributes
    attr_reader :environment, :pathname

    def initialize(environment, path)
      @environment = environment
      @pathname = path.is_a?(Pathname) ? path : Pathname.new(path.to_s)
    end

    # Replaces `$root` placeholder with actual environment root.
    def expand_root
      pathname.to_s.sub(/^\$root/, environment.root)
    end

    # Replaces environment root with `$root` placeholder.
    def relativize_root
      pathname.to_s.sub(/^#{Regexp.escape(environment.root)}/, '$root')
    end

    # Returns paths search the load path for.
    def search_paths
      paths = [pathname.to_s]

      if pathname.basename(extensions.join).to_s != 'index'
        path_without_extensions = extensions.inject(pathname) { |p, ext| p.sub(ext, '') }
        index_path = path_without_extensions.join("index#{extensions.join}").to_s
        paths << index_path
      end

      paths
    end

    # Reverse guess logical path for fully expanded path.
    #
    # This has some known issues. For an example if a file is
    # shaddowed in the path, but is required relatively, its logical
    # path will be incorrect.
    def logical_path
      raise ArgumentError unless pathname.absolute?

      if root_path = environment.paths.detect { |path| pathname.to_s[path] }
        path = pathname.relative_path_from(Pathname.new(root_path)).to_s
        path = engine_extensions.inject(path) { |p, ext| p.sub(ext, '') }
        path = "#{path}#{engine_format_extension}" unless format_extension
        path
      else
        raise FileOutsidePaths, "#{pathname} isn't in paths: #{environment.paths.join(', ')}"
      end
    end

    # Returns `Array` of extension `String`s.
    #
    #     "foo.js.coffee"
    #     # => [".js", ".coffee"]
    #
    def extensions
      @extensions ||= @pathname.basename.to_s.scan(/\.[^.]+/)
    end

    # Returns the format extension.
    #
    #     "foo.js.coffee"
    #     # => ".js"
    #
    def format_extension
      extensions.reverse.detect { |ext|
        @environment.mime_types(ext) && !@environment.engines(ext)
      }
    end

    # Returns an `Array` of engine extensions.
    #
    #     "foo.js.coffee.erb"
    #     # => [".coffee", ".erb"]
    #
    def engine_extensions
      exts = extensions

      if offset = extensions.index(format_extension)
        exts = extensions[offset+1..-1]
      end

      exts.select { |ext| @environment.engines(ext) }
    end

    # Returns engine classes.
    def engines
      engine_extensions.map { |ext| @environment.engines(ext) }
    end

    # Returns all processors to run on the path.
    def processors
      environment.preprocessors(content_type) +
        engines.reverse +
        environment.postprocessors(content_type)
    end

    # Returns the content type for the pathname. Falls back to `application/octet-stream`.
    def content_type
      @content_type ||= begin
        if format_extension.nil?
          engine_content_type || 'application/octet-stream'
        else
          @environment.mime_types(format_extension) ||
            engine_content_type ||
            'application/octet-stream'
        end
      end
    end

    # Gets digest fingerprint.
    #
    #     "foo-0aa2105d29558f3eb790d411d7d8fb66.js"
    #     # => "0aa2105d29558f3eb790d411d7d8fb66"
    #
    def path_fingerprint
      pathname.basename(extensions.join).to_s =~ /-([0-9a-f]{7,40})$/ ? $1 : nil
    end

    # Injects digest fingerprint into path.
    #
    #     "foo.js"
    #     # => "foo-0aa2105d29558f3eb790d411d7d8fb66.js"
    #
    def path_with_fingerprint(digest)
      if old_digest = path_fingerprint
        pathname.sub(old_digest, digest).to_s
      else
        pathname.to_s.sub(/\.(\w+)$/) { |ext| "-#{digest}#{ext}" }
      end
    end

    private
      # Returns implicit engine content type.
      #
      # `.coffee` files carry an implicit `application/javascript`
      # content type.
      def engine_content_type
        engines.reverse.each do |engine|
          if engine.respond_to?(:default_mime_type) && engine.default_mime_type
            return engine.default_mime_type
          end
        end
        nil
      end

      def engine_format_extension
        if content_type = engine_content_type
          environment.extension_for_mime_type(content_type)
        end
      end
  end
end
