//
//  KMBookmarkController.swift
//  PDF Reader Pro
//
//  Created by lizhe on 2024/2/5.
//

import Cocoa

private let kLabelIdentifier = NSUserInterfaceItemIdentifier("label")
private let kFileIdentifier  = NSUserInterfaceItemIdentifier("file")
private let kPageIdentifier  = NSUserInterfaceItemIdentifier("page")

let kTextWithIconStringKey = "string";
let kTextWithIconImageKey = "image";

let kBookmarksToolbarIdentifier                 = "BookmarksToolbarIdentifier"
let kBookmarksNewFolderToolbarItemIdentifier    = "BookmarksNewFolderToolbarItemIdentifier"
let kBookmarksNewSeparatorToolbarItemIdentifier = "BookmarksNewSeparatorToolbarItemIdentifier"
let kBookmarksDeleteToolbarItemIdentifier       = "BookmarksDeleteToolbarItemIdentifier"

let kPasteboardTypeBookmarkRows = NSPasteboard.PasteboardType(rawValue: "pasteboard.bookmarkrows")
class KMBookmarkController: NSWindowController {
    
    @IBOutlet weak var outlineView: KMCustomOutlineView!
    
    var draggedBookmarks: [KMBookmark] = []
    var recentDocuments: [[String: Any]] {
        get {
            return KMBookmarkManager.manager.recentDocuments
        }
        
        set {
            
        }
    }
    
    var bookmarkRoot: KMRootBookmark {
        get {
            return KMBookmarkManager.manager.rootBookmark
        }
        set {
            
        }
    }
    
    var toolbarItems: [String: NSToolbarItem] = [:]
    override func windowDidLoad() {
        super.windowDidLoad()
        setupToolbar()
        
        outlineView.delegate = self
        outlineView.dataSource = self
        outlineView.registerForDraggedTypes([kPasteboardTypeBookmarkRows, .fileURL, .string])
        outlineView.doubleAction = #selector(doubleClickBookmark(_:))
    }

    func updateStatus() {
        let row = outlineView.selectedRow
        var message = ""
        if row != -1 {
            if let bookmark = outlineView.item(atRow: row) as? KMBookmark {
                switch bookmark.bookmarkType {
                case .bookmark:
                    message = bookmark.fileURL?.path ?? ""
                case .folder:
                    let count = bookmark.children.count
                    message = count == 1 ? NSLocalizedString("1 item", comment: "Bookmark folder description") : String(format: NSLocalizedString("%ld items", comment: "Bookmark folder description"), count)
                default:
                    break
                }
            }
        }
//        statusBar.leftStringValue = message
    }
//    
    static func showBookmarkController() -> KMBookmarkController {
        let controller = KMBookmarkController.init(windowNibName: "KMBookmarkController")
        NSWindow.currentWindow().addChildWindow(controller.window!, ordered: NSWindow.OrderingMode.above)
        controller.window?.center()
        return controller
    }
//    
//    
//    //MARK: Recent Documents
    func recentDocumentInfo(at fileURL: URL) -> [String: Any]? {
        let path = fileURL.path
        for info in recentDocuments {
//            if let aliasData = info[ALIASDATA_KEY] as? Data,
//               let alias = SKAlias(aliasData),
//               alias.fileURLNoUI?.path.caseInsensitiveCompare(path) == .orderedSame {
//                return info
//            }
        }
        return nil
    }
//
    func addRecentDocument(for fileURL: URL, pageIndex: UInt, scaleFactor factor: CGFloat, snapshots setups: [Any]?) {
//        if let info = recentDocumentInfo(at: fileURL) {
//            recentDocuments.removeObject(info)
//        }
//        
//        if let alias = SKAlias(url: fileURL) {
//            var bm: [String: Any] = [
//                PAGEINDEX_KEY: pageIndex,
//                SCALE_KEY: factor,
//                ALIASDATA_KEY: alias.data,
//                ALIAS_KEY: alias,
//                SNAPSHOTS_KEY: setups ?? []
//            ]
//            recentDocuments.insert(bm, at: 0)
//            if recentDocuments.count > maxRecentDocumentsCount {
//                recentDocuments.removeLastObject()
//            }
//        }
    }
//
//    func pageIndex(forRecentDocumentAt fileURL: URL) -> UInt {
//        guard let fileURL = fileURL else { return UInt.max }
//        if let pageIndex = recentDocumentInfo(at: fileURL)?[PAGEINDEX_KEY] as? UInt {
//            return pageIndex
//        }
//        return UInt.max
//    }
//
//    func scaleFactor(forRecentDocumentAt fileURL: URL) -> CGFloat {
//        guard let fileURL = fileURL else { return 0 }
//        if let scaleFactor = recentDocumentInfo(at: fileURL)?[SCALE_KEY] as? CGFloat {
//            return scaleFactor
//        }
//        return 0
//    }
//
//    func snapshots(forRecentDocumentAt fileURL: URL) -> [Any]? {
//        guard let fileURL = fileURL else { return nil }
//        if let setups = recentDocumentInfo(at: fileURL)?[SNAPSHOTS_KEY] as? [Any], !setups.isEmpty {
//            return setups
//        }
//        return nil
//    }
//    
//    //MARK: Bookmarks support
    func getInsertionFolder(_ bookmarkPtr: inout KMBookmark?, childIndex indexPtr: inout Int) {
        let rowIndex = outlineView.clickedRow
        var indexes = outlineView.selectedRowIndexes
        if rowIndex != -1 && !indexes.contains(rowIndex) {
            indexes = IndexSet(integer: rowIndex)
        }
        let rowIdx = indexes.last ?? NSNotFound
        
        var item = KMBookmarkManager.manager.rootBookmark
        var idx = item.children.count
        
        if rowIdx != NSNotFound {
            if let selectedItem = outlineView.item(atRow: rowIdx) as? KMBookmark {
                if outlineView.isItemExpanded(selectedItem) {
                    item = selectedItem as! KMRootBookmark
                    idx = item.children.count
                } else if let parent = selectedItem.parent, let itemIdx = parent.children.firstIndex(of: selectedItem) {
                    item = parent as! KMRootBookmark
                    idx = itemIdx + 1
                }
            }
        }
        
        bookmarkPtr = item
        indexPtr = idx
    }

