package main

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

	tea "github.com/charmbracelet/bubbletea"
)

// NardyModel — TUI поверх NardyGameState. Ходы выбираются из
// пронумерованного списка легальных ходов (а не курсором по доске)
// — так же, как уже сделано для боевых действий Странника и
// действий Трупных батончиков: в нардах набор легальных ходов после
// броска костей обычно невелик (как правило до 8-9), и пронумерованный
// список полностью снимает вопрос двумерной навигации по доске.
type NardyModel struct {
	game       *NardyGameState
	bot        NardyBot
	rng        *rand.Rand
	humanColor NardyPlayer

	message string
	isError bool

	// choosingDie / pendingSourceMoves — подрежим выбора кости, когда
	// у выбранного источника больше одного варианта хода (см.
	// nardySourceGroups). Разделение на два шага нужно, потому что
	// плоский список всех легальных ходов в мидгейме коротких нард
	// может заметно превышать 9 пунктов, а различных ИСТОЧНИКОВ хода
	// (с какой шашки) на практике почти никогда не бывает больше 8.
	choosingDie        bool
	pendingSourceMoves []NardyMove

	quitting   bool
	backToMenu bool
}

// NewNardyModel начинает новую партию. Кто ходит первым — решается
// случайным броском одного зара каждой стороне (по правилам, у кого
// больше — тот и первый); при равенстве — переброс, как и
// предписывают правила. Человек всегда играет белыми в этой версии
// (цвет — условность отображения, на баланс сил не влияет).
func NewNardyModel(variant NardyVariant, difficulty NardyDifficulty, colorChoice ColorChoice) NardyModel {
	rng := rand.New(rand.NewSource(time.Now().UnixNano()))
	humanColor := resolveNardyColor(colorChoice, rng)
	firstPlayer := NardyWhite
	for {
		human, bot := rng.Intn(6)+1, rng.Intn(6)+1
		if human != bot {
			if human > bot {
				firstPlayer = humanColor
			} else {
				firstPlayer = nardyOpponent(humanColor)
			}
			break
		}
	}
	return NardyModel{
		game:       NewNardyGame(variant, firstPlayer),
		bot:        NardyBot{Difficulty: difficulty},
		rng:        rng,
		humanColor: humanColor,
	}
}

// resolveNardyColor переводит выбор цвета из настроек в конкретный
// NardyPlayer, используя уже созданный генератор модели для
// случайного выбора (в отличие от Шашек/Шахмат/Го, у Нард и так уже
// есть постоянный rng для бросков костей).
func resolveNardyColor(choice ColorChoice, rng *rand.Rand) NardyPlayer {
	switch choice {
	case ColorChoiceA:
		return NardyWhite
	case ColorChoiceB:
		return NardyBlack
	default:
		if rng.Intn(2) == 0 {
			return NardyWhite
		}
		return NardyBlack
	}
}

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

func (m NardyModel) maybeScheduleBot() tea.Cmd {
	if m.game.Phase == NardyPhaseOver {
		return nil
	}
	if m.game.CurrentPlayer == m.humanColor {
		return nil
	}
	return tea.Tick(botMoveDelay, func(time.Time) tea.Msg { return botMoveMsg{} })
}

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

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

func (m NardyModel) 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 NardyModel) handleBotMove() (tea.Model, tea.Cmd) {
	if m.game.Phase == NardyPhaseOver || 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("nardy.msg.bot_moved")
	return m, m.maybeScheduleBot()
}

// nardySourceGroups группирует легальные ходы по источнику (с бара
// или с конкретной точки) — на практике различных источников редко
// бывает больше 8, даже когда общий список ходов (по всем костям и
// точкам вместе) заметно превышает 9.
func nardySourceGroups(moves []NardyMove) [][]NardyMove {
	var order []string
	groups := map[string][]NardyMove{}
	for _, mv := range moves {
		key := fmt.Sprintf("%v-%d", mv.FromBar, mv.From)
		if _, ok := groups[key]; !ok {
			order = append(order, key)
		}
		groups[key] = append(groups[key], mv)
	}
	out := make([][]NardyMove, 0, len(order))
	for _, key := range order {
		out = append(out, groups[key])
	}
	return out
}

func nardySourceLabel(mv NardyMove) string {
	if mv.FromBar {
		return T("nardy.source_bar")
	}
	return fmt.Sprintf("%d", mv.From+1)
}

func (m NardyModel) 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.Phase == NardyPhaseOver || m.game.CurrentPlayer != m.humanColor {
		return m, nil
	}

	if m.choosingDie {
		if key == "esc" {
			m.choosingDie = false
			m.pendingSourceMoves = nil
			return m, nil
		}
		for i := 1; i <= 9; i++ {
			if key != fmt.Sprintf("%d", i) || i > len(m.pendingSourceMoves) {
				continue
			}
			mv := m.pendingSourceMoves[i-1]
			m.choosingDie = false
			m.pendingSourceMoves = nil
			return m.applyHumanMove(mv)
		}
		return m, nil
	}

	if m.game.Phase == NardyPhaseRoll {
		if key == "r" || key == "enter" || key == " " {
			m.game.Roll(m.rng.Intn(6)+1, m.rng.Intn(6)+1)
			if m.game.CurrentPlayer != m.humanColor {
				m.setInfo("nardy.msg.no_legal_moves")
				return m, m.maybeScheduleBot()
			}
			m.message = ""
			return m, nil
		}
		return m, nil
	}

	// NardyPhaseMove: выбор источника хода (шаг 1 из потенциально двух)
	groups := nardySourceGroups(m.game.LegalMovesNow())
	for i := 1; i <= 9; i++ {
		if key != fmt.Sprintf("%d", i) || i > len(groups) {
			continue
		}
		group := groups[i-1]
		if len(group) == 1 {
			return m.applyHumanMove(group[0])
		}
		m.choosingDie = true
		m.pendingSourceMoves = group
		return m, nil
	}
	return m, nil
}

