import Foundation
import Network
import os

private let lnlLog = Logger(subsystem: "dev.smoll.KDChat", category: "LiteNetLib")

// MARK: - Constants

private enum LNL {
    static let protocolId: Int32 = 13
    static let maxSequence: UInt16 = 32768
    static let halfMaxSequence: Int = 16384
    static let windowSize: Int = 64
    static let channeledHeaderSize: Int = 4
    static let reliableOrderedChannelId: UInt8 = 2 // channel 0, ReliableOrdered (0*4+2)
    static let pingIntervalMs: Int = 1000
    static let disconnectTimeoutMs: Int = 5000
    static let resendBaseMs: Double = 25
    static let resendRttMultiplier: Double = 2.1
    static let updateIntervalMs: Int = 50

    // .NET epoch offset: ticks between 0001-01-01 and 1970-01-01
    static let dotNetEpochOffset: Double = 62_135_596_800

    static var dotNetTicks: Int64 {
        Int64((Date.now.timeIntervalSince1970 + dotNetEpochOffset) * 10_000_000)
    }
}

// MARK: - Packet Property

private enum PacketProperty: UInt8 {
    case unreliable = 0
    case channeled = 1
    case ack = 2
    case ping = 3
    case pong = 4
    case connectRequest = 5
    case connectAccept = 6
    case disconnect = 7
    case unconnectedMessage = 8
    case mtuCheck = 9
    case mtuOk = 10
    case broadcast = 11
    case merged = 12
    case shutdownOk = 13
    case peerNotFound = 14
    case invalidProtocol = 15
    case natMessage = 16
    case empty = 17
}

// MARK: - Connection State

private enum ConnectionState {
    case disconnected
    case connecting
    case connected
    case shutdownRequested
}

// MARK: - Pending Packet

private struct PendingPacket {
    var data: Data
    var timestamp: UInt64
    var isSent: Bool
}

// MARK: - Fragment Reassembly

private struct IncomingFragment {
    let totalParts: Int
    let channelId: UInt8
    var parts: [Data?]
    var receivedCount: Int

    init(totalParts: Int, channelId: UInt8) {
        self.totalParts = totalParts
        self.channelId = channelId
        self.parts = Array(repeating: nil, count: totalParts)
        self.receivedCount = 0
    }
}

// MARK: - Client Event

extension LNLClient {
    enum Event: Sendable {
        case connected
        case disconnected(String?)
        case received(Data)
    }
}

// MARK: - LNLClient