    @IBAction func openBookmark(_ sender: Any) {
        if let bookmark = (sender as AnyObject).representedObject as? KMBookmark {
            bookmark.open()
        }
    }

    @IBAction func doubleClickBookmark(_ sender: Any) {
        let row = outlineView.clickedRow
        if let bm = (row != -1 ? outlineView.item(atRow: row) : nil) as? KMBookmark,
           [KMBookmarkType.bookmark, .session].contains(bm.bookmarkType) {
            bm.open()
        }
    }

    
    func deleteBookmarks(bookmarks: [KMBookmark]) {
        for item in minimumCoverForBookmarks(bookmarks).reversed() {
            guard let parent = item.parent, let itemIndex = parent.children.firstIndex(of: item) else { continue }
            parent.removeObjectFromChildren(index: itemIndex)
        }
        outlineView.reloadData()
    }
    
    
    @IBAction func insertBookmarkFolder(_ sender: Any) {
        let folder = KMFolderBookmark.folderBookmark(label: NSLocalizedString("Folder", comment: "default folder name"))
        var item: KMBookmark?
        var idx: Int = 0
        
        getInsertionFolder(&item, childIndex: &idx)
        item?.insert(child: folder, atIndex: idx)
        
        let row = outlineView.row(forItem: folder)
        outlineView.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
        outlineView.editColumn(0, row: row, with: nil, select: true)
    }

    @IBAction func insertBookmarkSeparator(_ sender: Any) {
        let separator = KMSeparatorBookmark()
        var item: KMBookmark?
        var idx: Int = 0
        
        getInsertionFolder(&item, childIndex: &idx)
        item?.insert(child: separator, atIndex: idx)
        
        let row = outlineView.row(forItem: separator)
        outlineView.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
    }

    @IBAction func addBookmark(_ sender: Any) {
        let openPanel = NSOpenPanel()
        var types = [String]()
        for docClass in NSDocumentController.shared.documentClassNames {
            if let docClass = NSClassFromString(docClass) as? NSDocument.Type {
                types += docClass.readableTypes
            }
        }
        openPanel.allowsMultipleSelection = true
        openPanel.canChooseDirectories = true
        openPanel.allowedFileTypes = types
        openPanel.beginSheetModal(for: self.window!) { (result) in
            guard result == .OK else { return }
            let newBookmarks = KMBookmark.bookmarks(urls: openPanel.urls)
            if newBookmarks != nil {
                var item: KMBookmark?
                var index: Int = 0
                self.getInsertionFolder(&item, childIndex: &index)
                var indexes = IndexSet(integersIn: Int(index)..<Int(index + newBookmarks.count))
                item?.mutableArrayValue(forKey: "children").insert(newBookmarks, at: indexes)
                if item == self.bookmarkRoot || self.outlineView.isItemExpanded(item) {
                    if item != self.bookmarkRoot {
                        indexes.shift(startingAt: 0, by: self.outlineView.row(forItem: item) + 1)
                    }
                    self.outlineView.selectRowIndexes(indexes, byExtendingSelection: false)
                }
            }
        }
    }
    
