//
//  KMSlider.swift
//  PDF Reader Pro
//
//  Created by kdanmobile on 2023/10/31.
//

import Cocoa
@objc(SJTSliderDelegate)
protocol SJTSliderDelegate: AnyObject {
    func tipForValue(inSlider slider: KMSlider, value: Double) -> String
    func valueDidSelect(inSlider slider: KMSlider)
}

let SJTSliderTipPopoverMinWidthX = 60.0;
let SJTSliderTipPopoverMinWidthY = 40.0;
let SJTSliderTipPopoverMinHeightX = 38.0;
let SJTSliderTipPopoverMinHeightY = 24.0;
let SJTSliderMinHeight = 21.0;
let SJTSliderMinHeightWithTickMark = 26.0;
let SJTSliderMinWidth = 100.0;
let SJTSliderTickMarkMinWidth = 5.0;
let SJTSliderPositioningRectkey = "positioningRect"

class KMSlider: NSSlider, NSPopoverDelegate{
    var tipEnabled: Bool = false
    var tipAutoAlignment: Bool = false
    var tipAlignment: NSTextAlignment = .left
    var tipAppearance: NSAppearance?
    var delegate: SJTSliderDelegate?
    private var tipPopover: NSPopover?
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        self.initView()
    }
    
    func initView() {
        self.isContinuous = true
        self.tipEnabled = true
        self.tipAutoAlignment = true
        self.tipAlignment = .center
        self.tipAppearance = NSAppearance(named: NSAppearance.Name.vibrantLight)
        let trackingArea = NSTrackingArea(rect: NSZeroRect, options: [.inVisibleRect, .mouseEnteredAndExited, .mouseMoved, .activeInActiveApp], owner: self, userInfo: nil)
        self.addTrackingArea(trackingArea)
        
        let tipView = NSTextField()
        tipView.isBordered = false
        tipView.backgroundColor = NSColor.clear
        tipView.isEditable = false
        tipView.isSelectable = false
        tipView.alignment = .center
        let contentView = NSView()
        contentView.addSubview(tipView)
        
        self.tipPopover = NSPopover()
        self.tipPopover?.contentViewController = NSViewController()
        self.tipPopover?.contentViewController?.view = contentView
        self.tipPopover?.delegate = self
    }
    
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if object as? NSObject == self.tipPopover && keyPath == SJTSliderPositioningRectkey {
            self.adjustTipAlignment()
        }
    }
    
    override func sendAction(_ action: Selector?, to target: Any?) -> Bool {
        if tipEnabled {
            showTipPopover(animated: false)
        }
        
        let event = NSApplication.shared.currentEvent
        if event?.type == NSEvent.EventType.leftMouseUp {
            delegate?.valueDidSelect(inSlider: self)
        }
        
        return super.sendAction(action, to: target)
    }
    
    func adjustTipAlignment() {
        guard let tipContentView = self.tipPopover?.contentViewController?.view else {
            return
        }
        guard let tipView = tipContentView.subviews.first as? NSTextField else {
            return
        }
        
        var tipAlignment: NSTextAlignment = .center
        if self.tipAutoAlignment {
            var tipContentFrame = tipContentView.convert(tipContentView.bounds, to: nil)
            tipContentFrame = tipContentView.window?.convertToScreen(tipContentFrame) ?? NSRect.zero
            tipContentFrame = self.window?.convertFromScreen(tipContentFrame) ?? NSRect.zero
            if let tipTargetFrame = self.tipPopover?.positioningRect {
                if NSMaxX(tipContentFrame) < NSMinX(tipTargetFrame) {
                    tipAlignment = .right
                } else if NSMinX(tipContentFrame) > NSMaxX(tipTargetFrame) {
                    tipAlignment = .left
                }
            }
        } else {
            tipAlignment = self.tipAlignment
        }
        tipView.alignment = tipAlignment
    }
    
    func showTipPopover(animated: Bool) {
        let tip = self.tipForValue(self.doubleValue)
        if !tip.isEmpty {
            let contentView = self.tipPopover?.contentViewController?.view
            if let tipView = contentView?.subviews[0] as? NSTextField {
                let knobRect = (self.cell as? NSSliderCell)?.knobRect(flipped: self.isFlipped)
                var preferredEdge: NSRectEdge?
                var newcell: NSSliderCell = self.cell as! NSSliderCell
                if newcell.sliderType == .circular {
                    let tickMartCenter = NSMakePoint(NSMidX(knobRect!), NSMidY(knobRect!))
                    let viewCenter = NSMakePoint(NSMidX(self.bounds), NSMidY(self.bounds))
                    let cutoffValue = sqrt(((viewCenter.x - tickMartCenter.x) * (viewCenter.x - tickMartCenter.x) + (viewCenter.y - tickMartCenter.y) * (viewCenter.y - tickMartCenter.y)) / 2) as CGFloat
                    
                    if viewCenter.x-tickMartCenter.x > cutoffValue {
                        preferredEdge = NSRectEdge.minX
                    } else if tickMartCenter.y-viewCenter.y > cutoffValue {
                        preferredEdge = NSRectEdge.maxY
                    } else if tickMartCenter.x-viewCenter.x > cutoffValue {
                        preferredEdge = NSRectEdge.maxX
                    } else {
                        preferredEdge = NSRectEdge.minY
                    }
                } else if self.isVertical {
                    if self.tickMarkPosition == NSSlider.TickMarkPosition.leading {
                        preferredEdge = NSRectEdge.minX
                    } else {
                        preferredEdge = NSRectEdge.maxX
                    }
                } else {
                    if self.tickMarkPosition == NSSlider.TickMarkPosition.below {
                        preferredEdge = self.isFlipped ? NSRectEdge.maxY : NSRectEdge.minY
                    } else {
                        preferredEdge = self.isFlipped ? NSRectEdge.minY : NSRectEdge.maxY
                    }
                }
                
                self.tipPopover?.appearance = self.tipAppearance
                tipView.stringValue = tip
                tipView.sizeToFit()
                var tipViewFrame = tipView.bounds
                var contentViewSize = tipView.bounds.size
                var minWidth, minHeight: Int
                
                if preferredEdge == NSRectEdge.minX || preferredEdge == NSRectEdge.maxX {
                    minWidth = Int(SJTSliderTipPopoverMinWidthX)
                    minHeight = Int(SJTSliderTipPopoverMinHeightX)
                } else {
                    minWidth = Int(SJTSliderTipPopoverMinWidthY)
                    minHeight = Int(SJTSliderTipPopoverMinHeightY)
                }
                
                if Int(tipViewFrame.size.width) < minWidth {
                    tipViewFrame.size.width = CGFloat(minWidth)
                    tipViewFrame.origin.x = (CGFloat(minWidth)-tipViewFrame.size.width)/2
                    contentViewSize.width = CGFloat(minWidth)
                }
                
                if Int(tipViewFrame.size.height) < minHeight {
                    tipViewFrame.origin.y = (CGFloat(minHeight)-tipViewFrame.size.height)/2
                    contentViewSize.height = CGFloat(minHeight)
                }
                
                contentView?.setFrameSize(contentViewSize)
                tipView.frame = tipViewFrame
                self.tipPopover?.contentSize = contentViewSize
                
                self.tipPopover?.animates = animated
                self.tipPopover?.show(relativeTo: knobRect!, of: self, preferredEdge: preferredEdge!)
            }
        }
    }
    
    func tipForValue(_ value: Double) -> String {
        if let tip = self.delegate?.tipForValue(inSlider: self, value: self.doubleValue) {
           return tip
        }
        return String(format: "%0.f", self.doubleValue)
    }
    
    func closeTipPopover(animated: Bool) {
        if ((self.tipPopover?.isShown) != nil) {
            self.tipPopover?.animates = animated 
            self.tipPopover?.close()
        }
    }
}

extension KMSlider {
    override func mouseDown(with event: NSEvent) {
        if self.tipEnabled {
            self.showTipPopover(animated: true)
        }
        super.mouseDown(with: event)
    }
    
    override func mouseEntered(with theEvent: NSEvent) {
        if self.tipEnabled {
            self.showTipPopover(animated: true)
        }
    }
    
    override func mouseExited(with theEvent: NSEvent) {
        self.closeTipPopover(animated: true)
    }
}