package edge

import (
	fb "scrabble/pkg/fbs/scrabblefb"
)

// Game is the decoded non-private game summary the driver needs to decide a turn.
type Game struct {
	ID        string
	Variant   string
	DictVer   string
	Status    string
	Players   int
	ToMove    int
	MoveCount int
	Seats     []string // account ids in seat order
}

// Active reports whether the game is still in progress.
func (g Game) Active() bool { return g.Status == "active" }

// SeatOf returns the seat index of accountID, or -1 if it is not seated.
func (g Game) SeatOf(accountID string) int {
	for i, id := range g.Seats {
		if id == accountID {
			return i
		}
	}
	return -1
}

// State is a player's private view: the shared game plus their seat, rack (alphabet
// indices; 255 a blank) and bag size.
type State struct {
	Game   Game
	Seat   int
	Rack   []byte
	BagLen int
}

// Tile is one placed tile from a decoded history record (concrete letter, blank flag).
type Tile struct {
	Row, Col int
	Letter   string
	Blank    bool
}

// Move is one decoded history record (a committed play carries Tiles; pass/exchange
// carry only Action).
type Move struct {
	Action string
	Dir    string
	Tiles  []Tile
}

// Invitation is the decoded subset the assembler matches on.
type Invitation struct {
	ID        string
	InviterID string
	Status    string
	GameID    string
}

func decodeGameView(gv *fb.GameView) Game {
	g := Game{
		ID:        string(gv.Id()),
		Variant:   string(gv.Variant()),
		DictVer:   string(gv.DictVersion()),
		Status:    string(gv.Status()),
		Players:   int(gv.Players()),
		ToMove:    int(gv.ToMove()),
		MoveCount: int(gv.MoveCount()),
	}
	n := gv.SeatsLength()
	g.Seats = make([]string, n)
	var sv fb.SeatView
	for j := 0; j < n; j++ {
		if gv.Seats(&sv, j) {
			g.Seats[sv.Seat()] = string(sv.AccountId())
		}
	}
	return g
}

// decodeState reads a StateView payload.
func decodeState(payload []byte) State {
	sv := fb.GetRootAsStateView(payload, 0)
	var gv fb.GameView
	st := State{
		Seat:   int(sv.Seat()),
		BagLen: int(sv.BagLen()),
		Rack:   append([]byte(nil), sv.RackBytes()...),
	}
	if g := sv.Game(&gv); g != nil {
		st.Game = decodeGameView(g)
	}
	return st
}

// decodeHistory reads a History payload into the decoded move journal.
func decodeHistory(payload []byte) []Move {
	h := fb.GetRootAsHistory(payload, 0)
	n := h.MovesLength()
	moves := make([]Move, 0, n)
	var mr fb.MoveRecord
	for j := 0; j < n; j++ {
		if !h.Moves(&mr, j) {
			continue
		}
		m := Move{Action: string(mr.Action()), Dir: string(mr.Dir())}
		tn := mr.TilesLength()
		m.Tiles = make([]Tile, 0, tn)
		var tr fb.TileRecord
		for k := 0; k < tn; k++ {
			if mr.Tiles(&tr, k) {
				m.Tiles = append(m.Tiles, Tile{
					Row: int(tr.Row()), Col: int(tr.Col()),
					Letter: string(tr.Letter()), Blank: tr.Blank(),
				})
			}
		}
		moves = append(moves, m)
	}
	return moves
}

// decodeMoveResultGame reads a MoveResult payload and returns its post-move game.
func decodeMoveResultGame(payload []byte) Game {
	mr := fb.GetRootAsMoveResult(payload, 0)
	var gv fb.GameView
	if g := mr.Game(&gv); g != nil {
		return decodeGameView(g)
	}
	return Game{}
}

// decodeGameList reads a GameList payload.
func decodeGameList(payload []byte) []Game {
	gl := fb.GetRootAsGameList(payload, 0)
	n := gl.GamesLength()
	games := make([]Game, 0, n)
	var gv fb.GameView
	for j := 0; j < n; j++ {
		if gl.Games(&gv, j) {
			games = append(games, decodeGameView(&gv))
		}
	}
	return games
}

// decodeInvitationList reads an InvitationList payload into the matched subset.
func decodeInvitationList(payload []byte) []Invitation {
	il := fb.GetRootAsInvitationList(payload, 0)
	n := il.InvitationsLength()
	out := make([]Invitation, 0, n)
	var inv fb.Invitation
	var ref fb.AccountRef
	for j := 0; j < n; j++ {
		if !il.Invitations(&inv, j) {
			continue
		}
		iv := Invitation{
			ID:     string(inv.Id()),
			Status: string(inv.Status()),
			GameID: string(inv.GameId()),
		}
		if r := inv.Inviter(&ref); r != nil {
			iv.InviterID = string(r.AccountId())
		}
		out = append(out, iv)
	}
	return out
}

// decodeMatch reads a MatchResult payload.
func decodeMatch(payload []byte) (matched bool, game Game) {
	mr := fb.GetRootAsMatchResult(payload, 0)
	if !mr.Matched() {
		return false, Game{}
	}
	var gv fb.GameView
	if g := mr.Game(&gv); g != nil {
		return true, decodeGameView(g)
	}
	return true, Game{}
}