/// Minimal LiteNetLib client supporting ReliableOrdered delivery over UDP.
actor LNLClient {
    // NWConnection requires a DispatchQueue for its callback-based API
    private let nwQueue = DispatchQueue(label: "com.kdchat.litenetlib.nw")
    private var connection: NWConnection?
    private var updateTask: Task<Void, Never>?

    // Event stream for consumers
    let events: AsyncStream<Event>
    private let eventContinuation: AsyncStream<Event>.Continuation

    // Connection state
    private var state: ConnectionState = .disconnected
    private var connectionTime: Int64 = 0
    private var connectionNumber: UInt8 = 0
    private var remotePeerId: Int32 = 0
    private var localPeerId: Int32 = Int32.random(in: 1...Int32.max)
    private var connectKey: String = ""
    private var connectAttempts: Int = 0
    private let maxConnectAttempts: Int = 10
    private var lastConnectSendTime: UInt64 = 0

    // Reliable ordered channel - send side
    private var localSequence: UInt16 = 0
    private var localWindowStart: UInt16 = 0
    private var pendingPackets: [PendingPacket?]
    private var outgoingQueue: [Data] = []

    // Reliable ordered channel - receive side
    private var remoteSequence: UInt16 = 0
    private var remoteWindowStart: UInt16 = 0
    private var receivedPackets: [Data?]
    private var ackBitfield: [UInt8]
    private var mustSendAcks: Bool = false

    // Fragment reassembly
    private var incomingFragments: [UInt16: IncomingFragment] = [:]

    // Ping / keepalive
    private var pingSequence: UInt16 = 0
    private var lastPingSentTime: UInt64 = 0
    private var lastPacketReceivedTime: UInt64 = 0
    private var avgRtt: Double = 50
    private var rttSum: Double = 0
    private var rttCount: Int = 0
    private var pingSentTimestamp: UInt64 = 0

    // Resend delay (computed from RTT)
    private var resendDelay: Double { LNL.resendBaseMs + avgRtt * LNL.resendRttMultiplier }

    // Send stats
    private var totalBytesSent: Int = 0
    private var totalPacketsSent: Int = 0

    init() {
        let ws = LNL.windowSize
        self.pendingPackets = Array(repeating: nil, count: ws)
        self.receivedPackets = Array(repeating: nil, count: ws)
        let ackSize = (ws - 1) / 8 + 2
        self.ackBitfield = Array(repeating: 0, count: ackSize)

        let (stream, continuation) = AsyncStream.makeStream(of: Event.self, bufferingPolicy: .unbounded)
        self.events = stream
        self.eventContinuation = continuation
    }

    deinit {
        eventContinuation.finish()
    }

    // MARK: - Public API

    func connect(host: String, port: UInt16, key: String) {
        guard state == .disconnected else {
            lnlLog.warning("connect() called but state is not disconnected")
            return
        }

        lnlLog.info("Connecting to \(host):\(port) with key '\(key)'")
        connectKey = key
        connectionTime = LNL.dotNetTicks
        connectAttempts = 0
        state = .connecting

        let nwHost = NWEndpoint.Host(host)
        guard let nwPort = NWEndpoint.Port(rawValue: port) else {
            lnlLog.error("Invalid port: \(port)")
            return
        }
        let conn = NWConnection(host: nwHost, port: nwPort, using: .udp)
        connection = conn

        conn.stateUpdateHandler = { [weak self] newState in
            Task { [weak self] in await self?.handleNWState(newState) }
        }

        conn.pathUpdateHandler = { path in
            lnlLog.info("Path status: \(String(describing: path.status)), isExpensive: \(path.isExpensive), isConstrained: \(path.isConstrained)")
        }

        conn.start(queue: nwQueue)
    }

    func send(data: Data) {
        guard state == .connected else { return }
        outgoingQueue.append(data)
    }

    func disconnect() {
        performDisconnect(reason: "Client disconnected")
    }

    // MARK: - NWConnection State

    private func handleNWState(_ newState: NWConnection.State) {
        lnlLog.info("NWConnection state: \(String(describing: newState))")
        switch newState {
        case .ready:
            if let path = connection?.currentPath {
                lnlLog.info("UDP socket ready. Local: \(String(describing: path.localEndpoint)), remote: \(String(describing: path.remoteEndpoint))")
                for iface in path.availableInterfaces {
                    lnlLog.info("Interface: \(iface.name) type: \(String(describing: iface.type))")
                }
            }
            startReceiving()
            startUpdateLoop()
            sendConnectRequest()
        case .failed(let error):
            lnlLog.error("NWConnection failed: \(error.localizedDescription)")
            performDisconnect(reason: "Connection failed: \(error.localizedDescription)")
        case .cancelled:
            lnlLog.info("NWConnection cancelled")
            performDisconnect(reason: nil)
        default:
            break
        }
    }

    // MARK: - Receive Loop

    private func startReceiving() {
        lnlLog.debug("Waiting for incoming UDP data...")
        connection?.receiveMessage { [weak self] data, _, _, error in
            Task { [weak self] in await self?.handleReceiveCallback(data: data, error: error) }
        }
    }

    private func handleReceiveCallback(data: Data?, error: NWError?) {
        if let error {
            lnlLog.error("Receive error: \(error.localizedDescription)")
        }
        if let data, !data.isEmpty {
            lnlLog.info("Received \(data.count) bytes: \(data.prefix(20).map { String(format: "%02x", $0) }.joined(separator: " "))\(data.count > 20 ? "..." : "")")
            lastPacketReceivedTime = currentMs()
            handleRawPacket(data)
        } else if data == nil && error == nil {
            lnlLog.warning("receiveMessage returned nil data with no error")
        }
        if error == nil && state != .disconnected {
            startReceiving()
        } else if state == .disconnected {
            lnlLog.info("Not re-registering receive (disconnected)")
        }
    }

    // MARK: - Update Loop

    private func startUpdateLoop() {
        updateTask = Task { [weak self] in
            while !Task.isCancelled {
                await self?.update()
                try? await Task.sleep(for: .milliseconds(LNL.updateIntervalMs))
            }
        }
    }

    private func update() {
        let now = currentMs()

        switch state {
        case .connecting:
            if now >= lastConnectSendTime, now - lastConnectSendTime > 500 {
                connectAttempts += 1
                lnlLog.info("ConnectRequest attempt \(self.connectAttempts)/\(self.maxConnectAttempts)")
                if connectAttempts > maxConnectAttempts {
                    lnlLog.error("Connection timed out after \(self.maxConnectAttempts) attempts")
                    performDisconnect(reason: "Connection timed out")
                    return
                }
                sendConnectRequest()
            }

        case .connected:
            if now >= lastPacketReceivedTime, now - lastPacketReceivedTime > UInt64(LNL.disconnectTimeoutMs) {
                performDisconnect(reason: "Connection timed out")
                return
            }
            flushOutgoingQueue()
            retransmitPending(now: now)
            if mustSendAcks {
                sendAck()
                mustSendAcks = false
            }
            if now >= lastPingSentTime, now - lastPingSentTime > UInt64(LNL.pingIntervalMs) {
                sendPing()
            }

        case .shutdownRequested, .disconnected:
            break
        }
    }

    // MARK: - Packet Handling

    private func handleRawPacket(_ data: Data) {
        guard !data.isEmpty else { return }
        let byte0 = data[data.startIndex]
        let property = byte0 & 0x1f
        let connNum = (byte0 >> 5) & 0x03

        guard let prop = PacketProperty(rawValue: property) else {
            lnlLog.debug("Unknown packet property: \(property)")
            return
        }
        lnlLog.debug("Received packet: \(String(describing: prop)), \(data.count) bytes")

        if state == .connected && prop != .connectAccept && prop != .connectRequest {
            if connNum != connectionNumber { return }
        }

        switch prop {
        case .connectAccept: handleConnectAccept(data)
        case .channeled:     handleChanneled(data)
        case .ack:           handleAck(data)
        case .ping:          handlePing(data)
        case .pong:          handlePong(data)
        case .disconnect:    handleServerDisconnect(data)
        case .merged:        handleMerged(data)
        case .shutdownOk:    performDisconnect(reason: nil)
        default:             break
        }
    }

    // MARK: - Connect Handshake

    private func sendConnectRequest() {
        lnlLog.debug("Sending ConnectRequest")
        var w = DataWriter()

        w.writeByte(PacketProperty.connectRequest.rawValue)
        w.writeInt32LE(LNL.protocolId)
        w.writeInt64LE(connectionTime)
        w.writeInt32LE(localPeerId)

        // Dummy IPv4 sockaddr (16 bytes, .NET SocketAddress format)
        w.writeByte(16)
        var addr = Data(count: 16)
        addr[0] = 2 // AF_INET
        w.writeData(addr)

        // Connection key: ushort16 LE (byteCount + 1) + UTF-8
        let keyBytes = Array(connectKey.utf8)
        w.writeUInt16LE(UInt16(keyBytes.count + 1))
        w.writeData(Data(keyBytes))

        lastConnectSendTime = currentMs()
        let packet = w.data
        lnlLog.info("ConnectRequest packet (\(packet.count) bytes): \(packet.map { String(format: "%02x", $0) }.joined(separator: " "))")
        sendRaw(packet)
    }

    private func handleConnectAccept(_ data: Data) {
        guard state == .connecting else {
            lnlLog.warning("ConnectAccept received but not in connecting state")
            return
        }
        guard data.count >= 15 else {
            lnlLog.error("ConnectAccept too short: \(data.count) bytes")
            return
        }

        var r = DataReader(data: data, offset: 1)
        guard let echoedTime = r.readInt64LE(),
              echoedTime == connectionTime else {
            lnlLog.error("ConnectAccept connectionTime mismatch")
            return
        }

        connectionNumber = r.readByte() ?? 0
        _ = r.readByte() // isReused
        remotePeerId = r.readInt32LE() ?? 0

        lnlLog.info("Connected! connNum=\(self.connectionNumber), remotePeerId=\(self.remotePeerId)")
        state = .connected
        lastPacketReceivedTime = currentMs()
        lastPingSentTime = currentMs()

        eventContinuation.yield(.connected)
    }

    // MARK: - Channeled (ReliableOrdered)

    private func handleChanneled(_ data: Data) {
        guard data.count >= LNL.channeledHeaderSize else { return }

        var r = DataReader(data: data, offset: 1)
        guard let seq = r.readUInt16LE() else { return }
        guard let channelId = r.readByte() else { return }
        guard channelId == LNL.reliableOrderedChannelId else { return }

        // Pass the full packet (including fragment header if present) through
        // the reliable ordered channel for sequencing and ACKs.
        // Delivery/reassembly happens in deliverOrderedPacket.
        let fullPayload = Data(data[data.startIndex ..< data.endIndex])
        processReliableOrdered(sequence: seq, data: fullPayload)
    }

    /// Called when a reliable ordered packet is delivered in-sequence.
    /// Handles fragment reassembly before yielding to the consumer.
    private func deliverOrderedPacket(_ data: Data) {
        guard data.count >= LNL.channeledHeaderSize else { return }
        let isFragmented = (data[data.startIndex] & 0x80) != 0

        if isFragmented {
            guard data.count >= 10 else { return }
            var r = DataReader(data: data, offset: 4) // skip channeled header
            guard let fragmentId = r.readUInt16LE() else { return }
            guard let fragmentPart = r.readUInt16LE() else { return }
            guard let fragmentsTotal = r.readUInt16LE() else { return }
            guard fragmentsTotal > 0, fragmentPart < fragmentsTotal else { return }

            let channelId = data[data.startIndex + 3]
            let fragmentData = Data(data[data.startIndex + 10 ..< data.endIndex])

            if incomingFragments[fragmentId] == nil {
                incomingFragments[fragmentId] = IncomingFragment(
                    totalParts: Int(fragmentsTotal), channelId: channelId
                )
            }
            guard var fragment = incomingFragments[fragmentId],
                  fragment.channelId == channelId,
                  Int(fragmentPart) < fragment.totalParts,
                  fragment.parts[Int(fragmentPart)] == nil else { return }

            fragment.parts[Int(fragmentPart)] = fragmentData
            fragment.receivedCount += 1
            incomingFragments[fragmentId] = fragment

            if fragment.receivedCount == fragment.totalParts {
                var assembled = Data()
                for part in fragment.parts {
                    if let part { assembled.append(part) }
                }
                incomingFragments.removeValue(forKey: fragmentId)
                lnlLog.info("Reassembled fragmented packet: \(fragment.totalParts) parts, \(assembled.count) bytes")
                let yieldResult = eventContinuation.yield(.received(assembled))
                lnlLog.info("Yield result for reassembled packet: \(String(describing: yieldResult))")
            }
        } else {
            let userData = Data(data[data.startIndex + LNL.channeledHeaderSize ..< data.endIndex])
            eventContinuation.yield(.received(userData))
        }
    }

    private func processReliableOrdered(sequence seq: UInt16, data: Data) {
        // Reject already-delivered sequences
        let relateToDelivered = relativeSequence(seq, to: remoteSequence)
        if relateToDelivered < 0 { return }

        let relate = relativeSequence(seq, to: remoteWindowStart)

        if relate >= LNL.windowSize {
            let diff = relate - LNL.windowSize + 1
            for _ in 0..<diff {
                let idx = Int(remoteWindowStart) % LNL.windowSize
                receivedPackets[idx] = nil
                let bitIndex = Int(remoteWindowStart) % LNL.windowSize
                ackBitfield[bitIndex / 8] &= ~(1 << (bitIndex % 8))
                remoteWindowStart = (remoteWindowStart &+ 1) % LNL.maxSequence
            }
        }

        let ackIdx = Int(seq) % LNL.windowSize
        ackBitfield[ackIdx / 8] |= (1 << (ackIdx % 8))
        mustSendAcks = true

        if seq == remoteSequence {
            deliverOrderedPacket(data)
            remoteSequence = (remoteSequence &+ 1) % LNL.maxSequence
            lnlLog.debug("Delivered seq \(seq), next expected: \(self.remoteSequence)")

            while true {
                let idx = Int(remoteSequence) % LNL.windowSize
                guard let buffered = receivedPackets[idx] else { break }
                receivedPackets[idx] = nil
                deliverOrderedPacket(buffered)
                remoteSequence = (remoteSequence &+ 1) % LNL.maxSequence
            }
        } else {
            let idx = Int(seq) % LNL.windowSize
            receivedPackets[idx] = data
            lnlLog.warning("Buffered out-of-order seq \(seq), expecting \(self.remoteSequence)")
        }
    }

    // MARK: - Send Reliable

    private func flushOutgoingQueue() {
        while !outgoingQueue.isEmpty {
            let windowUsed = relativeSequence(localSequence, to: localWindowStart)
            if windowUsed >= LNL.windowSize { break }

            let userData = outgoingQueue.removeFirst()
            let packet = buildChanneledPacket(sequence: localSequence, data: userData)

            let idx = Int(localSequence) % LNL.windowSize
            pendingPackets[idx] = PendingPacket(data: packet, timestamp: currentMs(), isSent: true)
            localSequence = (localSequence &+ 1) % LNL.maxSequence

            sendRaw(packet)
        }
    }

    private func retransmitPending(now: UInt64) {
        var seq = localWindowStart
        while seq != localSequence {
            let idx = Int(seq) % LNL.windowSize
            if let pending = pendingPackets[idx] {
                if now >= pending.timestamp, Double(now - pending.timestamp) >= resendDelay {
                    pendingPackets[idx]?.timestamp = now
                    sendRaw(pending.data)
                }
            }
            seq = (seq &+ 1) % LNL.maxSequence
        }
    }

    private func buildChanneledPacket(sequence: UInt16, data: Data) -> Data {
        var w = DataWriter()
        w.writeByte((connectionNumber << 5) | PacketProperty.channeled.rawValue)
        w.writeUInt16LE(sequence)
        w.writeByte(LNL.reliableOrderedChannelId)
        w.writeData(data)
        return w.data
    }

    // MARK: - ACK

    private func sendAck() {
        var w = DataWriter()
        w.writeByte((connectionNumber << 5) | PacketProperty.ack.rawValue)
        w.writeUInt16LE(remoteWindowStart)
        w.writeByte(LNL.reliableOrderedChannelId)
        w.writeData(Data(ackBitfield))
        sendRaw(w.data)
    }

    private func handleAck(_ data: Data) {
        guard data.count >= LNL.channeledHeaderSize else { return }

        var r = DataReader(data: data, offset: 1)
        guard let ackWindowStart = r.readUInt16LE() else { return }
        guard let channelId = r.readByte(), channelId == LNL.reliableOrderedChannelId else { return }

        let bitfieldStart = data.startIndex + LNL.channeledHeaderSize
        let bitfieldData = data[bitfieldStart...]

        var seq = localWindowStart
        while seq != localSequence {
            let relToAckStart = relativeSequence(seq, to: ackWindowStart)
            if relToAckStart < 0 { break }
            if relToAckStart >= LNL.windowSize { break }

            let byteIdx = relToAckStart / 8
            let bitIdx = relToAckStart % 8
            if byteIdx < bitfieldData.count {
                let acked = (bitfieldData[bitfieldStart + byteIdx] & (1 << bitIdx)) != 0
                if acked {
                    let idx = Int(seq) % LNL.windowSize
                    pendingPackets[idx] = nil
                    if seq == localWindowStart {
                        localWindowStart = (localWindowStart &+ 1) % LNL.maxSequence
                    }
                }
            }
            seq = (seq &+ 1) % LNL.maxSequence
        }

        while localWindowStart != localSequence {
            let idx = Int(localWindowStart) % LNL.windowSize
            if pendingPackets[idx] != nil { break }
            localWindowStart = (localWindowStart &+ 1) % LNL.maxSequence
        }
    }

    // MARK: - Ping / Pong

    private func sendPing() {
        var w = DataWriter()
        w.writeByte((connectionNumber << 5) | PacketProperty.ping.rawValue)
        w.writeUInt16LE(pingSequence)
        pingSequence = (pingSequence &+ 1) % LNL.maxSequence
        pingSentTimestamp = currentMs()
        lastPingSentTime = pingSentTimestamp
        sendRaw(w.data)
    }

    private func handlePing(_ data: Data) {
        guard data.count >= 3 else { return }
        var r = DataReader(data: data, offset: 1)
        guard let seq = r.readUInt16LE() else { return }

        var w = DataWriter()
        w.writeByte((connectionNumber << 5) | PacketProperty.pong.rawValue)
        w.writeUInt16LE(seq)
        w.writeInt64LE(LNL.dotNetTicks)
        sendRaw(w.data)
    }

    private func handlePong(_ data: Data) {
        guard data.count >= 3 else { return }
        let now = currentMs()
        guard now >= pingSentTimestamp else { return }
        let elapsed = Double(now - pingSentTimestamp)
        rttSum += elapsed
        rttCount += 1
        avgRtt = rttSum / Double(rttCount)

        if rttCount > 10 {
            rttSum = avgRtt
            rttCount = 1
        }
    }

    // MARK: - Disconnect

    private func handleServerDisconnect(_ data: Data) {
        guard data.count >= 9 else { return }
        var w = DataWriter()
        w.writeByte((connectionNumber << 5) | PacketProperty.shutdownOk.rawValue)
        sendRaw(w.data)
        performDisconnect(reason: "Server disconnected")
    }

    private func handleMerged(_ data: Data) {
        var offset = 1
        while offset + 2 <= data.count {
            let sizeBytes = data[data.startIndex + offset ..< data.startIndex + offset + 2]
            let size = Int(UInt16(littleEndian: sizeBytes.withUnsafeBytes { $0.loadUnaligned(as: UInt16.self) }))
            offset += 2
            guard offset + size <= data.count else { break }
            let subPacket = Data(data[data.startIndex + offset ..< data.startIndex + offset + size])
            offset += size
            handleRawPacket(subPacket)
        }
    }

    private func performDisconnect(reason: String?) {
        guard state != .disconnected else { return }
        lnlLog.info("Disconnecting: \(reason ?? "no reason")")

        if state == .connected {
            var w = DataWriter()
            w.writeByte((connectionNumber << 5) | PacketProperty.disconnect.rawValue)
            w.writeInt64LE(connectionTime)
            sendRaw(w.data)
        }

        state = .disconnected
        updateTask?.cancel()
        updateTask = nil
        connection?.cancel()
        connection = nil

        // Reset channel state
        localSequence = 0
        localWindowStart = 0
        remoteSequence = 0
        remoteWindowStart = 0
        outgoingQueue.removeAll()
        for i in 0..<LNL.windowSize {
            pendingPackets[i] = nil
            receivedPackets[i] = nil
        }
        for i in 0..<ackBitfield.count {
            ackBitfield[i] = 0
        }
        incomingFragments.removeAll()

        eventContinuation.yield(.disconnected(reason))
    }

    // MARK: - Raw Send

    private func sendRaw(_ data: Data) {
        guard let conn = connection else {
            lnlLog.error("sendRaw called but connection is nil")
            return
        }
        if conn.state != .ready {
            lnlLog.warning("sendRaw called but NWConnection state is \(String(describing: conn.state))")
        }
        totalPacketsSent += 1
        totalBytesSent += data.count
        let pktNum = totalPacketsSent
        conn.send(content: data, completion: .contentProcessed { error in
            if let error {
                lnlLog.error("Send #\(pktNum) failed: \(error.localizedDescription)")
            } else {
                lnlLog.debug("Send #\(pktNum) completed (\(data.count) bytes)")
            }
        })
    }

    // MARK: - Utility

    private func currentMs() -> UInt64 {
        UInt64(DispatchTime.now().uptimeNanoseconds / 1_000_000)
    }

    private func relativeSequence(_ number: UInt16, to expected: UInt16) -> Int {
        let n = Int(number)
        let e = Int(expected)
        let max = Int(LNL.maxSequence)
        return (n - e + max + LNL.halfMaxSequence) % max - LNL.halfMaxSequence
    }
}

