package transcoder

import (
	"errors"
	"fmt"
	"time"

	lobbymodel "galaxy/model/lobby"
	lobbyfbs "galaxy/schema/fbs/lobby"

	flatbuffers "github.com/google/flatbuffers/go"
)

// MyGamesListRequestToPayload converts a typed lobbymodel.MyGamesListRequest
// to FlatBuffers bytes suitable for the authenticated gateway transport.
func MyGamesListRequestToPayload(request *lobbymodel.MyGamesListRequest) ([]byte, error) {
	if request == nil {
		return nil, errors.New("encode my games list request payload: request is nil")
	}

	builder := flatbuffers.NewBuilder(32)
	lobbyfbs.MyGamesListRequestStart(builder)
	offset := lobbyfbs.MyGamesListRequestEnd(builder)
	lobbyfbs.FinishMyGamesListRequestBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToMyGamesListRequest converts FlatBuffers payload bytes into
// lobbymodel.MyGamesListRequest.
func PayloadToMyGamesListRequest(data []byte) (result *lobbymodel.MyGamesListRequest, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode my games list request payload: data is empty")
	}

	defer recoverLobbyDecodePanic("decode my games list request payload", &result, &err)

	_ = lobbyfbs.GetRootAsMyGamesListRequest(data, 0)
	return &lobbymodel.MyGamesListRequest{}, nil
}

// MyGamesListResponseToPayload converts lobbymodel.MyGamesListResponse to
// FlatBuffers bytes suitable for the authenticated gateway transport.
func MyGamesListResponseToPayload(response *lobbymodel.MyGamesListResponse) ([]byte, error) {
	if response == nil {
		return nil, errors.New("encode my games list response payload: response is nil")
	}

	builder := flatbuffers.NewBuilder(512)

	itemOffsets := make([]flatbuffers.UOffsetT, len(response.Items))
	for index := range response.Items {
		itemOffsets[index] = encodeGameSummary(builder, response.Items[index])
	}

	itemsVector := finishOffsetVector(builder, itemOffsets)

	lobbyfbs.MyGamesListResponseStart(builder)
	if itemsVector != 0 {
		lobbyfbs.MyGamesListResponseAddItems(builder, itemsVector)
	}
	offset := lobbyfbs.MyGamesListResponseEnd(builder)
	lobbyfbs.FinishMyGamesListResponseBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToMyGamesListResponse converts FlatBuffers payload bytes into
// lobbymodel.MyGamesListResponse.
func PayloadToMyGamesListResponse(data []byte) (result *lobbymodel.MyGamesListResponse, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode my games list response payload: data is empty")
	}

	defer recoverLobbyDecodePanic("decode my games list response payload", &result, &err)

	response := lobbyfbs.GetRootAsMyGamesListResponse(data, 0)
	out := &lobbymodel.MyGamesListResponse{
		Items: make([]lobbymodel.GameSummary, 0, response.ItemsLength()),
	}

	summary := new(lobbyfbs.GameSummary)
	for index := 0; index < response.ItemsLength(); index++ {
		if !response.Items(summary, index) {
			return nil, fmt.Errorf("decode my games list response payload: items[%d] is missing", index)
		}
		out.Items = append(out.Items, decodeGameSummary(summary))
	}
	return out, nil
}

// PublicGamesListRequestToPayload converts lobbymodel.PublicGamesListRequest to
// FlatBuffers bytes.
func PublicGamesListRequestToPayload(request *lobbymodel.PublicGamesListRequest) ([]byte, error) {
	if request == nil {
		return nil, errors.New("encode public games list request payload: request is nil")
	}

	builder := flatbuffers.NewBuilder(32)
	lobbyfbs.PublicGamesListRequestStart(builder)
	lobbyfbs.PublicGamesListRequestAddPage(builder, int32(request.Page))
	lobbyfbs.PublicGamesListRequestAddPageSize(builder, int32(request.PageSize))
	offset := lobbyfbs.PublicGamesListRequestEnd(builder)
	lobbyfbs.FinishPublicGamesListRequestBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToPublicGamesListRequest converts FlatBuffers payload bytes into
// lobbymodel.PublicGamesListRequest.
func PayloadToPublicGamesListRequest(data []byte) (result *lobbymodel.PublicGamesListRequest, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode public games list request payload: data is empty")
	}

	defer recoverLobbyDecodePanic("decode public games list request payload", &result, &err)

	request := lobbyfbs.GetRootAsPublicGamesListRequest(data, 0)
	return &lobbymodel.PublicGamesListRequest{
		Page:     int(request.Page()),
		PageSize: int(request.PageSize()),
	}, nil
}

