import Foundation
import UIKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import LegacyComponents
import TelegramPresentationData
import TelegramUIPreferences
import ItemListUI
import PresentationDataUtils
import AccountContext
import OpenInExternalAppUI
import ItemListPeerActionItem
import StorageUsageScreen
import PresentationDataUtils

public enum AutomaticSaveIncomingPeerType {
    case privateChats
    case groups
    case channels
}

private final class DataAndStorageControllerArguments {
    let openStorageUsage: () -> Void
    let openNetworkUsage: () -> Void
    let openProxy: () -> Void
    let openAutomaticDownloadConnectionType: (AutomaticDownloadConnectionType) -> Void
    let resetAutomaticDownload: () -> Void
    let toggleVoiceUseLessData: (Bool) -> Void
    let openSaveIncoming: (AutomaticSaveIncomingPeerType) -> Void
    let toggleSaveEditedPhotos: (Bool) -> Void
    let togglePauseMusicOnRecording: (Bool) -> Void
    let toggleRaiseToListen: (Bool) -> Void
    let toggleDownloadInBackground: (Bool) -> Void
    let openBrowserSelection: () -> Void
    let openIntents: () -> Void
    let toggleSensitiveContent: (Bool) -> Void

    init(
        openStorageUsage: @escaping () -> Void,
        openNetworkUsage: @escaping () -> Void,
        openProxy: @escaping () -> Void,
        openAutomaticDownloadConnectionType: @escaping (AutomaticDownloadConnectionType) -> Void,
        resetAutomaticDownload: @escaping () -> Void,
        toggleVoiceUseLessData: @escaping (Bool) -> Void,
        openSaveIncoming: @escaping (AutomaticSaveIncomingPeerType) -> Void,
        toggleSaveEditedPhotos: @escaping (Bool) -> Void,
        togglePauseMusicOnRecording: @escaping (Bool) -> Void,
        toggleRaiseToListen: @escaping (Bool) -> Void,
        toggleDownloadInBackground: @escaping (Bool) -> Void,
        openBrowserSelection: @escaping () -> Void,
        openIntents: @escaping () -> Void,
        toggleSensitiveContent: @escaping (Bool) -> Void
    ) {
        self.openStorageUsage = openStorageUsage
        self.openNetworkUsage = openNetworkUsage
        self.openProxy = openProxy
        self.openAutomaticDownloadConnectionType = openAutomaticDownloadConnectionType
        self.resetAutomaticDownload = resetAutomaticDownload
        self.toggleVoiceUseLessData = toggleVoiceUseLessData
        self.openSaveIncoming = openSaveIncoming
        self.toggleSaveEditedPhotos = toggleSaveEditedPhotos
        self.togglePauseMusicOnRecording = togglePauseMusicOnRecording
        self.toggleRaiseToListen = toggleRaiseToListen
        self.toggleDownloadInBackground = toggleDownloadInBackground
        self.openBrowserSelection = openBrowserSelection
        self.openIntents = openIntents
        self.toggleSensitiveContent = toggleSensitiveContent
    }
}

private enum DataAndStorageSection: Int32 {
    case usage
    case autoDownload
    case autoSave
    case backgroundDownload
    case voiceCalls
    case other
    case connection
    case sensitiveContent
}

public enum DataAndStorageEntryTag: ItemListItemTag, Equatable {
    case automaticDownloadReset
    case saveEditedPhotos
    case downloadInBackground
    case pauseMusicOnRecording
    case raiseToListen
    case autoSave(AutomaticSaveIncomingPeerType)
    case sensitiveContent
    
    public func isEqual(to other: ItemListItemTag) -> Bool {
        if let other = other as? DataAndStorageEntryTag, self == other {
            return true
        } else {
            return false
        }
    }
}

private enum DataAndStorageEntry: ItemListNodeEntry {
    case storageUsage(PresentationTheme, String, String)
    case networkUsage(PresentationTheme, String, String)
    case automaticDownloadHeader(PresentationTheme, String)
    case automaticDownloadCellular(PresentationTheme, String, String)
    case automaticDownloadWifi(PresentationTheme, String, String)
    case automaticDownloadReset(PresentationTheme, String, Bool)
    
    case autoSaveHeader(String)
    case autoSaveItem(index: Int, type: AutomaticSaveIncomingPeerType, title: String, label: String, value: String)
    case autoSaveInfo(String)
    
    case downloadInBackground(PresentationTheme, String, Bool)
    case downloadInBackgroundInfo(PresentationTheme, String)
    
    case useLessVoiceData(PresentationTheme, String, Bool)
    case useLessVoiceDataInfo(PresentationTheme, String)
    case otherHeader(PresentationTheme, String)
    case openLinksIn(PresentationTheme, String, String)
    case shareSheet(PresentationTheme, String)
    case saveEditedPhotos(PresentationTheme, String, Bool)
    case pauseMusicOnRecording(PresentationTheme, String, Bool)
    case raiseToListen(PresentationTheme, String, Bool)
    case raiseToListenInfo(PresentationTheme, String)
    
    case sensitiveContent(String, Bool)
    case sensitiveContentInfo(String)
    
    case connectionHeader(PresentationTheme, String)
    case connectionProxy(PresentationTheme, String, String)
    
