//
//  KMTableView.swift
//  PDF Reader Pro
//
//  Created by tangchao on 2023/11/16.
//

import Cocoa

@objc protocol KMBotaTableViewDelegate: NSTableViewDelegate {
    @objc optional func tableView(_ aTableView: NSTableView, deleteRowsWithIndexes rowIndexes: IndexSet)
    @objc optional func tableView(_ aTableView: NSTableView, canDeleteRowsWithIndexes rowIndexes: IndexSet) -> Bool
    
    @objc optional func tableView(_ aTableView: NSTableView, copyRowsWithIndexes rowIndexes: IndexSet)
    @objc optional func tableView(_ aTableView: NSTableView, canCopyRowsWithIndexes rowIndexes: IndexSet) -> Bool
    
    @objc optional func tableView(_ aTableView: NSTableView, pasteFromPasteboard pboard: NSPasteboard)
    @objc optional func tableView(_ aTableView: NSTableView, canPasteFromPasteboard pboard: NSPasteboard) -> Bool
    
    @objc optional func tableViewMoveLeft(_ aTableView: NSTableView)
    @objc optional func tableViewMoveRight(_ aTableView: NSTableView)
    
    @objc optional func tableViewMoveUp(_ aTableView: NSTableView)
    @objc optional func tableViewMoveDown(_ aTableView: NSTableView)
    
    @objc optional func tableView(_ aTableView: NSTableView, imageContextForRow rowIndex: Int) -> AnyObject?
    
    @objc optional func tableView(_ aTableView: NSTableView, typeSelectHelperSelectionStrings aTypeSelectHelper: SKTypeSelectHelper) -> NSArray
    @objc optional func tableView(_ aTableView: NSTableView, typeSelectHelper aTypeSelectHelper: SKTypeSelectHelper, didFailToFindMatchForSearchString searchString: String)
    @objc optional func tableView(_ aTableView: NSTableView, typeSelectHelper aTypeSelectHelper: SKTypeSelectHelper, updateSearchString searchString: String)
}

class KMBotaTableView: KMTableView {
    var kmTrackingAreas: NSMutableSet?
    var supportsQuickLook = false
    var typeSelectHelper: SKTypeSelectHelper?
    weak var botaDelegate: KMBotaTableViewDelegate? {
        didSet {
            self._rebuildTrackingAreas()
        }
    }
    
    var hasImageToolTips: Bool {
        get {
            return self.kmTrackingAreas != nil
        }
        set {
            if self.kmTrackingAreas == nil && newValue {
                self.kmTrackingAreas = NSMutableSet()
                if self.window != nil {
                    self._rebuildTrackingAreas()
                }
            } else if self.kmTrackingAreas != nil && newValue == false {
                if self.window != nil {
                    self._removeTrackingAreas()
                }
            }
        }
    }
    
    func setTypeSelectHelper(_ newTypeSelectHelper: SKTypeSelectHelper) {
        if typeSelectHelper != newTypeSelectHelper {
            if self.isEqual(to: self.typeSelectHelper?.delegate) {
                typeSelectHelper?.delegate = nil
            }
            typeSelectHelper = newTypeSelectHelper
            typeSelectHelper?.delegate = self
        }
    }
    