// PublicGamesListResponseToPayload converts lobbymodel.PublicGamesListResponse
// to FlatBuffers bytes.
func PublicGamesListResponseToPayload(response *lobbymodel.PublicGamesListResponse) ([]byte, error) {
	if response == nil {
		return nil, errors.New("encode public games list response payload: response is nil")
	}

	builder := flatbuffers.NewBuilder(512)

	itemOffsets := make([]flatbuffers.UOffsetT, len(response.Items))
	for index := range response.Items {
		itemOffsets[index] = encodeGameSummary(builder, response.Items[index])
	}
	itemsVector := finishOffsetVector(builder, itemOffsets)

	lobbyfbs.PublicGamesListResponseStart(builder)
	if itemsVector != 0 {
		lobbyfbs.PublicGamesListResponseAddItems(builder, itemsVector)
	}
	lobbyfbs.PublicGamesListResponseAddPage(builder, int32(response.Page))
	lobbyfbs.PublicGamesListResponseAddPageSize(builder, int32(response.PageSize))
	lobbyfbs.PublicGamesListResponseAddTotal(builder, int32(response.Total))
	offset := lobbyfbs.PublicGamesListResponseEnd(builder)
	lobbyfbs.FinishPublicGamesListResponseBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToPublicGamesListResponse converts FlatBuffers payload bytes into
// lobbymodel.PublicGamesListResponse.
func PayloadToPublicGamesListResponse(data []byte) (result *lobbymodel.PublicGamesListResponse, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode public games list response payload: data is empty")
	}

	defer recoverLobbyDecodePanic("decode public games list response payload", &result, &err)

	response := lobbyfbs.GetRootAsPublicGamesListResponse(data, 0)
	out := &lobbymodel.PublicGamesListResponse{
		Items:    make([]lobbymodel.GameSummary, 0, response.ItemsLength()),
		Page:     int(response.Page()),
		PageSize: int(response.PageSize()),
		Total:    int(response.Total()),
	}

	summary := new(lobbyfbs.GameSummary)
	for index := 0; index < response.ItemsLength(); index++ {
		if !response.Items(summary, index) {
			return nil, fmt.Errorf("decode public games list response payload: items[%d] is missing", index)
		}
		out.Items = append(out.Items, decodeGameSummary(summary))
	}
	return out, nil
}

// MyApplicationsListRequestToPayload converts lobbymodel.MyApplicationsListRequest
// to FlatBuffers bytes.
func MyApplicationsListRequestToPayload(request *lobbymodel.MyApplicationsListRequest) ([]byte, error) {
	if request == nil {
		return nil, errors.New("encode my applications list request payload: request is nil")
	}

	builder := flatbuffers.NewBuilder(32)
	lobbyfbs.MyApplicationsListRequestStart(builder)
	offset := lobbyfbs.MyApplicationsListRequestEnd(builder)
	lobbyfbs.FinishMyApplicationsListRequestBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToMyApplicationsListRequest converts FlatBuffers payload bytes into
// lobbymodel.MyApplicationsListRequest.
func PayloadToMyApplicationsListRequest(data []byte) (result *lobbymodel.MyApplicationsListRequest, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode my applications list request payload: data is empty")
	}

	defer recoverLobbyDecodePanic("decode my applications list request payload", &result, &err)

	_ = lobbyfbs.GetRootAsMyApplicationsListRequest(data, 0)
	return &lobbymodel.MyApplicationsListRequest{}, nil
}

// MyApplicationsListResponseToPayload converts lobbymodel.MyApplicationsListResponse
// to FlatBuffers bytes.
func MyApplicationsListResponseToPayload(response *lobbymodel.MyApplicationsListResponse) ([]byte, error) {
	if response == nil {
		return nil, errors.New("encode my applications list response payload: response is nil")
	}

	builder := flatbuffers.NewBuilder(512)

	itemOffsets := make([]flatbuffers.UOffsetT, len(response.Items))
	for index := range response.Items {
		itemOffsets[index] = encodeApplicationSummary(builder, response.Items[index])
	}
	itemsVector := finishOffsetVector(builder, itemOffsets)

	lobbyfbs.MyApplicationsListResponseStart(builder)
	if itemsVector != 0 {
		lobbyfbs.MyApplicationsListResponseAddItems(builder, itemsVector)
	}
	offset := lobbyfbs.MyApplicationsListResponseEnd(builder)
	lobbyfbs.FinishMyApplicationsListResponseBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToMyApplicationsListResponse converts FlatBuffers payload bytes into
// lobbymodel.MyApplicationsListResponse.
func PayloadToMyApplicationsListResponse(data []byte) (result *lobbymodel.MyApplicationsListResponse, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode my applications list response payload: data is empty")
	}

	defer recoverLobbyDecodePanic("decode my applications list response payload", &result, &err)

	response := lobbyfbs.GetRootAsMyApplicationsListResponse(data, 0)
	out := &lobbymodel.MyApplicationsListResponse{
		Items: make([]lobbymodel.ApplicationSummary, 0, response.ItemsLength()),
	}

	app := new(lobbyfbs.ApplicationSummary)
	for index := 0; index < response.ItemsLength(); index++ {
		if !response.Items(app, index) {
			return nil, fmt.Errorf("decode my applications list response payload: items[%d] is missing", index)
		}
		out.Items = append(out.Items, decodeApplicationSummary(app))
	}
	return out, nil
}

