Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/SignalServiceKit/Messages/Interactions/Polls/OWSPoll.swift
1 views
//
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import Foundation
public import LibSignalClient

public struct OWSPoll: Equatable {
    public enum Constants {
        static let maxCharacterLength = 100
    }

    public enum PendingVoteType {
        case pendingVote
        case pendingUnvote
    }

    public typealias OptionIndex = UInt32

    public struct OWSPollOption: Equatable, Identifiable {
        public let optionIndex: OptionIndex
        public let text: String
        public let acis: [Aci]
        public var id: OptionIndex { optionIndex }
        public let latestPendingState: PendingVoteType?

        init(
            optionIndex: OptionIndex,
            text: String,
            acis: [Aci],
            latestPendingState: PendingVoteType?,
        ) {
            self.optionIndex = optionIndex
            self.text = text
            self.acis = acis
            self.latestPendingState = latestPendingState
        }

        public func localUserHasVoted(localAci: Aci) -> Bool {
            return acis.contains(localAci)
        }
    }

    public let interactionId: Int64
    public let question: String
    public var isEnded: Bool
    public let allowsMultiSelect: Bool
    public let ownerIsLocalUser: Bool
    private let options: [OptionIndex: OWSPollOption]

    public init(
        interactionId: Int64,
        question: String,
        options: [String],
        localUserPendingState: [OptionIndex: PendingVoteType],
        allowsMultiSelect: Bool,
        votes: [OptionIndex: [Aci]],
        isEnded: Bool,
        ownerIsLocalUser: Bool,
    ) {
        self.interactionId = interactionId
        self.question = question
        self.allowsMultiSelect = allowsMultiSelect
        self.isEnded = isEnded
        self.ownerIsLocalUser = ownerIsLocalUser

        self.options = Dictionary(uniqueKeysWithValues: options.enumerated().map { index, option in
            let optionIndex = OWSPoll.OptionIndex(index)
            let votes = votes[optionIndex] ?? []
            var latestPendingState: PendingVoteType?
            if let pendingState = localUserPendingState[optionIndex] {
                switch pendingState {
                case .pendingVote:
                    latestPendingState = .pendingVote
                case .pendingUnvote:
                    latestPendingState = .pendingUnvote
                }
            }
            return (optionIndex, OWSPollOption(optionIndex: optionIndex, text: option, acis: votes, latestPendingState: latestPendingState))
        })
    }

    public static func ==(lhs: OWSPoll, rhs: OWSPoll) -> Bool {
        return lhs.interactionId == rhs.interactionId
            && lhs.isEnded == rhs.isEnded
            && lhs.options == rhs.options
    }

    public func totalVoters() -> Int {
        return Set(options.values.flatMap { $0.acis }).count
    }

    public func sortedOptions() -> [OWSPollOption] {
        return options.sorted { $0.key < $1.key }.map { $0.value }
    }

    public func optionForIndex(optionIndex: OptionIndex) -> OWSPollOption? {
        return options[optionIndex]
    }

    public func pendingVotesCount() -> Int {
        return options.count { $0.value.latestPendingState != nil }
    }

    public func maxVoteCount() -> Int {
        return options.values.map { $0.acis.count }.max() ?? 0
    }
}