import Foundation
import CoreServices

public protocol FolderWatcherDelegate: AnyObject {
    func folderWatcher(_ watcher: FolderWatcher, didDetectChanges paths: [String])
}

public class FolderWatcher {
    public weak var delegate: FolderWatcherDelegate?
    
    private var stream: FSEventStreamRef?
    private let folderPath: String
    private var debounceTimer: Timer?
    private var pendingPaths: Set<String> = []
    private let queue = DispatchQueue(label: "com.cairnquire.folderwatcher")
    
    public init(folderPath: String) {
        self.folderPath = folderPath
    }
    
    public func start() {
        stop()
        
        let pathsToWatch = [folderPath as CFString]
        var context = FSEventStreamContext(
            version: 0,
            info: Unmanaged.passUnretained(self).toOpaque(),
            retain: nil,
            release: nil,
            copyDescription: nil
        )
        
        let callback: FSEventStreamCallback = { (streamRef, clientCallBackInfo, numEvents, eventPaths, eventFlags, eventIds) in
            let watcher = Unmanaged<FolderWatcher>.fromOpaque(clientCallBackInfo!).takeUnretainedValue()
            watcher.handleEvents(numEvents: numEvents, eventPaths: eventPaths, eventFlags: eventFlags)
        }
        
        stream = FSEventStreamCreate(
            kCFAllocatorDefault,
            callback,
            &context,
            pathsToWatch as CFArray,
            FSEventStreamEventId(kFSEventStreamEventIdSinceNow),
            0.5, // latency in seconds
            FSEventStreamCreateFlags(kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagUseCFTypes)
        )
        
        if let stream = stream {
            FSEventStreamSetDispatchQueue(stream, queue)
            FSEventStreamStart(stream)
        }
    }
    
    public func stop() {
        if let stream = stream {
            FSEventStreamStop(stream)
            FSEventStreamInvalidate(stream)
            FSEventStreamRelease(stream)
            self.stream = nil
        }
    }
    
    private func handleEvents(numEvents: Int, eventPaths: UnsafeMutableRawPointer, eventFlags: UnsafePointer<FSEventStreamEventFlags>) {
        guard let paths = Unmanaged<CFArray>.fromOpaque(eventPaths).takeUnretainedValue() as? [String] else { return }
        
        var changedPaths: [String] = []
        for i in 0..<numEvents {
            let path = paths[i]
            let flags = eventFlags[i]
            
            // Filter for relevant file events
            if flags & UInt32(kFSEventStreamEventFlagItemCreated) != 0 ||
               flags & UInt32(kFSEventStreamEventFlagItemModified) != 0 ||
               flags & UInt32(kFSEventStreamEventFlagItemRemoved) != 0 ||
               flags & UInt32(kFSEventStreamEventFlagItemRenamed) != 0 {
                
                // Only track markdown files
                if path.hasSuffix(".md") {
                    changedPaths.append(path)
                }
            }
        }
        
        guard !changedPaths.isEmpty else { return }
        
        DispatchQueue.main.async { [weak self] in
            guard let self = self else { return }
            
            self.pendingPaths.formUnion(changedPaths)
            self.debounceTimer?.invalidate()
            self.debounceTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { [weak self] _ in
                guard let self = self else { return }
                let paths = Array(self.pendingPaths)
                self.pendingPaths.removeAll()
                self.delegate?.folderWatcher(self, didDetectChanges: paths)
            }
        }
    }
    
    deinit {
        stop()
    }
}
