// Copyright © 2024 Rangeproof Pty Ltd. All rights reserved.

import UIKit
import SessionUIKit

open class OWSViewController: UIViewController {
    public var shouldUseTheme: Bool = true
    public var shouldIgnoreKeyboardChanges: Bool = false
    public var shouldAnimateBottomLayout: Bool = false
    
    /// If `true`, the bottom view never "reclaims" layout space if the keyboard is dismissed.
    /// Defaults to `false`.
    public var shouldBottomViewReserveSpaceForKeyboard: Bool = false
    
    private weak var bottomLayoutView: UIView?
    private var bottomLayoutConstraint: NSLayoutConstraint?
    
    open override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        return (UIDevice.current.isIPad ? .all : .portrait)
    }
    
    // MARK: - Initialization
    
    public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nil, bundle: nil)
        
        self.observeActivation()
    }
    
    required public init?(coder: NSCoder) {
        super.init(coder: coder)
        
        self.observeActivation()
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
    
    // MARK: - Lifecycle
    
    open override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        self.shouldAnimateBottomLayout = true
    }
    
    open override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        
        self.shouldAnimateBottomLayout = false
    }
    
    // MARK: - Functions
    
    public func pinViewToBottomOfViewControllerOrKeyboard(_ view: UIView, avoidNotch: Bool) {
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(keyboardWillShow(_:)),
            name: UIResponder.keyboardWillShowNotification,
            object: nil
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(keyboardDidShow(_:)),
            name: UIResponder.keyboardDidShowNotification,
            object: nil
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(keyboardWillHide(_:)),
            name: UIResponder.keyboardWillHideNotification,
            object: nil
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(keyboardDidHide(_:)),
            name: UIResponder.keyboardDidHideNotification,
            object: nil
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(keyboardWillChangeFrame(_:)),
            name: UIResponder.keyboardWillChangeFrameNotification,
            object: nil
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(keyboardDidChangeFrame(_:)),
            name: UIResponder.keyboardDidChangeFrameNotification,
            object: nil
        )

        self.bottomLayoutView = view
        self.bottomLayoutConstraint = view.pin(
            .bottom,
            to: .bottom,
            of: (avoidNotch ? self.view.safeAreaLayoutGuide : self.view)
        )
    }
    
    // MARK: - Observations
    
    private func observeActivation() {
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(appDidBecomeActive(_:)),
            name: UIApplication.didBecomeActiveNotification,
            object: nil
        )
    }
    
    @objc private func appDidBecomeActive(_ notification: NSNotification) {
        self.setNeedsStatusBarAppearanceUpdate()
    }
    
    @objc private func keyboardWillShow(_ notification: NSNotification) {
        self.handleKeyboardNotificationBase(notification)
    }
    
    @objc private func keyboardDidShow(_ notification: NSNotification) {
        self.handleKeyboardNotificationBase(notification)
    }
    
    @objc private func keyboardWillHide(_ notification: NSNotification) {
        self.handleKeyboardNotificationBase(notification)
    }
    
    @objc private func keyboardDidHide(_ notification: NSNotification) {
        self.handleKeyboardNotificationBase(notification)
    }
    
    @objc private func keyboardWillChangeFrame(_ notification: NSNotification) {
        self.handleKeyboardNotificationBase(notification)
    }
    
    @objc private func keyboardDidChangeFrame(_ notification: NSNotification) {
        self.handleKeyboardNotificationBase(notification)
    }

    // We use the name `handleKeyboardNotificationBase` instead of
    // `handleKeyboardNotification` to avoid accidentally
    // calling similarly methods with that name in subclasses,
    // e.g. ConversationViewController.
    private func handleKeyboardNotificationBase(_ notification: NSNotification) {
        guard !shouldIgnoreKeyboardChanges else { return }
        
        let userInfo: [AnyHashable: Any] = (notification.userInfo ?? [:])
        let keyboardRect: CGRect = ((userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect) ?? CGRect.zero)
        let convertedKeyboardRect: CGRect = view.convert(keyboardRect, from: nil)
        
        /// Adjust the position of the bottom view to account for the keyboard's intrusion into the view.
        ///
        /// On iPhoneX, when no keyboard is present, we include a buffer at the bottom of the screen so the bottom view clears the
        /// floating "home button". But because the keyboard includes it's own buffer, we subtract the length (height) of the bottomLayoutGuide,
        /// else we'd have an unnecessary buffer between the popped keyboard and the input bar.
        let offset: CGFloat = -max(0, (self.view.bounds.height - (self.view.window?.safeAreaInsets.bottom ?? 0) - convertedKeyboardRect.minY))
        let updateLayout: () -> () = { [weak self] in
            guard self?.shouldBottomViewReserveSpaceForKeyboard == false || offset < 0 else {
                /// To avoid unnecessary animations / layout jitter, some views never reclaim layout space when the keyboard is dismissed.
                ///
                /// They _do_ need to relayout if the user switches keyboards.
                return
            }
            
            self?.bottomLayoutConstraint?.constant = offset
            self?.bottomLayoutView?.superview?.layoutIfNeeded()
        }
        
        /// UIKit by default animates all changes in response to keyboard events.
        /// We want to suppress those animations if the view isn't visible, otherwise presentation animations don't work properly.
        guard shouldAnimateBottomLayout else {
            return UIView.performWithoutAnimation {
                updateLayout()
            }
        }
        
        updateLayout()
    }
}
