import Cocoa
import Carbon

// MARK: - QWERTY Adjacency Map for Realistic Typos

let adjacencyMap: [Character: [Character]] = [
    "q": ["w", "a", "s"],
    "w": ["q", "e", "a", "s", "d"],
    "e": ["w", "r", "s", "d", "f"],
    "r": ["e", "t", "d", "f", "g"],
    "t": ["r", "y", "f", "g", "h"],
    "y": ["t", "u", "g", "h", "j"],
    "u": ["y", "i", "h", "j", "k"],
    "i": ["u", "o", "j", "k", "l"],
    "o": ["i", "p", "k", "l"],
    "p": ["o", "l"],
    "a": ["q", "w", "s", "z", "x"],
    "s": ["a", "w", "e", "d", "z", "x", "c"],
    "d": ["s", "e", "r", "f", "x", "c", "v"],
    "f": ["d", "r", "t", "g", "c", "v", "b"],
    "g": ["f", "t", "y", "h", "v", "b", "n"],
    "h": ["g", "y", "u", "j", "b", "n", "m"],
    "j": ["h", "u", "i", "k", "n", "m"],
    "k": ["j", "i", "o", "l", "m"],
    "l": ["k", "o", "p"],
    "z": ["a", "s", "x"],
    "x": ["z", "s", "d", "c"],
    "c": ["x", "d", "f", "v"],
    "v": ["c", "f", "g", "b"],
    "b": ["v", "g", "h", "n"],
    "n": ["b", "h", "j", "m"],
    "m": ["n", "j", "k"],
    "1": ["2", "q"], "2": ["1", "3", "w"], "3": ["2", "4", "e"],
    "4": ["3", "5", "r"], "5": ["4", "6", "t"], "6": ["5", "7", "y"],
    "7": ["6", "8", "u"], "8": ["7", "9", "i"], "9": ["8", "0", "o"],
    "0": ["9", "p"],
]

// Common transposition pairs (words where people swap letters)
let commonTranspositions = ["teh": "the", "taht": "that", "wiht": "with", "fro": "for", "adn": "and"]

// MARK: - Keycode Map

let keycodeMap: [Character: CGKeyCode] = [
    "a": 0x00, "s": 0x01, "d": 0x02, "f": 0x03, "h": 0x04, "g": 0x05,
    "z": 0x06, "x": 0x07, "c": 0x08, "v": 0x09, "b": 0x0B, "q": 0x0C,
    "w": 0x0D, "e": 0x0E, "r": 0x0F, "t": 0x11, "y": 0x10, "u": 0x20,
    "i": 0x22, "o": 0x1F, "p": 0x23, "l": 0x25, "j": 0x26, "k": 0x28,
    "n": 0x2D, "m": 0x2E,
    "1": 0x12, "2": 0x13, "3": 0x14, "4": 0x15, "5": 0x17, "6": 0x16,
    "7": 0x1A, "8": 0x1C, "9": 0x19, "0": 0x1D,
    " ": 0x31, "\t": 0x30,
    "-": 0x1B, "=": 0x18, "[": 0x21, "]": 0x1E, "\\": 0x2A,
    ";": 0x29, "'": 0x27, ",": 0x2B, ".": 0x2F, "/": 0x2C, "`": 0x32,
]

let shiftKeycodeMap: [Character: CGKeyCode] = [
    "!": 0x12, "@": 0x13, "#": 0x14, "$": 0x15, "%": 0x17, "^": 0x16,
    "&": 0x1A, "*": 0x1C, "(": 0x19, ")": 0x1D,
    "_": 0x1B, "+": 0x18, "{": 0x21, "}": 0x1E, "|": 0x2A,
    ":": 0x29, "\"": 0x27, "<": 0x2B, ">": 0x2F, "?": 0x2C, "~": 0x32,
    "A": 0x00, "B": 0x0B, "C": 0x08, "D": 0x02, "E": 0x0E, "F": 0x03,
    "G": 0x05, "H": 0x04, "I": 0x22, "J": 0x26, "K": 0x28, "L": 0x25,
    "M": 0x2E, "N": 0x2D, "O": 0x1F, "P": 0x23, "Q": 0x0C, "R": 0x0F,
    "S": 0x01, "T": 0x11, "U": 0x20, "V": 0x09, "W": 0x0D, "X": 0x07,
    "Y": 0x10, "Z": 0x06,
]

// MARK: - Typing Engine

enum TypoKind: CaseIterable {
    case adjacentKey       // Hit neighbor key (e→r)
    case doubleTap         // Hit same key twice (helllo)
    case transposition     // Swap two chars (teh → the)
    case earlySpace        // Space too early (bec ause)
    case missedShift       // HEllo instead of Hello
}

class GhostWriter {
    var isTyping = false
    /// True from hotkey accepted until `typeText` starts (covers pre-type delay so UI is not "idle").
    var isPendingTypeSession = false
    var isPaused = false
    var shouldStop = false
    var modifierHeld = false  // True when any modifier is down — freezes output
    var typoRate: Double = 0.04
    var baseDelay: UInt32 = 45000
    var delayVariance: UInt32 = 35000
    var thinkingPausesEnabled = true
    var sentenceCount = 0
    var nextThinkingTarget = Int.random(in: 3...6)

    // Burst typing state
    var burstRemaining = 0
    var inBurst = false

    // Resume support
    var lastStoppedIndex = 0
    var lastStoppedText = ""

    // Stats
    var totalCharsTyped = 0
    var totalTypos = 0
    var currentQueueItem = ""
    var totalCharsInCurrent = 0

