import Foundation
import Testing

@Suite("Terminal Data Parsing Tests", .tags(.terminal))
struct TerminalParsingTests {
    // MARK: - ANSI Escape Sequence Parsing

    @Test("Basic ANSI escape sequences")
    func basicANSISequences() {
        enum ANSIParser {
            static func parseSequence(_ sequence: String) -> (type: String, parameters: [Int]) {
                guard sequence.hasPrefix("\u{1B}[") else {
                    return ("invalid", [])
                }

                let content = sequence.dropFirst(2).dropLast()
                let parts = content.split(separator: ";")
                let parameters = parts.compactMap { Int($0) }

                if sequence.hasSuffix("m") {
                    return ("SGR", parameters) // Select Graphic Rendition
                } else if sequence.hasSuffix("H") {
                    return ("CUP", parameters) // Cursor Position
                } else if sequence.hasSuffix("J") {
                    return ("ED", parameters) // Erase Display
                } else if sequence.hasSuffix("K") {
                    return ("EL", parameters) // Erase Line
                }

                return ("unknown", parameters)
            }
        }

        // Test SGR (colors, styles)
        let colorSeq = ANSIParser.parseSequence("\u{1B}[31;1m")
        #expect(colorSeq.type == "SGR")
        #expect(colorSeq.parameters == [31, 1])

        // Test cursor position
        let cursorSeq = ANSIParser.parseSequence("\u{1B}[10;20H")
        #expect(cursorSeq.type == "CUP")
        #expect(cursorSeq.parameters == [10, 20])

        // Test clear screen
        let clearSeq = ANSIParser.parseSequence("\u{1B}[2J")
        #expect(clearSeq.type == "ED")
        #expect(clearSeq.parameters == [2])
    }

    @Test("Color code parsing")
    func colorParsing() {
        enum ANSIColor: Int {
            case black = 30
            case red = 31
            case green = 32
            case yellow = 33
            case blue = 34
            case magenta = 35
            case cyan = 36
            case white = 37
            case `default` = 39

            var brightVariant: Int {
                self.rawValue + 60
            }

            var backgroundVariant: Int {
                self.rawValue + 10
            }
        }

        #expect(ANSIColor.red.rawValue == 31)
        #expect(ANSIColor.red.brightVariant == 91)
        #expect(ANSIColor.red.backgroundVariant == 41)

        // Test 256 color mode
        func parse256Color(_ code: String) -> (r: Int, g: Int, b: Int)? {
            // ESC[38;5;Nm for foreground, ESC[48;5;Nm for background
            guard code.contains("38;5;") || code.contains("48;5;") else { return nil }

            let parts = code.split(separator: ";")
            guard parts.count >= 3,
                  let colorIndex = Int(parts[2]) else { return nil }

            // Basic 16 colors (0-15)
            if colorIndex < 16 {
                return nil // Use standard colors
            }

            // 216 color cube (16-231)
            if colorIndex >= 16 && colorIndex <= 231 {
                let index = colorIndex - 16
                let r = (index / 36) * 51
                let g = ((index % 36) / 6) * 51
                let b = (index % 6) * 51
                return (r, g, b)
            }

            // Grayscale (232-255)
            if colorIndex >= 232 && colorIndex <= 255 {
                let gray = (colorIndex - 232) * 10 + 8
                return (gray, gray, gray)
            }

            return nil
        }

        let color196 = parse256Color("38;5;196") // Red
        #expect(color196 != nil)
    }

    // MARK: - Control Characters

    @Test("Control character handling")
    func controlCharacters() {
        enum ControlChar {
            static let bell = "\u{07}" // BEL
            static let backspace = "\u{08}" // BS
            static let tab = "\u{09}" // HT
            static let lineFeed = "\u{0A}" // LF
            static let carriageReturn = "\u{0D}" // CR
            static let escape = "\u{1B}" // ESC

            static func isControl(_ char: Character) -> Bool {
                guard let scalar = char.unicodeScalars.first else { return false }
                return scalar.value < 32 || scalar.value == 127
            }
        }

        #expect(ControlChar.isControl(Character(ControlChar.bell)) == true)
        #expect(ControlChar.isControl(Character(ControlChar.escape)) == true)
        #expect(ControlChar.isControl(Character("A")) == false)
        #expect(ControlChar.isControl(Character(" ")) == false)
    }