// MyInvitesListRequestToPayload converts lobbymodel.MyInvitesListRequest
// to FlatBuffers bytes.
func MyInvitesListRequestToPayload(request *lobbymodel.MyInvitesListRequest) ([]byte, error) {
	if request == nil {
		return nil, errors.New("encode my invites list request payload: request is nil")
	}

	builder := flatbuffers.NewBuilder(32)
	lobbyfbs.MyInvitesListRequestStart(builder)
	offset := lobbyfbs.MyInvitesListRequestEnd(builder)
	lobbyfbs.FinishMyInvitesListRequestBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToMyInvitesListRequest converts FlatBuffers payload bytes into
// lobbymodel.MyInvitesListRequest.
func PayloadToMyInvitesListRequest(data []byte) (result *lobbymodel.MyInvitesListRequest, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode my invites list request payload: data is empty")
	}

	defer recoverLobbyDecodePanic("decode my invites list request payload", &result, &err)

	_ = lobbyfbs.GetRootAsMyInvitesListRequest(data, 0)
	return &lobbymodel.MyInvitesListRequest{}, nil
}

// MyInvitesListResponseToPayload converts lobbymodel.MyInvitesListResponse
// to FlatBuffers bytes.
func MyInvitesListResponseToPayload(response *lobbymodel.MyInvitesListResponse) ([]byte, error) {
	if response == nil {
		return nil, errors.New("encode my invites list response payload: response is nil")
	}

	builder := flatbuffers.NewBuilder(512)

	itemOffsets := make([]flatbuffers.UOffsetT, len(response.Items))
	for index := range response.Items {
		itemOffsets[index] = encodeInviteSummary(builder, response.Items[index])
	}
	itemsVector := finishOffsetVector(builder, itemOffsets)

	lobbyfbs.MyInvitesListResponseStart(builder)
	if itemsVector != 0 {
		lobbyfbs.MyInvitesListResponseAddItems(builder, itemsVector)
	}
	offset := lobbyfbs.MyInvitesListResponseEnd(builder)
	lobbyfbs.FinishMyInvitesListResponseBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToMyInvitesListResponse converts FlatBuffers payload bytes into
// lobbymodel.MyInvitesListResponse.
func PayloadToMyInvitesListResponse(data []byte) (result *lobbymodel.MyInvitesListResponse, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode my invites list response payload: data is empty")
	}

	defer recoverLobbyDecodePanic("decode my invites list response payload", &result, &err)

	response := lobbyfbs.GetRootAsMyInvitesListResponse(data, 0)
	out := &lobbymodel.MyInvitesListResponse{
		Items: make([]lobbymodel.InviteSummary, 0, response.ItemsLength()),
	}

	invite := new(lobbyfbs.InviteSummary)
	for index := 0; index < response.ItemsLength(); index++ {
		if !response.Items(invite, index) {
			return nil, fmt.Errorf("decode my invites list response payload: items[%d] is missing", index)
		}
		out.Items = append(out.Items, decodeInviteSummary(invite))
	}
	return out, nil
}

// OpenEnrollmentRequestToPayload converts lobbymodel.OpenEnrollmentRequest to
// FlatBuffers bytes suitable for the authenticated gateway transport.
func OpenEnrollmentRequestToPayload(request *lobbymodel.OpenEnrollmentRequest) ([]byte, error) {
	if request == nil {
		return nil, errors.New("encode open enrollment request payload: request is nil")
	}

	builder := flatbuffers.NewBuilder(64)
	gameID := builder.CreateString(request.GameID)

	lobbyfbs.OpenEnrollmentRequestStart(builder)
	lobbyfbs.OpenEnrollmentRequestAddGameId(builder, gameID)
	offset := lobbyfbs.OpenEnrollmentRequestEnd(builder)
	lobbyfbs.FinishOpenEnrollmentRequestBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToOpenEnrollmentRequest converts FlatBuffers payload bytes into
// lobbymodel.OpenEnrollmentRequest.
func PayloadToOpenEnrollmentRequest(data []byte) (result *lobbymodel.OpenEnrollmentRequest, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode open enrollment request payload: data is empty")
	}

	defer recoverLobbyDecodePanic("decode open enrollment request payload", &result, &err)

	request := lobbyfbs.GetRootAsOpenEnrollmentRequest(data, 0)
	return &lobbymodel.OpenEnrollmentRequest{
		GameID: string(request.GameId()),
	}, nil
}