    // Reuse CGEventSource (P1 fix)
    let eventSource = CGEventSource(stateID: .hidSystemState)

    // MARK: - Low-level key events

    func typeCharacter(_ char: Character, shift: Bool = false) {
        let keycode: CGKeyCode
        let needsShift: Bool

        if let code = keycodeMap[char] {
            keycode = code
            needsShift = shift
        } else if let code = shiftKeycodeMap[char] {
            keycode = code
            needsShift = true
        } else {
            typeUnicode(char)
            return
        }

        if needsShift {
            let shiftDown = CGEvent(keyboardEventSource: eventSource, virtualKey: 0x38, keyDown: true)
            shiftDown?.post(tap: .cghidEventTap)
            usleep(4000)
        }

        let keyDown = CGEvent(keyboardEventSource: eventSource, virtualKey: keycode, keyDown: true)
        let keyUp = CGEvent(keyboardEventSource: eventSource, virtualKey: keycode, keyDown: false)

        if needsShift {
            keyDown?.flags = .maskShift
            keyUp?.flags = .maskShift
        }

        keyDown?.post(tap: .cghidEventTap)
        usleep(6000)
        keyUp?.post(tap: .cghidEventTap)

        if needsShift {
            let shiftUp = CGEvent(keyboardEventSource: eventSource, virtualKey: 0x38, keyDown: false)
            shiftUp?.post(tap: .cghidEventTap)
        }
        usleep(2000)
    }

    func typeUnicode(_ char: Character) {
        let utf16 = Array(String(char).utf16)
        let keyDown = CGEvent(keyboardEventSource: eventSource, virtualKey: 0, keyDown: true)
        keyDown?.keyboardSetUnicodeString(stringLength: utf16.count, unicodeString: utf16)
        keyDown?.post(tap: .cghidEventTap)
        let keyUp = CGEvent(keyboardEventSource: eventSource, virtualKey: 0, keyDown: false)
        keyUp?.post(tap: .cghidEventTap)
        usleep(4000)
    }

    func pressBackspace(_ count: Int = 1) {
        for _ in 0..<count {
            let keyDown = CGEvent(keyboardEventSource: eventSource, virtualKey: 0x33, keyDown: true)
            let keyUp = CGEvent(keyboardEventSource: eventSource, virtualKey: 0x33, keyDown: false)
            keyDown?.post(tap: .cghidEventTap)
            usleep(6000)
            keyUp?.post(tap: .cghidEventTap)
            usleep(UInt32.random(in: 25_000...60_000))
        }
    }

    func pressReturn() {
        let keyDown = CGEvent(keyboardEventSource: eventSource, virtualKey: 0x24, keyDown: true)
        let keyUp = CGEvent(keyboardEventSource: eventSource, virtualKey: 0x24, keyDown: false)
        keyDown?.post(tap: .cghidEventTap)
        usleep(6000)
        keyUp?.post(tap: .cghidEventTap)
        usleep(8000)
    }

    func cmdShortcut(key: Character) {
        guard let keycode = keycodeMap[key] else { return }
        let keyDown = CGEvent(keyboardEventSource: eventSource, virtualKey: keycode, keyDown: true)
        let keyUp = CGEvent(keyboardEventSource: eventSource, virtualKey: keycode, keyDown: false)
        keyDown?.flags = .maskCommand
        keyUp?.flags = .maskCommand
        keyDown?.post(tap: .cghidEventTap)
        usleep(6000)
        keyUp?.post(tap: .cghidEventTap)
        usleep(25000)
    }

    func cmdOptionShortcut(key: Character) {
        guard let keycode = keycodeMap[key] else { return }
        let keyDown = CGEvent(keyboardEventSource: eventSource, virtualKey: keycode, keyDown: true)
        let keyUp = CGEvent(keyboardEventSource: eventSource, virtualKey: keycode, keyDown: false)
        keyDown?.flags = [.maskCommand, .maskAlternate]
        keyUp?.flags = [.maskCommand, .maskAlternate]
        keyDown?.post(tap: .cghidEventTap)
        usleep(6000)
        keyUp?.post(tap: .cghidEventTap)
        usleep(25000)
    }

    // MARK: - Burst Typing Rhythm (P0 fix)

    func humanDelay(after char: Character, wordLength: Int = 0) {
        // Burst rhythm: type 3-8 chars fast, then micro-pause
        if burstRemaining <= 0 {
            // Start new burst
            burstRemaining = Int.random(in: 3...8)
            inBurst = true
            // Inter-burst pause
            usleep(UInt32.random(in: 80_000...250_000))
        }
        burstRemaining -= 1

        // Base delay within burst (faster) vs between bursts
        let burstMultiplier: Double = inBurst ? Double.random(in: 0.5...0.8) : 1.0
        let base = Double(baseDelay) * burstMultiplier
        let variance = Double(arc4random_uniform(delayVariance))
        var delay = base + variance

        // Slow down for longer words (P1 fix)
        if wordLength > 7 {
            delay *= 1.0 + Double(wordLength - 7) * 0.06
        }

        // Punctuation pauses
        switch char {
        case ",":
            delay += Double.random(in: 120_000...300_000)
            burstRemaining = 0  // Reset burst at punctuation
        case ".", "!", "?":
            delay += Double.random(in: 250_000...600_000)
            sentenceCount += 1
            burstRemaining = 0
        case "\n":
            delay += Double.random(in: 500_000...1_000_000)
            burstRemaining = 0
        case " ":
            if arc4random_uniform(100) < 10 {
                delay += Double.random(in: 80_000...200_000)
            }
        default:
            break
        }

        // Thinking pauses (P0 fix: check setting, P1 fix: stable target)
        if thinkingPausesEnabled && sentenceCount >= nextThinkingTarget {
            if arc4random_uniform(100) < 45 {
                delay += Double.random(in: 1_200_000...3_000_000)
            }
            sentenceCount = 0
            nextThinkingTarget = Int.random(in: 3...6)
        }

        usleep(UInt32(delay))
    }