    @IBAction func deleteBookmark(_ sender: Any) {
        print("deleteBookmark")
        guard let bookmark = outlineView.selectedItem() as? KMBookmark else { return }
        self.deleteBookmarks(bookmarks: [bookmark])
    }
//
//    @IBAction func toggleStatusBar(_ sender: Any) {
//        UserDefaults.standard.set(!statusBar.isVisible, forKey: SKShowBookmarkStatusBarKey)
//        statusBar.toggle(below: outlineView.enclosingScrollView, animate: sender != nil)
//    }
//
//    func clickedBookmarks() -> [Any]? {
//        let row = outlineView.clickedRow
//        guard row != -1 else { return nil }
//        var indexes = outlineView.selectedRowIndexes
//        if !indexes.contains(row) {
//            indexes = IndexSet(integer: row)
//        }
//        return indexes.compactMap { outlineView.item(atRow: $0) }
//    }
//
//    @IBAction func deleteBookmarks(_ sender: Any) {
//        guard let items = clickedBookmarks() as? [KMBookmark] else { return }
//        for item in items.reversed() {
//            if let parent = item.parent, let itemIndex = parent.children.index(of: item) {
//                parent.removeObject(fromChildrenAtIndex: itemIndex)
//            }
//        }
//    }
//
//    @IBAction func openBookmarks(_ sender: Any) {
//        guard let items = clickedBookmarks() as? [KMBookmark] else { return }
//        for item in items.reversed() {
//            item.open()
//        }
//    }
//
//    @IBAction func previewBookmarks(_ sender: Any) {
//        if QLPreviewPanel.sharedPreviewPanelExists() && QLPreviewPanel.shared().isVisible {
//            QLPreviewPanel.shared().orderOut(nil)
//        } else if let row = outlineView.clickedRow {
//            outlineView.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
//            QLPreviewPanel.shared().makeKeyAndOrderFront(nil)
//        }
//    }
//
//    
//    // MARK: - NSMenu delegate methods

//    func addItemForBookmark(_ bookmark: KMBookmark, toMenu menu: NSMenu, isFolder: Bool, isAlternate: Bool) {
//        var item: NSMenuItem?
//        if isFolder {
//            item = menu.addItem(withSubmenuAndTitle: bookmark.label)
//            item?.submenu?.delegate = self
//        } else {
//            item = menu.addItem(withTitle: bookmark.label, action: #selector(openBookmark(_:)), target: self)
//        }
//        item?.representedObject = bookmark
//        if isAlternate {
//            item?.keyEquivalentModifierMask = .alternate
//            item?.isAlternate = true
//            item?.setImageAndSize(bookmark.alternateIcon)
//        } else {
//            item?.setImageAndSize(bookmark.icon)
//        }
//    }
//
//    func menuNeedsUpdate(_ menu: NSMenu) {
//        if menu == outlineView.menu {
//            let row = outlineView.clickedRow
//            menu.removeAllItems()
//            if row != -1 {
//                menu.addItem(withTitle: NSLocalizedString("Remove", comment: "Menu item title"), action: #selector(deleteBookmarks(_:)), target: self)
//                menu.addItem(withTitle: NSLocalizedString("Open", comment: "Menu item title"), action: #selector(openBookmarks(_:)), target: self)
//                menu.addItem(withTitle: NSLocalizedString("Quick Look", comment: "Menu item title"), action: #selector(previewBookmarks(_:)), target: self)
//                menu.addItem(.separator())
//            }
//            menu.addItem(withTitle: NSLocalizedString("New Folder", comment: "Menu item title"), action: #selector(insertBookmarkFolder(_:)), target: self)
//            menu.addItem(withTitle: NSLocalizedString("New Separator", comment: "Menu item title"), action: #selector(insertBookmarkSeparator(_:)), target: self)
//        } else {
//            guard let supermenu = menu.supermenu, let idx = supermenu.indexOfItem(withSubmenu: menu), let bm = (supermenu == NSApp.mainMenu) ? bookmarkRoot : supermenu.item(at: idx)?.representedObject as? KMBookmark else { return }
//            
//            let bookmarks = bm.children
//            var i = menu.numberOfItems
//            
//            while i > 0 {
//                if let menuItem = menu.item(at: i - 1), menuItem.isSeparatorItem || menuItem.representedObject != nil {
//                    menu.removeItem(menuItem)
//                }
//                i -= 1
//            }
//            
//            if supermenu == NSApp.mainMenu, let previousSession = previousSession {
//                menu.addItem(.separator())
//                addItemForBookmark(previousSession, toMenu: menu, isFolder: false, isAlternate: false)
//                addItemForBookmark(previousSession, toMenu: menu, isFolder: true, isAlternate: true)
//            }
//            
//            if menu.numberOfItems > 0, bookmarks.count > 0 {
//                menu.addItem(.separator())
//            }
//            
//            for bm in bookmarks {
//                switch bm.bookmarkType {
//                case .folder:
//                    addItemForBookmark(bm, toMenu: menu, isFolder: true, isAlternate: false)
//                    addItemForBookmark(bm, toMenu: menu, isFolder: false, isAlternate: true)
//                case .session:
//                    addItemForBookmark(bm, toMenu: menu, isFolder: false, isAlternate: false)
//                    addItemForBookmark(bm, toMenu: menu, isFolder: true, isAlternate: true)
//                case .separator:
//                    menu.addItem(.separator())
//                default:
//                    addItemForBookmark(bm, toMenu: menu, isFolder: false, isAlternate: false)
//                }
//            }
//        }
//    }
//
//    // avoid rebuilding the bookmarks menu on every key event
//    func menuHasKeyEquivalent(_ menu: NSMenu, for event: NSEvent, target: AutoreleasingUnsafeMutablePointer<AnyObject?>?, action: UnsafeMutablePointer<Selector?>?) -> Bool { false }
//    
//    // MARK: - Toolbar
//
    func setupToolbar() {
        // Create a new toolbar instance, and attach it to our document window
        let toolbar = NSToolbar(identifier: kBookmarksToolbarIdentifier)
        var dict = [String: NSToolbarItem]()
        
        // Set up toolbar properties: Allow customization, give a default display mode, and remember state in user defaults
        toolbar.allowsUserCustomization = true
        toolbar.autosavesConfiguration = true
        toolbar.displayMode = .default
        
        // We are the delegate
        toolbar.delegate = self
        
        // Add template toolbar items
        
        var item = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier(kBookmarksNewFolderToolbarItemIdentifier))
        item.label = NSLocalizedString("New Folder", comment: "Toolbar item label")
        item.paletteLabel = NSLocalizedString("New Folder", comment: "Toolbar item label")
        item.toolTip = NSLocalizedString("Add a New Folder", comment: "Tool tip message")
//        item.image = NSImage(named: "NewFolder")
        item.image = NSImage(named: NSImage.folderName)!
        item.target = self
        item.action = #selector(insertBookmarkFolder(_:))
        dict[kBookmarksNewFolderToolbarItemIdentifier] = item
        