    var section: ItemListSectionId {
        switch self {
            case .storageUsage, .networkUsage:
                return DataAndStorageSection.usage.rawValue
            case .automaticDownloadHeader, .automaticDownloadCellular, .automaticDownloadWifi, .automaticDownloadReset:
                return DataAndStorageSection.autoDownload.rawValue
            case .autoSaveHeader, .autoSaveItem, .autoSaveInfo:
                return DataAndStorageSection.autoSave.rawValue
            case .downloadInBackground, .downloadInBackgroundInfo:
                return DataAndStorageSection.backgroundDownload.rawValue
            case .useLessVoiceData, .useLessVoiceDataInfo:
                return DataAndStorageSection.voiceCalls.rawValue
            case .otherHeader, .openLinksIn, .shareSheet, .saveEditedPhotos, .pauseMusicOnRecording, .raiseToListen, .raiseToListenInfo:
                return DataAndStorageSection.other.rawValue
            case .sensitiveContent, .sensitiveContentInfo:
                return DataAndStorageSection.sensitiveContent.rawValue
            case .connectionHeader, .connectionProxy:
                return DataAndStorageSection.connection.rawValue
        }
    }
    
    var stableId: Int32 {
        switch self {
            case .storageUsage:
                return 0
            case .networkUsage:
                return 1
            case .automaticDownloadHeader:
                return 2
            case .automaticDownloadCellular:
                return 3
            case .automaticDownloadWifi:
                return 4
            case .automaticDownloadReset:
                return 5
            case .autoSaveHeader:
                return 6
            case let .autoSaveItem(index, _, _, _, _):
                return 7 + Int32(index)
            case .autoSaveInfo:
                return 20
            case .downloadInBackground:
                return 21
            case .downloadInBackgroundInfo:
                return 22
            case .useLessVoiceData:
                return 23
            case .useLessVoiceDataInfo:
                return 24
            case .otherHeader:
                return 29
            case .openLinksIn:
                return 30
            case .shareSheet:
                return 31
            case .saveEditedPhotos:
                return 32
            case .pauseMusicOnRecording:
                return 33
            case .raiseToListen:
                return 34
            case .raiseToListenInfo:
                return 35
            case .sensitiveContent:
                return 36
            case .sensitiveContentInfo:
                return 37
            case .connectionHeader:
                return 38
            case .connectionProxy:
                return 39
        }
    }
    