    // MARK: - Typo System (P0 fix: multiple typo types)

    func shouldMakeTypo() -> Bool {
        return Double.random(in: 0...1) < typoRate
    }

    func pickTypoKind(for char: Character, wordBuffer: String) -> TypoKind {
        // Weight the typo types by realism
        let roll = Int.random(in: 0...99)
        if roll < 45 { return .adjacentKey }
        if roll < 65 { return .doubleTap }
        if roll < 80 && wordBuffer.count >= 3 { return .transposition }
        if roll < 92 && wordBuffer.count >= 3 { return .earlySpace }
        if char.isUppercase { return .missedShift }
        return .adjacentKey  // Fallback
    }

    func adjacentKey(for char: Character) -> Character? {
        let lower = Character(String(char).lowercased())
        guard let neighbors = adjacencyMap[lower], !neighbors.isEmpty else { return nil }
        let typo = neighbors.randomElement()!
        return char.isUppercase ? Character(String(typo).uppercased()) : typo
    }

    func executeTypo(kind: TypoKind, char: Character, wordBuffer: String) {
        let fixes = Double.random(in: 0...1) < 0.55
        totalTypos += 1

        switch kind {
        case .adjacentKey:
            guard let wrong = adjacentKey(for: char) else {
                typeCharacter(char)
                return
            }
            typeCharacter(wrong)
            if fixes {
                usleep(UInt32.random(in: 150_000...450_000))
                pressBackspace()
                usleep(UInt32.random(in: 40_000...120_000))
                typeCharacter(char)
            }

        case .doubleTap:
            // Type the char twice
            typeCharacter(char)
            usleep(UInt32.random(in: 15_000...40_000))
            typeCharacter(char)
            if fixes {
                usleep(UInt32.random(in: 100_000...300_000))
                pressBackspace()
            }

        case .transposition:
            // Already typed previous chars in wordBuffer; we'll simulate by
            // backspacing 1, typing current then previous
            if wordBuffer.count >= 2 {
                let prevChar = wordBuffer[wordBuffer.index(wordBuffer.endIndex, offsetBy: -1)]
                pressBackspace()
                usleep(UInt32.random(in: 20_000...50_000))
                typeCharacter(char)
                usleep(UInt32.random(in: 20_000...50_000))
                typeCharacter(prevChar)
                if fixes {
                    usleep(UInt32.random(in: 200_000...500_000))
                    pressBackspace(2)
                    usleep(UInt32.random(in: 40_000...100_000))
                    typeCharacter(prevChar)
                    usleep(UInt32.random(in: 20_000...50_000))
                    typeCharacter(char)
                }
            } else {
                typeCharacter(char)
            }

        case .earlySpace:
            // Hit space before finishing the word, then continue
            typeCharacter(" ")
            usleep(UInt32.random(in: 80_000...200_000))
            if fixes {
                usleep(UInt32.random(in: 150_000...350_000))
                pressBackspace()
                usleep(UInt32.random(in: 40_000...100_000))
            }
            typeCharacter(char)

        case .missedShift:
            // Type first 1-2 chars uppercase then rest lowercase, or vice versa
            // Simulate by typing uppercase version of a lowercase char
            if char.isLetter {
                let wrong = char.isUppercase ? Character(String(char).lowercased()) : Character(String(char).uppercased())
                typeCharacter(wrong)
                if fixes {
                    usleep(UInt32.random(in: 200_000...400_000))
                    pressBackspace()
                    usleep(UInt32.random(in: 40_000...100_000))
                    typeCharacter(char)
                }
            } else {
                typeCharacter(char)
            }
        }
    }

    // MARK: - Markdown Parser

    struct TextSegment {
        enum Style {
            case plain, bold, italic, boldItalic, heading(Int)
        }
        var text: String
        var style: Style
    }

    func parseMarkdown(_ input: String) -> [TextSegment] {
        var segments: [TextSegment] = []
        let lines = input.components(separatedBy: "\n")

        for (lineIdx, line) in lines.enumerated() {
            if let level = headingLevel(line) {
                let text = String(line.dropFirst(level + 1))
                segments.append(TextSegment(text: text, style: .heading(level)))
            } else {
                segments.append(contentsOf: parseInlineFormatting(line))
            }
            if lineIdx < lines.count - 1 {
                segments.append(TextSegment(text: "\n", style: .plain))
            }
        }
        return segments
    }

    func headingLevel(_ line: String) -> Int? {
        var count = 0
        for ch in line {
            if ch == "#" { count += 1 } else { break }
        }
        if count >= 1 && count <= 6 && line.count > count && line[line.index(line.startIndex, offsetBy: count)] == " " {
            return count
        }
        return nil
    }

