package main

import (
	"fmt"
	"math/rand"
	"strings"
	"time"

	tea "github.com/charmbracelet/bubbletea"
)

// UrModel — TUI поверх UrGameState.
type UrModel struct {
	game       *UrGameState
	bot        UrBot
	rng        *rand.Rand
	humanColor UrPlayer

	message string
	isError bool

	quitting   bool
	backToMenu bool
}

// NewUrModel начинает новую партию; кто ходит первым — решается
// случайным броском (как и предписывают правила).
func NewUrModel(difficulty UrDifficulty, colorChoice ColorChoice) UrModel {
	rng := rand.New(rand.NewSource(time.Now().UnixNano()))
	humanColor := resolveUrColor(colorChoice, rng)
	first := UrPlayerA
	if rng.Intn(2) == 1 {
		first = UrPlayerB
	}
	return UrModel{
		game:       NewUrGame(first),
		bot:        UrBot{Difficulty: difficulty},
		rng:        rng,
		humanColor: humanColor,
	}
}

// resolveUrColor переводит выбор цвета из настроек в конкретного
// UrPlayer, используя уже созданный генератор модели.
func resolveUrColor(choice ColorChoice, rng *rand.Rand) UrPlayer {
	switch choice {
	case ColorChoiceA:
		return UrPlayerA
	case ColorChoiceB:
		return UrPlayerB
	default:
		if rng.Intn(2) == 0 {
			return UrPlayerA
		}
		return UrPlayerB
	}
}

func (m UrModel) Init() tea.Cmd {
	return m.maybeScheduleBot()
}

func (m UrModel) maybeScheduleBot() tea.Cmd {
	if m.game.Result != nil || m.game.CurrentPlayer == m.humanColor {
		return nil
	}
	return tea.Tick(botMoveDelay, func(time.Time) tea.Msg { return botMoveMsg{} })
}

func (m *UrModel) setInfo(key string) {
	m.message = T(key)
	m.isError = false
}

func (m *UrModel) setError(err error) {
	m.message = err.Error()
	m.isError = true
}

func (m UrModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case botMoveMsg:
		return m.handleBotMove()
	case tea.KeyMsg:
		return m.handleKey(msg)
	}
	return m, nil
}

func (m UrModel) handleBotMove() (tea.Model, tea.Cmd) {
	if m.game.Result != nil || m.game.CurrentPlayer == m.humanColor {
		return m, nil
	}
	if err := m.bot.PlayFullTurn(m.game, m.rng); err != nil {
		m.setError(err)
		return m, nil
	}
	m.setInfo("ur.msg.bot_moved")
	return m, m.maybeScheduleBot()
}

func (m UrModel) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
	key := msg.String()
	if key == "ctrl+c" {
		m.quitting = true
		return m, tea.Quit
	}
	if key == "q" {
		m.backToMenu = true
		return m, nil
	}

	if m.game.Result != nil || m.game.CurrentPlayer != m.humanColor {
		return m, nil
	}

	if !m.game.Rolled {
		if key == "r" || key == "enter" || key == " " {
			if err := m.game.Roll(urRollDice(m.rng)); err != nil {
				m.setError(err)
				return m, nil
			}
			if m.game.CurrentPlayer != m.humanColor {
				m.setInfo("ur.msg.no_legal_moves")
				return m, m.maybeScheduleBot()
			}
			m.message = ""
			return m, nil
		}
		return m, nil
	}

	for i := 1; i <= 9; i++ {
		if key != fmt.Sprintf("%d", i) {
			continue
		}
		moves := m.game.LegalMoves()
		if i > len(moves) {
			return m, nil
		}
		if err := m.game.Move(moves[i-1].PieceIdx); err != nil {
			m.setError(err)
			return m, nil
		}
		m.message = ""
		if m.game.Result != nil {
			m.setInfo("ur.msg.game_over")
			return m, nil
		}
		return m, m.maybeScheduleBot()
	}
	return m, nil
}

func urPlayerSymbol(p UrPlayer) string {
	if p == UrPlayerA {
		return "○"
	}
	return "●"
}

// urMoveLabel описывает один ход человекочитаемо: откуда и куда
// (в терминах 1-индексированного пути игрока, 0=рука, 15=снятие).
func urMoveLabel(g *UrGameState, mv UrMove) string {
	pos := g.Path[g.CurrentPlayer][mv.PieceIdx]
	dest := pos + g.DiceValue
	from := T("ur.hand")
	if pos > 0 {
		from = fmt.Sprintf("%d", pos)
	}
	to := T("ur.off")
	if dest <= UrPathLength {
		to = fmt.Sprintf("%d", dest)
	}
	return Tf("ur.move_label", from, to)
}