    static func ==(lhs: DataAndStorageEntry, rhs: DataAndStorageEntry) -> Bool {
        switch lhs {
            case let .storageUsage(lhsTheme, lhsText, lhsValue):
                if case let .storageUsage(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
                    return true
                } else {
                    return false
                }
            case let .networkUsage(lhsTheme, lhsText, lhsValue):
                if case let .networkUsage(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
                    return true
                } else {
                    return false
                }
            case let .automaticDownloadHeader(lhsTheme, lhsText):
                if case let .automaticDownloadHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
                    return true
                } else {
                    return false
                }
            case let .automaticDownloadCellular(lhsTheme, lhsText, lhsValue):
                if case let .automaticDownloadCellular(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
                    return true
                } else {
                    return false
                }
            case let .automaticDownloadWifi(lhsTheme, lhsText, lhsEnabled):
                if case let .automaticDownloadWifi(rhsTheme, rhsText, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsEnabled == rhsEnabled {
                    return true
                } else {
                    return false
                }
            case let .automaticDownloadReset(lhsTheme, lhsText, lhsEnabled):
                if case let .automaticDownloadReset(rhsTheme, rhsText, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsEnabled == rhsEnabled {
                    return true
                } else {
                    return false
                }
            case let .autoSaveHeader(text):
                if case .autoSaveHeader(text) = rhs {
                    return true
                } else {
                    return false
                }
            case let .autoSaveItem(index, type, title, label, value):
                if case .autoSaveItem(index, type, title, label, value) = rhs {
                    return true
                } else {
                    return false
                }
            case let .autoSaveInfo(text):
                if case .autoSaveInfo(text) = rhs {
                    return true
                } else {
                    return false
                }
            case let .useLessVoiceData(lhsTheme, lhsText, lhsValue):
                if case let .useLessVoiceData(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
                    return true
                } else {
                    return false
                }
            case let .useLessVoiceDataInfo(lhsTheme, lhsText):
                if case let .useLessVoiceDataInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
                    return true
                } else {
                    return false
                }
            case let .otherHeader(lhsTheme, lhsText):
                if case let .otherHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
                    return true
                } else {
                    return false
                }
            case let .openLinksIn(lhsTheme, lhsText, lhsValue):
                if case let .openLinksIn(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
                    return true
                } else {
                    return false
                }
            case let .shareSheet(lhsTheme, lhsText):
                if case let .shareSheet(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
                    return true
                } else {
                    return false
                }
            case let .saveEditedPhotos(lhsTheme, lhsText, lhsValue):
                if case let .saveEditedPhotos(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
                    return true
                } else {
                    return false
                }
            case let .pauseMusicOnRecording(lhsTheme, lhsText, lhsValue):
                if case let .pauseMusicOnRecording(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
                    return true
                } else {
                    return false
                }
            case let .raiseToListen(lhsTheme, lhsText, lhsValue):
                if case let .raiseToListen(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
                    return true
                } else {
                    return false
                }
            case let .raiseToListenInfo(lhsTheme, lhsText):
                if case let .raiseToListenInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
                    return true
                } else {
                    return false
                }
            case let .sensitiveContent(text, value):
                if case .sensitiveContent(text, value) = rhs {
                    return true
                } else {
                    return false
                }
            case let .sensitiveContentInfo(text):
                if case .sensitiveContentInfo(text) = rhs {
                    return true
                } else {
                    return false
                }
            case let .downloadInBackground(lhsTheme, lhsText, lhsValue):
                if case let .downloadInBackground(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
                    return true
                } else {
                    return false
                }
            case let .downloadInBackgroundInfo(lhsTheme, lhsText):
                if case let .downloadInBackgroundInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
                    return true
                } else {
                    return false
                }
            case let .connectionHeader(lhsTheme, lhsText):
                if case let .connectionHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
                    return true
                } else {
                    return false
                }
            case let .connectionProxy(lhsTheme, lhsText, lhsValue):
                if case let .connectionProxy(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
                    return true
                } else {
                    return false
                }
        }
    }
    
    static func <(lhs: DataAndStorageEntry, rhs: DataAndStorageEntry) -> Bool {
        return lhs.stableId < rhs.stableId
    }
    
    func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
        let arguments = arguments as! DataAndStorageControllerArguments
        switch self {
            case let .storageUsage(_, text, value):
                return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Storage")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
                    arguments.openStorageUsage()
                })
            case let .networkUsage(_, text, value):
                return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Network")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
                    arguments.openNetworkUsage()
                })
            case let .automaticDownloadHeader(_, text):
                return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
            case let .automaticDownloadCellular(_, text, value):
                return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Cellular")?.precomposed(), title: text, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
                    arguments.openAutomaticDownloadConnectionType(.cellular)
                })
            case let .automaticDownloadWifi(_, text, value):
                return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/WiFi")?.precomposed(), title: text, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
                    arguments.openAutomaticDownloadConnectionType(.wifi)
                })
            case let .automaticDownloadReset(theme, text, enabled):
                var icon = PresentationResourcesItemList.resetIcon(theme)
                if !enabled {
                    icon = generateTintedImage(image: icon, color: theme.list.itemDisabledTextColor)
                }
                return ItemListPeerActionItem(presentationData: presentationData, icon: icon, title: text, sectionId: self.section, height: .generic, color: enabled ? .accent : .disabled, editing: false, action: {
                    if enabled {
                        arguments.resetAutomaticDownload()
                    }
                })
            case let .autoSaveHeader(text):
                return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
            case let .autoSaveItem(_, type, title, label, value):
                let iconName: String
                switch type {
                case .privateChats:
                    iconName = "Settings/Menu/EditProfile"
                case .groups:
                    iconName = "Settings/Menu/GroupChats"
                case .channels:
                    iconName = "Settings/Menu/Channels"
                }
                return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: iconName)?.precomposed(), title: title, label: value, labelStyle: .text, additionalDetailLabel: label.isEmpty ? nil : label, sectionId: self.section, style: .blocks, action: {
                    arguments.openSaveIncoming(type)
                })
            case let .autoSaveInfo(text):
                return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
            case let .useLessVoiceData(_, text, value):
                return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
                    arguments.toggleVoiceUseLessData(value)
                }, tag: nil)
            case let .useLessVoiceDataInfo(_, text):
                return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
            case let .otherHeader(_, text):
                return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
            case let .openLinksIn(_, text, value):
                return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: {
                    arguments.openBrowserSelection()
                })
            case let .shareSheet(_, text):
                return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: {
                    arguments.openIntents()
                })
            case let .saveEditedPhotos(_, text, value):
                return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
                    arguments.toggleSaveEditedPhotos(value)
                }, tag: DataAndStorageEntryTag.saveEditedPhotos)
            case let .pauseMusicOnRecording(_, text, value):
                return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
                    arguments.togglePauseMusicOnRecording(value)
                }, tag: DataAndStorageEntryTag.pauseMusicOnRecording)
            case let .raiseToListen(_, text, value):
                return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
                    arguments.toggleRaiseToListen(value)
                }, tag: DataAndStorageEntryTag.raiseToListen)
            case let .raiseToListenInfo(_, text):
                return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
            case let .sensitiveContent(text, value):
                return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: false, sectionId: self.section, style: .blocks, updated: { value in
                    arguments.toggleSensitiveContent(value)
                }, tag: DataAndStorageEntryTag.sensitiveContent)
            case let .sensitiveContentInfo(text):
                return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
            case let .downloadInBackground(_, text, value):
                return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
                    arguments.toggleDownloadInBackground(value)
                }, tag: DataAndStorageEntryTag.downloadInBackground)
            case let .downloadInBackgroundInfo(_, text):
                return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
            case let .connectionHeader(_, text):
                return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
            case let .connectionProxy(_, text, value):
                return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: {
                    arguments.openProxy()
                })
        }
    }
}

private struct DataAndStorageControllerState: Equatable {
    static func ==(lhs: DataAndStorageControllerState, rhs: DataAndStorageControllerState) -> Bool {
        return true
    }
}

private struct DataAndStorageData: Equatable {
    let automaticMediaDownloadSettings: MediaAutoDownloadSettings
    let autodownloadSettings: AutodownloadSettings
    let generatedMediaStoreSettings: GeneratedMediaStoreSettings
    let mediaInputSettings: MediaInputSettings
    let voiceCallSettings: VoiceCallSettings
    let proxySettings: ProxySettings?
    
