package transcode

import (
	flatbuffers "github.com/google/flatbuffers/go"

	"scrabble/gateway/internal/backendclient"
	fb "scrabble/pkg/fbs/scrabblefb"
	"scrabble/pkg/wire"
)

// Social encoders: friends, blocks, invitations, statistics and GCG. They follow
// encode.go's bottom-up rule (build every string/child vector before the table).

// buildAccountRef builds an AccountRef table and returns its offset.
func buildAccountRef(b *flatbuffers.Builder, r backendclient.AccountRefResp) flatbuffers.UOffsetT {
	return wire.BuildAccountRef(b, wire.AccountRef{AccountID: r.AccountID, DisplayName: r.DisplayName})
}

// buildAccountRefVector builds a [AccountRef] vector using the table-specific
// StartXVector function and returns the vector offset.
func buildAccountRefVector(b *flatbuffers.Builder, refs []backendclient.AccountRefResp, start func(*flatbuffers.Builder, int) flatbuffers.UOffsetT) flatbuffers.UOffsetT {
	offs := make([]flatbuffers.UOffsetT, len(refs))
	for i, r := range refs {
		offs[i] = buildAccountRef(b, r)
	}
	start(b, len(offs))
	for i := len(offs) - 1; i >= 0; i-- {
		b.PrependUOffsetT(offs[i])
	}
	return b.EndVector(len(offs))
}

// encodeFriendList builds a FriendList payload.
func encodeFriendList(r backendclient.FriendListResp) []byte {
	b := flatbuffers.NewBuilder(256)
	v := buildAccountRefVector(b, r.Friends, fb.FriendListStartFriendsVector)
	fb.FriendListStart(b)
	fb.FriendListAddFriends(b, v)
	b.Finish(fb.FriendListEnd(b))
	return b.FinishedBytes()
}

// encodeIncomingList builds an IncomingRequestList payload.
func encodeIncomingList(r backendclient.IncomingListResp) []byte {
	b := flatbuffers.NewBuilder(256)
	v := buildAccountRefVector(b, r.Requests, fb.IncomingRequestListStartRequestsVector)
	fb.IncomingRequestListStart(b)
	fb.IncomingRequestListAddRequests(b, v)
	b.Finish(fb.IncomingRequestListEnd(b))
	return b.FinishedBytes()
}

// buildRobotFriendVector builds the OutgoingRequestList robots vector (per-game
// disguised-robot friend requests).
func buildRobotFriendVector(b *flatbuffers.Builder, robots []backendclient.RobotFriendReqResp) flatbuffers.UOffsetT {
	offs := make([]flatbuffers.UOffsetT, len(robots))
	for i, r := range robots {
		id := b.CreateString(r.ID)
		name := b.CreateString(r.DisplayName)
		gid := b.CreateString(r.GameID)
		fb.RobotFriendRefStart(b)
		fb.RobotFriendRefAddId(b, id)
		fb.RobotFriendRefAddDisplayName(b, name)
		fb.RobotFriendRefAddGameId(b, gid)
		fb.RobotFriendRefAddSeat(b, int32(r.Seat))
		offs[i] = fb.RobotFriendRefEnd(b)
	}
	fb.OutgoingRequestListStartRobotsVector(b, len(offs))
	for i := len(offs) - 1; i >= 0; i-- {
		b.PrependUOffsetT(offs[i])
	}
	return b.EndVector(len(offs))
}

// encodeOutgoingList builds an OutgoingRequestList payload.
func encodeOutgoingList(r backendclient.OutgoingListResp) []byte {
	b := flatbuffers.NewBuilder(256)
	v := buildAccountRefVector(b, r.Requests, fb.OutgoingRequestListStartRequestsVector)
	rv := buildRobotFriendVector(b, r.Robots)
	fb.OutgoingRequestListStart(b)
	fb.OutgoingRequestListAddRequests(b, v)
	fb.OutgoingRequestListAddRobots(b, rv)
	b.Finish(fb.OutgoingRequestListEnd(b))
	return b.FinishedBytes()
}

