import Foundation
import AppKit
import Carbon
import os

private let logger = Logger(subsystem: "com.VoiceInk", category: "CursorPaster")

class CursorPaster {
    private typealias ClipboardSnapshot = [(NSPasteboard.PasteboardType, Data)]

    static func pasteAtCursor(_ text: String) {
        Task {
            await MainActor.run {
                startPasteAtCursor(text)
            }.value
        }
    }

    @MainActor
    @discardableResult
    static func startPasteAtCursor(_ text: String) -> Task<Void, Never> {
        let pasteboard = NSPasteboard.general
        let shouldRestoreClipboard = UserDefaults.standard.bool(forKey: "restoreClipboardAfterPaste")
        let savedContents = shouldRestoreClipboard ? snapshotClipboard(from: pasteboard) : []

        _ = ClipboardManager.setClipboard(text, transient: shouldRestoreClipboard)

        return Task { @MainActor in
            try? await Task.sleep(nanoseconds: 50_000_000)
            postPasteCommand()

            if shouldRestoreClipboard {
                scheduleClipboardRestore(savedContents, on: pasteboard)
            }
        }
    }

    @MainActor
    static func pasteAtCursorAndWaitUntilPosted(_ text: String) async {
        await startPasteAtCursor(text).value
    }

    private static func snapshotClipboard(from pasteboard: NSPasteboard) -> ClipboardSnapshot {
        var savedContents: ClipboardSnapshot = []
        let currentItems = pasteboard.pasteboardItems ?? []

        for item in currentItems {
            for type in item.types {
                if let data = item.data(forType: type) {
                    savedContents.append((type, data))
                }
            }
        }

        return savedContents
    }

    private static func postPasteCommand() {
        if UserDefaults.standard.bool(forKey: "useAppleScriptPaste") {
            pasteUsingAppleScript()
        } else {
            pasteFromClipboard()
        }
    }

    private static func scheduleClipboardRestore(_ savedContents: ClipboardSnapshot, on pasteboard: NSPasteboard) {
        let restoreDelay = UserDefaults.standard.double(forKey: "clipboardRestoreDelay")
        let delay = max(restoreDelay, 0.25)

        DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
            if !savedContents.isEmpty {
                pasteboard.clearContents()
                for (type, data) in savedContents {
                    pasteboard.setData(data, forType: type)
                }
            }
        }
    }

    // MARK: - AppleScript paste

    // "X – QWERTY ⌘" layouts remap to QWERTY when Command is held, so keystroke "v" resolves
    // the wrong key code. key code 9 (physical V) bypasses layout translation for those layouts.
    private static func makeScript(_ source: String) -> NSAppleScript? {
        let script = NSAppleScript(source: source)
        var error: NSDictionary?
        script?.compileAndReturnError(&error)
        return script
    }

    private static let pasteScriptKeystroke = makeScript("tell application \"System Events\" to keystroke \"v\" using command down")
    private static let pasteScriptKeyCode   = makeScript("tell application \"System Events\" to key code 9 using command down")

    private static var layoutSwitchesToQWERTYOnCommand: Bool {
        let source = TISCopyCurrentKeyboardInputSource().takeRetainedValue()
        guard let nameRef = TISGetInputSourceProperty(source, kTISPropertyLocalizedName) else { return false }
        return (Unmanaged<CFString>.fromOpaque(nameRef).takeUnretainedValue() as String).hasSuffix("⌘")
    }

    private static func pasteUsingAppleScript() {
        let script = layoutSwitchesToQWERTYOnCommand ? pasteScriptKeyCode : pasteScriptKeystroke
        var error: NSDictionary?
        script?.executeAndReturnError(&error)
        if let error = error {
            logger.error("AppleScript paste failed: \(error, privacy: .public)")
        }
    }

    // MARK: - CGEvent paste

    // Posts Cmd+V via CGEvent without modifying the active input source.
    private static func pasteFromClipboard() {
        guard AXIsProcessTrusted() else {
            logger.error("Accessibility not trusted — cannot paste")
            return
        }

        let source = CGEventSource(stateID: .privateState)

        let cmdDown = CGEvent(keyboardEventSource: source, virtualKey: 0x37, keyDown: true)
        let vDown   = CGEvent(keyboardEventSource: source, virtualKey: 0x09, keyDown: true)
        let vUp     = CGEvent(keyboardEventSource: source, virtualKey: 0x09, keyDown: false)
        let cmdUp   = CGEvent(keyboardEventSource: source, virtualKey: 0x37, keyDown: false)

        cmdDown?.flags = .maskCommand
        vDown?.flags   = .maskCommand
        vUp?.flags     = .maskCommand

        cmdDown?.post(tap: .cghidEventTap)
        vDown?.post(tap: .cghidEventTap)
        vUp?.post(tap: .cghidEventTap)
        cmdUp?.post(tap: .cghidEventTap)

        logger.notice("CGEvents posted for Cmd+V")
    }

    // MARK: - Auto Send Keys

    static func performAutoSend(_ key: AutoSendKey) {
        guard key.isEnabled else { return }
        guard AXIsProcessTrusted() else { return }

        if UserDefaults.standard.bool(forKey: "useAppleScriptPaste") {
            autoSendUsingAppleScript(key)
            return
        }

        let source = CGEventSource(stateID: .privateState)
        let enterDown = CGEvent(keyboardEventSource: source, virtualKey: 0x24, keyDown: true)
        let enterUp   = CGEvent(keyboardEventSource: source, virtualKey: 0x24, keyDown: false)

        switch key {
        case .none: return
        case .enter: break
        case .shiftEnter:
            enterDown?.flags = .maskShift
            enterUp?.flags   = .maskShift
        case .commandEnter:
            enterDown?.flags = .maskCommand
            enterUp?.flags   = .maskCommand
        }

        enterDown?.post(tap: .cghidEventTap)
        enterUp?.post(tap: .cghidEventTap)
    }

    // MARK: - AppleScript Auto Send (upstream PR #619 — fixes terminal emulator autosend)

    private static let autoSendScriptEnter        = makeAutoSendScript("")
    private static let autoSendScriptShiftEnter   = makeAutoSendScript("using shift down")
    private static let autoSendScriptCommandEnter = makeAutoSendScript("using command down")

    private static func makeAutoSendScript(_ modifier: String) -> NSAppleScript? {
        let script = NSAppleScript(source: """
            tell application "System Events"
                key code 36 \(modifier)
            end tell
            """)
        var error: NSDictionary?
        script?.compileAndReturnError(&error)
        return script
    }

    private static func autoSendUsingAppleScript(_ key: AutoSendKey) {
        let script: NSAppleScript?
        switch key {
        case .none: return
        case .enter: script = autoSendScriptEnter
        case .shiftEnter: script = autoSendScriptShiftEnter
        case .commandEnter: script = autoSendScriptCommandEnter
        }
        var error: NSDictionary?
        script?.executeAndReturnError(&error)
        if let error = error {
            logger.error("AppleScript auto-send failed: \(error, privacy: .public)")
        }
    }
}
