import UIKit

@objcMembers
public final class LNVAPWrapView: UIView {
    private var playerView: LNVAPPlayerView?
    private var delegateBridge = LNWrapDelegateBridge()

    public weak var delegate: LNVAPWrapPlaybackDelegate?

    public var contentModeOption: LNVAPWrapContentMode = .scaleToFill {
        didSet {
            applyContentModeIfPossible()
        }
    }

    @objc(lnWrapContentMode)
    public var lnWrapContentMode: Int {
        get { contentModeOption.legacyRawValue }
        set { contentModeOption = LNVAPWrapContentMode(rawValue: newValue) ?? .scaleToFill }
    }

    @nonobjc public var autoDestroyAfterFinish: Bool = true

    @objc(autoDestoryAfterFinish)
    public var autoDestoryAfterFinish: Bool {
        get { autoDestroyAfterFinish }
        set { autoDestroyAfterFinish = newValue }
    }

    public override init(frame: CGRect) {
        super.init(frame: frame)
        delegateBridge.owner = self
    }

    public required init?(coder: NSCoder) {
        super.init(coder: coder)
        delegateBridge.owner = self
    }

    public override func layoutSubviews() {
        super.layoutSubviews()
        if contentModeOption == .scaleToFill {
            playerView?.frame = bounds
        }
    }

    @objc(lnPlayWithFilePath:repeatCount:)
    public func lnPlay(filePath: String, repeatCount: Int) {
        delegateBridge.lastPlayedFrameIndex = 0
        let player = initPlayerViewIfNeed()
        player.lnPlay(filePath: filePath, repeatCount: repeatCount)
    }


    @objc(lnPlayWithFilePath:)
    public func lnPlay(filePath: String) {
        lnPlay(filePath: filePath, repeatCount: 0)
    }

    @objc(lnPlayDeprecatedWithFilePath:fps:blendMode:repeatCount:delegate:)
    public func lnPlayDeprecated(filePath: String,
                                 fps: Int,
                                 blendMode: Int,
                                 repeatCount: Int,
                                 delegate: LNVAPWrapPlaybackDelegate?) {
        _ = blendMode
        self.delegate = delegate
        delegateBridge.lastPlayedFrameIndex = 0
        let player = initPlayerViewIfNeed()
        player.fps = fps
        player.lnPlay(filePath: filePath, repeatCount: repeatCount)
    }


    @objc(lnStop)
    public func lnStop() {
        playerView?.lnStop()
    }



    @objc(lnPause)
    public func lnPause() {
        playerView?.lnPause()
    }



    @objc(lnResume)
    public func lnResume() {
        playerView?.lnResume()
    }



    @objc(lnSetMute:)
    public func lnSetMute(_ mute: Bool) {
        let player = initPlayerViewIfNeed()
        player.lnSetMute(mute)
    }




    @objc(lnAddVapTapGesture:)
    public func lnAddVapTapGesture(_ handler: @escaping LNVAPGestureEventBlock) {
        let player = initPlayerViewIfNeed()
        player.lnAddVapTapGesture(handler)
    }

    @objc(lnAddVapGesture:callback:)
    public func lnAddVapGesture(_ gestureRecognizer: UIGestureRecognizer, callback: @escaping LNVAPGestureEventBlock) {
        let player = initPlayerViewIfNeed()
        player.lnAddVapGesture(gestureRecognizer, callback: callback)
    }

    public override func hitTest(_ hitPoint: CGPoint, with event: UIEvent?) -> UIView? {
        if !isUserInteractionEnabled || isHidden || alpha < 0.01 {
            return nil
        }
        if point(inside: hitPoint, with: event) {
            for subview in subviews.reversed() {
                let convertedPoint = convert(hitPoint, to: subview)
                if let hitView = subview.hitTest(convertedPoint, with: event) {
                    return hitView
                }
            }
            return nil
        }
        return nil
    }

    fileprivate func shouldStart(with config: LNVAPConfigModel) -> Bool {
        applyContentMode(with: config)
        return delegate?.lnWrapViewShouldStart?(self, config: config) ?? true
    }

    fileprivate func notifyStart() {
        delegate?.lnWrapViewDidStart?(self)
    }

    fileprivate func notifyPlay(_ frame: LNMP4AnimatedImageFrame) {
        delegate?.lnWrapViewDidPlay?(self, frame: frame)
    }

    fileprivate func notifyFinish(_ totalFrameCount: Int) {
        let computedCount = max(totalFrameCount, delegateBridge.lastPlayedFrameIndex + 1)
        delegate?.lnWrapViewDidFinish?(self, totalFrameCount: computedCount)
    }

