//
//  CollapsableNotification.swift
//  MVMCoreUI
//
//  Created by Scott Pfeil on 9/18/20.
//  Copyright © 2020 Verizon Wireless. All rights reserved.
//

import Foundation
import Combine
import Dispatch
import MVMCore
import VDSTokens

@objcMembers open class CollapsableNotification: View {
    //--------------------------------------------------
    // MARK: - Outlets
    //--------------------------------------------------

    public let topView = CollapsableNotificationTopView()
    public let bottomView = NotificationMoleculeView()
    public var verticalStack: UIStackView!
    
    public var cancellables = Set<AnyCancellable>()
    private var timerSource: DispatchSourceTimer?

    //--------------------------------------------------
    // MARK: - Life Cycle
    //--------------------------------------------------

    public override func setupView() {
        super.setupView()

        verticalStack = UIStackView(arrangedSubviews: [topView, bottomView])
        verticalStack.translatesAutoresizingMaskIntoConstraints = false
        verticalStack.axis = .vertical
        verticalStack.alignment = .fill
        verticalStack.distribution = .fill
        addSubview(verticalStack)
        NSLayoutConstraint.constraintPinSubview(verticalStack, pinTop: true, topConstant: 0, pinBottom: true, bottomConstant: 0, pinLeft: true, leftConstant: 0, pinRight: true, rightConstant: 0)
        
        reset()
        subscribeForNotifications()
    }
    
    open override func updateView(_ size: CGFloat) {
        super.updateView(size)
        verticalStack.updateView(size)
    }
    
    open override func reset() {
        super.reset()
        verticalStack.reset()
        backgroundColor = bottomView.backgroundColor
    }
    
    open func subscribeForNotifications() {
        // Resets state when about to show.
        NotificationHandler.shared()?.onNotificationWillShow.receive(on: DispatchQueue.main)
            .sink(receiveValue: { [weak self] (view, _) in
                guard let self = self,
                      self == view else { return }
                self.initialState()
            }).store(in: &cancellables)
        // Begins the collapse timer when shown.
        NotificationHandler.shared()?.onNotificationShown.receive(on: DispatchQueue.main)
            .sink(receiveValue: { [weak self] (view, _) in
                guard let self = self,
                      self == view,
                      let model = self.model as? CollapsableNotificationModel else { return }
                if !model.initiallyCollapsed {
                    self.autoCollapse()
                }
            }).store(in: &cancellables)
        // Cancels any collapse timer when dismissing
        NotificationHandler.shared()?.onNotificationWillDismiss.sink(receiveValue: { [weak self] (view, _) in
                guard let self = self,
                      self == view else { return }
                timerSource?.cancel()
                timerSource = nil
            }).store(in: &cancellables)
    }
    
    /// Set initial collapse/expand state.
    open func initialState() {
        guard let model = model as? CollapsableNotificationModel else { return }
        topView.isHidden = !model.alwaysShowTopLabel && !model.initiallyCollapsed
        topView.button.isUserInteractionEnabled = model.initiallyCollapsed
        bottomView.isHidden = model.initiallyCollapsed
        verticalStack.layoutIfNeeded()
    }
    
    //--------------------------------------------------
    // MARK: - Molecule
    //--------------------------------------------------
    
    open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
        super.set(with: model, delegateObject, additionalData)
        guard let model = model as? CollapsableNotificationModel else { return }
        topView.set(with: model, delegateObject, additionalData)
        bottomView.set(with: model, delegateObject, additionalData)
        topView.label.textColorConfiguration = bottomView.titleLabel.textColorConfiguration
        topView.label.surface = bottomView.surface
        