    func parseInlineFormatting(_ text: String) -> [TextSegment] {
        var segments: [TextSegment] = []
        var remaining = text

        while !remaining.isEmpty {
            if let range = remaining.range(of: "\\*\\*\\*(.+?)\\*\\*\\*", options: .regularExpression) {
                let before = String(remaining[remaining.startIndex..<range.lowerBound])
                if !before.isEmpty { segments.append(TextSegment(text: before, style: .plain)) }
                let inner = String(String(remaining[range]).dropFirst(3).dropLast(3))
                segments.append(TextSegment(text: inner, style: .boldItalic))
                remaining = String(remaining[range.upperBound...])
            } else if let range = remaining.range(of: "\\*\\*(.+?)\\*\\*", options: .regularExpression) {
                let before = String(remaining[remaining.startIndex..<range.lowerBound])
                if !before.isEmpty { segments.append(TextSegment(text: before, style: .plain)) }
                let inner = String(String(remaining[range]).dropFirst(2).dropLast(2))
                segments.append(TextSegment(text: inner, style: .bold))
                remaining = String(remaining[range.upperBound...])
            } else if let range = remaining.range(of: "\\*(.+?)\\*", options: .regularExpression) {
                let before = String(remaining[remaining.startIndex..<range.lowerBound])
                if !before.isEmpty { segments.append(TextSegment(text: before, style: .plain)) }
                let inner = String(String(remaining[range]).dropFirst(1).dropLast(1))
                segments.append(TextSegment(text: inner, style: .italic))
                remaining = String(remaining[range.upperBound...])
            } else {
                segments.append(TextSegment(text: remaining, style: .plain))
                remaining = ""
            }
        }
        return segments
    }

    // MARK: - Segment Typing

    func typeSegment(_ segment: TextSegment) {
        switch segment.style {
        case .heading(let level):
            let key = Character("\(level)")
            cmdOptionShortcut(key: key)
            usleep(50000)
            typePlainText(segment.text)
            pressReturn()
            cmdOptionShortcut(key: "0")
            usleep(30000)
        case .bold:
            cmdShortcut(key: "b"); usleep(25000)
            typePlainText(segment.text)
            cmdShortcut(key: "b"); usleep(25000)
        case .italic:
            cmdShortcut(key: "i"); usleep(25000)
            typePlainText(segment.text)
            cmdShortcut(key: "i"); usleep(25000)
        case .boldItalic:
            cmdShortcut(key: "b"); usleep(15000)
            cmdShortcut(key: "i"); usleep(25000)
            typePlainText(segment.text)
            cmdShortcut(key: "i"); usleep(15000)
            cmdShortcut(key: "b"); usleep(25000)
        case .plain:
            typePlainText(segment.text)
        }
    }

    // MARK: - Main Typing Loop

    func typePlainText(_ text: String) {
        var wordBuffer = ""
        var currentWordLength = 0

        // Pre-scan to find word boundaries for word-length awareness
        let chars = Array(text)

        for (idx, char) in chars.enumerated() {
            guard !shouldStop else {
                // Save position for resume
                lastStoppedIndex = totalCharsTyped
                return
            }

            while isPaused && !shouldStop {
                usleep(100_000)
            }
            // Freeze output while modifier keys are held (prevents hotkey collision)
            while modifierHeld && !shouldStop {
                usleep(10_000)
            }
            guard !shouldStop else {
                lastStoppedIndex = totalCharsTyped
                return
            }

            if char == "\n" {
                wordBuffer = ""
                currentWordLength = 0
                pressReturn()
                humanDelay(after: char)
                totalCharsTyped += 1
                continue
            }

            // Calculate current word length (look ahead)
            if char == " " || char == "," || char == "." || char == "!" || char == "?" || char == ";" || char == ":" {
                wordBuffer = ""
                currentWordLength = 0
                typeCharacter(char)
            } else {
                wordBuffer.append(char)

                // Calculate full word length by looking ahead
                if currentWordLength == 0 {
                    currentWordLength = 1
                    var look = idx + 1
                    while look < chars.count && chars[look].isLetter {
                        currentWordLength += 1
                        look += 1
                    }
                }

                // Typo chance: letters only, after 2+ chars, not first char of word
                if char.isLetter && wordBuffer.count >= 2 && shouldMakeTypo() {
                    let kind = pickTypoKind(for: char, wordBuffer: wordBuffer)
                    executeTypo(kind: kind, char: char, wordBuffer: wordBuffer)
                } else {
                    typeCharacter(char)
                }
            }

            totalCharsTyped += 1
            humanDelay(after: char, wordLength: currentWordLength)
        }
    }

    func typeText(_ text: String, startFrom: Int = 0) {
        isPendingTypeSession = false
        isTyping = true
        shouldStop = false
        isPaused = false
        sentenceCount = 0
        burstRemaining = 0
        nextThinkingTarget = Int.random(in: 3...6)
        totalCharsTyped = startFrom
        totalTypos = 0
        lastStoppedText = text
        lastStoppedIndex = 0
        currentQueueItem = String(text.prefix(50))
        totalCharsInCurrent = text.count

        let textToType: String
        if startFrom > 0 {
            // Resume: skip already-typed characters
            let startIdx = text.index(text.startIndex, offsetBy: min(startFrom, text.count))
            textToType = String(text[startIdx...])
        } else {
            textToType = text
        }

        let segments = parseMarkdown(textToType)
        for segment in segments {
            guard !shouldStop else { break }
            typeSegment(segment)
        }

        isTyping = false
        isPaused = false
        modifierHeld = false

        // If stopped early, save remaining text for resume
        if shouldStop && totalCharsTyped < text.count {
            lastStoppedText = text
            // lastStoppedIndex already set in typePlainText
        } else {
            lastStoppedText = ""
            lastStoppedIndex = 0
        }
        shouldStop = false
        saveStatus()
    }
}

// MARK: - Queue Data

