package edge

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

	fb "scrabble/pkg/fbs/scrabblefb"
)

// PlayTile is one tile to place, addressed by alphabet index (255 marks a blank's
// carrier letter together with Blank=true), as the submit-play request carries it.
type PlayTile struct {
	Row, Col int
	Letter   byte
	Blank    bool
}

// gameAction builds a GameActionRequest payload (just a game id): pass, nudge,
// history, draft.get.
func gameAction(gameID string) []byte {
	b := flatbuffers.NewBuilder(64)
	gid := b.CreateString(gameID)
	fb.GameActionRequestStart(b)
	fb.GameActionRequestAddGameId(b, gid)
	b.Finish(fb.GameActionRequestEnd(b))
	return b.FinishedBytes()
}

// stateReq builds a StateRequest payload. includeAlphabet asks the backend to embed
// the variant alphabet table (the driver sets it once per variant).
func stateReq(gameID string, includeAlphabet bool) []byte {
	b := flatbuffers.NewBuilder(64)
	gid := b.CreateString(gameID)
	fb.StateRequestStart(b)
	fb.StateRequestAddGameId(b, gid)
	fb.StateRequestAddIncludeAlphabet(b, includeAlphabet)
	b.Finish(fb.StateRequestEnd(b))
	return b.FinishedBytes()
}

// submitPlay builds a SubmitPlayRequest payload. tiles are the newly-placed tiles in
// main-word order; the backend infers the play's orientation from them and the board.
func submitPlay(gameID string, tiles []PlayTile) []byte {
	b := flatbuffers.NewBuilder(256)
	gid := b.CreateString(gameID)
	offs := make([]flatbuffers.UOffsetT, len(tiles))
	for i, t := range tiles {
		fb.PlayTileStart(b)
		fb.PlayTileAddRow(b, int32(t.Row))
		fb.PlayTileAddCol(b, int32(t.Col))
		fb.PlayTileAddLetter(b, t.Letter)
		fb.PlayTileAddBlank(b, t.Blank)
		offs[i] = fb.PlayTileEnd(b)
	}
	fb.SubmitPlayRequestStartTilesVector(b, len(offs))
	for i := len(offs) - 1; i >= 0; i-- {
		b.PrependUOffsetT(offs[i])
	}
	tilesVec := b.EndVector(len(offs))
	fb.SubmitPlayRequestStart(b)
	fb.SubmitPlayRequestAddGameId(b, gid)
	fb.SubmitPlayRequestAddTiles(b, tilesVec)
	b.Finish(fb.SubmitPlayRequestEnd(b))
	return b.FinishedBytes()
}

// evalReq builds an EvalRequest payload (game id plus the tentative newly-placed tiles).
// It mirrors submitPlay's shape — the backend infers the play's orientation the same way —
// so a preview previews exactly what submitting those tiles would score.
func evalReq(gameID string, tiles []PlayTile) []byte {
	b := flatbuffers.NewBuilder(256)
	gid := b.CreateString(gameID)
	offs := make([]flatbuffers.UOffsetT, len(tiles))
	for i, t := range tiles {
		fb.PlayTileStart(b)
		fb.PlayTileAddRow(b, int32(t.Row))
		fb.PlayTileAddCol(b, int32(t.Col))
		fb.PlayTileAddLetter(b, t.Letter)
		fb.PlayTileAddBlank(b, t.Blank)
		offs[i] = fb.PlayTileEnd(b)
	}
	fb.EvalRequestStartTilesVector(b, len(offs))
	for i := len(offs) - 1; i >= 0; i-- {
		b.PrependUOffsetT(offs[i])
	}
	tilesVec := b.EndVector(len(offs))
	fb.EvalRequestStart(b)
	fb.EvalRequestAddGameId(b, gid)
	fb.EvalRequestAddTiles(b, tilesVec)
	b.Finish(fb.EvalRequestEnd(b))
	return b.FinishedBytes()
}

// exchange builds an ExchangeRequest payload swapping the listed rack tiles (alphabet
// indices; 255 a blank).
func exchange(gameID string, tiles []byte) []byte {
	b := flatbuffers.NewBuilder(64)
	gid := b.CreateString(gameID)
	vec := b.CreateByteVector(tiles)
	fb.ExchangeRequestStart(b)
	fb.ExchangeRequestAddGameId(b, gid)
	fb.ExchangeRequestAddTiles(b, vec)
	b.Finish(fb.ExchangeRequestEnd(b))
	return b.FinishedBytes()
}

// checkWord builds a CheckWordRequest payload (alphabet indices for the word).
func checkWord(gameID string, word []byte) []byte {
	b := flatbuffers.NewBuilder(64)
	gid := b.CreateString(gameID)
	vec := b.CreateByteVector(word)
	fb.CheckWordRequestStart(b)
	fb.CheckWordRequestAddGameId(b, gid)
	fb.CheckWordRequestAddWord(b, vec)
	b.Finish(fb.CheckWordRequestEnd(b))
	return b.FinishedBytes()
}