// buildRobotBlockVector builds the BlockList robots vector (per-game disguised-robot blocks).
func buildRobotBlockVector(b *flatbuffers.Builder, robots []backendclient.RobotBlockResp) flatbuffers.UOffsetT {
	offs := make([]flatbuffers.UOffsetT, len(robots))
	for i, r := range robots {
		id := b.CreateString(r.ID)
		name := b.CreateString(r.DisplayName)
		gid := b.CreateString(r.GameID)
		fb.RobotBlockRefStart(b)
		fb.RobotBlockRefAddId(b, id)
		fb.RobotBlockRefAddDisplayName(b, name)
		fb.RobotBlockRefAddGameId(b, gid)
		fb.RobotBlockRefAddSeat(b, int32(r.Seat))
		offs[i] = fb.RobotBlockRefEnd(b)
	}
	fb.BlockListStartRobotsVector(b, len(offs))
	for i := len(offs) - 1; i >= 0; i-- {
		b.PrependUOffsetT(offs[i])
	}
	return b.EndVector(len(offs))
}

// encodeBlockList builds a BlockList payload.
func encodeBlockList(r backendclient.BlockListResp) []byte {
	b := flatbuffers.NewBuilder(256)
	v := buildAccountRefVector(b, r.Blocked, fb.BlockListStartBlockedVector)
	rv := buildRobotBlockVector(b, r.Robots)
	fb.BlockListStart(b)
	fb.BlockListAddBlocked(b, v)
	fb.BlockListAddRobots(b, rv)
	b.Finish(fb.BlockListEnd(b))
	return b.FinishedBytes()
}

// encodeFriendCode builds a FriendCode payload.
func encodeFriendCode(r backendclient.FriendCodeResp) []byte {
	b := flatbuffers.NewBuilder(64)
	code := b.CreateString(r.Code)
	fb.FriendCodeStart(b)
	fb.FriendCodeAddCode(b, code)
	fb.FriendCodeAddExpiresAtUnix(b, r.ExpiresAtUnix)
	b.Finish(fb.FriendCodeEnd(b))
	return b.FinishedBytes()
}

// encodeRedeemResult builds a RedeemResult payload.
func encodeRedeemResult(r backendclient.RedeemResultResp) []byte {
	b := flatbuffers.NewBuilder(128)
	friend := buildAccountRef(b, r.Friend)
	fb.RedeemResultStart(b)
	fb.RedeemResultAddFriend(b, friend)
	b.Finish(fb.RedeemResultEnd(b))
	return b.FinishedBytes()
}

// buildBestMoveTile builds a BestMoveTile table and returns its offset.
func buildBestMoveTile(b *flatbuffers.Builder, t backendclient.BestMoveTileResp) flatbuffers.UOffsetT {
	letter := b.CreateString(t.Letter)
	fb.BestMoveTileStart(b)
	fb.BestMoveTileAddLetter(b, letter)
	fb.BestMoveTileAddValue(b, int32(t.Value))
	fb.BestMoveTileAddBlank(b, t.Blank)
	return fb.BestMoveTileEnd(b)
}

// buildBestMove builds a BestMoveView table — its word tile vector first, per the
// bottom-up rule — and returns its offset.
func buildBestMove(b *flatbuffers.Builder, m backendclient.BestMoveResp) flatbuffers.UOffsetT {
	tiles := make([]flatbuffers.UOffsetT, len(m.Word))
	for i, t := range m.Word {
		tiles[i] = buildBestMoveTile(b, t)
	}
	fb.BestMoveViewStartWordVector(b, len(tiles))
	for i := len(tiles) - 1; i >= 0; i-- {
		b.PrependUOffsetT(tiles[i])
	}
	word := b.EndVector(len(tiles))
	variant := b.CreateString(m.Variant)
	fb.BestMoveViewStart(b)
	fb.BestMoveViewAddVariant(b, variant)
	fb.BestMoveViewAddScore(b, int32(m.Score))
	fb.BestMoveViewAddWord(b, word)
	return fb.BestMoveViewEnd(b)
}