// OpenEnrollmentResponseToPayload converts lobbymodel.OpenEnrollmentResponse to
// FlatBuffers bytes suitable for the authenticated gateway transport.
func OpenEnrollmentResponseToPayload(response *lobbymodel.OpenEnrollmentResponse) ([]byte, error) {
	if response == nil {
		return nil, errors.New("encode open enrollment response payload: response is nil")
	}

	builder := flatbuffers.NewBuilder(64)
	gameID := builder.CreateString(response.GameID)
	status := builder.CreateString(response.Status)

	lobbyfbs.OpenEnrollmentResponseStart(builder)
	lobbyfbs.OpenEnrollmentResponseAddGameId(builder, gameID)
	lobbyfbs.OpenEnrollmentResponseAddStatus(builder, status)
	offset := lobbyfbs.OpenEnrollmentResponseEnd(builder)
	lobbyfbs.FinishOpenEnrollmentResponseBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToOpenEnrollmentResponse converts FlatBuffers payload bytes into
// lobbymodel.OpenEnrollmentResponse.
func PayloadToOpenEnrollmentResponse(data []byte) (result *lobbymodel.OpenEnrollmentResponse, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode open enrollment response payload: data is empty")
	}

	defer recoverLobbyDecodePanic("decode open enrollment response payload", &result, &err)

	response := lobbyfbs.GetRootAsOpenEnrollmentResponse(data, 0)
	return &lobbymodel.OpenEnrollmentResponse{
		GameID: string(response.GameId()),
		Status: string(response.Status()),
	}, nil
}

// GameCreateRequestToPayload converts lobbymodel.GameCreateRequest to
// FlatBuffers bytes.
func GameCreateRequestToPayload(request *lobbymodel.GameCreateRequest) ([]byte, error) {
	if request == nil {
		return nil, errors.New("encode game create request payload: request is nil")
	}

	builder := flatbuffers.NewBuilder(256)
	gameName := builder.CreateString(request.GameName)
	description := builder.CreateString(request.Description)
	turnSchedule := builder.CreateString(request.TurnSchedule)
	targetEngineVersion := builder.CreateString(request.TargetEngineVersion)

	lobbyfbs.GameCreateRequestStart(builder)
	lobbyfbs.GameCreateRequestAddGameName(builder, gameName)
	lobbyfbs.GameCreateRequestAddDescription(builder, description)
	lobbyfbs.GameCreateRequestAddMinPlayers(builder, int32(request.MinPlayers))
	lobbyfbs.GameCreateRequestAddMaxPlayers(builder, int32(request.MaxPlayers))
	lobbyfbs.GameCreateRequestAddStartGapHours(builder, int32(request.StartGapHours))
	lobbyfbs.GameCreateRequestAddStartGapPlayers(builder, int32(request.StartGapPlayers))
	lobbyfbs.GameCreateRequestAddEnrollmentEndsAtMs(builder, request.EnrollmentEndsAt.UTC().UnixMilli())
	lobbyfbs.GameCreateRequestAddTurnSchedule(builder, turnSchedule)
	lobbyfbs.GameCreateRequestAddTargetEngineVersion(builder, targetEngineVersion)
	offset := lobbyfbs.GameCreateRequestEnd(builder)
	lobbyfbs.FinishGameCreateRequestBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToGameCreateRequest converts FlatBuffers payload bytes into
// lobbymodel.GameCreateRequest.
func PayloadToGameCreateRequest(data []byte) (result *lobbymodel.GameCreateRequest, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode game create request payload: data is empty")
	}

	defer recoverLobbyDecodePanic("decode game create request payload", &result, &err)

	request := lobbyfbs.GetRootAsGameCreateRequest(data, 0)
	return &lobbymodel.GameCreateRequest{
		GameName:            string(request.GameName()),
		Description:         string(request.Description()),
		MinPlayers:          int(request.MinPlayers()),
		MaxPlayers:          int(request.MaxPlayers()),
		StartGapHours:       int(request.StartGapHours()),
		StartGapPlayers:     int(request.StartGapPlayers()),
		EnrollmentEndsAt:    time.UnixMilli(request.EnrollmentEndsAtMs()).UTC(),
		TurnSchedule:        string(request.TurnSchedule()),
		TargetEngineVersion: string(request.TargetEngineVersion()),
	}, nil
}

// GameCreateResponseToPayload converts lobbymodel.GameCreateResponse to
// FlatBuffers bytes.
func GameCreateResponseToPayload(response *lobbymodel.GameCreateResponse) ([]byte, error) {
	if response == nil {
		return nil, errors.New("encode game create response payload: response is nil")
	}

	builder := flatbuffers.NewBuilder(256)
	gameOffset := encodeGameSummary(builder, response.Game)

	lobbyfbs.GameCreateResponseStart(builder)
	lobbyfbs.GameCreateResponseAddGame(builder, gameOffset)
	offset := lobbyfbs.GameCreateResponseEnd(builder)
	lobbyfbs.FinishGameCreateResponseBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToGameCreateResponse converts FlatBuffers payload bytes into
// lobbymodel.GameCreateResponse.
func PayloadToGameCreateResponse(data []byte) (result *lobbymodel.GameCreateResponse, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode game create response payload: data is empty")
	}

	defer recoverLobbyDecodePanic("decode game create response payload", &result, &err)

	response := lobbyfbs.GetRootAsGameCreateResponse(data, 0)
	game := response.Game(nil)
	if game == nil {
		return nil, errors.New("decode game create response payload: game is missing")
	}
	return &lobbymodel.GameCreateResponse{
		Game: decodeGameSummary(game),
	}, nil
}