    init(automaticMediaDownloadSettings: MediaAutoDownloadSettings, autodownloadSettings: AutodownloadSettings, generatedMediaStoreSettings: GeneratedMediaStoreSettings, mediaInputSettings: MediaInputSettings, voiceCallSettings: VoiceCallSettings, proxySettings: ProxySettings?) {
        self.automaticMediaDownloadSettings = automaticMediaDownloadSettings
        self.autodownloadSettings = autodownloadSettings
        self.generatedMediaStoreSettings = generatedMediaStoreSettings
        self.mediaInputSettings = mediaInputSettings
        self.voiceCallSettings = voiceCallSettings
        self.proxySettings = proxySettings
    }
    
    static func ==(lhs: DataAndStorageData, rhs: DataAndStorageData) -> Bool {
        return lhs.automaticMediaDownloadSettings == rhs.automaticMediaDownloadSettings && lhs.generatedMediaStoreSettings == rhs.generatedMediaStoreSettings && lhs.mediaInputSettings == rhs.mediaInputSettings && lhs.voiceCallSettings == rhs.voiceCallSettings && lhs.proxySettings == rhs.proxySettings
    }
}

private func stringForUseLessDataSetting(_ dataSaving: VoiceCallDataSaving, strings: PresentationStrings) -> String {
    switch dataSaving {
        case .never:
            return strings.CallSettings_Never
        case .cellular:
            return strings.CallSettings_OnMobile
        case .always:
            return strings.CallSettings_Always
        case .default:
            return ""
    }
}

private func stringForAutoDownloadTypes(strings: PresentationStrings, decimalSeparator: String, photo: Bool, videoSize: Int64?, fileSize: Int64?) -> String {
    var types: [String] = []
    if photo && videoSize == nil {
        types.append(strings.ChatSettings_AutoDownloadSettings_TypePhoto)
    }
    if let videoSize = videoSize {
        if photo {
            types.append(strings.ChatSettings_AutoDownloadSettings_TypeMedia(autodownloadDataSizeString(videoSize, decimalSeparator: decimalSeparator)).string)
        } else {
            types.append(strings.ChatSettings_AutoDownloadSettings_TypeVideo(autodownloadDataSizeString(videoSize, decimalSeparator: decimalSeparator)).string)
        }
    }
    if let fileSize = fileSize {
        types.append(strings.ChatSettings_AutoDownloadSettings_TypeFile(autodownloadDataSizeString(fileSize, decimalSeparator: decimalSeparator)).string)
    }

    if types.isEmpty {
        return strings.ChatSettings_AutoDownloadSettings_OffForAll
    }
    
    var string: String = ""
    for i in 0 ..< types.count {
        if !string.isEmpty {
            string.append(strings.ChatSettings_AutoDownloadSettings_Delimeter)
        }
        string.append(types[i])
    }
    return string
}

private func stringForAutoDownloadSetting(strings: PresentationStrings, decimalSeparator: String, settings: MediaAutoDownloadSettings, connectionType: AutomaticDownloadConnectionType) -> String {
    let connection: MediaAutoDownloadConnection
    switch connectionType {
        case .cellular:
            connection = settings.cellular
        case .wifi:
            connection = settings.wifi
    }
    if !connection.enabled {
        return strings.ChatSettings_AutoDownloadSettings_OffForAll
    } else {
        let categories = effectiveAutodownloadCategories(settings: settings, networkType: connectionType.automaticDownloadNetworkType)
        
        let photo = isAutodownloadEnabledForAnyPeerType(category: categories.photo)
        let video = isAutodownloadEnabledForAnyPeerType(category: categories.video)
        let file = isAutodownloadEnabledForAnyPeerType(category: categories.file)
    
        return stringForAutoDownloadTypes(strings: strings, decimalSeparator: decimalSeparator, photo: photo, videoSize: video ? categories.video.sizeLimit : nil, fileSize: file ? categories.file.sizeLimit : nil)
    }
}

private func autosaveLabelAndValue(presentationData: PresentationData, settings: MediaAutoSaveSettings, peerType: AutomaticSaveIncomingPeerType, exceptionPeers: [EnginePeer.Id: EnginePeer?]) -> (label: String, value: String) {
    var exceptionCount = 0
    let configuration: MediaAutoSaveConfiguration
    switch peerType {
    case .privateChats:
        configuration = settings.configurations[.users] ?? .default
    case .groups:
        configuration = settings.configurations[.groups] ?? .default
    case .channels:
        configuration = settings.configurations[.channels] ?? .default
    }
    
    for exception in settings.exceptions {
        if let maybePeer = exceptionPeers[exception.id], let peer = maybePeer {
            let peerTypeValue: AutomaticSaveIncomingPeerType
            switch peer {
            case .user, .secretChat:
                peerTypeValue = .privateChats
            case .legacyGroup:
                peerTypeValue = .groups
            case let .channel(channel):
                if case .broadcast = channel.info {
                    peerTypeValue = .channels
                } else {
                    peerTypeValue = .groups
                }
            }
            
            if peerTypeValue == peerType {
                exceptionCount += 1
            }
        }
    }
    
    let value: String
    if configuration.photo || configuration.video {
        value = presentationData.strings.Settings_AutosaveMediaOn
    } else {
        value = presentationData.strings.Settings_AutosaveMediaOff
    }
    
    var label = ""
    if configuration.photo && configuration.video {
        label.append(presentationData.strings.Settings_AutosaveMediaAllMedia(dataSizeString(Int(configuration.maximumVideoSize), formatting: DataSizeStringFormatting(presentationData: presentationData))).string)
    } else {
        if configuration.photo {
            if !label.isEmpty {
                label.append(", ")
            }
            label.append(presentationData.strings.Settings_AutosaveMediaPhoto)
        } else if configuration.video {
            if !label.isEmpty {
                label.append(", ")
            }
            label.append(presentationData.strings.Settings_AutosaveMediaVideo(dataSizeString(Int(configuration.maximumVideoSize), formatting: DataSizeStringFormatting(presentationData: presentationData))).string)
        }
    }
    
    if exceptionCount != 0 {
        if !label.isEmpty {
            label.append(", ")
        }
        label.append(presentationData.strings.Notifications_CategoryExceptions(Int32(exceptionCount)))
    }
    
    return (label, value)
}