        item = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier(kBookmarksNewSeparatorToolbarItemIdentifier))
        item.label = NSLocalizedString("New Separator", comment: "Toolbar item label")
        item.paletteLabel = NSLocalizedString("New Separator", comment: "Toolbar item label")
        item.toolTip = NSLocalizedString("Add a New Separator", comment: "Tool tip message")
//        item.image = NSImage(named: "NewSeparator")
        item.image = NSImage(named: NSImage.shareTemplateName)!
        item.target = self
        item.action = #selector(insertBookmarkSeparator(_:))
        dict[kBookmarksNewSeparatorToolbarItemIdentifier] = item
        
        item = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier(kBookmarksDeleteToolbarItemIdentifier))
        item.label = NSLocalizedString("Delete", comment: "Toolbar item label")
        item.paletteLabel = NSLocalizedString("Delete", comment: "Toolbar item label")
        item.toolTip = NSLocalizedString("Delete Selected Items", comment: "Tool tip message")
        item.image = NSWorkspace.shared.icon(forFileType: NSFileTypeForHFSTypeCode(OSType(kToolbarDeleteIcon)))
        item.target = self
        item.action = #selector(deleteBookmark(_:))
        dict[kBookmarksDeleteToolbarItemIdentifier] = item
        
        toolbarItems = dict
        
        // Attach the toolbar to the window
        self.window?.toolbar = toolbar
    }