// ApplicationSubmitRequestToPayload converts lobbymodel.ApplicationSubmitRequest
// to FlatBuffers bytes.
func ApplicationSubmitRequestToPayload(request *lobbymodel.ApplicationSubmitRequest) ([]byte, error) {
	if request == nil {
		return nil, errors.New("encode application submit request payload: request is nil")
	}

	builder := flatbuffers.NewBuilder(128)
	gameID := builder.CreateString(request.GameID)
	raceName := builder.CreateString(request.RaceName)

	lobbyfbs.ApplicationSubmitRequestStart(builder)
	lobbyfbs.ApplicationSubmitRequestAddGameId(builder, gameID)
	lobbyfbs.ApplicationSubmitRequestAddRaceName(builder, raceName)
	offset := lobbyfbs.ApplicationSubmitRequestEnd(builder)
	lobbyfbs.FinishApplicationSubmitRequestBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToApplicationSubmitRequest converts FlatBuffers payload bytes into
// lobbymodel.ApplicationSubmitRequest.
func PayloadToApplicationSubmitRequest(data []byte) (result *lobbymodel.ApplicationSubmitRequest, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode application submit request payload: data is empty")
	}

	defer recoverLobbyDecodePanic("decode application submit request payload", &result, &err)

	request := lobbyfbs.GetRootAsApplicationSubmitRequest(data, 0)
	return &lobbymodel.ApplicationSubmitRequest{
		GameID:   string(request.GameId()),
		RaceName: string(request.RaceName()),
	}, nil
}

// ApplicationSubmitResponseToPayload converts lobbymodel.ApplicationSubmitResponse
// to FlatBuffers bytes.
func ApplicationSubmitResponseToPayload(response *lobbymodel.ApplicationSubmitResponse) ([]byte, error) {
	if response == nil {
		return nil, errors.New("encode application submit response payload: response is nil")
	}

	builder := flatbuffers.NewBuilder(256)
	appOffset := encodeApplicationSummary(builder, response.Application)

	lobbyfbs.ApplicationSubmitResponseStart(builder)
	lobbyfbs.ApplicationSubmitResponseAddApplication(builder, appOffset)
	offset := lobbyfbs.ApplicationSubmitResponseEnd(builder)
	lobbyfbs.FinishApplicationSubmitResponseBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToApplicationSubmitResponse converts FlatBuffers payload bytes into
// lobbymodel.ApplicationSubmitResponse.
func PayloadToApplicationSubmitResponse(data []byte) (result *lobbymodel.ApplicationSubmitResponse, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode application submit response payload: data is empty")
	}

	defer recoverLobbyDecodePanic("decode application submit response payload", &result, &err)

	response := lobbyfbs.GetRootAsApplicationSubmitResponse(data, 0)
	app := response.Application(nil)
	if app == nil {
		return nil, errors.New("decode application submit response payload: application is missing")
	}
	return &lobbymodel.ApplicationSubmitResponse{
		Application: decodeApplicationSummary(app),
	}, nil
}

// InviteRedeemRequestToPayload converts lobbymodel.InviteRedeemRequest to
// FlatBuffers bytes.
func InviteRedeemRequestToPayload(request *lobbymodel.InviteRedeemRequest) ([]byte, error) {
	if request == nil {
		return nil, errors.New("encode invite redeem request payload: request is nil")
	}
	return encodeInviteAction(request.GameID, request.InviteID, true)
}

// PayloadToInviteRedeemRequest converts FlatBuffers payload bytes into
// lobbymodel.InviteRedeemRequest.
func PayloadToInviteRedeemRequest(data []byte) (result *lobbymodel.InviteRedeemRequest, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode invite redeem request payload: data is empty")
	}

	defer recoverLobbyDecodePanic("decode invite redeem request payload", &result, &err)

	request := lobbyfbs.GetRootAsInviteRedeemRequest(data, 0)
	return &lobbymodel.InviteRedeemRequest{
		GameID:   string(request.GameId()),
		InviteID: string(request.InviteId()),
	}, nil
}

