import Foundation
import UIKit
import Display
import TelegramCore
import SwiftSignalKit
import AsyncDisplayKit
import Postbox
import AccountContext
import TelegramPresentationData
import TelegramStringFormatting
import Photos
import CheckNode
import LegacyComponents
import PhotoResources
import InvisibleInkDustNode
import ImageBlur
import FastBlur
import MediaEditor
import RadialStatusNode

private let leftShadowImage: UIImage = {
    let baseImage = UIImage(bundleImageName: "Peer Info/MediaGridShadow")!
    let image = generateImage(baseImage.size, rotatedContext: { size, context in
        context.clear(CGRect(origin: CGPoint(), size: size))
        
        context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
        context.scaleBy(x: -1.0, y: 1.0)
        context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
        
        UIGraphicsPushContext(context)
        baseImage.draw(in: CGRect(origin: CGPoint(), size: size))
        UIGraphicsPopContext()
    })
    return image!
}()

private let rightShadowImage: UIImage = {
    let baseImage = UIImage(bundleImageName: "Peer Info/MediaGridShadow")!
    let image = generateImage(baseImage.size, rotatedContext: { size, context in
        context.clear(CGRect(origin: CGPoint(), size: size))
        
        UIGraphicsPushContext(context)
        baseImage.draw(in: CGRect(origin: CGPoint(), size: size))
        UIGraphicsPopContext()
    })
    return image!
}()

enum MediaPickerGridItemContent: Equatable {
    case asset(PHFetchResult<PHAsset>, Int)
    case media(MediaPickerScreen.Subject.Media, Int)
    case draft(MediaEditorDraft, Int)
}

final class MediaPickerGridItem: GridItem {
    let content: MediaPickerGridItemContent
    let interaction: MediaPickerInteraction
    let theme: PresentationTheme
    let strings: PresentationStrings
    let selectable: Bool
    let enableAnimations: Bool
    let stories: Bool
    
    let section: GridSection? = nil
    
    init(content: MediaPickerGridItemContent, interaction: MediaPickerInteraction, theme: PresentationTheme, strings: PresentationStrings, selectable: Bool, enableAnimations: Bool, stories: Bool) {
        self.content = content
        self.interaction = interaction
        self.strings = strings
        self.theme = theme
        self.selectable = selectable
        self.enableAnimations = enableAnimations
        self.stories = stories
    }
    
    func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode {
        switch self.content {
        case let .asset(fetchResult, index):
            let node = MediaPickerGridItemNode()
            node.setup(interaction: self.interaction, fetchResult: fetchResult, index: index, theme: self.theme, selectable: self.selectable, enableAnimations: self.enableAnimations, stories: self.stories)
            return node
        case let .media(media, index):
            let node = MediaPickerGridItemNode()
            node.setup(interaction: self.interaction, media: media, index: index, theme: self.theme, selectable: self.selectable, enableAnimations: self.enableAnimations, stories: self.stories)
            return node
        case let .draft(draft, index):
            let node = MediaPickerGridItemNode()
            node.setup(interaction: self.interaction, draft: draft, index: index, theme: self.theme, strings: self.strings, selectable: self.selectable, enableAnimations: self.enableAnimations, stories: self.stories)
            return node
        }
    }
    
    func update(node: GridItemNode) {
        guard let node = node as? MediaPickerGridItemNode else {
            assertionFailure()
            return
        }
        switch self.content {
        case let .asset(fetchResult, index):
            node.setup(interaction: self.interaction, fetchResult: fetchResult, index: index, theme: self.theme, selectable: self.selectable, enableAnimations: self.enableAnimations, stories: self.stories)
        case let .media(media, index):
            node.setup(interaction: self.interaction, media: media, index: index, theme: self.theme, selectable: self.selectable, enableAnimations: self.enableAnimations, stories: self.stories)
        case let .draft(draft, index):
            node.setup(interaction: self.interaction, draft: draft, index: index, theme: self.theme, strings: self.strings, selectable: self.selectable, enableAnimations: self.enableAnimations, stories: self.stories)
        }
    }
}