        // Update top view default noop to expand.
        if let topAction = model.topAction,
            topAction.actionType == ActionNoopModel.identifier {
            topView.button.addActionBlock(event: .touchUpInside) { [weak self] (button) in
                Task(priority: .userInitiated) {
                    try await Button.performButtonAction(with: topAction, button: button, delegateObject: delegateObject, additionalData: additionalData)
                }
                self?.expand(topViewShowing: model.alwaysShowTopLabel)
            }
        }
        initialState()
        backgroundColor = bottomView.backgroundColor
    }
    
    open func performBlockOperation(with block: @escaping (MVMCoreBlockOperation) -> Void) {
        let operation = MVMCoreBlockOperation(block: block)!
        NavigationHandler.shared().navigationQueue.addOperation(operation)
    }
    
    /// Collapses after a delay
    open func autoCollapse() {
        let delay: DispatchTimeInterval = DispatchTimeInterval.seconds((model as? CollapsableNotificationModel)?.collapseTime ?? 5)
        timerSource = DispatchSource.makeTimerSource()
        timerSource?.setEventHandler(handler: { [weak self] in
            // If accessibility focused, delay collapse.
            guard let self = self else { return }
            self.timerSource = nil
            MVMCoreDispatchUtility.performBlock(onMainThread: {
                if MVMCoreUIUtility.viewContainsAccessiblityFocus(self) {
                    NotificationCenter.default.addObserver(self, selector: #selector(self.accessibilityFocusChanged(notification:)), name: UIAccessibility.elementFocusedNotification, object: nil)
                    NotificationCenter.default.addObserver(self, selector: #selector(self.accessibilityFocusChanged(notification:)), name: UIAccessibility.voiceOverStatusDidChangeNotification, object: nil)

                } else {
                    self.collapse()
                }
            })
        })
        timerSource?.schedule(deadline: .now() + delay)
        timerSource?.activate()
    }
    
    /// Collapses to show just the top view.
    open func collapse(animated: Bool = true) {
        guard !bottomView.isHidden else { return }
        performBlockOperation { [weak self] (operation) in
            guard let strongSelf = self else { return }
            MVMCoreDispatchUtility.performBlock(onMainThread: {
                strongSelf.superview?.superview?.layoutIfNeeded()
                let animation = {
                    strongSelf.topView.isHidden = false
                    strongSelf.bottomView.isHidden = true
                    strongSelf.verticalStack.layoutIfNeeded()
                }
                let completion: (Bool) -> Void = { (finished) in
                    strongSelf.topView.button.isUserInteractionEnabled = true
                    strongSelf.superview?.superview?.layoutIfNeeded()
                    UIAccessibility.post(notification: .layoutChanged, argument: strongSelf.getAccessibilityLayoutChangedArgument())
                    operation.markAsFinished()
                }
                
                if animated {
                    UIView.animate(withDuration: 0.5, animations: animation, completion: completion)
                } else {
                    animation()
                    completion(true)
                }
            })
        }
    }
    
    /// Expands to show the bottom view.
    open func expand(topViewShowing: Bool = false, animated: Bool = true) {
        guard bottomView.isHidden else { return }
        performBlockOperation { [weak self] (operation) in
            guard let strongSelf = self else { return }
            MVMCoreDispatchUtility.performBlock(onMainThread: {
                strongSelf.superview?.superview?.layoutIfNeeded()
                strongSelf.topView.button.isUserInteractionEnabled = false
                let animation = {
                    strongSelf.topView.isHidden = !topViewShowing
                    strongSelf.bottomView.isHidden = false
                    strongSelf.verticalStack.layoutIfNeeded()
                }
                let completion: (Bool) -> Void = { (finished) in
                    strongSelf.superview?.superview?.layoutIfNeeded()
                    UIAccessibility.post(notification: .layoutChanged, argument: strongSelf.getAccessibilityLayoutChangedArgument())
                    strongSelf.autoCollapse()
                    operation.markAsFinished()
                }
                
                if animated {
                    UIView.animate(withDuration: 0.5, animations: animation, completion: completion)
                } else {
                    animation()
                    completion(true)
                }
            })
        }
    }
    
    open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
        return 120
    }
   
    /// Collapse if focus is no longer on this top alert.
    @objc private func accessibilityFocusChanged(notification: Notification) {
        if !UIAccessibility.isVoiceOverRunning || ((notification.userInfo?[UIAccessibility.focusedElementUserInfoKey] != nil) && !MVMCoreUIUtility.viewContainsAccessiblityFocus(self)) {
            NotificationCenter.default.removeObserver(self, name: UIAccessibility.elementFocusedNotification, object: nil)
            NotificationCenter.default.removeObserver(self, name: UIAccessibility.voiceOverStatusDidChangeNotification, object: nil)
            collapse()
        }
    }
}

extension CollapsableNotification: StatusBarUI {
    public func getStatusBarUI() -> (color: UIColor, style: UIStatusBarStyle) {
        let color = backgroundColor ?? VDSColor.feedbackInformationBackgroundOnlight
        var greyScale: CGFloat = 0
        topView.label.textColor.getWhite(&greyScale, alpha: nil)
        return (color, greyScale > 0.5 ? .lightContent : .default)
    }
}

extension CollapsableNotification: AccessibilityProtocol {
    public func getAccessibilityLayoutChangedArgument() -> Any? {
        if !topView.isHidden {
            return topView
        } else {
            return bottomView.titleLabel
        }
    }
}

extension CollapsableNotification: CollapsableNotificationProtocol {
    @MainActor
    public func collapse() {
        collapse(animated: true)
    }
}