// InviteRedeemResponseToPayload converts lobbymodel.InviteRedeemResponse to
// FlatBuffers bytes.
func InviteRedeemResponseToPayload(response *lobbymodel.InviteRedeemResponse) ([]byte, error) {
	if response == nil {
		return nil, errors.New("encode invite redeem response payload: response is nil")
	}

	builder := flatbuffers.NewBuilder(256)
	inviteOffset := encodeInviteSummary(builder, response.Invite)

	lobbyfbs.InviteRedeemResponseStart(builder)
	lobbyfbs.InviteRedeemResponseAddInvite(builder, inviteOffset)
	offset := lobbyfbs.InviteRedeemResponseEnd(builder)
	lobbyfbs.FinishInviteRedeemResponseBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToInviteRedeemResponse converts FlatBuffers payload bytes into
// lobbymodel.InviteRedeemResponse.
func PayloadToInviteRedeemResponse(data []byte) (result *lobbymodel.InviteRedeemResponse, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode invite redeem response payload: data is empty")
	}

	defer recoverLobbyDecodePanic("decode invite redeem response payload", &result, &err)

	response := lobbyfbs.GetRootAsInviteRedeemResponse(data, 0)
	invite := response.Invite(nil)
	if invite == nil {
		return nil, errors.New("decode invite redeem response payload: invite is missing")
	}
	return &lobbymodel.InviteRedeemResponse{
		Invite: decodeInviteSummary(invite),
	}, nil
}

// InviteDeclineRequestToPayload converts lobbymodel.InviteDeclineRequest to
// FlatBuffers bytes.
func InviteDeclineRequestToPayload(request *lobbymodel.InviteDeclineRequest) ([]byte, error) {
	if request == nil {
		return nil, errors.New("encode invite decline request payload: request is nil")
	}
	return encodeInviteAction(request.GameID, request.InviteID, false)
}

// PayloadToInviteDeclineRequest converts FlatBuffers payload bytes into
// lobbymodel.InviteDeclineRequest.
func PayloadToInviteDeclineRequest(data []byte) (result *lobbymodel.InviteDeclineRequest, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode invite decline request payload: data is empty")
	}

	defer recoverLobbyDecodePanic("decode invite decline request payload", &result, &err)

	request := lobbyfbs.GetRootAsInviteDeclineRequest(data, 0)
	return &lobbymodel.InviteDeclineRequest{
		GameID:   string(request.GameId()),
		InviteID: string(request.InviteId()),
	}, nil
}

// InviteDeclineResponseToPayload converts lobbymodel.InviteDeclineResponse to
// FlatBuffers bytes.
func InviteDeclineResponseToPayload(response *lobbymodel.InviteDeclineResponse) ([]byte, error) {
	if response == nil {
		return nil, errors.New("encode invite decline response payload: response is nil")
	}

	builder := flatbuffers.NewBuilder(256)
	inviteOffset := encodeInviteSummary(builder, response.Invite)

	lobbyfbs.InviteDeclineResponseStart(builder)
	lobbyfbs.InviteDeclineResponseAddInvite(builder, inviteOffset)
	offset := lobbyfbs.InviteDeclineResponseEnd(builder)
	lobbyfbs.FinishInviteDeclineResponseBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToInviteDeclineResponse converts FlatBuffers payload bytes into
// lobbymodel.InviteDeclineResponse.
func PayloadToInviteDeclineResponse(data []byte) (result *lobbymodel.InviteDeclineResponse, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode invite decline response payload: data is empty")
	}

	defer recoverLobbyDecodePanic("decode invite decline response payload", &result, &err)

	response := lobbyfbs.GetRootAsInviteDeclineResponse(data, 0)
	invite := response.Invite(nil)
	if invite == nil {
		return nil, errors.New("decode invite decline response payload: invite is missing")
	}
	return &lobbymodel.InviteDeclineResponse{
		Invite: decodeInviteSummary(invite),
	}, nil
}

// LobbyErrorResponseToPayload converts lobbymodel.ErrorResponse to FlatBuffers
// bytes suitable for the authenticated gateway transport.
func LobbyErrorResponseToPayload(response *lobbymodel.ErrorResponse) ([]byte, error) {
	if response == nil {
		return nil, errors.New("encode lobby error response payload: response is nil")
	}

	builder := flatbuffers.NewBuilder(128)
	code := builder.CreateString(response.Error.Code)
	message := builder.CreateString(response.Error.Message)

	lobbyfbs.ErrorBodyStart(builder)
	lobbyfbs.ErrorBodyAddCode(builder, code)
	lobbyfbs.ErrorBodyAddMessage(builder, message)
	errorOffset := lobbyfbs.ErrorBodyEnd(builder)

	lobbyfbs.ErrorResponseStart(builder)
	lobbyfbs.ErrorResponseAddError(builder, errorOffset)
	offset := lobbyfbs.ErrorResponseEnd(builder)
	lobbyfbs.FinishErrorResponseBuffer(builder, offset)

	return builder.FinishedBytes(), nil
}