    fileprivate func notifyStop() {
        delegate?.lnWrapViewDidStop?(self)
        delegateBridge.lastPlayedFrameIndex = 0
        DispatchQueue.main.async { [weak self] in
            guard let self else { return }
            if self.autoDestroyAfterFinish {
                self.playerView?.removeFromSuperview()
                self.playerView = nil
            }
        }
    }

    fileprivate func notifyFail(_ error: NSError) {
        delegate?.lnWrapViewDidFail?(self, error: error)
    }

    fileprivate func contentForTag(_ tag: String, resource: LNVAPSourceInfo) -> String? {
        delegate?.lnWrapViewContent?(forTag: tag, resource: resource)
    }

    fileprivate func loadImage(withURL url: String, context: NSDictionary, completion: @escaping LNVAPImageCompletion) {
        if let handler = delegate?.lnWrapViewLoadImage {
            handler(url, context, completion)
            return
        }
        completion(nil, nil, url)
    }

    private func initPlayerViewIfNeed() -> LNVAPPlayerView {
        if let playerView {
            return playerView
        }
        let view = LNVAPPlayerView(frame: bounds)
        view.delegate = delegateBridge
        addSubview(view)
        playerView = view
        applyContentModeIfPossible()
        return view
    }

    private func applyContentModeIfPossible() {
        guard let playerView else { return }
        guard let config = delegateBridge.lastConfig else {
            playerView.frame = bounds
            return
        }
        applyContentMode(with: config)
    }

    private func applyContentMode(with config: LNVAPConfigModel) {
        guard let playerView else { return }
        guard let info = config.info, info.size.width > 0, info.size.height > 0 else {
            playerView.frame = bounds
            return
        }

        let layoutWidth = bounds.width
        let layoutHeight = bounds.height
        guard layoutWidth > 0, layoutHeight > 0 else { return }

        let layoutRatio = layoutWidth / layoutHeight
        let videoRatio = info.size.width / info.size.height

        switch contentModeOption {
        case .scaleToFill:
            playerView.frame = bounds
        case .aspectFit:
            let realSize: CGSize
            if layoutRatio < videoRatio {
                let width = layoutWidth
                realSize = CGSize(width: width, height: width / videoRatio)
            } else {
                let height = layoutHeight
                realSize = CGSize(width: height * videoRatio, height: height)
            }
            playerView.frame = CGRect(origin: .zero, size: realSize)
            playerView.center = CGPoint(x: bounds.midX, y: bounds.midY)
        case .aspectFill:
            let realSize: CGSize
            if layoutRatio > videoRatio {
                let width = layoutWidth
                realSize = CGSize(width: width, height: width / videoRatio)
            } else {
                let height = layoutHeight
                realSize = CGSize(width: height * videoRatio, height: height)
            }
            playerView.frame = CGRect(origin: .zero, size: realSize)
            playerView.center = CGPoint(x: bounds.midX, y: bounds.midY)
        }
    }
}

private final class LNWrapDelegateBridge: NSObject, LNVAPPlaybackDelegate {
    weak var owner: LNVAPWrapView?
    var lastConfig: LNVAPConfigModel?
    var lastPlayedFrameIndex: Int = 0

    func lnPlayerShouldStart(_ playerView: LNVAPPlayerView, config: LNVAPConfigModel) -> Bool {
        lastConfig = config
        return owner?.shouldStart(with: config) ?? true
    }

    func lnPlayerDidStart(_ playerView: LNVAPPlayerView) {
        owner?.notifyStart()
    }

    func lnPlayerDidPlay(_ playerView: LNVAPPlayerView, frame: LNMP4AnimatedImageFrame) {
        lastPlayedFrameIndex = frame.frameIndex
        owner?.notifyPlay(frame)
    }

    func lnPlayerDidStop(_ playerView: LNVAPPlayerView) {
        owner?.notifyStop()
    }

    func lnPlayerDidFinish(_ playerView: LNVAPPlayerView, totalFrameCount: Int) {
        owner?.notifyFinish(totalFrameCount)
    }

    func lnPlayerDidFail(_ playerView: LNVAPPlayerView, error: NSError) {
        owner?.notifyFail(error)
    }

    func lnPlayerContent(forTag tag: String, resource: LNVAPSourceInfo) -> String? {
        owner?.contentForTag(tag, resource: resource)
    }

    func lnPlayerLoadImage(withURL url: String, context: NSDictionary, completion: @escaping LNVAPImageCompletion) {
        owner?.loadImage(withURL: url, context: context, completion: completion)
    }
}