struct QueueItem: Codable {
    var id: String
    var text: String
    var label: String
    var createdAt: String
    var resumeFrom: Int?  // Character index to resume from
}

struct QueueData: Codable {
    var items: [QueueItem]
    var settings: Settings

    struct Settings: Codable {
        var typoRate: Double
        var baseWPM: Int
        var thinkingPauses: Bool
    }
}

// MARK: - Queue Manager with File Locking (P3 fix)

class QueueManager {
    let queueDir: String
    let queueFile: String
    let lockQueue = DispatchQueue(label: "ghostwriter.queue.lock")

    init() {
        queueDir = NSHomeDirectory() + "/.ghostwriter"
        queueFile = queueDir + "/queue.json"

        try? FileManager.default.createDirectory(atPath: queueDir, withIntermediateDirectories: true)

        if !FileManager.default.fileExists(atPath: queueFile) {
            let initial = QueueData(
                items: [],
                settings: QueueData.Settings(typoRate: 4, baseWPM: 110, thinkingPauses: true)
            )
            saveUnsafe(initial)
        }
    }

    private func loadUnsafe() -> QueueData? {
        guard let data = FileManager.default.contents(atPath: queueFile) else { return nil }
        return try? JSONDecoder().decode(QueueData.self, from: data)
    }

    private func saveUnsafe(_ queue: QueueData) {
        let encoder = JSONEncoder()
        encoder.outputFormatting = .prettyPrinted
        guard let data = try? encoder.encode(queue) else { return }
        try? data.write(to: URL(fileURLWithPath: queueFile), options: .atomic)
    }

    func load() -> QueueData? {
        lockQueue.sync { loadUnsafe() }
    }

    func save(_ queue: QueueData) {
        lockQueue.sync { saveUnsafe(queue) }
    }

    func popFirst() -> QueueItem? {
        lockQueue.sync {
            guard var queue = loadUnsafe(), !queue.items.isEmpty else { return nil }
            let item = queue.items.removeFirst()
            saveUnsafe(queue)
            return item
        }
    }

    func pushFront(_ item: QueueItem) {
        lockQueue.sync {
            var queue = loadUnsafe() ?? QueueData(items: [], settings: QueueData.Settings(typoRate: 4, baseWPM: 110, thinkingPauses: true))
            queue.items.insert(item, at: 0)
            saveUnsafe(queue)
        }
    }

    func moveItem(id: String, direction: String) {
        lockQueue.sync {
            guard var queue = loadUnsafe() else { return }
            guard let idx = queue.items.firstIndex(where: { $0.id == id }) else { return }
            if direction == "up" && idx > 0 {
                queue.items.swapAt(idx, idx - 1)
            } else if direction == "down" && idx < queue.items.count - 1 {
                queue.items.swapAt(idx, idx + 1)
            }
            saveUnsafe(queue)
        }
    }

    func getSettings() -> QueueData.Settings {
        return load()?.settings ?? QueueData.Settings(typoRate: 4, baseWPM: 110, thinkingPauses: true)
    }
}

// MARK: - Status

func saveStatus(typing: Bool = false, paused: Bool = false, progress: String = "idle", chars: Int = 0, total: Int = 0, typos: Int = 0, canResume: Bool = false) {
    let status: [String: Any] = [
        "typing": typing,
        "paused": paused,
        "progress": progress,
        "charsTyped": chars,
        "totalChars": total,
        "typos": typos,
        "canResume": canResume,
        "timestamp": ISO8601DateFormatter().string(from: Date())
    ]
    if let data = try? JSONSerialization.data(withJSONObject: status, options: .prettyPrinted) {
        let statusFile = NSHomeDirectory() + "/.ghostwriter/status.json"
        try? data.write(to: URL(fileURLWithPath: statusFile), options: .atomic)
    }
}

// MARK: - HTTP Server (P1 fix: larger buffer, chunked reads)

class MiniHTTPServer {
    let port: UInt16
    var serverSocket: Int32 = -1
    let ghostWriter: GhostWriter
    let queueManager: QueueManager
    let htmlPath: String

    init(port: UInt16, ghostWriter: GhostWriter, queueManager: QueueManager, htmlPath: String) {
        self.port = port
        self.ghostWriter = ghostWriter
        self.queueManager = queueManager
        self.htmlPath = htmlPath
    }

    func start() {
        serverSocket = socket(AF_INET, SOCK_STREAM, 0)
        guard serverSocket >= 0 else {
            print("[GhostWriter] Failed to create socket")
            return
        }

        var yes: Int32 = 1
        setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, &yes, socklen_t(MemoryLayout<Int32>.size))
        // Reduce socket linger for snappy responses
        var linger = linger(l_onoff: 1, l_linger: 0)
        setsockopt(serverSocket, SOL_SOCKET, SO_LINGER, &linger, socklen_t(MemoryLayout<linger>.size))

        var addr = sockaddr_in()
        addr.sin_family = sa_family_t(AF_INET)
        addr.sin_port = port.bigEndian
        addr.sin_addr.s_addr = inet_addr("127.0.0.1")

        let bindResult = withUnsafePointer(to: &addr) { ptr in
            ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { sockPtr in
                bind(serverSocket, sockPtr, socklen_t(MemoryLayout<sockaddr_in>.size))
            }
        }

        guard bindResult == 0 else {
            print("[GhostWriter] Failed to bind to port \(port)")
            return
        }

        listen(serverSocket, 10)
        print("[GhostWriter] HTTP server on http://127.0.0.1:\(port)")