// PayloadToLobbyErrorResponse converts FlatBuffers payload bytes into
// lobbymodel.ErrorResponse.
func PayloadToLobbyErrorResponse(data []byte) (result *lobbymodel.ErrorResponse, err error) {
	if len(data) == 0 {
		return nil, errors.New("decode lobby error response payload: data is empty")
	}

	defer recoverLobbyDecodePanic("decode lobby error response payload", &result, &err)

	response := lobbyfbs.GetRootAsErrorResponse(data, 0)
	body := response.Error(nil)
	if body == nil {
		return nil, errors.New("decode lobby error response payload: error is missing")
	}

	return &lobbymodel.ErrorResponse{
		Error: lobbymodel.ErrorBody{
			Code:    string(body.Code()),
			Message: string(body.Message()),
		},
	}, nil
}

func encodeInviteAction(gameID, inviteID string, redeem bool) ([]byte, error) {
	builder := flatbuffers.NewBuilder(128)
	gameOffset := builder.CreateString(gameID)
	inviteOffset := builder.CreateString(inviteID)

	if redeem {
		lobbyfbs.InviteRedeemRequestStart(builder)
		lobbyfbs.InviteRedeemRequestAddGameId(builder, gameOffset)
		lobbyfbs.InviteRedeemRequestAddInviteId(builder, inviteOffset)
		offset := lobbyfbs.InviteRedeemRequestEnd(builder)
		lobbyfbs.FinishInviteRedeemRequestBuffer(builder, offset)
	} else {
		lobbyfbs.InviteDeclineRequestStart(builder)
		lobbyfbs.InviteDeclineRequestAddGameId(builder, gameOffset)
		lobbyfbs.InviteDeclineRequestAddInviteId(builder, inviteOffset)
		offset := lobbyfbs.InviteDeclineRequestEnd(builder)
		lobbyfbs.FinishInviteDeclineRequestBuffer(builder, offset)
	}
	return builder.FinishedBytes(), nil
}

func finishOffsetVector(builder *flatbuffers.Builder, offsets []flatbuffers.UOffsetT) flatbuffers.UOffsetT {
	if len(offsets) == 0 {
		return 0
	}
	builder.StartVector(4, len(offsets), 4)
	for index := len(offsets) - 1; index >= 0; index-- {
		builder.PrependUOffsetT(offsets[index])
	}
	return builder.EndVector(len(offsets))
}

func encodeGameSummary(builder *flatbuffers.Builder, summary lobbymodel.GameSummary) flatbuffers.UOffsetT {
	gameID := builder.CreateString(summary.GameID)
	gameName := builder.CreateString(summary.GameName)
	gameType := builder.CreateString(summary.GameType)
	status := builder.CreateString(summary.Status)
	ownerUserID := builder.CreateString(summary.OwnerUserID)

	lobbyfbs.GameSummaryStart(builder)
	lobbyfbs.GameSummaryAddGameId(builder, gameID)
	lobbyfbs.GameSummaryAddGameName(builder, gameName)
	lobbyfbs.GameSummaryAddGameType(builder, gameType)
	lobbyfbs.GameSummaryAddStatus(builder, status)
	lobbyfbs.GameSummaryAddOwnerUserId(builder, ownerUserID)
	lobbyfbs.GameSummaryAddMinPlayers(builder, int32(summary.MinPlayers))
	lobbyfbs.GameSummaryAddMaxPlayers(builder, int32(summary.MaxPlayers))
	lobbyfbs.GameSummaryAddEnrollmentEndsAtMs(builder, summary.EnrollmentEndsAt.UTC().UnixMilli())
	lobbyfbs.GameSummaryAddCreatedAtMs(builder, summary.CreatedAt.UTC().UnixMilli())
	lobbyfbs.GameSummaryAddUpdatedAtMs(builder, summary.UpdatedAt.UTC().UnixMilli())
	lobbyfbs.GameSummaryAddCurrentTurn(builder, summary.CurrentTurn)
	return lobbyfbs.GameSummaryEnd(builder)
}

func decodeGameSummary(summary *lobbyfbs.GameSummary) lobbymodel.GameSummary {
	return lobbymodel.GameSummary{
		GameID:           string(summary.GameId()),
		GameName:         string(summary.GameName()),
		GameType:         string(summary.GameType()),
		Status:           string(summary.Status()),
		OwnerUserID:      string(summary.OwnerUserId()),
		MinPlayers:       int(summary.MinPlayers()),
		MaxPlayers:       int(summary.MaxPlayers()),
		EnrollmentEndsAt: time.UnixMilli(summary.EnrollmentEndsAtMs()).UTC(),
		CreatedAt:        time.UnixMilli(summary.CreatedAtMs()).UTC(),
		UpdatedAt:        time.UnixMilli(summary.UpdatedAtMs()).UTC(),
		CurrentTurn:      summary.CurrentTurn(),
	}
}