final class MediaPickerGridItemNode: GridItemNode {
    var currentMediaState: (TGMediaSelectableItem, Int)?
    var currentAssetState: (PHFetchResult<PHAsset>, Int)?
    var currentAsset: PHAsset?
    
    var currentDraftState: (MediaEditorDraft, Int)?
    var enableAnimations: Bool = true
    var stories: Bool = false
    private var selectable: Bool = false
    
    private let backgroundNode: ASImageNode
    private let imageNode: ImageNode
    private var checkNode: InteractiveCheckNode?
    private let leftShadowNode: ASImageNode
    private let rightShadowNode: ASImageNode
    private let typeIconNode: ASImageNode
    private let durationNode: ImmediateTextNode
    private let draftNode: ImmediateTextNode
    private var statusNode: RadialStatusNode?
    
    private let activateAreaNode: AccessibilityAreaNode
    
    private var interaction: MediaPickerInteraction?
    private var theme: PresentationTheme?
        
    private struct SelectionState: Equatable {
        let selected: Bool
        let index: Int?
        let count: Int
    }
    private let selectionPromise = ValuePromise<SelectionState>(SelectionState(selected: false, index: nil, count: 0))
    private let spoilerDisposable = MetaDisposable()
    var spoilerNode: SpoilerOverlayNode?
    var priceNode: PriceNode?
    
    private let progressDisposable = MetaDisposable()
    
    private var currentIsPreviewing = false
            
    var selected: (() -> Void)?
        
    override init() {
        self.backgroundNode = ASImageNode()
        self.backgroundNode.contentMode = .scaleToFill
        self.backgroundNode.isLayerBacked = true
        
        self.imageNode = ImageNode()
        self.imageNode.clipsToBounds = true
        self.imageNode.contentMode = .scaleAspectFill
        self.imageNode.isLayerBacked = true
        self.imageNode.animateFirstTransition = false
        
        self.leftShadowNode = ASImageNode()
        self.leftShadowNode.displaysAsynchronously = false
        self.leftShadowNode.displayWithoutProcessing = true
        self.leftShadowNode.image = leftShadowImage
        self.leftShadowNode.isLayerBacked = true
        
        self.rightShadowNode = ASImageNode()
        self.rightShadowNode.displaysAsynchronously = false
        self.rightShadowNode.displayWithoutProcessing = true
        self.rightShadowNode.image = rightShadowImage
        self.rightShadowNode.isLayerBacked = true
        
        self.typeIconNode = ASImageNode()
        self.typeIconNode.displaysAsynchronously = false
        self.typeIconNode.displayWithoutProcessing = true
        self.typeIconNode.isLayerBacked = true
        
        self.durationNode = ImmediateTextNode()
        self.durationNode.isLayerBacked = true
        self.durationNode.textShadowColor = UIColor(white: 0.0, alpha: 0.4)
        self.durationNode.textShadowBlur = 4.0
        self.draftNode = ImmediateTextNode()
        
        self.activateAreaNode = AccessibilityAreaNode()
        self.activateAreaNode.accessibilityTraits = [.image]
                        
        super.init()
        
        self.clipsToBounds = true
        
        self.addSubnode(self.imageNode)
        self.addSubnode(self.activateAreaNode)
        
        self.imageNode.contentUpdated = { [weak self] image in
            self?.spoilerNode?.setImage(image)
        }
    }
    
    deinit {
        self.spoilerDisposable.dispose()
    }

    var identifier: String {
        if let (draft, _) = self.currentDraftState {
            return draft.path
        } else {
            return self.selectableItem?.uniqueIdentifier ?? ""
        }
    }
    
    var selectableItem: TGMediaSelectableItem? {
        if let (media, _) = self.currentMediaState {
            return media
        } else if let (fetchResult, index) = self.currentAssetState {
            return TGMediaAsset(phAsset: fetchResult[index])
        } else {
            return nil
        }
    }
    