        DispatchQueue.global(qos: .userInteractive).async { [weak self] in
            while true {
                guard let self = self else { return }
                let client = accept(self.serverSocket, nil, nil)
                guard client >= 0 else { continue }
                DispatchQueue.global(qos: .userInteractive).async {
                    self.handleClient(client)
                }
            }
        }
    }

    func readFullRequest(_ client: Int32) -> String {
        var accumulated = Data()
        var buffer = [UInt8](repeating: 0, count: 8192)

        // Read headers first
        let firstRead = read(client, &buffer, buffer.count)
        guard firstRead > 0 else { return "" }
        accumulated.append(contentsOf: buffer[0..<firstRead])

        let headerStr = String(data: accumulated, encoding: .utf8) ?? ""

        // Check Content-Length for POST bodies
        if let clRange = headerStr.range(of: "Content-Length: (\\d+)", options: .regularExpression) {
            let clStr = headerStr[clRange].components(separatedBy: " ").last ?? "0"
            let contentLength = Int(clStr) ?? 0

            // Find header/body boundary
            if let bodyStart = headerStr.range(of: "\r\n\r\n") {
                let headerSize = headerStr.distance(from: headerStr.startIndex, to: bodyStart.upperBound)
                let bodyReadSoFar = accumulated.count - headerSize
                var remaining = contentLength - bodyReadSoFar

                while remaining > 0 {
                    let toRead = min(remaining, buffer.count)
                    let n = read(client, &buffer, toRead)
                    if n <= 0 { break }
                    accumulated.append(contentsOf: buffer[0..<n])
                    remaining -= n
                }
            }
        }

        return String(data: accumulated, encoding: .utf8) ?? ""
    }

    func handleClient(_ client: Int32) {
        let request = readFullRequest(client)
        guard !request.isEmpty else { close(client); return }

        let lines = request.components(separatedBy: "\r\n")
        guard let requestLine = lines.first else { close(client); return }
        let parts = requestLine.components(separatedBy: " ")
        guard parts.count >= 2 else { close(client); return }

        let method = parts[0]
        let path = parts[1]

        var body = ""
        if let bodyStart = request.range(of: "\r\n\r\n") {
            body = String(request[bodyStart.upperBound...])
        }

        switch (method, path) {
        case ("GET", "/"):
            serveHTML(client)
        case ("GET", "/api/status"):
            serveStatus(client)
        case ("GET", "/api/queue"):
            serveQueue(client)
        case ("POST", "/api/queue"):
            addToQueue(client, body: body)
        case ("POST", "/api/queue/delete"):
            deleteFromQueue(client, body: body)
        case ("POST", "/api/queue/move"):
            moveInQueue(client, body: body)
        case ("POST", "/api/queue/clear"):
            clearQueue(client)
        case ("POST", "/api/settings"):
            updateSettings(client, body: body)
        default:
            send404(client)
        }
    }

    func serveHTML(_ client: Int32) {
        if let html = try? String(contentsOfFile: htmlPath, encoding: .utf8) {
            let data = Array(html.utf8)
            let header = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: \(data.count)\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n"
            write(client, header, header.utf8.count)
            data.withUnsafeBufferPointer { buf in
                _ = write(client, buf.baseAddress!, data.count)
            }
        } else {
            send404(client)
        }
        close(client)
    }

    func serveStatus(_ client: Int32) {
        let canResume = !ghostWriter.lastStoppedText.isEmpty && ghostWriter.lastStoppedIndex > 0
        let sessionLive = ghostWriter.isTyping || ghostWriter.isPendingTypeSession
        let status: [String: Any] = [
            "typing": sessionLive,
            "paused": ghostWriter.isPaused && sessionLive,
            "charsTyped": ghostWriter.totalCharsTyped,
            "totalChars": ghostWriter.totalCharsInCurrent,
            "typos": ghostWriter.totalTypos,
            "current": ghostWriter.currentQueueItem,
            "canResume": canResume
        ]
        sendJSON(client, status)
    }

    func serveQueue(_ client: Int32) {
        if let queue = queueManager.load() {
            let encoder = JSONEncoder()
            encoder.outputFormatting = .prettyPrinted
            if let data = try? encoder.encode(queue), let json = String(data: data, encoding: .utf8) {
                let response = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n\(json)"
                write(client, response, response.utf8.count)
                close(client)
                return
            }
        }
        sendJSON(client, ["items": [], "settings": ["typoRate": 4, "baseWPM": 110, "thinkingPauses": true]])
    }

    func addToQueue(_ client: Int32, body: String) {
        guard let data = body.data(using: .utf8),
              let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
              let text = json["text"] as? String else {
            sendJSON(client, ["error": "Invalid request"])
            return
        }

        let label = (json["label"] as? String).flatMap({ $0.isEmpty ? nil : $0 }) ?? String(text.prefix(40))
        let item = QueueItem(
            id: UUID().uuidString.lowercased().prefix(8).description,
            text: text,
            label: label,
            createdAt: ISO8601DateFormatter().string(from: Date())
        )

        var queue = queueManager.load() ?? QueueData(items: [], settings: QueueData.Settings(typoRate: 4, baseWPM: 110, thinkingPauses: true))
        queue.items.append(item)
        queueManager.save(queue)
        sendJSON(client, ["ok": true, "id": item.id])
    }

    func deleteFromQueue(_ client: Int32, body: String) {
        guard let data = body.data(using: .utf8),
              let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
              let id = json["id"] as? String else {
            sendJSON(client, ["error": "Invalid request"])
            return
        }
        var queue = queueManager.load() ?? QueueData(items: [], settings: QueueData.Settings(typoRate: 4, baseWPM: 110, thinkingPauses: true))
        queue.items.removeAll { $0.id == id }
        queueManager.save(queue)
        sendJSON(client, ["ok": true])
    }

    func moveInQueue(_ client: Int32, body: String) {
        guard let data = body.data(using: .utf8),
              let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
              let id = json["id"] as? String,
              let direction = json["direction"] as? String else {
            sendJSON(client, ["error": "Invalid request"])
            return
        }
        queueManager.moveItem(id: id, direction: direction)
        sendJSON(client, ["ok": true])
    }

    func clearQueue(_ client: Int32) {
        var queue = queueManager.load() ?? QueueData(items: [], settings: QueueData.Settings(typoRate: 4, baseWPM: 110, thinkingPauses: true))
        queue.items.removeAll()
        queueManager.save(queue)
        sendJSON(client, ["ok": true])
    }

    func updateSettings(_ client: Int32, body: String) {
        guard let data = body.data(using: .utf8),
              let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
            sendJSON(client, ["error": "Invalid request"])
            return
        }

        var queue = queueManager.load() ?? QueueData(items: [], settings: QueueData.Settings(typoRate: 4, baseWPM: 110, thinkingPauses: true))

        if let rate = json["typoRate"] as? Double { queue.settings.typoRate = rate }
        if let wpm = json["baseWPM"] as? Int { queue.settings.baseWPM = wpm }
        if let pauses = json["thinkingPauses"] as? Bool { queue.settings.thinkingPauses = pauses }

        queueManager.save(queue)

        // Apply live
        ghostWriter.typoRate = queue.settings.typoRate / 100.0
        ghostWriter.thinkingPausesEnabled = queue.settings.thinkingPauses
        let wpm = max(20, min(200, queue.settings.baseWPM))
        ghostWriter.baseDelay = UInt32(60_000_000 / (wpm * 5))
        ghostWriter.delayVariance = ghostWriter.baseDelay / 2

        sendJSON(client, ["ok": true])
    }

    func sendJSON(_ client: Int32, _ obj: Any) {
        let data = (try? JSONSerialization.data(withJSONObject: obj, options: [])) ?? Data()
        let json = String(data: data, encoding: .utf8) ?? "{}"
        let response = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n\(json)"
        write(client, response, response.utf8.count)
        close(client)
    }

    func send404(_ client: Int32) {
        let response = "HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\nNot Found"
        write(client, response, response.utf8.count)
        close(client)
    }
}