//
//    // MARK: - Quick Look Panel Support
//
//    func acceptsPreviewPanelControl(_ panel: QLPreviewPanel) -> Bool {
//        return true
//    }
//
//    func beginPreviewPanelControl(_ panel: QLPreviewPanel) {
//        panel.delegate = self
//        panel.dataSource = self
//    }
//
//    func endPreviewPanelControl(_ panel: QLPreviewPanel) {
//    }
//
//    func previewItems() -> [KMBookmark] {
//        var items = [KMBookmark]()
//        
//        outlineView.selectedRowIndexes.enumerated().forEach { (idx, _) in
//            if let item = outlineView.item(atRow: idx) as? KMBookmark {
//                if item.bookmarkType == .bookmark {
//                    items.append(item)
//                } else if item.bookmarkType == .session {
//                    items.append(contentsOf: item.children)
//                }
//            }
//        }
//        return items
//    }
//
//    func numberOfPreviewItems(in panel: QLPreviewPanel) -> Int {
//        return previewItems().count
//    }
//
//    func previewPanel(_ panel: QLPreviewPanel, previewItemAt anIndex: Int) -> QLPreviewItem {
//        return previewItems()[anIndex]
//    }
//
//    func previewPanel(_ panel: QLPreviewPanel, sourceFrameOnScreenForPreviewItem item: QLPreviewItem) -> NSRect {
//        var item = item
//        if let parent = (item as? KMBookmark)?.parent, parent.bookmarkType == .session {
//            item = parent
//        }
//        let row = outlineView.row(forItem: item)
//        var iconRect = NSZeroRect
//        if let item = item as? KMBookmark, row != -1 {
//            let cell = outlineView.preparedCell(atColumn: 0, row: row) as? SKTextWithIconCell
//            iconRect = cell?.iconRect(forBounds: outlineView.frameOfCell(atColumn: 0, row: row)) ?? NSZeroRect
//            if outlineView.visibleRect.intersects(iconRect) {
//                iconRect = outlineView.convert(iconRect, to: nil)
//            } else {
//                iconRect = NSZeroRect
//            }
//        }
//        return iconRect
//    }
//
//    func previewPanel(_ panel: QLPreviewPanel, transitionImageForPreviewItem item: QLPreviewItem, contentRect: UnsafeMutablePointer<NSRect>) -> NSImage? {
//        var item = item
//        if let parent = (item as? KMBookmark)?.parent, parent.bookmarkType == .session {
//            item = parent
//        }
//        return (item as? KMBookmark)?.icon
//    }
//
//    func previewPanel(_ panel: QLPreviewPanel, handle event: NSEvent) -> Bool {
//        if event.type == .keyDown {
//            outlineView.keyDown(with: event)
//            return true
//        }
//        return false
//    }
//

}

extension KMBookmarkController: NSOutlineViewDelegate, NSOutlineViewDataSource {
    //MARK: NSOutlineViewDataSource
    
    func minimumCoverForBookmarks(_ items: [KMBookmark]) -> [KMBookmark] {
        var lastBm: KMBookmark?
        var minimalCover = [KMBookmark]()

        for bm in items {
            if !(bm.isDescendant(of: lastBm)) {
                minimalCover.append(bm)
                lastBm = bm
            }
        }
        return minimalCover
    }
    
    func outlineView(_ ov: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
        let bookmark = item as? KMBookmark ?? bookmarkRoot
        return bookmark.bookmarkType == .folder ? bookmark.children.count : 0
    }

    func outlineView(_ ov: NSOutlineView, isItemExpandable item: Any) -> Bool {
        let bookmark = item as! KMBookmark
        return bookmark.bookmarkType == .folder
    }

    func outlineView(_ ov: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
        let bookmark = (item as? KMBookmark) ?? bookmarkRoot
        return bookmark.objectOfChidren(index: index)
    }

    func outlineView(_ ov: NSOutlineView, objectValueFor tableColumn: NSTableColumn?, byItem item: Any?) -> Any? {
        guard let column = tableColumn else { return nil }
        guard let bm = item as? KMBookmark else { return nil }
        let tcID = column.identifier
        
        switch tcID {
        case kLabelIdentifier:
            return [kTextWithIconStringKey: bm.label, kTextWithIconImageKey: bm.icon]
        case kFileIdentifier:
            if bm.bookmarkType == .folder || bm.bookmarkType == .session {
                let count = bm.children.count
                return count == 1 ? NSLocalizedString("1 item", comment: "Bookmark folder description") : String.localizedStringWithFormat(NSLocalizedString("%ld items", comment: "Bookmark folder description"), count)
            } else {
                return bm.fileURL?.path ?? ""
            }
        case kPageIdentifier:
            return bm.pageNumber
        default:
            return nil
        }
    }