// chatPost builds a ChatPostRequest payload.
func chatPost(gameID, body string) []byte {
	b := flatbuffers.NewBuilder(128)
	gid := b.CreateString(gameID)
	bd := b.CreateString(body)
	fb.ChatPostRequestStart(b)
	fb.ChatPostRequestAddGameId(b, gid)
	fb.ChatPostRequestAddBody(b, bd)
	b.Finish(fb.ChatPostRequestEnd(b))
	return b.FinishedBytes()
}

// draftSave builds a DraftRequest payload carrying the opaque composition JSON.
func draftSave(gameID, jsonStr string) []byte {
	b := flatbuffers.NewBuilder(128)
	gid := b.CreateString(gameID)
	j := b.CreateString(jsonStr)
	fb.DraftRequestStart(b)
	fb.DraftRequestAddGameId(b, gid)
	fb.DraftRequestAddJson(b, j)
	b.Finish(fb.DraftRequestEnd(b))
	return b.FinishedBytes()
}

// updateProfile builds an UpdateProfileRequest payload. It resends the marker display
// name and sane defaults so the account stays findable by the seeder's Cleanup.
func updateProfile(displayName, lang string) []byte {
	b := flatbuffers.NewBuilder(192)
	name := b.CreateString(displayName)
	pl := b.CreateString(lang)
	tz := b.CreateString("UTC")
	as := b.CreateString("00:00")
	ae := b.CreateString("07:00")
	fb.UpdateProfileRequestStart(b)
	fb.UpdateProfileRequestAddDisplayName(b, name)
	fb.UpdateProfileRequestAddPreferredLanguage(b, pl)
	fb.UpdateProfileRequestAddTimeZone(b, tz)
	fb.UpdateProfileRequestAddAwayStart(b, as)
	fb.UpdateProfileRequestAddAwayEnd(b, ae)
	fb.UpdateProfileRequestAddBlockChat(b, false)
	fb.UpdateProfileRequestAddBlockFriendRequests(b, false)
	fb.UpdateProfileRequestAddNotificationsInAppOnly(b, true)
	b.Finish(fb.UpdateProfileRequestEnd(b))
	return b.FinishedBytes()
}

// createInvitation builds a CreateInvitationRequest payload. turnTimeoutSecs 0 asks
// the backend for its default; dropoutTiles "remove" is the standard policy.
func createInvitation(inviteeIDs []string, variant string, turnTimeoutSecs int) []byte {
	b := flatbuffers.NewBuilder(256)
	idOffs := make([]flatbuffers.UOffsetT, len(inviteeIDs))
	for i, id := range inviteeIDs {
		idOffs[i] = b.CreateString(id)
	}
	fb.CreateInvitationRequestStartInviteeIdsVector(b, len(idOffs))
	for i := len(idOffs) - 1; i >= 0; i-- {
		b.PrependUOffsetT(idOffs[i])
	}
	ids := b.EndVector(len(idOffs))
	variantOff := b.CreateString(variant)
	dropout := b.CreateString("remove")
	fb.CreateInvitationRequestStart(b)
	fb.CreateInvitationRequestAddInviteeIds(b, ids)
	fb.CreateInvitationRequestAddVariant(b, variantOff)
	fb.CreateInvitationRequestAddTurnTimeoutSecs(b, int32(turnTimeoutSecs))
	fb.CreateInvitationRequestAddHintsAllowed(b, true)
	fb.CreateInvitationRequestAddHintsPerPlayer(b, 1)
	fb.CreateInvitationRequestAddDropoutTiles(b, dropout)
	b.Finish(fb.CreateInvitationRequestEnd(b))
	return b.FinishedBytes()
}

// invitationAction builds an InvitationActionRequest payload (accept / decline /
// cancel by id).
func invitationAction(invitationID string) []byte {
	b := flatbuffers.NewBuilder(64)
	id := b.CreateString(invitationID)
	fb.InvitationActionRequestStart(b)
	fb.InvitationActionRequestAddInvitationId(b, id)
	b.Finish(fb.InvitationActionRequestEnd(b))
	return b.FinishedBytes()
}

// enqueueReq builds an EnqueueRequest payload (join the per-variant auto-match pool).
func enqueueReq(variant string) []byte {
	b := flatbuffers.NewBuilder(64)
	v := b.CreateString(variant)
	fb.EnqueueRequestStart(b)
	fb.EnqueueRequestAddVariant(b, v)
	b.Finish(fb.EnqueueRequestEnd(b))
	return b.FinishedBytes()
}