// encodeStats builds a StatsView payload, embedding the per-variant best moves (each a
// BestMoveView with its word tiles) when present.
func encodeStats(r backendclient.StatsResp) []byte {
	b := flatbuffers.NewBuilder(256)
	var bestMoves flatbuffers.UOffsetT
	if len(r.BestMoves) > 0 {
		offs := make([]flatbuffers.UOffsetT, len(r.BestMoves))
		for i, m := range r.BestMoves {
			offs[i] = buildBestMove(b, m)
		}
		fb.StatsViewStartBestMovesVector(b, len(offs))
		for i := len(offs) - 1; i >= 0; i-- {
			b.PrependUOffsetT(offs[i])
		}
		bestMoves = b.EndVector(len(offs))
	}
	fb.StatsViewStart(b)
	fb.StatsViewAddWins(b, int32(r.Wins))
	fb.StatsViewAddLosses(b, int32(r.Losses))
	fb.StatsViewAddDraws(b, int32(r.Draws))
	fb.StatsViewAddMaxGamePoints(b, int32(r.MaxGamePoints))
	fb.StatsViewAddMaxWordPoints(b, int32(r.MaxWordPoints))
	fb.StatsViewAddMoves(b, int32(r.Moves))
	fb.StatsViewAddHintsUsed(b, int32(r.HintsUsed))
	if len(r.BestMoves) > 0 {
		fb.StatsViewAddBestMoves(b, bestMoves)
	}
	b.Finish(fb.StatsViewEnd(b))
	return b.FinishedBytes()
}

// buildInvitation builds an Invitation table and returns its offset.
func buildInvitation(b *flatbuffers.Builder, inv backendclient.InvitationResp) flatbuffers.UOffsetT {
	invitees := make([]wire.InvitationInvitee, len(inv.Invitees))
	for i, iv := range inv.Invitees {
		invitees[i] = wire.InvitationInvitee{
			AccountID:   iv.AccountID,
			DisplayName: iv.DisplayName,
			Seat:        iv.Seat,
			Response:    iv.Response,
		}
	}
	return wire.BuildInvitation(b, wire.Invitation{
		ID:                   inv.ID,
		Inviter:              wire.AccountRef{AccountID: inv.Inviter.AccountID, DisplayName: inv.Inviter.DisplayName},
		Invitees:             invitees,
		Variant:              inv.Variant,
		TurnTimeoutSecs:      inv.TurnTimeoutSecs,
		HintsAllowed:         inv.HintsAllowed,
		HintsPerPlayer:       inv.HintsPerPlayer,
		MultipleWordsPerTurn: inv.MultipleWordsPerTurn,
		DropoutTiles:         inv.DropoutTiles,
		Status:               inv.Status,
		GameID:               inv.GameID,
		ExpiresAtUnix:        inv.ExpiresAtUnix,
	})
}

// encodeInvitation builds an Invitation payload.
func encodeInvitation(inv backendclient.InvitationResp) []byte {
	b := flatbuffers.NewBuilder(512)
	b.Finish(buildInvitation(b, inv))
	return b.FinishedBytes()
}

// encodeInvitationList builds an InvitationList payload.
func encodeInvitationList(r backendclient.InvitationListResp) []byte {
	b := flatbuffers.NewBuilder(1024)
	offs := make([]flatbuffers.UOffsetT, len(r.Invitations))
	for i, inv := range r.Invitations {
		offs[i] = buildInvitation(b, inv)
	}
	fb.InvitationListStartInvitationsVector(b, len(offs))
	for i := len(offs) - 1; i >= 0; i-- {
		b.PrependUOffsetT(offs[i])
	}
	v := b.EndVector(len(offs))
	fb.InvitationListStart(b)
	fb.InvitationListAddInvitations(b, v)
	b.Finish(fb.InvitationListEnd(b))
	return b.FinishedBytes()
}

// encodeExportURL builds an ExportUrl payload.
func encodeExportURL(r backendclient.ExportURLResp) []byte {
	b := flatbuffers.NewBuilder(256)
	path := b.CreateString(r.Path)
	fn := b.CreateString(r.Filename)
	fb.ExportUrlStart(b)
	fb.ExportUrlAddPath(b, path)
	fb.ExportUrlAddFilename(b, fn)
	b.Finish(fb.ExportUrlEnd(b))
	return b.FinishedBytes()
}

// encodeGcg builds a GcgExport payload.
func encodeGcg(r backendclient.GcgResp) []byte {
	b := flatbuffers.NewBuilder(1024)
	gid := b.CreateString(r.GameID)
	fn := b.CreateString(r.Filename)
	content := b.CreateString(r.Content)
	fb.GcgExportStart(b)
	fb.GcgExportAddGameId(b, gid)
	fb.GcgExportAddFilename(b, fn)
	fb.GcgExportAddContent(b, content)
	b.Finish(fb.GcgExportEnd(b))
	return b.FinishedBytes()
}