// MARK: - Hotkey Listener (safe keys: Ctrl+Shift+G / Ctrl+Shift+Space / Esc)

class HotkeyListener {
    let ghostWriter: GhostWriter
    let queueManager: QueueManager

    init(ghostWriter: GhostWriter, queueManager: QueueManager) {
        self.ghostWriter = ghostWriter
        self.queueManager = queueManager
    }

    func start() {
        let mask: CGEventMask = (1 << CGEventType.keyDown.rawValue) | (1 << CGEventType.flagsChanged.rawValue)

        guard let tap = CGEvent.tapCreate(
            tap: .cgSessionEventTap,
            place: .headInsertEventTap,
            options: .defaultTap,
            eventsOfInterest: mask,
            callback: { (proxy, type, event, refcon) -> Unmanaged<CGEvent>? in
                guard let refcon = refcon else { return Unmanaged.passRetained(event) }
                let listener = Unmanaged<HotkeyListener>.fromOpaque(refcon).takeUnretainedValue()
                return listener.handleEvent(proxy: proxy, type: type, event: event)
            },
            userInfo: Unmanaged.passUnretained(self).toOpaque()
        ) else {
            print("[GhostWriter] ERROR: Cannot create event tap.")
            print("[GhostWriter] Grant Accessibility permission in System Settings > Privacy & Security > Accessibility")
            // Don't exit — HTTP server still works for queuing
            return
        }

        let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, tap, 0)
        CFRunLoopAddSource(CFRunLoopGetMain(), runLoopSource, .commonModes)
        CGEvent.tapEnable(tap: tap, enable: true)

