         return layoutDelegate.avatarSize(for: message, at: indexPath, in: messagesCollectionView)
     }
 
+    /// The origin for the avatar view.
     fileprivate func avatarOrigin(for attributes: MessagesCollectionViewLayoutAttributes, with avatarPosition: AvatarPosition) -> CGPoint {
 
         var origin = CGPoint.zero


    // MARK: - Avatar Calculations

    fileprivate func avatarSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {

        guard let messagesCollectionView = messagesCollectionView else { return .zero }


    // MARK: - Cell Top Label Calculations

    /// The total value of the horizontal insets for the cell top label.
    private var topLabelHorizontalInsets: CGFloat {
        return cellTopLabelInsets.left + cellTopLabelInsets.right
    }

    /// The total value of the vertical insets for the cell top label.
    private var topLabelVerticalInsets: CGFloat {
        return cellTopLabelInsets.top + cellTopLabelInsets.bottom
    }

    /// The max width available for the cell top label.
    private func topLabelMaxWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
        if topLabelExtendsPastAvatar {
            return itemWidth - topLabelHorizontalInsets
        } else {
            let reservedWidth = nonMessageContentWidth(for: message, at: indexPath) + topLabelHorizontalInsets
            return itemWidth - reservedWidth
        }
    }

    /// The size of the cell top label.
    fileprivate func cellTopLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {

        guard let messagesCollectionView = messagesCollectionView else { return .zero }
        guard let dataSource = messagesCollectionView.messagesDataSource else { return .zero }
        guard let topLabelText = dataSource.cellTopLabelAttributedText(for: message, at: indexPath) else { return .zero }

        let maxWidth = topLabelMaxWidth(for: message, at: indexPath)
        var size = labelSize(for: topLabelText, considering: maxWidth)

        size.width += topLabelHorizontalInsets
        size.height += topLabelVerticalInsets

        return size

    }

    /// The origin for the cell top label.
    fileprivate func cellTopLabelOrigin(for message: MessageType, and attributes: MessagesCollectionViewLayoutAttributes) -> CGPoint {

        guard let messagesCollectionView = messagesCollectionView else { return .zero }
        guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return .zero }
        let position = layoutDelegate.cellTopLabelPosition(for: message, at: attributes.indexPath, in: messagesCollectionView)

        var origin: CGPoint = .zero

        switch position {
        case .cellTrailing:
            origin.x = attributes.frame.width - attributes.cellTopLabelFrame.width
        case .cellLeading:
            origin.x = 0
        case .cellCenter:
            origin.x = itemWidth / 2 - (attributes.cellTopLabelFrame.width / 2)
        case .messageTrailing:
            switch attributes.direction {
            case .incoming:
                origin.x = attributes.avatarFrame.width + avatarMessagePadding + attributes.messageContainerFrame.width - attributes.cellTopLabelFrame.width
            case .outgoing:
                origin.x = attributes.frame.width - attributes.avatarFrame.width - avatarMessagePadding - attributes.cellTopLabelFrame.width
            }
        case .messageLeading:
            switch attributes.direction {
            case .incoming:
                origin.x = attributes.avatarFrame.width + avatarMessagePadding
            case .outgoing:
                origin.x = attributes.frame.width - attributes.avatarFrame.width - avatarMessagePadding - attributes.messageContainerFrame.width
            }
        case .messageCenter:
            switch attributes.direction {
            case .incoming:
                origin.x = attributes.avatarFrame.width + avatarMessagePadding + (attributes.messageContainerFrame.width / 2) - (attributes.cellTopLabelFrame.width / 2)
            case .outgoing:
                origin.x = attributes.frame.width - attributes.avatarFrame.width - avatarMessagePadding - (attributes.messageContainerFrame.width / 2) - (attributes.cellTopLabelFrame.width / 2)
            }
        }
        
        return origin

    }

    // MARK: - Cell Bottom Label Calculations

    /// The total value of the horizontal insets for the cell bottom label
    private var bottomLabelHorizontalInsets: CGFloat {
        return cellBottomLabelInsets.left + cellBottomLabelInsets.right
    }

    /// The total value of the vertical insets for the cell bottom label.
    private var bottomLabelVerticalInsets: CGFloat {
        return cellBottomLabelInsets.top + cellBottomLabelInsets.bottom
    }

    /// The max with available for the cell bottom label.
    private func bottomLabelMaxWidth(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
        if bottomLabelExtendsPastAvatar {
            return itemWidth - bottomLabelHorizontalInsets
        } else {
            let reservedWidth = nonMessageContentWidth(for: message, at: indexPath) + bottomLabelHorizontalInsets
            return itemWidth - reservedWidth
        }
    }

    /// The size for the cell bottom label.
    fileprivate func cellBottomLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {

        guard let messagesCollectionView = messagesCollectionView else { return .zero }
        guard let dataSource = messagesCollectionView.messagesDataSource else { return .zero }
        guard let bottomLabelText = dataSource.cellBottomLabelAttributedText(for: message, at: indexPath) else { return .zero }

        let maxWidth = bottomLabelMaxWidth(for: message, at: indexPath)
        var size = labelSize(for: bottomLabelText, considering: maxWidth)

        size.width += bottomLabelHorizontalInsets
        size.height += bottomLabelVerticalInsets

        return size
        
    }

    /// The origin for the cell bottom label.
    fileprivate func cellBottomLabelOrigin(for message: MessageType, and attributes: MessagesCollectionViewLayoutAttributes) -> CGPoint {

        guard let messagesCollectionView = messagesCollectionView else { return .zero }
        guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return .zero }
        let position = layoutDelegate.cellBottomLabelPosition(for: message, at: attributes.indexPath, in: messagesCollectionView)

        var origin: CGPoint = .zero

        origin.y = attributes.frame.height - attributes.cellBottomLabelFrame.height

        switch position {
        case .cellTrailing:
            origin.x = attributes.frame.width - attributes.cellBottomLabelFrame.width
        case .cellLeading:
            origin.x = 0
        case .cellCenter:
            origin.x = itemWidth / 2 - (attributes.cellBottomLabelFrame.width / 2)
        case .messageTrailing:
            switch attributes.direction {
            case .incoming:
                origin.x = attributes.avatarFrame.width + avatarMessagePadding + attributes.messageContainerFrame.width - attributes.cellBottomLabelFrame.width
            case .outgoing:
                origin.x = attributes.frame.width - attributes.avatarFrame.width - avatarMessagePadding - attributes.cellBottomLabelFrame.width
            }
        case .messageLeading:
            switch attributes.direction {
            case .incoming:
                origin.x = attributes.avatarFrame.width + avatarMessagePadding
            case .outgoing:
                origin.x = attributes.frame.width - attributes.avatarFrame.width - avatarMessagePadding - attributes.messageContainerFrame.width
            }
        case .messageCenter:
            switch attributes.direction {
            case .incoming:
                origin.x = attributes.avatarFrame.width + avatarMessagePadding + (attributes.messageContainerFrame.width / 2) - (attributes.cellBottomLabelFrame.width / 2)
            case .outgoing:
                origin.x = attributes.avatarFrame.width + avatarMessagePadding + (attributes.messageContainerFrame.width / 2) - (attributes.cellBottomLabelFrame.width / 2)
            }
        }

        return origin

    }

    // MARK: - Cell Size Calculations

    /// The minimum height for the cell.
    private func minimumCellHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat {

        guard let messagesCollectionView = messagesCollectionView else { return 0 }
        guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else { return 0 }

        let avatarPosition = layoutDelegate.avatarPosition(for: message, at: indexPath, in: messagesCollectionView)

        let topLabelHeight = cellTopLabelSize(for: message, at: indexPath).height
        let avatarHeight = avatarSize(for: message, at: indexPath).height
        let bottomLabelHeight = cellBottomLabelSize(for: message, at: indexPath).height

        switch avatarPosition {
        case .cellTop:
            return max(avatarHeight, topLabelHeight) + bottomLabelHeight
        case .cellBottom:
            return max(avatarHeight, bottomLabelHeight) + topLabelHeight
        case .messageTop, .messageCenter, .messageBottom:
            return topLabelHeight + avatarHeight + bottomLabelHeight
        }

    }

    /// The calculated height of the cell not considering the minimum height.
    private func estimatedCellHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat {

        let messageContainerHeight = messageContainerSize(for: message, at: indexPath).height
        let cellTopLabelHeight = cellTopLabelSize(for: message, at: indexPath).height
        let cellBottomLabelHeight = cellBottomLabelSize(for: message, at: indexPath).height

        return messageContainerHeight + cellBottomLabelHeight + cellTopLabelHeight

    }

    /// The size for the cell considering all cell contents.
    open func sizeForItem(at indexPath: IndexPath) -> CGSize {
        
        guard let messagesCollectionView = messagesCollectionView else { return .zero }
        guard let dataSource = messagesCollectionView.messagesDataSource else { return .zero }
        
        let message = dataSource.messageForItem(at: indexPath, in: messagesCollectionView)
        
        let minimumHeight = minimumCellHeight(for: message, at: indexPath)
        let estimatedHeight = estimatedCellHeight(for: message, at: indexPath)

        let itemHeight = estimatedHeight > minimumHeight ? estimatedHeight : minimumHeight

        return CGSize(width: itemWidth, height: itemHeight)
        
    }
    