    func outlineView(_ ov: NSOutlineView, setObjectValue object: Any?, for tableColumn: NSTableColumn?, byItem item: Any?) {
        guard let column = tableColumn else { return }
        guard let bm = item as? KMBookmark else { return }
        let tcID = column.identifier
        
        switch tcID {
        case kLabelIdentifier:
            if let newLabel = (object as? [String: Any])?[kTextWithIconStringKey] as? String, newLabel != bm.label {
                bm.label = newLabel
            }
        case kPageIdentifier:
            if let newPageNumber = object as? Int, newPageNumber != bm.pageNumber.intValue {
                bm.pageNumber = newPageNumber as NSNumber
            }
        default:
            break
        }
    }

    func outlineView(_ ov: NSOutlineView, writeItems items: [Any], to pboard: NSPasteboard) -> Bool {
        draggedBookmarks = minimumCoverForBookmarks(items as! [KMBookmark])
        pboard.clearContents()
        pboard.setData(Data(), forType: kPasteboardTypeBookmarkRows)
        return true
    }

    func outlineView(_ ov: NSOutlineView, validateDrop info: NSDraggingInfo, proposedItem item: Any?, proposedChildIndex index: Int) -> NSDragOperation {
        guard index != NSOutlineViewDropOnItemIndex else { return [] }
        
        let pboard = info.draggingPasteboard

        if pboard.canReadItem(withDataConformingToTypes: [kPasteboardTypeBookmarkRows.rawValue]) && info.draggingSource as? NSOutlineView == ov {
            return .move
        } else if NSURL.canReadFileURL(from: pboard) {
            return .every
        }
        return []
    }

