import Foundation
import MessagePack

// MARK: - Server Methods (client -> server)

enum ServerMethod: Int32 {
    case createAccount = 0
    case requestToken = 1
    case requestLogin = 2
    case sendMessage = 3
    case createCharacter = 4
    case leaveGame = 5
    // 6 = TimeSync, 7 = RequestProfile
    case directMessage = 8
    // 9..21 = game-specific methods
    case remoteFormEvent = 22
}

// MARK: - Client Methods (server -> client)

enum ClientMethod: Int32 {
    case accountCreationUpdate = 0
    case tokenRequestUpdate = 1
    case loginRequestUpdate = 2
    case addBuffer = 3
    case addMessage = 4
    case requestGameplay = 5
    case leaveGameUpdate = 6
    // 7 = TimeSync (unhandled)
    // 8 = Profile (unhandled)
    case say = 9
    case loadInstance = 10
    // 11..33 = game-specific methods (unhandled)
    case showRemoteForm = 34
    case updateRemoteForm = 35
    case closeRemoteForm = 36
    case disconnect = 37
}

// MARK: - Remote Form Event Types

enum RemoteFormEventType: Int, Sendable {
    case buttonClicked = 0
    case textChanged = 1
    case textSubmitted = 2
    case checkBoxValueChanged = 3
    case listBoxSelectionChanged = 4
    case listBoxItemClicked = 5
    case multiSelectListSelectionChanged = 6
    case sliderMoved = 7
    case treeNodeSelected = 8
    case treeNodeClicked = 9
    case formClosed = 10
    case formCloseRequested = 11
}

// MARK: - Form Event Value (for outgoing form events)

enum FormEventValue: Sendable {
    case none
    case bool(Bool)
    case string(String)
    case uint(UInt64)
    case uintArray([UInt64])
}

// MARK: - Outgoing Packet Types

private struct TwoStringPacket: Encodable {
    let a: String, b: String
    func encode(to encoder: Encoder) throws {
        var c = encoder.unkeyedContainer()
        try c.encode(a); try c.encode(b)
    }
}

private struct OneStringPacket: Encodable {
    let a: String
    func encode(to encoder: Encoder) throws {
        var c = encoder.unkeyedContainer()
        try c.encode(a)
    }
}

private struct RemoteFormEventPacket: Encodable {
    let formId: String
    let widgetId: String
    let eventType: Int
    let value: FormEventValue

    func encode(to encoder: Encoder) throws {
        var c = encoder.unkeyedContainer()
        try c.encode(formId)
        try c.encode(widgetId)
        try c.encode(eventType)
        switch value {
        case .none: try c.encodeNil()
        case .bool(let v): try c.encode(v)
        case .string(let v): try c.encode(v)
        case .uint(let v): try c.encode(v)
        case .uintArray(let v): try c.encode(v)
        }
    }
}

// MARK: - Incoming Packet Types

struct GenericResponse: Decodable, Sendable {
    let success: Bool
    let message: String?

    init(from decoder: Decoder) throws {
        var c = try decoder.unkeyedContainer()
        success = try c.decode(Bool.self)
        if c.isAtEnd { message = nil }
        else { message = try c.decodeNil() ? nil : c.decode(String.self) }
    }
}

struct GenericStringResponse: Decodable, Sendable {
    let success: Bool
    let message: String?
    let data: String?

    init(from decoder: Decoder) throws {
        var c = try decoder.unkeyedContainer()
        success = try c.decode(Bool.self)
        if c.isAtEnd { message = nil; data = nil; return }
        message = try c.decodeNil() ? nil : c.decode(String.self)
        if c.isAtEnd { data = nil; return }
        data = try c.decodeNil() ? nil : c.decode(String.self)
    }
}

struct ServerSay: Decodable, Sendable {
    let message: String
    let interrupt: Bool

    init(from decoder: Decoder) throws {
        var c = try decoder.unkeyedContainer()
        message = try c.decode(String.self)
        interrupt = c.isAtEnd ? false : try c.decode(Bool.self)
    }
}

struct ServerDisconnectInfo: Decodable, Sendable {
    let characterId: String
    let reason: String

    init(from decoder: Decoder) throws {
        var c = try decoder.unkeyedContainer()
        characterId = try c.decode(String.self)
        reason = c.isAtEnd ? "Unknown reason" : try c.decode(String.self)
    }
}

// MARK: - Packet Encoding

enum PacketEncoder {
    private static func encode<T: Encodable>(method: ServerMethod, payload: T) -> Data {
        var data = Data()
        withUnsafeBytes(of: method.rawValue.littleEndian) { data.append(contentsOf: $0) }
        data.append(try! MessagePackEncoder().encode(payload))
        return data
    }

    static func tokenRequest(email: String, password: String) -> Data {
        encode(method: .requestToken, payload: TwoStringPacket(a: email, b: password))
    }

    static func createAccountRequest(email: String, password: String) -> Data {
        encode(method: .createAccount, payload: TwoStringPacket(a: email, b: password))
    }

    static func loginRequest(token: String) -> Data {
        encode(method: .requestLogin, payload: OneStringPacket(a: token))
    }

    static func sendMessage(buffer: String, content: String) -> Data {
        encode(method: .sendMessage, payload: TwoStringPacket(a: buffer, b: content))
    }

    static func directMessage(recipientId: String, message: String) -> Data {
        encode(method: .directMessage, payload: TwoStringPacket(a: recipientId, b: message))
    }

    static func leaveGame(characterId: String) -> Data {
        encode(method: .leaveGame, payload: OneStringPacket(a: characterId))
    }

    static func remoteFormEvent(
        formId: String,
        widgetId: String,
        eventType: RemoteFormEventType,
        value: FormEventValue = .none
    ) -> Data {
        encode(method: .remoteFormEvent, payload: RemoteFormEventPacket(
            formId: formId, widgetId: widgetId,
            eventType: eventType.rawValue, value: value
        ))
    }
}

// MARK: - Packet Decoding

enum PacketDecoder {
    static func extractMethod(from data: Data) -> (ClientMethod, Data)? {
        guard data.count >= 4 else { return nil }
        let methodRaw = data.withUnsafeBytes {
            Int32(littleEndian: $0.loadUnaligned(as: Int32.self))
        }
        guard let method = ClientMethod(rawValue: methodRaw) else { return nil }
        return (method, Data(data[data.startIndex + 4 ..< data.endIndex]))
    }

    static func decode<T: Decodable>(_ type: T.Type, from data: Data) -> T? {
        try? MessagePackDecoder().decode(type, from: data)
    }

    static func decodeDynamic(from data: Data) -> MsgPackValue? {
        try? MessagePackDecoder().decode(MsgPackValue.self, from: data)
    }
}
