module Ddr
  module Auth
    module Roles
      #
      # The assignment of a role to an agent within a scope.
      #
      class Role < Valkyrie::Resource

        DEFAULT_SCOPE = Roles::RESOURCE_SCOPE

        ValidScope = Valkyrie::Types::Coercible::String.default(DEFAULT_SCOPE).enum(*(Roles::SCOPES))
        ValidRoleType = Valkyrie::Types::Strict::String.enum(*(Roles.titles))
        ValidAgent = Valkyrie::Types::Coercible::String.constrained(min_size: 1)

        # Symbolize input keys
        # https://dry-rb.org/gems/dry-struct/1.0/recipes/#symbolize-input-keys
        transform_keys do |key|
          _k = key.to_sym
          # For backwards compat allow :type as an alias for :role_type
          _k == :type ? :role_type : _k
        end

        # Make nils use default values
        # https://dry-rb.org/gems/dry-struct/1.0/recipes/#resolving-default-values-on-code-nil-code
        transform_types do |type|
          if type.default?
            type.constructor do |value|
              value.nil? ? Dry::Types::Undefined : value
            end
          else
            type
          end
        end

        attribute :agent, ValidAgent
        attribute :role_type, ValidRoleType
        attribute :scope, ValidScope

        # DEPRECATED: Use constructor
        def self.build(args={})
          new(args)
        end

        # Roles are considered equal (==) if they
        # are of the same type and have the same agent and scope.
        # @param other [Object] the object of comparison
        # @return [Boolean] the result
        def ==(other)
          self.class == other.class &&
            role_type == other.role_type &&
            scope == other.scope &&
            agent == other.agent
        end

        alias_method :eql?, :==

        def in_resource_scope?
          scope == Roles::RESOURCE_SCOPE
        end

        def in_policy_scope?
          scope == Roles::POLICY_SCOPE
        end

        def inspect
          "#<#{self.class.name} role_type=#{role_type.inspect}, " \
          "agent=#{agent.inspect}, scope=#{scope.inspect}>"
        end

        # Returns the permissions associated with the role
        # @return [Array<Symbol>] the permissions
        def permissions
          Roles.type_map[role_type].permissions
        end

      end
    end
  end
end