    func outlineView(_ ov: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool {
        let pboard = info.draggingPasteboard

        if pboard.canReadItem(withDataConformingToTypes: [kPasteboardTypeBookmarkRows.rawValue]) && info.draggingSource as? NSOutlineView == ov {
            var movedBookmarks = [KMBookmark]()
            var indexes = IndexSet()
            var insertionIndex = index

            let targetItem = item as? KMBookmark ?? bookmarkRoot

            for bookmark in draggedBookmarks {
                guard let parent = bookmark.parent else { continue }
                guard let bookmarkIndex = parent.children.firstIndex(of: bookmark) else { continue }

                if targetItem == parent {
                    if insertionIndex > bookmarkIndex {
                        insertionIndex -= 1
                    }
                    if insertionIndex == bookmarkIndex {
                        continue
                    }
                }
                parent.removeObjectFromChildren(index: bookmarkIndex)
                targetItem.insert(child: bookmark, atIndex: insertionIndex)
                movedBookmarks.append(bookmark)
                insertionIndex += 1
            }

            for bookmark in movedBookmarks {
                let row = ov.row(forItem: bookmark)
                if row != -1 {
                    indexes.insert(row)
                }
            }
            if !indexes.isEmpty {
                ov.selectRowIndexes(indexes, byExtendingSelection: false)
            }
            return true
        } else {
            let urls = NSURL.readFileURLs(from: pboard)

            let newBookmarks = KMBookmark.bookmarks(urls: urls)
            if !newBookmarks.isEmpty {
                var indexes = IndexSet(integersIn: index..<(index + newBookmarks.count))
                (item as? KMBookmark ?? bookmarkRoot).mutableArrayValue(forKey: "children").insert(newBookmarks, at: indexes)
                if (item as? KMBookmark ?? bookmarkRoot) === bookmarkRoot || ov.isItemExpanded(item) {
                    if (item as? KMBookmark ?? bookmarkRoot) !== bookmarkRoot {
                        indexes.shift(startingAt: 0, by: ov.row(forItem: item) + 1)
                    }
                    ov.selectRowIndexes(indexes, byExtendingSelection: false)
                }
                return true
            }
            return false
        }
    }

    func outlineView(_ ov: NSOutlineView, dragEndedWith operation: NSDragOperation) {
        draggedBookmarks.removeAll()
    }


    //    MARK: NSOutlineViewDelegate

    func outlineView(_ ov: NSOutlineView, dataCellFor tableColumn: NSTableColumn?, item: Any) -> Any? {
        if tableColumn == nil {
            return (item as? KMBookmark)?.bookmarkType == .separator ? KMSeparatorCell() : nil
        }
        return tableColumn?.dataCell(forRow: ov.row(forItem: item))
    }
    
    func outlineView(_ ov: NSOutlineView, willDisplayCell cell: Any, for tableColumn: NSTableColumn?, item: Any) {
        guard let column = tableColumn else { return }
        guard let cell = cell as? NSCell else { return }

        if column.identifier == kFileIdentifier {
            if let bm = item as? KMBookmark {
//                if bm.bookmarkType == .folder || bm.bookmarkType == .session {
//                    cell.textColor = .disabledControlTextColor
//                } else {
//                    cell.textColor = .controlTextColor
//                }
            }
        }
    }

    func outlineView(_ ov: NSOutlineView, shouldEdit tableColumn: NSTableColumn?, item: Any) -> Bool {
        guard let column = tableColumn else { return false }
        guard let bm = item as? KMBookmark else { return false }

        let tcID = column.identifier
        switch tcID {
        case kLabelIdentifier:
            return bm.bookmarkType != .separator
        case kPageIdentifier:
            return bm.pageIndex != NSNotFound
        default:
            return false
        }
    }

    func outlineView(_ ov: NSOutlineView, toolTipFor cell: NSCell, rect: UnsafeMutablePointer<NSRect>, tableColumn tc: NSTableColumn?, item: Any, mouseLocation: NSPoint) -> String {
        guard let column = tc else { return "" }
        guard let bm = item as? KMBookmark else { return "" }

        let tcID = column.identifier
        switch tcID {
        case kLabelIdentifier:
            return bm.label
        case kFileIdentifier:
            if bm.bookmarkType == .session {
                return ""
//                return bm.children.map { $0.path ?? "" }.joined(separator: "\n")
            } else if bm.bookmarkType == .folder {
                let count = bm.children.count
                return count == 1 ? NSLocalizedString("1 item", comment: "Bookmark folder description") : String.localizedStringWithFormat(NSLocalizedString("%ld items", comment: "Bookmark folder description"), count)
            } else {
                return bm.fileURL?.path ?? ""
            }
        case kPageIdentifier:
            return bm.pageNumber.stringValue
        default:
            return ""
        }
    }

    func outlineViewSelectionDidChange(_ notification: Notification) {
        updateStatus()
        if QLPreviewPanel.sharedPreviewPanelExists(), let previewPanel = QLPreviewPanel.shared(), previewPanel.isVisible, previewPanel.dataSource === self {
            previewPanel.reloadData()
        }
    }

    func outlineView(_ ov: NSOutlineView, deleteItems items: [Any]) {
        for item in minimumCoverForBookmarks(items as! [KMBookmark]).reversed() {
            guard let parent = item.parent, let itemIndex = parent.children.firstIndex(of: item) else { continue }
            parent.removeObjectFromChildren(index: itemIndex)
        }
    }

    func outlineView(_ ov: NSOutlineView, canDeleteItems items: [Any]) -> Bool {
        return !items.isEmpty
    }

    func outlineView(_ ov: NSOutlineView, copyItems items: [Any]) {
        var urls = [URL]()
        addBookmarkURLsToArray(minimumCoverForBookmarks(items as! [KMBookmark]), &urls)
        if !urls.isEmpty {
            let pboard = NSPasteboard.general
            pboard.clearContents()
            pboard.writeObjects(urls as [NSPasteboardWriting])
        }
    }

    func outlineView(_ ov: NSOutlineView, canCopyItems items: [Any]) -> Bool {
        return !items.isEmpty
    }

    func outlineView(_ ov: NSOutlineView, pasteFromPasteboard pboard: NSPasteboard) {
        let urls = NSURL.readFileURLs(from: pboard)

        let newBookmarks = KMBookmark.bookmarks(urls: urls)
        if !newBookmarks.isEmpty {
            var item: KMBookmark?
            var anIndex = 0
            getInsertionFolder(&item, childIndex: &anIndex)
            var indexes = IndexSet(integersIn: anIndex..<(anIndex + newBookmarks.count))
            (item ?? bookmarkRoot).mutableArrayValue(forKey: "children").insert(newBookmarks, at: indexes)
            if item === bookmarkRoot || ov.isItemExpanded(item) {
                if item !== bookmarkRoot {
                    indexes.shift(startingAt: 0, by: ov.row(forItem: item) + 1)
                }
                ov.selectRowIndexes(indexes, byExtendingSelection: false)
            }
        }
    }

    func outlineView(_ ov: NSOutlineView, canPasteFromPasteboard pboard: NSPasteboard) -> Bool {
        return NSURL.canReadFileURL(from: pboard)
    }

    func outlineView(_ ov: NSOutlineView, typeSelectHelperSelectionStrings typeSelectHelper: SKTypeSelectHelper) -> [String] {
        let count = ov.numberOfRows
        var labels = [String]()
        for i in 0..<count {
            if let label = ov.item(atRow: i) {
                labels.append(label as! String)
            }
        }
        return labels
    }

//    func outlineView(_ ov: NSOutlineView, typeSelectHelper typeSelectHelper: SKTypeSelectHelper, didFailToFindMatchForSearchString searchString: String) {
//        statusBar.setLeftStringValue(String.localizedStringWithFormat(NSLocalizedString("No match: \"%@\"", comment: "Status message"), searchString))
//    }
//
//    func outlineView(_ ov: NSOutlineView, typeSelectHelper typeSelectHelper: SKTypeSelectHelper, updateSearchString searchString: String?) {
//        if let searchString = searchString {
//            statusBar.setLeftStringValue(String.localizedStringWithFormat(NSLocalizedString("Finding: \"%@\"", comment: "Status message"), searchString))
//        } else {
//            updateStatus()
//        }
//    }
    
    func addBookmarkURLsToArray(_ items: [KMBookmark], _ array: inout [URL]) {
        for bm in items {
            if bm.bookmarkType == .bookmark {
                if let url = bm.fileURL {
                    array.append(url)
                }
            } else if bm.bookmarkType != .separator {
                addBookmarkURLsToArray(bm.children, &array)
            }
        }
    }
    
}

extension KMBookmarkController: NSToolbarDelegate, NSToolbarItemValidation {
    func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
//        return [.flexibleSpace, .yourItem1, .yourItem2, .yourItem3]
        return [
            NSToolbarItem.Identifier(kBookmarksNewFolderToolbarItemIdentifier),
            NSToolbarItem.Identifier(kBookmarksNewSeparatorToolbarItemIdentifier),
            NSToolbarItem.Identifier(kBookmarksDeleteToolbarItemIdentifier)
        ]
    }
    