func encodeApplicationSummary(builder *flatbuffers.Builder, app lobbymodel.ApplicationSummary) flatbuffers.UOffsetT {
	applicationID := builder.CreateString(app.ApplicationID)
	gameID := builder.CreateString(app.GameID)
	applicantUserID := builder.CreateString(app.ApplicantUserID)
	raceName := builder.CreateString(app.RaceName)
	status := builder.CreateString(app.Status)

	lobbyfbs.ApplicationSummaryStart(builder)
	lobbyfbs.ApplicationSummaryAddApplicationId(builder, applicationID)
	lobbyfbs.ApplicationSummaryAddGameId(builder, gameID)
	lobbyfbs.ApplicationSummaryAddApplicantUserId(builder, applicantUserID)
	lobbyfbs.ApplicationSummaryAddRaceName(builder, raceName)
	lobbyfbs.ApplicationSummaryAddStatus(builder, status)
	lobbyfbs.ApplicationSummaryAddCreatedAtMs(builder, app.CreatedAt.UTC().UnixMilli())
	lobbyfbs.ApplicationSummaryAddDecidedAtMs(builder, unixMilliFromOptional(app.DecidedAt))
	return lobbyfbs.ApplicationSummaryEnd(builder)
}

func decodeApplicationSummary(app *lobbyfbs.ApplicationSummary) lobbymodel.ApplicationSummary {
	return lobbymodel.ApplicationSummary{
		ApplicationID:   string(app.ApplicationId()),
		GameID:          string(app.GameId()),
		ApplicantUserID: string(app.ApplicantUserId()),
		RaceName:        string(app.RaceName()),
		Status:          string(app.Status()),
		CreatedAt:       time.UnixMilli(app.CreatedAtMs()).UTC(),
		DecidedAt:       optionalUnixMilli(app.DecidedAtMs()),
	}
}

func encodeInviteSummary(builder *flatbuffers.Builder, invite lobbymodel.InviteSummary) flatbuffers.UOffsetT {
	inviteID := builder.CreateString(invite.InviteID)
	gameID := builder.CreateString(invite.GameID)
	inviterUserID := builder.CreateString(invite.InviterUserID)
	invitedUserID := builder.CreateString(invite.InvitedUserID)
	code := builder.CreateString(invite.Code)
	raceName := builder.CreateString(invite.RaceName)
	status := builder.CreateString(invite.Status)

	lobbyfbs.InviteSummaryStart(builder)
	lobbyfbs.InviteSummaryAddInviteId(builder, inviteID)
	lobbyfbs.InviteSummaryAddGameId(builder, gameID)
	lobbyfbs.InviteSummaryAddInviterUserId(builder, inviterUserID)
	lobbyfbs.InviteSummaryAddInvitedUserId(builder, invitedUserID)
	lobbyfbs.InviteSummaryAddCode(builder, code)
	lobbyfbs.InviteSummaryAddRaceName(builder, raceName)
	lobbyfbs.InviteSummaryAddStatus(builder, status)
	lobbyfbs.InviteSummaryAddCreatedAtMs(builder, invite.CreatedAt.UTC().UnixMilli())
	lobbyfbs.InviteSummaryAddExpiresAtMs(builder, invite.ExpiresAt.UTC().UnixMilli())
	lobbyfbs.InviteSummaryAddDecidedAtMs(builder, unixMilliFromOptional(invite.DecidedAt))
	return lobbyfbs.InviteSummaryEnd(builder)
}

func decodeInviteSummary(invite *lobbyfbs.InviteSummary) lobbymodel.InviteSummary {
	return lobbymodel.InviteSummary{
		InviteID:      string(invite.InviteId()),
		GameID:        string(invite.GameId()),
		InviterUserID: string(invite.InviterUserId()),
		InvitedUserID: string(invite.InvitedUserId()),
		Code:          string(invite.Code()),
		RaceName:      string(invite.RaceName()),
		Status:        string(invite.Status()),
		CreatedAt:     time.UnixMilli(invite.CreatedAtMs()).UTC(),
		ExpiresAt:     time.UnixMilli(invite.ExpiresAtMs()).UTC(),
		DecidedAt:     optionalUnixMilli(invite.DecidedAtMs()),
	}
}

// unixMilliFromOptional returns 0 when the pointer is nil, else the
// millisecond UTC timestamp. The transcoder uses 0 as the "not set"
// sentinel because FlatBuffers tables do not carry per-field presence
// flags, and 0 is unreachable for any realistic decided/redeemed time.
// optionalUnixMilli (defined in user.go) is the decode counterpart.
func unixMilliFromOptional(t *time.Time) int64 {
	if t == nil {
		return 0
	}
	return t.UTC().UnixMilli()
}

func recoverLobbyDecodePanic[T any](message string, result **T, err *error) {
	if recovered := recover(); recovered != nil {
		*result = nil
		*err = fmt.Errorf("%s: panic recovered: %v", message, recovered)
	}
}