    override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)

        // Drawing code here.
    }
    
    override func reloadData() {
        super.reloadData()
        
        self._rebuildTrackingAreas()
        self.typeSelectHelper?.rebuildTypeSelectSearchCache()
    }
    
    // MARK: - Tracking
    
    override func updateTrackingAreas() {
        super.updateTrackingAreas()
        
        self._rebuildTrackingAreas()
    }
    
    override func noteNumberOfRowsChanged() {
        super.noteNumberOfRowsChanged()
        
        self._rebuildTrackingAreas()
    }
    
    override func mouseEntered(with event: NSEvent) {
        if (self.kmTrackingAreas == nil) {
            return
        }
        
        let userInfo = event.trackingArea?.userInfo as? [String : Any]
        let row = userInfo?["row"] as? Int
        if row != nil {
            let context = self.botaDelegate?.tableView?(self, imageContextForRow: row!)
            if context is KMImageToolTipContext {
                KMImageToolTipWindow.shared.showForImageContext(context as! KMImageToolTipContext, at: .zero)
            }
        }
    }
    
    override func mouseExited(with event: NSEvent) {
        if (self.kmTrackingAreas == nil) {
            return
        }
        
        let userInfo = event.trackingArea?.userInfo as? [String : Any]
        let row = userInfo?["row"] as? Int
        if row != nil {
            KMImageToolTipWindow.shared.fadeOut()
        }
    }
    
    func scrollToBeginningOfDocument(_ sender: AnyObject?) {
        if self.numberOfRows > 0 {
            self.scrollRowToVisible(0)
        }
    }
    
    func scrollToEndOfDocument(_ sender: AnyObject?) {
        if self.numberOfRows > 0 {
            self.scrollRowToVisible(self.numberOfRows-1)
        }
    }
    
    func moveLeft(_ sender: AnyObject?) {
        self.botaDelegate?.tableViewMoveLeft?(self)
    }

    func moveRight(_ sender: AnyObject?) {
        self.botaDelegate?.tableViewMoveRight?(self)
    }
    
    func moveUp(_ sender: AnyObject?) {
        self.botaDelegate?.tableViewMoveUp?(self)
    }

    func moveDown(_ sender: AnyObject?) {
        self.botaDelegate?.tableViewMoveDown?(self)
    }
    
    func canDelete() -> Bool {
        let indexes = self.selectedRowIndexes
        if indexes.isEmpty {
            return false
        }
        
        return self.botaDelegate?.tableView?(self, canDeleteRowsWithIndexes: indexes) ?? false
    }
    
    @objc func delete(_ sender: AnyObject?) {
        if self.canDelete() {
            self.botaDelegate?.tableView?(self, deleteRowsWithIndexes: self.selectedRowIndexes)
        }
    }
    
    func canCopy() -> Bool {
        let indexes = self.selectedRowIndexes
        if indexes.isEmpty {
            return false
        }
        
        return self.botaDelegate?.tableView?(self, canCopyRowsWithIndexes: indexes) ?? false
    }
    
    @objc func copy(_ sender: AnyObject?) {
        if self.canCopy() {
            self.botaDelegate?.tableView?(self, copyRowsWithIndexes: self.selectedRowIndexes)
        }
    }
    
    func canPaste() -> Bool {
        return self.botaDelegate?.tableView?(self, canPasteFromPasteboard: NSPasteboard.general) ?? false
    }
    
    @objc func paste(_ sender: AnyObject?) {
        if self.canPaste() {
            self.botaDelegate?.tableView?(self, pasteFromPasteboard: NSPasteboard.general)
        }
    }
    
    override func keyDown(with event: NSEvent) {
        let eventChar = event.firstCharacter()
        let modifierFlags = event.deviceIndependentModifierFlags()

        if ((eventChar == NSNewlineCharacter || eventChar == NSEnterCharacter || eventChar == NSCarriageReturnCharacter) && modifierFlags == 0) {
            if self.doubleAction == nil || self.sendAction(self.doubleAction, to: self.target) == false {
                NSSound.beep()
            }
        } else if ((eventChar == NSDeleteCharacter || eventChar == NSDeleteFunctionKey) && modifierFlags == 0 && self.canDelete()) {
            self.delete(self)
        } else if ((eventChar == SKSpaceCharacter) && modifierFlags == 0) {
            if (self.supportsQuickLook == false) {
                self.enclosingScrollView?.pageDown(nil)
            } else if QLPreviewPanel.sharedPreviewPanelExists() && QLPreviewPanel.shared().isVisible {
                QLPreviewPanel.shared().orderOut(nil)
            } else {
                QLPreviewPanel.shared().makeKeyAndOrderFront(nil)
            }
        } else if ((eventChar == SKSpaceCharacter) && modifierFlags == NSEvent.ModifierFlags.shift.rawValue) {
            if (self.supportsQuickLook == false) {
                self.enclosingScrollView?.pageUp(nil)
            }
        }
//        else if (eventChar == NSHomeFunctionKey && (modifierFlags & ~NSFunctionKeyMask) == 0) {
//            [self scrollToBeginningOfDocument:nil];
//        } else if (eventChar == NSEndFunctionKey && (modifierFlags & ~NSFunctionKeyMask) == 0) {
//            [self scrollToEndOfDocument:nil];
//        }
        else if (eventChar == NSLeftArrowFunctionKey && modifierFlags == 0) {
            self.moveLeft(nil)
        } else if (eventChar == NSRightArrowFunctionKey && modifierFlags == 0) {
            self.moveRight(nil)
        } else if eventChar == NSUpArrowFunctionKey {
            self.moveUp(nil)
        } else if eventChar == NSDownArrowFunctionKey {
            self.moveDown(nil)
        }
//        else if ([typeSelectHelper handleEvent:theEvent] == NO) {
//            [super keyDown:theEvent];
//        }
    }
    
    override var font: NSFont? {
        get {
            for tc in self.tableColumns {
                if let cell = tc.dataCell as? NSCell {
                    if cell.type == .textCellType {
                        return cell.font
                    }
                }
            }
            return nil
        }
        set {
            for tc in self.tableColumns {
                if let cell = tc.dataCell as? NSCell {
                    if cell.type == .textCellType {
                        cell.font = newValue
                    }
                }
            }
            
            var rowHeight = font?.defaultViewLineHeight() ?? 34
            if self.selectionHighlightStyle == .sourceList {
                rowHeight += 2.0
            }
            self.rowHeight = rowHeight
            self.noteHeightOfRows(withIndexesChanged: IndexSet(0..<self.numberOfRows))
        }
    }
    
    override func dragImageForRows(with dragRows: IndexSet, tableColumns: [NSTableColumn], event dragEvent: NSEvent, offset dragImageOffset: NSPointPointer) -> NSImage {
        return super.dragImageForRows(with: dragRows, tableColumns: tableColumns[0..<1].shuffled(), event: dragEvent, offset: dragImageOffset)
    }
}