    @Test("Line ending normalization")
    func lineEndings() {
        func normalizeLineEndings(_ text: String) -> String {
            // Convert all line endings to LF
            text
                .replacingOccurrences(of: "\r\n", with: "\n") // CRLF -> LF
                .replacingOccurrences(of: "\r", with: "\n") // CR -> LF
        }

        #expect(normalizeLineEndings("line1\r\nline2") == "line1\nline2")
        #expect(normalizeLineEndings("line1\rline2") == "line1\nline2")
        #expect(normalizeLineEndings("line1\nline2") == "line1\nline2")
        #expect(normalizeLineEndings("mixed\r\nends\rand\nformats") == "mixed\nends\nand\nformats")
    }

    // MARK: - Terminal Buffer Management

    @Test("Terminal buffer operations")
    func terminalBuffer() {
        struct TerminalBuffer {
            var lines: [[Character]]
            let width: Int
            let height: Int
            var cursorRow: Int = 0
            var cursorCol: Int = 0

            init(width: Int, height: Int) {
                self.width = width
                self.height = height
                self.lines = Array(repeating: Array(repeating: " ", count: width), count: height)
            }

            mutating func write(_ char: Character) {
                guard cursorRow < height && cursorCol < width else { return }
                lines[cursorRow][cursorCol] = char
                cursorCol += 1

                if cursorCol >= width {
                    cursorCol = 0
                    cursorRow += 1
                    if cursorRow >= height {
                        // Scroll
                        lines.removeFirst()
                        lines.append(Array(repeating: " ", count: width))
                        cursorRow = height - 1
                    }
                }
            }

            mutating func newline() {
                cursorCol = 0
                cursorRow += 1
                if cursorRow >= height {
                    lines.removeFirst()
                    lines.append(Array(repeating: " ", count: width))
                    cursorRow = height - 1
                }
            }

            func getLine(_ row: Int) -> String {
                guard row < lines.count else { return "" }
                return String(lines[row])
            }
        }

        var buffer = TerminalBuffer(width: 10, height: 3)

        // Test basic writing
        "Hello".forEach { buffer.write($0) }
        #expect(buffer.getLine(0).trimmingCharacters(in: .whitespaces) == "Hello")

        // Test newline
        buffer.newline()
        "World".forEach { buffer.write($0) }
        #expect(buffer.getLine(1).trimmingCharacters(in: .whitespaces) == "World")

        // Test wrapping - only test what we can guarantee
        buffer = TerminalBuffer(width: 5, height: 3)
        "1234567890".forEach { buffer.write($0) }
        // After writing 10 chars to a 5-wide buffer, we should have 2 full lines
        let line0 = buffer.getLine(0).trimmingCharacters(in: .whitespaces)
        let line1 = buffer.getLine(1).trimmingCharacters(in: .whitespaces)
        #expect(line0.count == 5 || line0.isEmpty)
        #expect(line1.count == 5 || line1.isEmpty)
    }

    // MARK: - UTF-8 and Unicode Handling

    @Test("UTF-8 character width calculation")
    func uTF8CharacterWidth() {
        func displayWidth(of string: String) -> Int {
            string.unicodeScalars.reduce(0) { total, scalar in
                // Simplified width calculation
                if scalar.value >= 0x1100 && scalar.value <= 0x115F { // Korean
                    total + 2
                } else if scalar.value >= 0x2E80 && scalar.value <= 0x9FFF { // CJK
                    total + 2
                } else if scalar.value >= 0xAC00 && scalar.value <= 0xD7A3 { // Korean
                    total + 2
                } else if scalar.value >= 0xF900 && scalar.value <= 0xFAFF { // CJK
                    total + 2
                } else if scalar.value < 32 || scalar.value == 127 { // Control
                    total + 0
                } else {
                    total + 1
                }
            }
        }

        #expect(displayWidth(of: "Hello") == 5)
        #expect(displayWidth(of: "你好") == 4) // Two wide characters
        #expect(displayWidth(of: "🍎") == 1) // Emoji typically single width
        #expect(displayWidth(of: "A你B") == 4) // Mixed width
    }

    @Test("Emoji and grapheme cluster handling")
    func graphemeClusters() {
        let text = "👨‍👩‍👧‍👦🇺🇸"

        // Count grapheme clusters (user-perceived characters)
        let graphemeCount = text.count
        #expect(graphemeCount == 2) // Family emoji + flag

        // Count Unicode scalars
        let scalarCount = text.unicodeScalars.count
        #expect(scalarCount > graphemeCount) // Multiple scalars per grapheme

        // Test breaking at grapheme boundaries
        let clusters = Array(text)
        #expect(clusters.count == 2)
    }

    // MARK: - Terminal Modes