        print("[GhostWriter] Hotkeys active:")
        print("  Ctrl/Cmd+Shift+G     = Type next in queue (or clipboard)")
        print("  Ctrl/Cmd+Shift+Space = Pause / Resume")
        print("  Escape               = Stop (remaining text saved for resume)")
        print("  Ctrl/Cmd+Shift+R     = Resume from where you stopped")
    }

    func handleEvent(proxy: CGEventTapProxy, type: CGEventType, event: CGEvent) -> Unmanaged<CGEvent>? {
        let flags = event.flags

        // Track modifier state — freeze typing the instant Ctrl or Shift goes down
        if type == .flagsChanged {
            let anyModifier = flags.contains(.maskControl) || flags.contains(.maskCommand)
            if ghostWriter.isTyping {
                ghostWriter.modifierHeld = anyModifier
            }
            return Unmanaged.passRetained(event)
        }

        let keycode = event.getIntegerValueField(.keyboardEventKeycode)

        let hasCtrl = flags.contains(.maskControl)
        let hasCmd = flags.contains(.maskCommand)
        let hasShift = flags.contains(.maskShift)
        let hasOption = flags.contains(.maskAlternate)
        let primaryMod = (hasCtrl || hasCmd) && hasShift && !hasOption

        if primaryMod {
            // Ctrl+Shift+G or Cmd+Shift+G (keycode 5 = G)
            if keycode == 5 {
                handleTypeHotkey()
                return nil
            }
            // Ctrl+Shift+Space or Cmd+Shift+Space (keycode 49 = Space)
            if keycode == 49 {
                handlePauseHotkey()
                return nil
            }
            // Ctrl+Shift+R or Cmd+Shift+R (keycode 15 = R) — Resume
            if keycode == 15 {
                handleResumeHotkey()
                return nil
            }
        }

        // Escape (keycode 53) — only when typing
        if keycode == 53 && ghostWriter.isTyping {
            handleStopHotkey()
            return nil
        }

        return Unmanaged.passRetained(event)
    }

    func applySettings() {
        let settings = queueManager.getSettings()
        ghostWriter.typoRate = settings.typoRate / 100.0
        ghostWriter.thinkingPausesEnabled = settings.thinkingPauses
        let wpm = max(20, min(200, settings.baseWPM))
        ghostWriter.baseDelay = UInt32(60_000_000 / (wpm * 5))
        ghostWriter.delayVariance = ghostWriter.baseDelay / 2
    }

    func handleTypeHotkey() {
        guard !ghostWriter.isTyping && !ghostWriter.isPendingTypeSession else { return }
        applySettings()

        guard let item = queueManager.popFirst() else {
            // Nothing in queue — try clipboard
            if let clipboard = NSPasteboard.general.string(forType: .string), !clipboard.isEmpty {
                print("[GhostWriter] Typing clipboard (\(clipboard.count) chars)")
                saveStatus(typing: true, progress: "clipboard", total: clipboard.count)
                ghostWriter.isPendingTypeSession = true
                DispatchQueue.global(qos: .userInitiated).async { [weak self] in
                    usleep(400_000)
                    self?.ghostWriter.typeText(clipboard)
                    print("[GhostWriter] Done (clipboard)")
                    saveStatus()
                }
            } else {
                print("[GhostWriter] Nothing to type.")
            }
            return
        }

        let resumeFrom = item.resumeFrom ?? 0
        print("[GhostWriter] Typing: \(item.label) (\(item.text.count) chars\(resumeFrom > 0 ? ", resuming from \(resumeFrom)" : ""))")
        saveStatus(typing: true, progress: item.label, total: item.text.count)
        ghostWriter.isPendingTypeSession = true

        DispatchQueue.global(qos: .userInitiated).async { [weak self] in
            usleep(400_000)
            self?.ghostWriter.typeText(item.text, startFrom: resumeFrom)
            print("[GhostWriter] Done: \(item.label)")
            saveStatus()
        }
    }

    func handleResumeHotkey() {
        guard !ghostWriter.isTyping && !ghostWriter.isPendingTypeSession else { return }
        guard !ghostWriter.lastStoppedText.isEmpty && ghostWriter.lastStoppedIndex > 0 else {
            print("[GhostWriter] Nothing to resume.")
            return
        }

        applySettings()

        let text = ghostWriter.lastStoppedText
        let from = ghostWriter.lastStoppedIndex
        print("[GhostWriter] Resuming from char \(from)/\(text.count)")
        saveStatus(typing: true, progress: "resuming...", chars: from, total: text.count)

        ghostWriter.lastStoppedText = ""
        ghostWriter.lastStoppedIndex = 0
        ghostWriter.isPendingTypeSession = true

        DispatchQueue.global(qos: .userInitiated).async { [weak self] in
            usleep(400_000)
            self?.ghostWriter.typeText(text, startFrom: from)
            print("[GhostWriter] Done (resumed)")
            saveStatus()
        }
    }

    func handlePauseHotkey() {
        // Only while real typing loop is active (not during pre-type arm delay — isPaused would be cleared in typeText).
        guard ghostWriter.isTyping else { return }
        ghostWriter.isPaused.toggle()
        let state = ghostWriter.isPaused ? "PAUSED" : "RESUMED"
        print("[GhostWriter] \(state)")
        saveStatus(typing: true, paused: ghostWriter.isPaused, progress: ghostWriter.isPaused ? "paused" : "typing",
                   chars: ghostWriter.totalCharsTyped, total: ghostWriter.totalCharsInCurrent)
    }

    func handleStopHotkey() {
        ghostWriter.shouldStop = true
        ghostWriter.isPaused = false
        print("[GhostWriter] STOPPED at char \(ghostWriter.totalCharsTyped)")
        let hasResume = !ghostWriter.lastStoppedText.isEmpty || ghostWriter.totalCharsTyped > 0
        saveStatus(canResume: hasResume)
    }
}

// MARK: - Main

let ghostWriter = GhostWriter()
let queueManager = QueueManager()

let execPath = CommandLine.arguments[0]
let execDir = (execPath as NSString).deletingLastPathComponent
var htmlPath = execDir + "/ghostwriter.html"
if !FileManager.default.fileExists(atPath: htmlPath) {
    htmlPath = FileManager.default.currentDirectoryPath + "/ghostwriter.html"
}

let server = MiniHTTPServer(port: 9753, ghostWriter: ghostWriter, queueManager: queueManager, htmlPath: htmlPath)
server.start()

let hotkeyListener = HotkeyListener(ghostWriter: ghostWriter, queueManager: queueManager)
hotkeyListener.start()

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
    let process = Process()
    process.executableURL = URL(fileURLWithPath: "/usr/bin/open")
    process.arguments = ["http://127.0.0.1:9753"]
    try? process.run()
}

print("[GhostWriter] Running. UI at http://127.0.0.1:9753")
print("[GhostWriter] Ctrl+C to quit")
RunLoop.main.run()
