package game

import (
	"encoding/json"
	"fmt"

	"scrabble/backend/internal/engine"
)

// movePayload is the JSON stored in game_moves.payload. It holds the decoded,
// dictionary-independent values needed both to replay the game through the engine
// and to render history / emit GCG without a dictionary (docs/ARCHITECTURE.md
// §9.1): the acting player's rack before the move, and per action the play's
// direction, main-word anchor, placed tiles and formed words, or an exchange's
// swapped tiles.
type movePayload struct {
	Rack      []string      `json:"rack"`
	Dir       string        `json:"dir,omitempty"`
	MainRow   int           `json:"main_row,omitempty"`
	MainCol   int           `json:"main_col,omitempty"`
	Tiles     []tilePayload `json:"tiles,omitempty"`
	Words     []string      `json:"words,omitempty"`
	Exchanged []string      `json:"exchanged,omitempty"`
}

// tilePayload is one placed tile in a play payload.
type tilePayload struct {
	Row    int    `json:"row"`
	Col    int    `json:"col"`
	Letter string `json:"letter"`
	Blank  bool   `json:"blank,omitempty"`
}

// buildPayload assembles the journal payload from the engine's decoded record,
// the acting rack captured before the move, and (for an exchange) the swapped
// tiles.
func buildPayload(rec engine.MoveRecord, rackBefore, exchanged []string) movePayload {
	p := movePayload{Rack: rackBefore}
	switch rec.Action {
	case engine.ActionPlay:
		p.Dir = rec.Dir.String()
		p.MainRow = rec.MainRow
		p.MainCol = rec.MainCol
		p.Words = rec.Words
		p.Tiles = make([]tilePayload, len(rec.Tiles))
		for i, t := range rec.Tiles {
			p.Tiles[i] = tilePayload{Row: t.Row, Col: t.Col, Letter: t.Letter, Blank: t.Blank}
		}
	case engine.ActionExchange:
		p.Exchanged = exchanged
	}
	return p
}

// marshal renders the payload as the JSON text stored in the column.
func (p movePayload) marshal() (string, error) {
	b, err := json.Marshal(p)
	if err != nil {
		return "", fmt.Errorf("game: marshal move payload: %w", err)
	}
	return string(b), nil
}

// parsePayload parses a stored payload back into its decoded fields.
func parsePayload(s string) (movePayload, error) {
	var p movePayload
	if err := json.Unmarshal([]byte(s), &p); err != nil {
		return movePayload{}, fmt.Errorf("game: parse move payload: %w", err)
	}
	return p, nil
}

// tileRecords converts payload tiles back into engine TileRecords for replay and
// history.
func (p movePayload) tileRecords() []engine.TileRecord {
	out := make([]engine.TileRecord, len(p.Tiles))
	for i, t := range p.Tiles {
		out[i] = engine.TileRecord{Row: t.Row, Col: t.Col, Letter: t.Letter, Blank: t.Blank}
	}
	return out
}

// direction parses the stored "H"/"V" into an engine.Direction.
func (p movePayload) direction() engine.Direction {
	if p.Dir == engine.Vertical.String() {
		return engine.Vertical
	}
	return engine.Horizontal
}