// MARK: - Binary Helpers

private struct DataWriter {
    var data = Data()

    mutating func writeByte(_ byte: UInt8) {
        data.append(byte)
    }

    mutating func writeUInt16LE(_ value: UInt16) {
        withUnsafeBytes(of: value.littleEndian) { data.append(contentsOf: $0) }
    }

    mutating func writeInt32LE(_ value: Int32) {
        withUnsafeBytes(of: value.littleEndian) { data.append(contentsOf: $0) }
    }

    mutating func writeInt64LE(_ value: Int64) {
        withUnsafeBytes(of: value.littleEndian) { data.append(contentsOf: $0) }
    }

    mutating func writeData(_ d: Data) {
        data.append(d)
    }
}

private struct DataReader {
    let data: Data
    var offset: Int

    mutating func readByte() -> UInt8? {
        let idx = data.startIndex + offset
        guard idx < data.endIndex else { return nil }
        offset += 1
        return data[idx]
    }

    mutating func readUInt16LE() -> UInt16? {
        let start = data.startIndex + offset
        guard start + 2 <= data.endIndex else { return nil }
        offset += 2
        return data[start..<start+2].withUnsafeBytes {
            UInt16(littleEndian: $0.loadUnaligned(as: UInt16.self))
        }
    }

    mutating func readInt32LE() -> Int32? {
        let start = data.startIndex + offset
        guard start + 4 <= data.endIndex else { return nil }
        offset += 4
        return data[start..<start+4].withUnsafeBytes {
            Int32(littleEndian: $0.loadUnaligned(as: Int32.self))
        }
    }

    mutating func readInt64LE() -> Int64? {
        let start = data.startIndex + offset
        guard start + 8 <= data.endIndex else { return nil }
        offset += 8
        return data[start..<start+8].withUnsafeBytes {
            Int64(littleEndian: $0.loadUnaligned(as: Int64.self))
        }
    }
}
