import UIKit
import AsyncDisplayKit

public enum PointerStyle {
    case `default`
    case insetRectangle(CGFloat, CGFloat)
    case rectangle(CGSize)
    case circle(CGFloat?)
    case caret
    case lift
    case hover
}

@available(iOSApplicationExtension 13.4, iOS 13.4, *)
private final class PointerInteractionImpl: NSObject, UIPointerInteractionDelegate {
    private weak var pointerInteraction: UIPointerInteraction?
    private weak var customInteractionView: UIView?
    
    private let style: PointerStyle
    
    private let willEnter: () -> Void
    private let willExit: () -> Void
    
    init(style: PointerStyle, willEnter: @escaping () -> Void, willExit: @escaping () -> Void) {
        self.style = style
        self.willEnter = willEnter
        self.willExit = willExit
        
        super.init()
    }
    
    deinit {
        if let pointerInteraction = self.pointerInteraction {
            pointerInteraction.view?.removeInteraction(pointerInteraction)
        }
    }
    
    func setup(view: UIView, customInteractionView: UIView?) {
        self.customInteractionView = customInteractionView
        
        let pointerInteraction = UIPointerInteraction(delegate: self)
        view.addInteraction(pointerInteraction)
        self.pointerInteraction = pointerInteraction
    }
    
    func pointerInteraction(_ interaction: UIPointerInteraction, styleFor region: UIPointerRegion) -> UIPointerStyle? {
        var pointerStyle: UIPointerStyle? = nil
        
        let interactionView = self.customInteractionView ?? interaction.view
        
        if let interactionView = interactionView {
            let targetedPreview = UITargetedPreview(view: interactionView)
            switch self.style {
                case .default:
                    let horizontalPadding: CGFloat = 10.0
                    let verticalPadding: CGFloat = 4.0
                    let minHeight: CGFloat = 40.0
                    let size: CGSize = CGSize(width: targetedPreview.size.width + horizontalPadding * 2.0, height: max(minHeight, targetedPreview.size.height + verticalPadding * 2.0))
                    pointerStyle = UIPointerStyle(effect: .highlight(targetedPreview), shape: .roundedRect(CGRect(origin: CGPoint(x: targetedPreview.view.center.x - size.width / 2.0, y: targetedPreview.view.center.y - size.height / 2.0), size: size), radius: UIPointerShape.defaultCornerRadius))
                case let .insetRectangle(x, y):
                    let insetSize = CGSize(width: targetedPreview.size.width - x * 2.0, height: targetedPreview.size.height - y * 2.0)
                    pointerStyle = UIPointerStyle(effect: .highlight(targetedPreview), shape: .roundedRect(CGRect(origin: CGPoint(x: targetedPreview.view.center.x - insetSize.width / 2.0, y: targetedPreview.view.center.y - insetSize.height / 2.0), size: insetSize), radius: UIPointerShape.defaultCornerRadius))
                case let .rectangle(size):
                    pointerStyle = UIPointerStyle(effect: .highlight(targetedPreview), shape: .roundedRect(CGRect(origin: CGPoint(x: targetedPreview.view.center.x - size.width / 2.0, y: targetedPreview.view.center.y - size.height / 2.0), size: size), radius: UIPointerShape.defaultCornerRadius))
                case let .circle(diameter):
                    let maxSide = max(targetedPreview.size.width, targetedPreview.size.height)
                    let finalDiameter = diameter ?? maxSide
                    pointerStyle = UIPointerStyle(effect: .highlight(targetedPreview), shape: .path(UIBezierPath(ovalIn: CGRect(origin: CGPoint(x: floorToScreenPixels(targetedPreview.view.center.x - finalDiameter / 2.0), y: floorToScreenPixels(targetedPreview.view.center.y - finalDiameter / 2.0)), size: CGSize(width: finalDiameter, height: finalDiameter)))))
                case .caret:
                    pointerStyle = UIPointerStyle(shape: .verticalBeam(length: 24.0), constrainedAxes: .vertical)
                case .lift:
                    pointerStyle = UIPointerStyle(effect: .lift(targetedPreview))
                case .hover:
                    pointerStyle = UIPointerStyle(effect: .hover(targetedPreview, preferredTintMode: .none, prefersShadow: false, prefersScaledContent: false))
            }
        }
        return pointerStyle
    }

    func pointerInteraction(_ interaction: UIPointerInteraction, willEnter region: UIPointerRegion, animator: UIPointerInteractionAnimating) {
        guard let _ = interaction.view else {
            return
        }

        animator.addAnimations {
            self.willEnter()
        }
     }

    func pointerInteraction(_ interaction: UIPointerInteraction, willExit region: UIPointerRegion, animator: UIPointerInteractionAnimating) {
        guard let _ = interaction.view else {
            return
        }

        animator.addAnimations {
            self.willExit()
        }
    }
}

public final class PointerInteraction {
    private var impl: AnyObject?
    private let style: PointerStyle
    
    private let willEnter: () -> Void
    private let willExit: () -> Void
    
    @available(iOSApplicationExtension 13.4, iOS 13.4, *)
    private func withImpl(_ f: (PointerInteractionImpl) -> Void) {
        if self.impl == nil {
            self.impl = PointerInteractionImpl(style: self.style, willEnter: self.willEnter, willExit: self.willExit)
        }
        f(self.impl as! PointerInteractionImpl)
    }
    
    public convenience init(node: ASDisplayNode, style: PointerStyle = .default, willEnter: @escaping () -> Void = {}, willExit: @escaping () -> Void = {}) {
        self.init(view: node.view, style: style, willEnter: willEnter, willExit: willExit)
    }
    
    public init(view: UIView, customInteractionView: UIView? = nil, style: PointerStyle = .default, willEnter: @escaping () -> Void = {}, willExit: @escaping () -> Void = {}) {
        self.style = style
        self.willEnter = willEnter
        self.willExit = willExit
        if #available(iOSApplicationExtension 13.4, iOS 13.4, *) {
            self.withImpl { impl in
                impl.setup(view: view, customInteractionView: customInteractionView)
            }
        }
    }
}