    func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
//        return [.yourItem1, .yourItem2, .yourItem3, .flexibleSpace, .space]
        return [
            NSToolbarItem.Identifier(kBookmarksNewFolderToolbarItemIdentifier),
            NSToolbarItem.Identifier(kBookmarksNewSeparatorToolbarItemIdentifier),
            NSToolbarItem.Identifier(kBookmarksDeleteToolbarItemIdentifier),
            .flexibleSpace,
            .space
        ]
    }
    
    func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
        return toolbarItems[itemIdentifier.rawValue]
    }
    
    func validateToolbarItem(_ item: NSToolbarItem) -> Bool {
//        guard let toolbar = self.window?.toolbar else { return false }
//
//        if toolbar.customizationPaletteIsRunning {
//            return false
//        } else if toolbarItem.itemIdentifier == kBookmarksDeleteToolbarItemIdentifier {
//            return outlineView.canDelete
//        }
        return true
    }
}

extension KMBookmarkController: NSMenuDelegate, NSMenuItemValidation {
    func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
//        if menuItem.action == #selector(toggleStatusBar(_:)) {
//            if statusBar.isVisible {
//                menuItem.title = NSLocalizedString("Hide Status Bar", comment: "Menu item title")
//            } else {
//                menuItem.title = NSLocalizedString("Show Status Bar", comment: "Menu item title")
//            }
//            return true
//        } else if menuItem.action == #selector(addBookmark(_:)) {
//            return menuItem.tag == 0
//        }
        return true
    }
}

extension NSURL {
    static func canReadFileURL(from pboard: NSPasteboard) -> Bool {
        let canReadFileURLsOnly = [NSPasteboard.ReadingOptionKey.urlReadingFileURLsOnly: true]
        let canReadClasses = [NSURL.self]
        
        return pboard.canReadObject(forClasses: canReadClasses, options: canReadFileURLsOnly) ||
        pboard.canReadItem(withDataConformingToTypes: [NSPasteboard.PasteboardType.fileURL.rawValue])
    }
    
    static func readFileURLs(from pboard: NSPasteboard) -> [URL] {
        if let fileURLs = pboard.readObjects(forClasses: [NSURL.self], options: [.urlReadingFileURLsOnly: true]) as? [URL], !fileURLs.isEmpty {
            return fileURLs
        } else if ((pboard.types?.contains(.fileURL)) != nil) {
            if let filenames = pboard.propertyList(forType: .fileURL) as? [String] {
                return filenames.compactMap { URL(fileURLWithPath: $0) }
            }
        }
        return []
    }
}