// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
//
// stringlint:disable

import Foundation

public extension SnodeAPI {
    enum Namespace: Int, Codable, Hashable, CustomStringConvertible {
        case `default` = 0
        
        case configUserProfile = 2
        case configContacts = 3
        case configConvoInfoVolatile = 4
        case configUserGroups = 5
        case configClosedGroupInfo = 11
        
        case legacyClosedGroup = -10
        
        /// This is used when we somehow receive a message from an unknown namespace (shouldn't really be possible)
        case unknown = -9999989
        
        /// This is a convenience namespace used to represent all other namespaces for specific API calls
        case all = -9999990
        
        // MARK: Variables
        
        var requiresReadAuthentication: Bool {
            switch self {
                // Legacy closed groups don't support authenticated retrieval
                case .legacyClosedGroup: return false
                default: return true
            }
        }
        
        var requiresWriteAuthentication: Bool {
            switch self {
                // Legacy closed groups don't support authenticated storage
                case .legacyClosedGroup: return false
                default: return true
            }
        }
        
        /// This flag indicates whether we should provide a `lastHash` when retrieving messages from the specified
        /// namespace, when `true` we will only receive messages added since the provided `lastHash`, otherwise
        /// we will retrieve **all** messages from the namespace
        public var shouldFetchSinceLastHash: Bool { true }
        
        /// This flag indicates whether we should dedupe messages from the specified namespace, when `true` we will
        /// attempt to `insert` a `SnodeReceivedMessageInfo` record (which will fail if we had already processed this
        /// message previously), when `false` we will still `upsert` a record so we don't run into the unique constraint allowing
        /// re-processing of a previously processed message
        public var shouldDedupeMessages: Bool {
            switch self {
                case .`default`, .legacyClosedGroup: return true
                    
                case .configUserProfile, .configContacts,
                    .configConvoInfoVolatile, .configUserGroups,
                    .configClosedGroupInfo, .unknown, .all:
                    return false
            }
        }
        
        var verificationString: String {
            switch self {
                case .`default`: return ""
                case .all: return "all"
                default: return "\(self.rawValue)"
            }
        }
        
        public var isCurrentUserNamespace: Bool {
            switch self {
                case .default, .configUserProfile, .configContacts, .configConvoInfoVolatile, .configUserGroups:
                    return true
                    
                case .configClosedGroupInfo, .legacyClosedGroup, .unknown, .all:
                    return false
            }
        }
        
        public var isConfigNamespace: Bool {
            switch self {
                case .configUserProfile, .configContacts, .configConvoInfoVolatile, .configUserGroups,
                    .configClosedGroupInfo:
                    return true
                    
                case .`default`, .legacyClosedGroup, .unknown, .all:
                    return false
            }
        }
        
        /// This value defines the order that the SharedConfigMessages should be processed in, while we re-process config
        /// messages every time we poll this will prevent an edge-case where data/logic between different config messages
        /// could be dependant on each other (eg. there could be `convoInfoVolatile` data related to a new conversation
        /// which hasn't been created yet because it's associated `contacts`/`userGroups` message hasn't yet been
        /// processed (without this we would have to wait until the next poll for it to be processed correctly)
        public var processingOrder: Int {
            switch self {
                case .configUserProfile, .configContacts: return 0
                case .configUserGroups, .configClosedGroupInfo: return 1
                case .configConvoInfoVolatile: return 2
                    
                case .`default`, .legacyClosedGroup, .unknown, .all:
                    return 3
            }
        }
        
        /// Flag which indicates whether messages from this namespace should be handled synchronously as part of the polling process
        /// or whether they can be scheduled to be handled asynchronously
        public var shouldHandleSynchronously: Bool {
            switch self {
                case .`default`, .legacyClosedGroup, .configUserProfile, .configContacts,
                    .configConvoInfoVolatile, .configUserGroups, .configClosedGroupInfo,
                    .unknown, .all:
                    return false
            }
        }
        
        /// When performing a batch request we want to try to use the amount of data available in the response as effectively as possible
        /// this priority allows us to split the response effectively between the number of namespaces we are requesting from where
        /// namespaces with the same priority will be given the same response size divider, for example:
        /// ```
        /// default          = 1
        /// config1, config2 = 2
        /// config3, config4 = 3
        ///
        /// Response data split:
        ///  _____________________________
        /// |                             |
        /// |           default           |
        /// |_____________________________|
        /// |         |         | config3 |
        /// | config1 | config2 | config4 |
        /// |_________|_________|_________|
        ///
        var batchRequestSizePriority: Int64 {
            switch self {
                case .`default`, .legacyClosedGroup: return 10
                    
                case .configUserProfile, .configContacts,
                    .configConvoInfoVolatile, .configUserGroups,
                    .configClosedGroupInfo, .unknown, .all:
                    return 1
            }
        }
        
        static func maxSizeMap(for namespaces: [Namespace]) -> [Namespace: Int64] {
            var lastSplit: Int64 = 1
            let namespacePriorityGroups: [Int64: [Namespace]] = namespaces
                .grouped { $0.batchRequestSizePriority }
            let lowestPriority: Int64 = (namespacePriorityGroups.keys.min() ?? 1)
            
            return namespacePriorityGroups
                .map { $0 }
                .sorted(by: { lhs, rhs -> Bool in lhs.key > rhs.key })
                .flatMap { priority, namespaces -> [(namespace: Namespace, maxSize: Int64)] in
                    lastSplit *= Int64(namespaces.count + (priority == lowestPriority ? 0 : 1))

                    return namespaces.map { ($0, lastSplit) }
                }
                .reduce(into: [:]) { result, next in
                    result[next.namespace] = -next.maxSize
                }
        }
        
        // MARK: - CustomStringConvertible
        
        public var description: String {
            switch self {
                case .`default`: return "default"
                case .configUserProfile: return "configUserProfile"
                case .configContacts: return "configContacts"
                case .configConvoInfoVolatile: return "configConvoInfoVolatile"
                case .configUserGroups: return "configUserGroups"
                case .configClosedGroupInfo: return "configClosedGroupInfo"
                case .legacyClosedGroup: return "legacyClosedGroup"
                
                case .unknown: return "unknown"
                case .all: return "all"
            }
        }
    }
}