    var _cachedTag: Int32?
    var tag: Int32? {
        if let tag = self._cachedTag {
            return tag
        } else if let (fetchResult, index) = self.currentAssetState {
            let asset = fetchResult.object(at: index)
            if let localTimestamp = asset.creationDate?.timeIntervalSince1970 {
                let tag = Month(localTimestamp: Int32(exactly: floor(localTimestamp)) ?? 0).packedValue
                self._cachedTag = tag
                return tag
            } else {
                return nil
            }
        } else if let (draft, _) = self.currentDraftState {
            let tag = Month(localTimestamp: draft.timestamp).packedValue
            self._cachedTag = tag
            return tag
        } else {
            return nil
        }
    }
    
    func updateSelectionState(animated: Bool = false) {
        if self.checkNode == nil, let _ = self.interaction?.selectionState, self.selectable, let theme = self.theme {
            let checkNode = InteractiveCheckNode(theme: CheckNodeTheme(theme: theme, style: .overlay))
            checkNode.valueChanged = { [weak self] value in
                if let strongSelf = self, let interaction = strongSelf.interaction, let selectableItem = strongSelf.selectableItem {
                    if !interaction.toggleSelection(selectableItem, value, false) {
                        strongSelf.checkNode?.setSelected(false, animated: false)
                    }
                }
            }
            self.addSubnode(checkNode)
            self.checkNode = checkNode
            self.setNeedsLayout()
        }

        if let interaction = self.interaction, let selectionState = interaction.selectionState {
            let selected = selectionState.isIdentifierSelected(self.identifier)
            var selectionIndex: Int?
            if let selectableItem = self.selectableItem {
                let index = selectionState.index(of: selectableItem)
                if index != NSNotFound {
                    self.checkNode?.content = .counter(Int(index))
                    selectionIndex = Int(index)
                }
            }
            self.checkNode?.setSelected(selected, animated: animated)
            self.selectionPromise.set(SelectionState(selected: selected, index: selectionIndex, count: selectionState.selectedItems().count))
        }
    }
    
    private var innerIsHidden = false
    func updateHiddenMedia() {
        let wasHidden = self.innerIsHidden
        if self.identifier == self.interaction?.hiddenMediaId {
            self.isHidden = true
            self.innerIsHidden = true
        } else {
            self.isHidden  = false
            self.innerIsHidden = false
            if wasHidden {
                self.animateFadeIn(animateCheckNode: true, animateSpoilerNode: true)
            }
        }
    }
    
    func animateFadeIn(animateCheckNode: Bool, animateSpoilerNode: Bool) {
        if animateCheckNode {
            self.checkNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
        }
        self.leftShadowNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
        self.rightShadowNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
        self.typeIconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
        self.durationNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
        self.draftNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
        self.priceNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
        if animateSpoilerNode || self.priceNode != nil {
            self.spoilerNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
        }
    }
        