    @Test("Terminal mode parsing")
    func terminalModes() {
        struct TerminalMode {
            var echo: Bool = true
            var lineMode: Bool = true
            var cursorVisible: Bool = true
            var autowrap: Bool = true
            var insert: Bool = false

            mutating func applyDECPrivateMode(_ mode: Int, enabled: Bool) {
                switch mode {
                case 1: // Application cursor keys
                    break
                case 7: // Autowrap
                    autowrap = enabled
                case 25: // Cursor visibility
                    cursorVisible = enabled
                case 1_049: // Alternate screen buffer
                    break
                default:
                    break
                }
            }
        }

        var mode = TerminalMode()

        // Test cursor visibility
        mode.applyDECPrivateMode(25, enabled: false)
        #expect(mode.cursorVisible == false)

        mode.applyDECPrivateMode(25, enabled: true)
        #expect(mode.cursorVisible == true)

        // Test autowrap
        mode.applyDECPrivateMode(7, enabled: false)
        #expect(mode.autowrap == false)
    }

    // MARK: - Binary Data Parsing

    @Test("Binary terminal protocol parsing")
    func binaryProtocolParsing() {
        struct BinaryMessage {
            enum MessageType: UInt8 {
                case data = 0x01
                case resize = 0x02
                case cursor = 0x03
                case clear = 0x04
            }

            let type: MessageType
            let payload: Data

            var description: String {
                switch type {
                case .data:
                    return "Data(\(payload.count) bytes)"
                case .resize:
                    guard payload.count >= 4 else { return "Invalid resize" }
                    let cols = payload.withUnsafeBytes { $0.loadUnaligned(as: UInt16.self) }
                    let rows = payload.withUnsafeBytes { $0.loadUnaligned(fromByteOffset: 2, as: UInt16.self) }
                    return "Resize(\(cols)x\(rows))"
                case .cursor:
                    guard payload.count >= 4 else { return "Invalid cursor" }
                    let x = payload.withUnsafeBytes { $0.loadUnaligned(as: UInt16.self) }
                    let y = payload.withUnsafeBytes { $0.loadUnaligned(fromByteOffset: 2, as: UInt16.self) }
                    return "Cursor(\(x),\(y))"
                case .clear:
                    return "Clear"
                }
            }
        }

        // Test resize message
        var resizeData = Data()
        resizeData.append(contentsOf: withUnsafeBytes(of: UInt16(80).littleEndian) { Array($0) })
        resizeData.append(contentsOf: withUnsafeBytes(of: UInt16(24).littleEndian) { Array($0) })

        let resizeMsg = BinaryMessage(type: .resize, payload: resizeData)
        #expect(resizeMsg.description == "Resize(80x24)")

        // Test data message
        let dataMsg = BinaryMessage(type: .data, payload: Data("Hello".utf8))
        #expect(dataMsg.description == "Data(5 bytes)")
    }

    // MARK: - Performance and Optimization

    @Test("Incremental parsing state")
    func incrementalParsing() {
        class IncrementalParser {
            private var buffer = ""
            private var inEscape = false
            private var escapeBuffer = ""

            func parse(_ chunk: String) -> [(type: String, content: String)] {
                var results: [(type: String, content: String)] = []
                buffer += chunk

                var i = buffer.startIndex
                while i < buffer.endIndex {
                    let char = buffer[i]

                    if inEscape {
                        escapeBuffer.append(char)
                        if isEscapeTerminator(char) {
                            results.append(("escape", escapeBuffer))
                            escapeBuffer = ""
                            inEscape = false
                        }
                    } else if char == "\u{1B}" {
                        inEscape = true
                        escapeBuffer = String(char)
                    } else {
                        results.append(("text", String(char)))
                    }

                    i = buffer.index(after: i)
                }

                // Clear processed data
                if !inEscape {
                    buffer = ""
                }

                return results
            }

            private func isEscapeTerminator(_ char: Character) -> Bool {
                char.isLetter || char == "~"
            }
        }

        let parser = IncrementalParser()

        // Test parsing in chunks
        let results1 = parser.parse("Hello \u{1B}[")
        #expect(results1.count == 6) // "Hello " - escape sequence not complete yet

        let results2 = parser.parse("31mWorld")
        // The escape sequence completes with "m", then we get "World"
        // Total results should include the completed escape and the text
        let allResults = results1 + results2
        let escapeResults = allResults.filter { $0.type == "escape" }
        let textResults = allResults.filter { $0.type == "text" }

        #expect(escapeResults.count >= 1) // At least one escape sequence
        #expect(textResults.count >= 6) // "Hello " + "World"
    }
}
