import Foundation

public enum TerminalEvent: Equatable {
    case text(String)
    case key(String)
    case mouse(button: Int, column: Int, row: Int, released: Bool)
    case quit
}

public struct TerminalInput {
    private var bytes: [UInt8] = []
    public init() {}
    public mutating func feed(_ input: [UInt8], flushEscape: Bool = false) -> [TerminalEvent] {
        bytes += input
        var events: [TerminalEvent] = []
        while !bytes.isEmpty {
            if bytes[0] == 29 { bytes.removeFirst(); events.append(.quit); continue }
            if bytes[0] == 27 {
                if bytes.count == 1 {
                    if flushEscape { bytes.removeFirst(); events.append(.key("escape")) }
                    break
                }
                if bytes[1] == 91 {
                    guard let end = bytes.indices.dropFirst(2).first(where: { (64...126).contains(bytes[$0]) }) else {
                        if bytes.count > 128 { bytes.removeAll() }
                        break
                    }
                    let sequence = String(decoding: bytes[0...end], as: UTF8.self)
                    bytes.removeFirst(end + 1)
                    let keys = ["\u{1b}[A":"up", "\u{1b}[B":"down", "\u{1b}[C":"right", "\u{1b}[D":"left", "\u{1b}[H":"home", "\u{1b}[F":"end", "\u{1b}[3~":"delete", "\u{1b}[5~":"pageup", "\u{1b}[6~":"pagedown", "\u{1b}[Z":"shift+tab"]
                    if let key = keys[sequence] { events.append(.key(key)) }
                    else if sequence.hasPrefix("\u{1b}[<"), sequence.hasSuffix("M") || sequence.hasSuffix("m") {
                        let parts = sequence.dropFirst(3).dropLast().split(separator: ";").compactMap { Int($0) }
                        if parts.count == 3, parts[1] > 0, parts[2] > 0 {
                            events.append(.mouse(button: parts[0], column: parts[1], row: parts[2], released: sequence.hasSuffix("m")))
                        }
                    }
                    continue
                }
                bytes.removeFirst(); events.append(.key("escape")); continue
            }
            let first = bytes[0]
            if first < 32 || first == 127 {
                bytes.removeFirst()
                if first == 13 || first == 10 { events.append(.key("return")) }
                else if first == 9 { events.append(.key("tab")) }
                else if first == 127 || first == 8 { events.append(.key("backspace")) }
                else if (1...26).contains(first) { events.append(.key("ctrl+" + String(UnicodeScalar(Int(first) + 96)!))) }
                continue
            }
            let length = first < 128 ? 1 : first >= 0xC2 && first <= 0xDF ? 2 : first <= 0xEF && first >= 0xE0 ? 3 : first >= 0xF0 && first <= 0xF4 ? 4 : 0
            guard length > 0 else { bytes.removeFirst(); continue }
            guard bytes.count >= length else { break }
            let fragment = Array(bytes.prefix(length))
            if let text = String(bytes: fragment, encoding: .utf8) { events.append(.text(text)); bytes.removeFirst(length) }
            else { bytes.removeFirst() }
        }
        return events
    }
}