    override func didLoad() {
        super.didLoad()
        
        self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:))))
    }
    
    func updateProgress(_ value: Float?, animated: Bool) {
        if let value {
            let statusNode: RadialStatusNode
            if let current = self.statusNode {
                statusNode = current
            } else {
                statusNode = RadialStatusNode(backgroundNodeColor: UIColor(rgb: 0x000000, alpha: 0.6))
                statusNode.isUserInteractionEnabled = false
                self.addSubnode(statusNode)
                self.statusNode = statusNode
            }
            let adjustedProgress = max(0.027, CGFloat(value))
            let state: RadialStatusNodeState = .progress(color: .white, lineWidth: nil, value: adjustedProgress, cancelEnabled: true, animateRotation: true)
            statusNode.transitionToState(state)
        } else if let statusNode = self.statusNode {
            self.statusNode = nil
            if animated {
                statusNode.transitionToState(.none, animated: true, completion: { [weak statusNode] in
                    statusNode?.removeFromSupernode()
                })
            } else {
                statusNode.removeFromSupernode()
            }
        }
    }
    
    func setup(interaction: MediaPickerInteraction, draft: MediaEditorDraft, index: Int, theme: PresentationTheme, strings: PresentationStrings, selectable: Bool, enableAnimations: Bool, stories: Bool) {
        self.interaction = interaction
        self.theme = theme
        self.selectable = selectable
        self.enableAnimations = enableAnimations
        
        self.backgroundColor = theme.list.mediaPlaceholderColor
        
        if self.currentDraftState == nil || self.currentDraftState?.0.path != draft.path || self.currentDraftState!.1 != index || self.currentAssetState != nil {
            let imageSignal: Signal<UIImage?, NoError> = .single(draft.thumbnail)
            self.imageNode.setSignal(imageSignal)
            
            self.currentDraftState = (draft, index)
            if self.currentAssetState != nil {
                self.currentAsset = nil
                self.currentAssetState = nil
                self.typeIconNode.removeFromSupernode()
                
                self.progressDisposable.set(nil)
                self.updateProgress(nil, animated: false)
                
                self.backgroundNode.image = nil
                self.imageNode.contentMode = .scaleAspectFill
            }
            
            if self.draftNode.supernode == nil {
                self.draftNode.attributedText = NSAttributedString(string: strings.MediaEditor_Draft, font: Font.semibold(12.0), textColor: .white)
                self.addSubnode(self.draftNode)
            }
            
            if draft.isVideo {
                self.typeIconNode.image = UIImage(bundleImageName: "Media Editor/MediaVideo")
                
                self.durationNode.attributedText = NSAttributedString(string: stringForDuration(Int32(draft.duration ?? 0.0)), font: Font.semibold(11.0), textColor: .white)
                
                if self.typeIconNode.supernode == nil {
                    self.addSubnode(self.rightShadowNode)
                    self.addSubnode(self.typeIconNode)
                    self.addSubnode(self.durationNode)
                    self.setNeedsLayout()
                }
            } else {
                if self.typeIconNode.supernode != nil {
                    self.typeIconNode.removeFromSupernode()
                }
                if self.durationNode.supernode != nil {
                    self.durationNode.removeFromSupernode()
                }
                if self.leftShadowNode.supernode != nil {
                    self.leftShadowNode.removeFromSupernode()
                }
                if self.rightShadowNode.supernode != nil {
                    self.rightShadowNode.removeFromSupernode()
                }
            }
            
            self.setNeedsLayout()
        }
        
        self.updateSelectionState()
        self.updateHiddenMedia()
    }
    
    func setup(interaction: MediaPickerInteraction, media: MediaPickerScreen.Subject.Media, index: Int, theme: PresentationTheme, selectable: Bool, enableAnimations: Bool, stories: Bool) {
        self.interaction = interaction
        self.theme = theme
        self.selectable = selectable
        self.enableAnimations = enableAnimations
        self.stories = stories
        
        self.backgroundColor = theme.list.mediaPlaceholderColor
        
        if stories {
            if self.backgroundNode.supernode == nil {
                self.insertSubnode(self.backgroundNode, at: 0)
            }
        }
        if self.draftNode.supernode != nil {
            self.draftNode.removeFromSupernode()
        }
                
        if self.currentMediaState == nil || self.currentMediaState!.0.uniqueIdentifier != media.identifier || self.currentMediaState!.1 != index {
            self.currentMediaState = (media.asset, index)
            
            if self.draftNode.supernode != nil {
                self.draftNode.removeFromSupernode()
            }
            
            self.setNeedsLayout()
        }
        
        self.updateSelectionState()
        self.updateHiddenMedia()
    }
        
    func setup(interaction: MediaPickerInteraction, fetchResult: PHFetchResult<PHAsset>, index: Int, theme: PresentationTheme, selectable: Bool, enableAnimations: Bool, stories: Bool) {
        self.interaction = interaction
        self.theme = theme
        self.selectable = selectable
        self.enableAnimations = enableAnimations
        self.stories = stories
        
        self.backgroundColor = theme.list.mediaPlaceholderColor
        
        if stories {
            if self.backgroundNode.supernode == nil {
                self.insertSubnode(self.backgroundNode, at: 0)
            }
        }
        if self.draftNode.supernode != nil {
            self.draftNode.removeFromSupernode()
        }
        
        if self.currentAssetState == nil || self.currentAssetState!.0 !== fetchResult || self.currentAssetState!.1 != index || self.currentDraftState != nil {
            let editingContext = interaction.editingState
            let asset = fetchResult.object(at: index)
            
            if asset.localIdentifier == self.currentAsset?.localIdentifier {
                return
            }
            self.backgroundNode.image = nil
            
            self.progressDisposable.set(
                (interaction.downloadManager.downloadProgress(identifier: asset.localIdentifier)
                 |> deliverOnMainQueue).start(next: { [weak self] status in
                     if let self {
                         switch status {
                         case .none, .completed:
                             self.updateProgress(nil, animated: true)
                         case let .progress(progress):
                             self.updateProgress(progress, animated: true)
                         }
                     }
                 })
            )
            
            self.backgroundNode.image = nil
            
            if #available(iOS 15.0, *) {
                self.activateAreaNode.accessibilityLabel = "Photo \(asset.creationDate?.formatted(date: .abbreviated, time: .standard) ?? "")"
            }
            
            let editedSignal = Signal<UIImage?, NoError> { subscriber in
                if let signal = editingContext.thumbnailImageSignal(forIdentifier: asset.localIdentifier) {
                    let disposable = signal.start(next: { next in
                        if let image = next as? UIImage {
                            subscriber.putNext(image)
                        } else {
                            subscriber.putNext(nil)
                        }
                    }, error: { _ in
                    }, completed: nil)!

                    return ActionDisposable {
                        disposable.dispose()
                    }
                } else {
                    return EmptyDisposable
                }
            }
            
            let scale = min(2.0, UIScreenScale)
            let targetSize: CGSize
            if stories {
                targetSize = CGSize(width: 128.0 * UIScreenScale, height: 128.0 * UIScreenScale)
            } else {
                targetSize = CGSize(width: 128.0 * scale, height: 128.0 * scale)
            }
            
            let assetImageSignal = assetImage(fetchResult: fetchResult, index: index, targetSize: targetSize, exact: false, deliveryMode: .opportunistic, synchronous: false)
//            |> then(
//                assetImage(fetchResult: fetchResult, index: index, targetSize: targetSize, exact: false, deliveryMode: .highQualityFormat, synchronous: false)
//                |> delay(0.03, queue: Queue.concurrentDefaultQueue())
//            )

            if stories {
                self.imageNode.contentUpdated = { [weak self] image in
                    if let self {
                        if self.backgroundNode.image == nil {
                            if let image, image.size.width > image.size.height {
                                self.imageNode.contentMode = .scaleAspectFit
                                Queue.concurrentDefaultQueue().async {
                                    let colors = mediaEditorGetGradientColors(from: image)
                                    let gradientImage = mediaEditorGenerateGradientImage(size: CGSize(width: 3.0, height: 128.0), colors: colors.array)
                                    Queue.mainQueue().async {
                                        self.backgroundNode.image = gradientImage
                                    }
                                }
                            } else {
                                self.imageNode.contentMode = .scaleAspectFill
                            }
                        }
                    }
                }
            }
            
            let originalSignal = assetImageSignal
            let imageSignal: Signal<UIImage?, NoError> = editedSignal
            |> mapToSignal { result in
                if let result = result {
                    return .single(result)
                } else {
                    return originalSignal
                }
            }
            self.imageNode.setSignal(imageSignal)

            let spoilerSignal = Signal<Bool, NoError> { subscriber in
                if let signal = editingContext.spoilerSignal(forIdentifier: asset.localIdentifier) {
                    let disposable = signal.start(next: { next in
                        if let next = next as? Bool {
                            subscriber.putNext(next)
                        }
                    }, error: { _ in
                    }, completed: nil)!
                    
                    return ActionDisposable {
                        disposable.dispose()
                    }
                } else {
                    return EmptyDisposable
                }
            }
            
            let priceSignal = Signal<Int64?, NoError> { subscriber in
                if let signal = editingContext.priceSignal(forIdentifier: asset.localIdentifier) {
                    let disposable = signal.start(next: { next in
                        subscriber.putNext(next as? Int64)
                    }, error: { _ in
                    }, completed: nil)!
                    
                    return ActionDisposable {
                        disposable.dispose()
                    }
                } else {
                    return EmptyDisposable
                }
            }
            
            self.spoilerDisposable.set((combineLatest(spoilerSignal, priceSignal, self.selectionPromise.get())
            |> deliverOnMainQueue).start(next: { [weak self] hasSpoiler, price, selectionState in
                guard let strongSelf = self else {
                    return
                }
                strongSelf.updateHasSpoiler(hasSpoiler, price: selectionState.selected ? price : nil, isSingle: selectionState.count == 1 || selectionState.index == 1)
            }))
            
            if self.currentDraftState != nil {
                self.currentDraftState = nil
            }
            
            var typeIcon: UIImage?
            var duration: String?
            if asset.mediaType == .video {
                if asset.mediaSubtypes.contains(.videoHighFrameRate) {
                    typeIcon = UIImage(bundleImageName: "Media Editor/MediaSlomo")
                } else if asset.mediaSubtypes.contains(.videoTimelapse) {
                    typeIcon = UIImage(bundleImageName: "Media Editor/MediaTimelapse")
                } else {
                    typeIcon = UIImage(bundleImageName: "Media Editor/MediaVideo")
                }
                duration = stringForDuration(Int32(asset.duration))
            }
            if asset.isFavorite {
                typeIcon = generateTintedImage(image: UIImage(bundleImageName: "Media Grid/Favorite"), color: .white)
            }
            
            if typeIcon != nil {
                if self.leftShadowNode.supernode == nil {
                    self.addSubnode(self.leftShadowNode)
                }
            } else if self.leftShadowNode.supernode != nil {
                self.leftShadowNode.removeFromSupernode()
            }
            
            if duration != nil {
                if self.rightShadowNode.supernode == nil {
                    self.addSubnode(self.rightShadowNode)
                }
            } else if self.rightShadowNode.supernode != nil {
                self.rightShadowNode.removeFromSupernode()
            }
            
            if let typeIcon {
                self.typeIconNode.image = typeIcon
                if self.typeIconNode.supernode == nil {
                    self.addSubnode(self.typeIconNode)
                }
            } else if self.typeIconNode.supernode != nil {
                self.typeIconNode.removeFromSupernode()
            }
            
            if let duration {
                self.durationNode.attributedText = NSAttributedString(string: duration, font: Font.semibold(11.0), textColor: .white)
                if self.durationNode.supernode == nil {
                    self.addSubnode(self.durationNode)
                }
            } else if self.durationNode.supernode != nil {
                self.durationNode.removeFromSupernode()
            }
            
            self.currentAssetState = (fetchResult, index)
            self.currentAsset = asset
            self.setNeedsLayout()
        }
        
        self.updateSelectionState()
        self.updateHiddenMedia()
    }
    
    private var currentPrice: Int64?
    private var didSetupSpoiler = false
    private func updateHasSpoiler(_ hasSpoiler: Bool, price: Int64?, isSingle: Bool) {
        var animated = true
        if !self.didSetupSpoiler {
            animated = false
            self.didSetupSpoiler = true
        }
        self.currentPrice = isSingle ? price : nil
        
        if hasSpoiler || price != nil {
            if self.spoilerNode == nil {
                let spoilerNode = SpoilerOverlayNode(enableAnimations: self.enableAnimations)
                self.insertSubnode(spoilerNode, aboveSubnode: self.imageNode)
                self.spoilerNode = spoilerNode
                
                spoilerNode.setImage(self.imageNode.image)
                
                if animated {
                    spoilerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
                }
            }
            let bounds = self.bounds
            self.spoilerNode?.update(size: bounds.size, transition: .immediate)
            self.spoilerNode?.frame = CGRect(origin: .zero, size: bounds.size)
            
            if let price {
                let priceNode: PriceNode
                if let currentPriceNode = self.priceNode {
                    priceNode = currentPriceNode
                } else {
                    priceNode = PriceNode()
                    if let spoilerNode = self.spoilerNode {
                        self.insertSubnode(priceNode, aboveSubnode: spoilerNode)
                    }
                    self.priceNode = priceNode
                    
                    if animated {
                        priceNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
                    }
                }
                
                self.priceNode?.update(size: bounds.size, price: isSingle ? price : nil, small: true, transition: .immediate)
            }
        } else if let spoilerNode = self.spoilerNode {
            self.spoilerNode = nil
            spoilerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak spoilerNode] _ in
                spoilerNode?.removeFromSupernode()
            })
            
            if let priceNode = self.priceNode {
                self.priceNode = nil
                priceNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak priceNode] _ in
                    priceNode?.removeFromSupernode()
                })
            }
        }
    }
    
    override func layout() {
        super.layout()
        
        let backgroundSize = CGSize(width: self.bounds.width, height: floorToScreenPixels(self.bounds.height / 9.0 * 16.0))
        self.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((self.bounds.height - backgroundSize.height) / 2.0)), size: backgroundSize)
        self.imageNode.frame = self.bounds
        self.leftShadowNode.frame = CGRect(x: 0.0, y: self.bounds.height - leftShadowImage.size.height, width: min(leftShadowImage.size.width, self.bounds.width), height: leftShadowImage.size.height)
        self.rightShadowNode.frame = CGRect(x: self.bounds.width - min(rightShadowImage.size.width, self.bounds.width), y: self.bounds.height - rightShadowImage.size.height, width: min(rightShadowImage.size.width, self.bounds.width), height: rightShadowImage.size.height)
        self.typeIconNode.frame = CGRect(x: 0.0, y: self.bounds.height - 20.0, width: 19.0, height: 19.0)
        self.activateAreaNode.frame = self.bounds
        
        if self.durationNode.supernode != nil {
            let durationSize = self.durationNode.updateLayout(self.bounds.size)
            self.durationNode.frame = CGRect(origin: CGPoint(x: self.bounds.size.width - durationSize.width - 6.0, y: self.bounds.height - durationSize.height - 6.0), size: durationSize)
        }
        
        if self.draftNode.supernode != nil {
            let draftSize = self.draftNode.updateLayout(self.bounds.size)
            self.draftNode.frame = CGRect(origin: CGPoint(x: 7.0, y: 5.0), size: draftSize)
        }
        
        let checkSize = CGSize(width: 29.0, height: 29.0)
        self.checkNode?.frame = CGRect(origin: CGPoint(x: self.bounds.width - checkSize.width - 3.0, y: 3.0), size: checkSize)
        
        if let spoilerNode = self.spoilerNode, self.bounds.width > 0.0 {
            spoilerNode.frame = self.bounds
            spoilerNode.update(size: self.bounds.size, transition: .immediate)
        }
        
        if let priceNode = self.priceNode, self.bounds.width > 0.0 {
            priceNode.frame = self.bounds
            priceNode.update(size: self.bounds.size, price: self.currentPrice, small: true, transition: .immediate)
        }
        
        let statusSize = CGSize(width: 40.0, height: 40.0)
        if let statusNode = self.statusNode {
            statusNode.view.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((self.bounds.width - statusSize.width) / 2.0), y: floorToScreenPixels((self.bounds.height - statusSize.height) / 2.0)), size: statusSize)
        }
    }
    
    func transitionView(snapshot: Bool) -> UIView {
        if snapshot {
            let view = self.imageNode.layer.snapshotContentTreeAsView(unhide: true)!
            view.frame = self.convert(self.bounds, to: nil)
            return view
        } else {
            return self.view
        }
    }
    
    func transitionImage() -> UIImage? {
        if let backgroundImage = self.backgroundNode.image {
            let size = CGSize(width: self.bounds.width, height: self.bounds.height / 9.0 * 16.0)
            return generateImage(size, contextGenerator: { size, context in
                if let cgImage = backgroundImage.cgImage {
                    context.draw(cgImage, in: CGRect(origin: .zero, size: size))
                    if let image = self.imageNode.image, let cgImage = image.cgImage {
                        let fittedSize = image.size.fitted(size)
                        let fittedFrame = CGRect(origin: CGPoint(x: (size.width - fittedSize.width) / 2.0, y: (size.height - fittedSize.height) / 2.0), size: fittedSize)
                        context.draw(cgImage, in: fittedFrame)
                    }
                }
            })
        } else {
            return self.imageNode.image
        }
    }
        
    @objc func imageNodeTap(_ recognizer: UITapGestureRecognizer) {
        if let (draft, _) = self.currentDraftState {
            self.interaction?.openDraft(draft, self.imageNode.image)
            return
        }
        guard let (fetchResult, index) = self.currentAssetState else {
            return
        }
        if self.statusNode != nil {
            if let asset = self.currentAsset {
                self.interaction?.downloadManager.cancel(identifier: asset.localIdentifier)
            }
        } else {
            self.interaction?.openMedia(fetchResult, index, self.imageNode.image)
        }
    }
}

