# frozen_string_literal: true

module JSONAPI
  class LinkBuilder
    attr_reader :base_url,
                :primary_resource_klass,
                :route_formatter,
                :engine,
                :engine_mount_point,
                :url_helpers

    @@url_helper_methods = {}

    def initialize(config = {})
      @base_url = config[:base_url]
      @primary_resource_klass = config[:primary_resource_klass]
      @route_formatter = config[:route_formatter]
      @engine = build_engine
      @engine_mount_point = @engine ? @engine.routes.find_script_name({}) : ""

      # url_helpers may be either a controller which has the route helper methods, or the application router's
      # url helpers module, `Rails.application.routes.url_helpers`. Because the method no longer behaves as a
      # singleton, and it's expensive to generate the module, the controller is preferred.
      @url_helpers = config[:url_helpers]
    end

    def engine?
      !!@engine
    end

    def primary_resources_url
      if @primary_resource_klass._routed
        primary_resources_path = resources_path(primary_resource_klass)
        @primary_resources_url_cached ||= "#{ base_url }#{ engine_mount_point }#{ primary_resources_path }"
      else
        if JSONAPI.configuration.warn_on_missing_routes && !@primary_resource_klass._warned_missing_route
          warn "primary_resources_url for #{@primary_resource_klass} could not be generated"
          @primary_resource_klass._warned_missing_route = true
        end
        nil
      end
    end

    def query_link(query_params)
      url = primary_resources_url
      return url if url.nil?
      "#{ url }?#{ query_params.to_query }"
    end

    def relationships_related_link(source, relationship, query_params = {})
      if relationship._routed
        url = "#{ self_link(source) }/#{ route_for_relationship(relationship) }"
        url = "#{ url }?#{ query_params.to_query }" if query_params.present?
        url
      else
        if JSONAPI.configuration.warn_on_missing_routes && !relationship._warned_missing_route
          warn "related_link for #{relationship} could not be generated"
          relationship._warned_missing_route = true
        end
        nil
      end
    end

    def relationships_self_link(source, relationship)
      if relationship._routed
        "#{ self_link(source) }/relationships/#{ route_for_relationship(relationship) }"
      else
        if JSONAPI.configuration.warn_on_missing_routes && !relationship._warned_missing_route
          warn "self_link for #{relationship} could not be generated"
          relationship._warned_missing_route = true
        end
        nil
      end
    end

    def self_link(source)
      if source.class._routed
        resource_url(source)
      else
        if JSONAPI.configuration.warn_on_missing_routes && !source.class._warned_missing_route
          warn "self_link for #{source.class} could not be generated"
          source.class._warned_missing_route = true
        end
        nil
      end
    end

    private

    def build_engine
      scopes = module_scopes_from_class(primary_resource_klass)

      begin
        unless scopes.empty?
          "#{ scopes.first.to_s.camelize }::Engine".safe_constantize
        end

          # :nocov:
      rescue LoadError => _e
        nil
        # :nocov:
      end
    end

    def format_route(route)
      route_formatter.format(route)
    end

    def formatted_module_path_from_class(klass)
      scopes = if @engine
                 module_scopes_from_class(klass)[1..-1]
               else
                 module_scopes_from_class(klass)
               end

      unless scopes.empty?
        "/#{ scopes.map {|scope| format_route(scope.to_s.underscore)}.compact.join('/') }/"
      else
        "/"
      end
    end

    def module_scopes_from_class(klass)
      klass.name.to_s.split("::")[0...-1]
    end

    def resources_path(source_klass)
      @_resources_path ||= {}
      @_resources_path[source_klass] ||= formatted_module_path_from_class(source_klass) + format_route(source_klass._type.to_s)
    end

    def resource_path(source)
      if source.class.singleton?
        resources_path(source.class)
      else
        "#{resources_path(source.class)}/#{source.id}"
      end
    end

    def resource_url(source)
      "#{ base_url }#{ engine_mount_point }#{ resource_path(source) }"
    end

    def route_for_relationship(relationship)
      format_route(relationship.name)
    end
  end
end