func (m UrModel) View() string {
	if m.quitting {
		return T("common.goodbye")
	}
	var b strings.Builder
	fmt.Fprintln(&b, titleStyle.Render(T("ur.title")))
	fmt.Fprintln(&b)
	fmt.Fprintln(&b, m.renderBoard())
	fmt.Fprintln(&b)
	fmt.Fprintln(&b, Tf("ur.you_are", urPlayerSymbol(m.humanColor)))
	fmt.Fprintln(&b, Tf("ur.hand_off_line",
		urPlayerSymbol(UrPlayerA), m.countInHand(UrPlayerA),
		urPlayerSymbol(UrPlayerB), m.countInHand(UrPlayerB),
		urPlayerSymbol(UrPlayerA), m.countBorneOff(UrPlayerA),
		urPlayerSymbol(UrPlayerB), m.countBorneOff(UrPlayerB)))

	switch {
	case m.game.Result != nil:
		if m.game.Result.Winner == m.humanColor {
			fmt.Fprintln(&b, winStyle.Render(T("ur.outcome.win")))
		} else {
			fmt.Fprintln(&b, errorStyle.Render(T("ur.outcome.lose")))
		}
	case !m.game.Rolled:
		if m.game.CurrentPlayer == m.humanColor {
			fmt.Fprintln(&b, T("ur.hint.roll"))
		} else {
			fmt.Fprintln(&b, T("common.thinking_generic"))
		}
	default:
		fmt.Fprintln(&b, Tf("ur.dice_line", m.game.DiceValue))
		if m.game.CurrentPlayer == m.humanColor {
			moves := m.game.LegalMoves()
			if len(moves) == 0 {
				fmt.Fprintln(&b, T("ur.hint.no_moves"))
			} else {
				fmt.Fprintln(&b, T("ur.hint.pick_move"))
				for i, mv := range moves {
					if i >= 9 {
						break
					}
					fmt.Fprintf(&b, "[%d] %s\n", i+1, urMoveLabel(m.game, mv))
				}
			}
		} else {
			fmt.Fprintln(&b, T("common.thinking_generic"))
		}
	}

	if m.message != "" {
		if m.isError {
			fmt.Fprintln(&b, errorStyle.Render("! "+m.message))
		} else {
			fmt.Fprintln(&b, infoStyle.Render(m.message))
		}
	}
	fmt.Fprintln(&b)
	fmt.Fprintln(&b, dimStyle.Render(T("ur.hint.quit")))
	return b.String()
}

func (m UrModel) countInHand(p UrPlayer) int {
	n := 0
	for _, pos := range m.game.Path[p] {
		if pos == 0 {
			n++
		}
	}
	return n
}

func (m UrModel) countBorneOff(p UrPlayer) int {
	n := 0
	for _, pos := range m.game.Path[p] {
		if pos == UrPathLength+1 {
			n++
		}
	}
	return n
}

// renderBoard рисует схематичную доску Ура: два частных ряда (по 4
// клетки входа и 2 выхода, с разрывом на месте "мостика", которого
// в этих рядах физически нет) и общий средний ряд из восьми клеток.
func (m UrModel) renderBoard() string {
	var b strings.Builder

	privateCell := func(player UrPlayer, pathIdx int) string {
		if _, occupied := m.game.occupantOnPrivate(player, pathIdx); occupied {
			return " " + urPlayerSymbol(player) + " "
		}
		if urRosettePathIndices[pathIdx] {
			return dimStyle.Render(" ✿ ")
		}
		return dimStyle.Render(" · ")
	}
	sharedCell := func(sharedIdx int) string {
		pathIdx := sharedIdx + 5
		if occ, _, occupied := m.game.occupantOnShared(sharedIdx); occupied {
			return " " + urPlayerSymbol(occ) + " "
		}
		if urRosettePathIndices[pathIdx] {
			return dimStyle.Render(" ✿ ")
		}
		return dimStyle.Render(" · ")
	}

	var topRow, midRow, botRow []string
	for i := 1; i <= 4; i++ {
		topRow = append(topRow, privateCell(UrPlayerA, i))
		botRow = append(botRow, privateCell(UrPlayerB, i))
	}
	topRow = append(topRow, "   ")
	botRow = append(botRow, "   ")
	for i := 13; i <= 14; i++ {
		topRow = append(topRow, privateCell(UrPlayerA, i))
		botRow = append(botRow, privateCell(UrPlayerB, i))
	}
	for i := 0; i <= 7; i++ {
		midRow = append(midRow, sharedCell(i))
	}

	fmt.Fprintln(&b, strings.Join(topRow, ""))
	fmt.Fprintln(&b, strings.Join(midRow, ""))
	fmt.Fprint(&b, strings.Join(botRow, ""))
	return b.String()
}