class SpoilerOverlayNode: ASDisplayNode {
    private let blurNode: ASImageNode
    let dustNode: MediaDustNode
  
    private var maskView: UIView?
    private var maskLayer: CAShapeLayer?
    
    init(enableAnimations: Bool) {
        self.blurNode = ASImageNode()
        self.blurNode.displaysAsynchronously = false
        self.blurNode.contentMode = .scaleAspectFill
         
        self.dustNode = MediaDustNode(enableAnimations: enableAnimations)
        
        super.init()
        
        self.clipsToBounds = true
        self.isUserInteractionEnabled = false
                
        self.addSubnode(self.blurNode)
        self.addSubnode(self.dustNode)
    }
    
    override func didLoad() {
        super.didLoad()
        
        let maskView = UIView()
        self.maskView = maskView
//        self.dustNode.view.mask = maskView
        
        let maskLayer = CAShapeLayer()
        maskLayer.fillRule = .evenOdd
        maskLayer.fillColor = UIColor.white.cgColor
        maskView.layer.addSublayer(maskLayer)
        self.maskLayer = maskLayer
    }
    
    func setImage(_ image: UIImage?) {
        self.blurNode.image = image.flatMap { blurredImage($0) }
    }
    
    func update(size: CGSize, transition: ContainedViewLayoutTransition) {
        transition.updateFrame(node: self.blurNode, frame: CGRect(origin: .zero, size: size))
        
        transition.updateFrame(node: self.dustNode, frame: CGRect(origin: .zero, size: size))
        self.dustNode.update(size: size, color: .white, transition: transition)
    }
}