private func dataAndStorageControllerEntries(state: DataAndStorageControllerState, data: DataAndStorageData, presentationData: PresentationData, defaultWebBrowser: String, contentSettingsConfiguration: ContentSettingsConfiguration?, networkUsage: Int64, storageUsage: Int64, mediaAutoSaveSettings: MediaAutoSaveSettings, autosaveExceptionPeers: [EnginePeer.Id: EnginePeer?], mediaSettings: MediaDisplaySettings) -> [DataAndStorageEntry] {
    var entries: [DataAndStorageEntry] = []
    
    entries.append(.storageUsage(presentationData.theme, presentationData.strings.ChatSettings_Cache, dataSizeString(storageUsage, formatting: DataSizeStringFormatting(presentationData: presentationData))))
    entries.append(.networkUsage(presentationData.theme, presentationData.strings.NetworkUsageSettings_Title, dataSizeString(networkUsage, formatting: DataSizeStringFormatting(presentationData: presentationData))))
    
    entries.append(.automaticDownloadHeader(presentationData.theme, presentationData.strings.ChatSettings_AutoDownloadTitle.uppercased()))
    entries.append(.automaticDownloadCellular(presentationData.theme, presentationData.strings.ChatSettings_AutoDownloadUsingCellular, stringForAutoDownloadSetting(strings: presentationData.strings, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator, settings: data.automaticMediaDownloadSettings, connectionType: .cellular)))
    entries.append(.automaticDownloadWifi(presentationData.theme, presentationData.strings.ChatSettings_AutoDownloadUsingWiFi, stringForAutoDownloadSetting(strings: presentationData.strings, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator, settings: data.automaticMediaDownloadSettings, connectionType: .wifi)))
    
    let defaultSettings = MediaAutoDownloadSettings.defaultSettings
    entries.append(.automaticDownloadReset(presentationData.theme, presentationData.strings.ChatSettings_AutoDownloadReset, data.automaticMediaDownloadSettings.cellular != defaultSettings.cellular || data.automaticMediaDownloadSettings.wifi != defaultSettings.wifi))
    
    entries.append(.autoSaveHeader(presentationData.strings.Settings_SaveToCameraRollSection))
    
    let privateLabelAndValue = autosaveLabelAndValue(presentationData: presentationData, settings: mediaAutoSaveSettings, peerType: .privateChats, exceptionPeers: autosaveExceptionPeers)
    let groupsLabelAndValue = autosaveLabelAndValue(presentationData: presentationData, settings: mediaAutoSaveSettings, peerType: .groups, exceptionPeers: autosaveExceptionPeers)
    let channelsLabelAndValue = autosaveLabelAndValue(presentationData: presentationData, settings: mediaAutoSaveSettings, peerType: .channels, exceptionPeers: autosaveExceptionPeers)
    
    entries.append(.autoSaveItem(index: 0, type: .privateChats, title: presentationData.strings.Notifications_PrivateChats, label: privateLabelAndValue.label, value: privateLabelAndValue.value))
    entries.append(.autoSaveItem(index: 1, type: .groups, title: presentationData.strings.Notifications_GroupChats, label: groupsLabelAndValue.label, value: groupsLabelAndValue.value))
    entries.append(.autoSaveItem(index: 2, type: .channels, title: presentationData.strings.Notifications_Channels, label: channelsLabelAndValue.label, value: channelsLabelAndValue.value))
    entries.append(.autoSaveInfo(presentationData.strings.Settings_SaveToCameraRollInfo))
    
    
    let dataSaving = effectiveDataSaving(for: data.voiceCallSettings, autodownloadSettings: data.autodownloadSettings)
    entries.append(.useLessVoiceData(presentationData.theme, presentationData.strings.ChatSettings_UseLessDataForCalls, dataSaving != .never))
    entries.append(.useLessVoiceDataInfo(presentationData.theme, presentationData.strings.CallSettings_UseLessDataLongDescription))
    
    entries.append(.otherHeader(presentationData.theme, presentationData.strings.ChatSettings_Other))
    entries.append(.openLinksIn(presentationData.theme, presentationData.strings.ChatSettings_OpenLinksIn, defaultWebBrowser))
    if #available(iOSApplicationExtension 13.2, iOS 13.2, *) {
        entries.append(.shareSheet(presentationData.theme, presentationData.strings.ChatSettings_IntentsSettings))
    }
    entries.append(.saveEditedPhotos(presentationData.theme, presentationData.strings.Settings_SaveEditedPhotos, data.generatedMediaStoreSettings.storeEditedPhotos))
    entries.append(.pauseMusicOnRecording(presentationData.theme, presentationData.strings.Settings_PauseMusicOnRecording, data.mediaInputSettings.pauseMusicOnRecording))
    entries.append(.raiseToListen(presentationData.theme, presentationData.strings.Settings_RaiseToListen, data.mediaInputSettings.enableRaiseToSpeak))
    entries.append(.raiseToListenInfo(presentationData.theme, presentationData.strings.Settings_RaiseToListenInfo))

    if !"".isEmpty, let contentSettingsConfiguration = contentSettingsConfiguration, contentSettingsConfiguration.canAdjustSensitiveContent {
        entries.append(.sensitiveContent(presentationData.strings.Settings_SensitiveContent, contentSettingsConfiguration.sensitiveContentEnabled))
        entries.append(.sensitiveContentInfo(presentationData.strings.Settings_SensitiveContentInfo))
    }
    
    let proxyValue: String
    if let proxySettings = data.proxySettings, let activeServer = proxySettings.activeServer, proxySettings.enabled {
        switch activeServer.connection {
            case .socks5:
                proxyValue = presentationData.strings.ChatSettings_ConnectionType_UseSocks5
            case .mtp:
                proxyValue = presentationData.strings.SocksProxySetup_ProxyTelegram
        }
    } else {
        proxyValue = presentationData.strings.GroupInfo_SharedMediaNone
    }
    entries.append(.connectionHeader(presentationData.theme, presentationData.strings.ChatSettings_ConnectionType_Title.uppercased()))
    entries.append(.connectionProxy(presentationData.theme, presentationData.strings.SocksProxySetup_Title, proxyValue))
        
    return entries
}

