//
//  LNKeyboardManager.swift
//  Lanu
//
//  Created by OneeChan on 2026/1/5.
//

import Foundation

protocol LNKeyboardNotify {
    func onKeyboardWillShow(curInput: UIView?, keyboardHeight: CGFloat)
    func onKeyboardShow(curInput: UIView?, keyboardHeight: CGFloat)
    func onKeyboardDidShow(curInput: UIView?, keyboardHeight: CGFloat)
    
    func onKeyboardWillHide(curInput: UIView?)
    func onKeyboardHide(curInput: UIView?)
    func onKeyboardDidHide(curInput: UIView?)
}
extension LNKeyboardNotify {
    func onKeyboardWillShow(curInput: UIView?, keyboardHeight: CGFloat) {}
    func onKeyboardShow(curInput: UIView?, keyboardHeight: CGFloat) {}
    func onKeyboardDidShow(curInput: UIView?, keyboardHeight: CGFloat) {}
    
    func onKeyboardWillHide(curInput: UIView?) {}
    func onKeyboardHide(curInput: UIView?) {}
    func onKeyboardDidHide(curInput: UIView?) {}
}

class LNKeyboardManager {
    static let shared = LNKeyboardManager()
    
    private weak var curInput: UIView?
    var isEditing: Bool {
        curInput != nil
    }
    
    private init() {
        addFirstResponderObservers()
        addKeyboardObservers()
    }
    
    private func addKeyboardObservers() {
        NotificationCenter.default.addObserver(
            forName: UIResponder.keyboardWillShowNotification,
            object: nil, queue: .main
        ) { [weak self] notify in
            guard let self else { return }
            
            guard let userInfo = notify.userInfo,
                  let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect
            else { return }
            let keyboardHeight = CGRectGetHeight(keyboardFrame)
            
            // 获取键盘动画时长（默认0.25秒）
            let animationDuration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval ?? 0.25
            
            // 获取键盘动画曲线（默认UIView.AnimationCurve.easeInOut）
            let animationCurveRawValue = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? Int ?? UIView.AnimationCurve.easeInOut.rawValue
            
            let visibleView = (curInput as? UITextInput)?.visibleView ?? curInput
            
            LNEventDeliver.notifyEvent {
                ($0 as? LNKeyboardNotify)?.onKeyboardWillShow(curInput: visibleView, keyboardHeight: keyboardHeight)
            }
            
            // 应用动画参数调整UI（使视图动画与键盘动画同步）
            UIView.animate(withDuration: animationDuration,
                           delay: 0,
                           options: UIView
                .AnimationOptions(rawValue: UInt(animationCurveRawValue)),
                           animations: {
                LNEventDeliver.notifyEvent {
                    ($0 as? LNKeyboardNotify)?.onKeyboardShow(curInput: visibleView, keyboardHeight: keyboardHeight)
                }
            }) { _ in
                LNEventDeliver.notifyEvent {
                    ($0 as? LNKeyboardNotify)?.onKeyboardDidShow(curInput: visibleView, keyboardHeight: keyboardHeight)
                }
            }
        }
        NotificationCenter.default.addObserver(
            forName: UIResponder.keyboardWillHideNotification,
            object: nil, queue: .main
        ) { [weak self] notify in
            guard let self else { return }
            
            guard let userInfo = notify.userInfo else { return }
            
            // 获取键盘动画时长（默认0.25秒）
            let animationDuration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval ?? 0.25
            
            // 获取键盘动画曲线（默认UIView.AnimationCurve.easeInOut）
            let animationCurveRawValue = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? Int ?? UIView.AnimationCurve.easeInOut.rawValue
            
            let visibleView = (curInput as? UITextInput)?.visibleView ?? curInput
            
            LNEventDeliver.notifyEvent {
                ($0 as? LNKeyboardNotify)?.onKeyboardWillHide(curInput: visibleView)
            }
            // 应用动画参数调整UI（使视图动画与键盘动画同步）
            UIView.animate(withDuration: animationDuration,
                           delay: 0,
                           options: UIView
                .AnimationOptions(rawValue: UInt(animationCurveRawValue)),
                           animations: {
                LNEventDeliver.notifyEvent {
                    ($0 as? LNKeyboardNotify)?.onKeyboardHide(curInput: visibleView)
                }
            }) { _ in
                LNEventDeliver.notifyEvent {
                    ($0 as? LNKeyboardNotify)?.onKeyboardDidHide(curInput: visibleView)
                }
            }
        }
    }
    
    private func addFirstResponderObservers() {
        // UITextField
        NotificationCenter.default.addObserver(
            forName: UITextField.textDidBeginEditingNotification,
            object: nil, queue: .main)
        { [weak self] notify in
            guard let self else { return }
            curInput = notify.object as? UITextField
        }
        NotificationCenter.default.addObserver(
            forName: UITextField.textDidEndEditingNotification,
            object: nil, queue: .main)
        { [weak self] notify in
            guard let self else { return }
            curInput = nil
        }
        
        // UITextView
        NotificationCenter.default.addObserver(
            forName: UITextView.textDidBeginEditingNotification,
            object: nil, queue: .main)
        { [weak self] notify in
            guard let self else { return }
            curInput = notify.object as? UITextView
        }
        NotificationCenter.default.addObserver(
            forName: UITextView.textDidEndEditingNotification,
            object: nil, queue: .main)
        { [weak self] notify in
            guard let self else { return }
            curInput = nil
        }
    }
}


private struct LNWeakProxy {
    weak var obj: UIView?
}
private var LNInputViewVisibleViewKey: UInt8 = 0
extension UITextInput {
    weak var visibleView: UIView? {
        get {
            let proxy = objc_getAssociatedObject(self, &LNInputViewVisibleViewKey) as? LNWeakProxy
            return proxy?.obj
        }
        set {
            objc_setAssociatedObject(self, &LNInputViewVisibleViewKey, LNWeakProxy(obj: newValue), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}