// MARK: - Private Methods

extension KMBotaTableView {
    private func _rebuildTrackingAreas() {
        if self.kmTrackingAreas == nil {
            return
        }
        
        let context = self.botaDelegate?.tableView?(self, imageContextForRow: 0)
        if context == nil {
            return
        }
        
        self._removeTrackingAreas()
        if self.window != nil {
            let visibleRect = self.visibleRect
            let rowRange = self.rows(in: visibleRect)
            for i in rowRange.location ..< NSMaxRange(rowRange) {
                self._addTrackingAreaForRow(Int(i))
            }
        }
    }
    
    private func _removeTrackingAreas() {
        if (self.kmTrackingAreas == nil) {
            return
        }
        
        for area in self.kmTrackingAreas! {
            self.removeTrackingArea(area as! NSTrackingArea)
        }
        self.kmTrackingAreas?.removeAllObjects()
    }
    
    private func _addTrackingAreaForRow(_ row: Int) {
        if (self.kmTrackingAreas == nil) {
            return
        }
        let userInfo = ["row" : row]
        var rect = self.rect(ofRow: row)
        rect.size.height -= 1
        let area = NSTrackingArea(rect: rect, options: [.mouseEnteredAndExited, .activeInActiveApp], owner: self, userInfo: userInfo)
        self.addTrackingArea(area)
        self.kmTrackingAreas?.add(area)
    }
}

// MARK: - NSMenuItemValidation

extension KMBotaTableView: NSMenuItemValidation {
    func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
        if menuItem.action == #selector(delete) {
            return self.canDelete()
        } else if menuItem.action == KMSelectorCopy {
            return self.canCopy()
        } else if menuItem.action == #selector(paste) {
            return self.canPaste()
        } else if menuItem.action == #selector(selectAll) {
            return self.allowsMultipleSelection
        } else if menuItem.action == #selector(deselectAll) {
            return self.allowsEmptySelection
        }
        return true
    }
}

// MARK: - SKTypeSelectHelperDelegate

extension KMBotaTableView: SKTypeSelectHelperDelegate {
    func typeSelectHelperSelectionStrings(_ typeSelectHelper: SKTypeSelectHelper) -> NSArray {
        return self.botaDelegate?.tableView?(self, typeSelectHelperSelectionStrings: typeSelectHelper) ?? NSArray()
    }
    
    func typeSelectHelperCurrentlySelectedIndex(_ typeSelectHelper: SKTypeSelectHelper) -> Int {
        return self.selectedRowIndexes.last ?? NSNotFound
    }
    
    func typeSelectHelper(_ typeSelectHelper: SKTypeSelectHelper, selectItemAtIndex itemIndex: Int) {
        self.selectRowIndexes(NSIndexSet(index: itemIndex) as IndexSet, byExtendingSelection: false)
        self.scrollRowToVisible(itemIndex)
    }
    
    func typeSelectHelper(_ typeSelectHelper: SKTypeSelectHelper, didFailToFindMatchForSearchString searchString: String) {
        self.botaDelegate?.tableView?(self, typeSelectHelper: typeSelectHelper, didFailToFindMatchForSearchString: searchString)
    }
    
    func typeSelectHelper(_ typeSelectHelper: SKTypeSelectHelper, updateSearchString searchString: String) {
        self.botaDelegate?.tableView?(self, typeSelectHelper: typeSelectHelper, updateSearchString: searchString)
    }
}