public func dataAndStorageController(context: AccountContext, focusOnItemTag: DataAndStorageEntryTag? = nil) -> ViewController {
    let initialState = DataAndStorageControllerState()
    let statePromise = ValuePromise(initialState, ignoreRepeated: true)
    
    var pushControllerImpl: ((ViewController) -> Void)?
    var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
    
    let actionsDisposable = DisposableSet()
    
    //let cacheUsagePromise = Promise<CacheUsageStatsResult?>()
    //cacheUsagePromise.set(cacheUsageStats(context: context))
    
    let updateSensitiveContentDisposable = MetaDisposable()
    actionsDisposable.add(updateSensitiveContentDisposable)
    
    let updatedContentSettingsConfiguration = contentSettingsConfiguration(network: context.account.network)
    |> map(Optional.init)
    let contentSettingsConfiguration = Promise<ContentSettingsConfiguration?>()
    contentSettingsConfiguration.set(.single(nil)
    |> then(updatedContentSettingsConfiguration))
    
    struct UsageData: Equatable {
        var network: Int64
        var storage: Int64
    }
    let usageSignal: Signal<UsageData, NoError> = combineLatest(
        context.account.postbox.mediaBox.storageBox.totalSize(),
        context.account.postbox.mediaBox.cacheStorageBox.totalSize(),
        accountNetworkUsageStats(account: context.account, reset: [])
    )
    |> map { disk1, disk2, networkStats -> UsageData in
        var network: Int64 = 0
        
        var keys: [KeyPath<NetworkUsageStats, Int64>] = []
        
        keys.append(\.generic.cellular.outgoing)
        keys.append(\.generic.cellular.incoming)
        keys.append(\.generic.wifi.incoming)
        keys.append(\.generic.wifi.outgoing)
        
        keys.append(\.image.cellular.outgoing)
        keys.append(\.image.cellular.incoming)
        keys.append(\.image.wifi.incoming)
        keys.append(\.image.wifi.outgoing)
        
        keys.append(\.video.cellular.outgoing)
        keys.append(\.video.cellular.incoming)
        keys.append(\.video.wifi.incoming)
        keys.append(\.video.wifi.outgoing)
        
        keys.append(\.audio.cellular.outgoing)
        keys.append(\.audio.cellular.incoming)
        keys.append(\.audio.wifi.incoming)
        keys.append(\.audio.wifi.outgoing)
        
        keys.append(\.file.cellular.outgoing)
        keys.append(\.file.cellular.incoming)
        keys.append(\.file.wifi.incoming)
        keys.append(\.file.wifi.outgoing)
        
        keys.append(\.call.cellular.outgoing)
        keys.append(\.call.cellular.incoming)
        keys.append(\.call.wifi.incoming)
        keys.append(\.call.wifi.outgoing)
        
        keys.append(\.sticker.cellular.outgoing)
        keys.append(\.sticker.cellular.incoming)
        keys.append(\.sticker.wifi.incoming)
        keys.append(\.sticker.wifi.outgoing)
        
        keys.append(\.voiceMessage.cellular.outgoing)
        keys.append(\.voiceMessage.cellular.incoming)
        keys.append(\.voiceMessage.wifi.incoming)
        keys.append(\.voiceMessage.wifi.outgoing)
        
        for key in keys {
            network += networkStats[keyPath: key]
        }
        
        return UsageData(network: network, storage: disk1 + disk2)
    }
    
    let dataAndStorageDataPromise = Promise<DataAndStorageData>()
    dataAndStorageDataPromise.set(context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings, ApplicationSpecificSharedDataKeys.automaticMediaDownloadSettings, ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings, ApplicationSpecificSharedDataKeys.voiceCallSettings, ApplicationSpecificSharedDataKeys.mediaInputSettings, SharedDataKeys.proxySettings])
    |> map { sharedData -> DataAndStorageData in
        var automaticMediaDownloadSettings: MediaAutoDownloadSettings
        if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.automaticMediaDownloadSettings]?.get(MediaAutoDownloadSettings.self) {
            automaticMediaDownloadSettings = value
        } else {
            automaticMediaDownloadSettings = .defaultSettings
        }
        
        var autodownloadSettings: AutodownloadSettings
        if let value = sharedData.entries[SharedDataKeys.autodownloadSettings]?.get(AutodownloadSettings.self) {
            autodownloadSettings = value
            automaticMediaDownloadSettings = automaticMediaDownloadSettings.updatedWithAutodownloadSettings(autodownloadSettings)
        } else {
            autodownloadSettings = .defaultSettings
        }
        
        let generatedMediaStoreSettings: GeneratedMediaStoreSettings
        if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings]?.get(GeneratedMediaStoreSettings.self) {
            generatedMediaStoreSettings = value
        } else {
            generatedMediaStoreSettings = GeneratedMediaStoreSettings.defaultSettings
        }
        
        let mediaInputSettings: MediaInputSettings
        if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.mediaInputSettings]?.get(MediaInputSettings.self) {
            mediaInputSettings = value
        } else {
            mediaInputSettings = MediaInputSettings.defaultSettings
        }
        
        let voiceCallSettings: VoiceCallSettings
        if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.voiceCallSettings]?.get(VoiceCallSettings.self) {
            voiceCallSettings = value
        } else {
            voiceCallSettings = VoiceCallSettings.defaultSettings
        }
        
        var proxySettings: ProxySettings?
        if let value = sharedData.entries[SharedDataKeys.proxySettings]?.get(ProxySettings.self) {
            proxySettings = value
        }
        
        return DataAndStorageData(automaticMediaDownloadSettings: automaticMediaDownloadSettings, autodownloadSettings: autodownloadSettings, generatedMediaStoreSettings: generatedMediaStoreSettings, mediaInputSettings: mediaInputSettings, voiceCallSettings: voiceCallSettings, proxySettings: proxySettings)
    })
    
    let arguments = DataAndStorageControllerArguments(openStorageUsage: {
        pushControllerImpl?(StorageUsageScreen(context: context, makeStorageUsageExceptionsScreen: { category in
            return storageUsageExceptionsScreen(context: context, category: category)
        }))
    }, openNetworkUsage: {
        let mediaAutoDownloadSettings = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.automaticMediaDownloadSettings])
        |> map { sharedData -> MediaAutoDownloadSettings in
            var automaticMediaDownloadSettings: MediaAutoDownloadSettings
            if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.automaticMediaDownloadSettings]?.get(MediaAutoDownloadSettings.self) {
                automaticMediaDownloadSettings = value
            } else {
                automaticMediaDownloadSettings = .defaultSettings
            }
            return automaticMediaDownloadSettings
        }
        
        let _ = (combineLatest(
            accountNetworkUsageStats(account: context.account, reset: []),
            mediaAutoDownloadSettings
        )
        |> take(1)
        |> deliverOnMainQueue).start(next: { stats, mediaAutoDownloadSettings in
            var stats = stats
            
            if stats.resetWifiTimestamp == 0 {
                var value = stat()
                if stat(context.account.basePath, &value) == 0 {
                    stats.resetWifiTimestamp = Int32(value.st_ctimespec.tv_sec)
                }
            }
            
            pushControllerImpl?(DataUsageScreen(context: context, stats: stats, mediaAutoDownloadSettings: mediaAutoDownloadSettings, makeAutodownloadSettingsController: { isCellular in
                return autodownloadMediaConnectionTypeController(context: context, connectionType: isCellular ? .cellular : .wifi)
            }))
        })
    }, openProxy: {
        pushControllerImpl?(proxySettingsController(context: context))
    }, openAutomaticDownloadConnectionType: { connectionType in
        pushControllerImpl?(autodownloadMediaConnectionTypeController(context: context, connectionType: connectionType))
    }, resetAutomaticDownload: {
        let presentationData = context.sharedContext.currentPresentationData.with { $0 }
        let actionSheet = ActionSheetController(presentationData: presentationData)
        actionSheet.setItemGroups([ActionSheetItemGroup(items: [
            ActionSheetTextItem(title: presentationData.strings.AutoDownloadSettings_ResetHelp),
            ActionSheetButtonItem(title: presentationData.strings.AutoDownloadSettings_Reset, color: .destructive, action: { [weak actionSheet] in
                actionSheet?.dismissAnimated()
                
                let _ = updateMediaDownloadSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings in
                    var settings = settings
                    let defaultSettings = MediaAutoDownloadSettings.defaultSettings
                    settings.cellular = defaultSettings.cellular
                    settings.wifi = defaultSettings.wifi
                    return settings
                }).start()
            })
            ]), ActionSheetItemGroup(items: [
                ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
                    actionSheet?.dismissAnimated()
                })
                ])])
        presentControllerImpl?(actionSheet, nil)
    }, toggleVoiceUseLessData: { value in
        let _ = updateVoiceCallSettingsSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
            var current = current
            current.dataSaving = value ? .always : .never
            return current
        }).start()
    }, openSaveIncoming: { type in
        pushControllerImpl?(saveIncomingMediaController(context: context, scope: .peerType(type)))
    }, toggleSaveEditedPhotos: { value in
        let _ = updateGeneratedMediaStoreSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
            return current.withUpdatedStoreEditedPhotos(value)
        }).start()
    }, togglePauseMusicOnRecording: { value in
        let _ = updateMediaInputSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
            return current.withUpdatedPauseMusicOnRecording(value)
        }).start()
    }, toggleRaiseToListen: { value in
        let _ = updateMediaInputSettingsInteractively(accountManager: context.sharedContext.accountManager, {
            $0.withUpdatedEnableRaiseToSpeak(value)
        }).start()
    }, toggleDownloadInBackground: { value in
        let _ = updateMediaDownloadSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings in
            var settings = settings
            settings.downloadInBackground = value
            return settings
        }).start()
    }, openBrowserSelection: {
        let controller = webBrowserSettingsController(context: context)
        pushControllerImpl?(controller)
    }, openIntents: {
        let controller = intentsSettingsController(context: context)
        pushControllerImpl?(controller)
    }, toggleSensitiveContent: { value in
        let update = {
            let _ = (contentSettingsConfiguration.get()
            |> take(1)
            |> deliverOnMainQueue).start(next: { [weak contentSettingsConfiguration] settings in
                if var settings = settings {
                    settings.sensitiveContentEnabled = value
                    contentSettingsConfiguration?.set(.single(settings))
                }
            })
            updateSensitiveContentDisposable.set(updateRemoteContentSettingsConfiguration(postbox: context.account.postbox, network: context.account.network, sensitiveContentEnabled: value).start())
        }
        if value {
            let presentationData = context.sharedContext.currentPresentationData.with { $0 }
            let alertController = textAlertController(context: context, title: presentationData.strings.SensitiveContent_Enable_Title, text: presentationData.strings.SensitiveContent_Enable_Text, actions: [
                TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}),
                TextAlertAction(type: .defaultAction, title: presentationData.strings.SensitiveContent_Enable_Confirm, action: {
                    update()
                })
            ])
            presentControllerImpl?(alertController, nil)
        } else {
            update()
        }
    })
    
    let preferencesKey: PostboxViewKey = .preferences(keys: Set([ApplicationSpecificPreferencesKeys.mediaAutoSaveSettings]))
    let preferences = context.account.postbox.combinedView(keys: [preferencesKey])
    |> map { views -> MediaAutoSaveSettings in
        guard let view = views.views[preferencesKey] as? PreferencesView else {
            return .default
        }
        return view.values[ApplicationSpecificPreferencesKeys.mediaAutoSaveSettings]?.get(MediaAutoSaveSettings.self) ?? MediaAutoSaveSettings.default
    }
    
    let autosaveExceptionPeers: Signal<[EnginePeer.Id: EnginePeer?], NoError> = preferences
    |> mapToSignal { mediaAutoSaveSettings -> Signal<[EnginePeer.Id: EnginePeer?], NoError> in
        let peerIds = mediaAutoSaveSettings.exceptions.map(\.id)
        return context.engine.data.get(EngineDataMap(
            peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:))
        ))
    }

    let sensitiveContent = Atomic<Bool?>(value: nil)
    
    let signal = combineLatest(queue: .mainQueue(),
        context.sharedContext.presentationData,
        statePromise.get(),
        dataAndStorageDataPromise.get(),
        context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.webBrowserSettings, ApplicationSpecificSharedDataKeys.mediaDisplaySettings]),
        contentSettingsConfiguration.get(),
        preferences,
        usageSignal,
        autosaveExceptionPeers
    )
    |> map { presentationData, state, dataAndStorageData, sharedData, contentSettingsConfiguration, mediaAutoSaveSettings, usageSignal, autosaveExceptionPeers -> (ItemListControllerState, (ItemListNodeState, Any)) in
        let webBrowserSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.webBrowserSettings]?.get(WebBrowserSettings.self) ?? WebBrowserSettings.defaultSettings
        let mediaSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.mediaDisplaySettings]?.get(MediaDisplaySettings.self) ?? MediaDisplaySettings.defaultSettings
        
        let options = availableOpenInOptions(context: context, item: .url(url: "https://telegram.org"))
        let defaultWebBrowser: String
        if let option = options.first(where: { $0.identifier == webBrowserSettings.defaultWebBrowser }) {
            defaultWebBrowser = option.title
        } else if webBrowserSettings.defaultWebBrowser == "inApp" {
            defaultWebBrowser = presentationData.strings.WebBrowser_InAppSafari
        } else {
            defaultWebBrowser = presentationData.strings.WebBrowser_Telegram
        }
        
        let previousSensitiveContent = sensitiveContent.swap(contentSettingsConfiguration?.sensitiveContentEnabled)
        var animateChanges = false
        if previousSensitiveContent != contentSettingsConfiguration?.sensitiveContentEnabled {
            animateChanges = true
        }
        
        let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChatSettings_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
        let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: dataAndStorageControllerEntries(state: state, data: dataAndStorageData, presentationData: presentationData, defaultWebBrowser: defaultWebBrowser, contentSettingsConfiguration: contentSettingsConfiguration, networkUsage: usageSignal.network, storageUsage: usageSignal.storage, mediaAutoSaveSettings: mediaAutoSaveSettings, autosaveExceptionPeers: autosaveExceptionPeers, mediaSettings: mediaSettings), style: .blocks, ensureVisibleItemTag: focusOnItemTag, emptyStateItem: nil, animateChanges: animateChanges)
        
        return (controllerState, (listState, arguments))
    } |> afterDisposed {
        actionsDisposable.dispose()
    }
    
    let controller = ItemListController(context: context, state: signal)
    pushControllerImpl = { [weak controller] c in
        if let controller = controller {
            (controller.navigationController as? NavigationController)?.pushViewController(c)
        }
    }
    presentControllerImpl = { [weak controller] c, a in
        controller?.present(c, in: .window(.root), with: a)
    }

    return controller
}