// applyHumanMove применяет один выбранный ход человека и планирует
// следующий шаг (ход бота, если дошла его очередь, либо ожидание
// следующего выбора).
func (m NardyModel) applyHumanMove(mv NardyMove) (tea.Model, tea.Cmd) {
	if err := m.game.ApplyMove(mv); err != nil {
		m.setError(err)
		return m, nil
	}
	m.message = ""
	if m.game.Phase == NardyPhaseOver {
		m.setInfo("nardy.msg.game_over")
		return m, nil
	}
	return m, m.maybeScheduleBot()
}

// nardyPlayerSymbol — короткое обозначение цвета для отображения.
func nardyPlayerSymbol(p NardyPlayer) string {
	if p == NardyWhite {
		return "○"
	}
	return "●"
}

// nardyMoveLabel формирует человекочитаемое описание одного хода в
// традиционной нотации нард (точки нумеруются 1-24 от дома игрока,
// а не 0-23 как во внутреннем представлении).
func nardyMoveLabel(mv NardyMove) string {
	from := "с бара"
	var dest string
	if mv.FromBar {
		entryRel := 24 - mv.Die
		dest = fmt.Sprintf("%d", entryRel+1)
	} else {
		from = fmt.Sprintf("%d", mv.From+1)
		if mv.BearOff {
			dest = "снятие"
		} else {
			dest = fmt.Sprintf("%d", mv.From-mv.Die+1)
		}
	}
	return Tf("nardy.move_label", from, dest, mv.Die)
}

func (m NardyModel) View() string {
	if m.quitting {
		return T("common.goodbye")
	}
	var b strings.Builder
	title := T("nardy.title.short")
	if m.game.Variant == NardyLong {
		title = T("nardy.title.long")
	}
	fmt.Fprintln(&b, titleStyle.Render(title))
	fmt.Fprintln(&b)
	fmt.Fprintln(&b, m.renderBoard())
	fmt.Fprintln(&b)
	fmt.Fprintln(&b, Tf("nardy.bar_off_line",
		nardyPlayerSymbol(NardyWhite), m.game.Bar[NardyWhite],
		nardyPlayerSymbol(NardyBlack), m.game.Bar[NardyBlack],
		nardyPlayerSymbol(NardyWhite), m.game.BorneOff[NardyWhite],
		nardyPlayerSymbol(NardyBlack), m.game.BorneOff[NardyBlack]))
	fmt.Fprintln(&b)

	you := Tf("nardy.you_are", nardyPlayerSymbol(m.humanColor))
	fmt.Fprintln(&b, dimStyle.Render(you))

	switch m.game.Phase {
	case NardyPhaseOver:
		if m.game.Result.Winner == m.humanColor {
			fmt.Fprintln(&b, winStyle.Render(T("nardy.outcome.win")))
		} else {
			fmt.Fprintln(&b, errorStyle.Render(T("nardy.outcome.lose")))
		}
	case NardyPhaseRoll:
		if m.game.CurrentPlayer == m.humanColor {
			fmt.Fprintln(&b, T("nardy.hint.roll"))
		} else {
			fmt.Fprintln(&b, T("common.thinking_generic"))
		}
	case NardyPhaseMove:
		fmt.Fprintln(&b, Tf("nardy.dice_line", m.game.Dice[0], m.game.Dice[1], strings.Trim(fmt.Sprint(m.game.DiceLeft), "[]")))
		if m.game.CurrentPlayer == m.humanColor {
			if m.choosingDie {
				fmt.Fprintln(&b, T("nardy.hint.pick_die"))
				for i, mv := range m.pendingSourceMoves {
					if i >= 9 {
						break
					}
					fmt.Fprintf(&b, "[%d] %s\n", i+1, nardyMoveLabel(mv))
				}
				fmt.Fprintln(&b, dimStyle.Render(T("nardy.hint.cancel_source")))
			} else {
				fmt.Fprintln(&b, T("nardy.hint.pick_source"))
				groups := nardySourceGroups(m.game.LegalMovesNow())
				for i, group := range groups {
					if i >= 9 {
						break
					}
					fmt.Fprintf(&b, "[%d] %s\n", i+1, nardySourceLabel(group[0]))
				}
			}
		} 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("nardy.hint.quit")))
	return b.String()
}

// renderBoard рисует компактное текстовое представление доски: две
// строки по 12 точек, всегда с точки зрения играющего за белых
// (традиционная нумерация 1-24) — единая система отсчёта на экране
// независимо от того, за кого в этой партии играет человек.
func (m NardyModel) renderBoard() string {
	var b strings.Builder
	cell := func(abs int) string {
		pt := m.game.Board[abs]
		if pt.Count == 0 {
			return dimStyle.Render(" . ")
		}
		return fmt.Sprintf("%s%-2d", nardyPlayerSymbol(pt.Owner), pt.Count)
	}

	var top []string
	for abs := 12; abs <= 23; abs++ {
		top = append(top, cell(abs))
	}
	fmt.Fprintln(&b, "13  14  15  16  17  18   19  20  21  22  23  24")
	fmt.Fprintln(&b, strings.Join(top, " "))
	fmt.Fprintln(&b)
	var bottom []string
	for abs := 11; abs >= 0; abs-- {
		bottom = append(bottom, cell(abs))
	}
	fmt.Fprintln(&b, strings.Join(bottom, " "))
	fmt.Fprint(&b, "12  11  10   9   8   7    6   5   4   3   2   1")
	return b.String()
}