private func blurredImage(_ image: UIImage) -> UIImage? {
    guard let image = image.cgImage else {
        return nil
    }
    
    let thumbnailSize = CGSize(width: image.width, height: image.height)
    let thumbnailContextSize = thumbnailSize.aspectFilled(CGSize(width: 20.0, height: 20.0))
    if let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) {
        thumbnailContext.withFlippedContext { c in
            c.interpolationQuality = .none
            c.draw(image, in: CGRect(origin: CGPoint(), size: thumbnailContextSize))
        }
        imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes)
        
        let thumbnailContext2Size = thumbnailSize.aspectFitted(CGSize(width: 100.0, height: 100.0))
        if let thumbnailContext2 = DrawingContext(size: thumbnailContext2Size, scale: 1.0) {
            thumbnailContext2.withFlippedContext { c in
                c.interpolationQuality = .none
                if let image = thumbnailContext.generateImage()?.cgImage {
                    c.draw(image, in: CGRect(origin: CGPoint(), size: thumbnailContext2Size))
                }
            }
            imageFastBlur(Int32(thumbnailContext2Size.width), Int32(thumbnailContext2Size.height), Int32(thumbnailContext2.bytesPerRow), thumbnailContext2.bytes)
            adjustSaturationInContext(context: thumbnailContext2, saturation: 1.7)
            return thumbnailContext2.generateImage()
        }
    }
    return nil
}