package main

import (
	"fmt"
	"strings"

	tea "github.com/charmbracelet/bubbletea"
)

// klondikePileRef — ссылка на стопку: колонна (0-6), отбой (-1) или
// "ничего не выбрано" (-2).
type klondikePileRef int

const (
	klondikeNoSelection klondikePileRef = -2
	klondikeWasteRef    klondikePileRef = -1
)

// KlondikeModel — TUI поверх KlondikeGameState. Управление в два
// шага: сначала выбираем источник (стопку, откуда берём карты),
// затем — куда их переложить.
type KlondikeModel struct {
	game     *KlondikeGameState
	selected klondikePileRef

	// runLen — сколько карт с конца выбранной колонны-источника
	// сейчас включено в перенос (от 1 до полной корректной
	// последовательности movableRun). Меняется клавишами ↑/↓, пока
	// выбрана колонна (для отбоя не имеет смысла — там всегда 1).
	runLen int

	message string
	isError bool

	quitting   bool
	backToMenu bool
}

// NewKlondikeModel создаёт новую партию пасьянса.
func NewKlondikeModel() KlondikeModel {
	return KlondikeModel{
		game:     NewKlondikeGame(),
		selected: klondikeNoSelection,
	}
}

func (m KlondikeModel) Init() tea.Cmd {
	return nil
}

func (m *KlondikeModel) setInfo(format string, args ...interface{}) {
	m.message = fmt.Sprintf(format, args...)
	m.isError = false
}

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

func (m *KlondikeModel) clearSelection() {
	m.selected = klondikeNoSelection
	m.runLen = 0
}

func (m KlondikeModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	keyMsg, ok := msg.(tea.KeyMsg)
	if !ok {
		return m, nil
	}
	return m.handleKey(keyMsg)
}

func (m KlondikeModel) 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.Won {
		if key == "n" {
			m.game = NewKlondikeGame()
			m.clearSelection()
			m.message = ""
			m.isError = false
		}
		return m, nil
	}

	switch key {
	case "n":
		m.game = NewKlondikeGame()
		m.clearSelection()
		m.message = ""
		m.isError = false
		return m, nil
	case "u":
		if err := m.game.Undo(); err != nil {
			m.setError(err)
			return m, nil
		}
		m.clearSelection()
		m.setInfo(T("klondike.msg.undone"))
		return m, nil
	case "esc":
		m.clearSelection()
		m.message = ""
		return m, nil
	case "d":
		m.clearSelection()
		if err := m.game.DrawStock(); err != nil {
			m.setError(err)
			return m, nil
		}
		m.setInfo(T("klondike.msg.drew"))
		return m, nil
	case "1", "2", "3", "4", "5", "6", "7":
		col := int(key[0] - '1')
		return m.handlePileKey(klondikePileRef(col))
	case "0":
		return m.handlePileKey(klondikeWasteRef)
	case "f":
		return m.handleFoundationKey()
	case "up", "k":
		if m.selected != klondikeNoSelection && m.selected != klondikeWasteRef && m.runLen < m.game.movableRun(int(m.selected)) {
			m.runLen++
		}
		return m, nil
	case "down", "j":
		if m.selected != klondikeNoSelection && m.selected != klondikeWasteRef && m.runLen > 1 {
			m.runLen--
		}
		return m, nil
	}
	return m, nil
}

// handlePileKey обрабатывает нажатие клавиши колонны (1-7) или
// отбоя (0): если ничего не выбрано — пытается выбрать эту стопку
// как источник; если что-то уже выбрано — пытается переложить туда.
func (m KlondikeModel) handlePileKey(ref klondikePileRef) (tea.Model, tea.Cmd) {
	if m.selected == klondikeNoSelection {
		if !m.pileHasMovableCard(ref) {
			m.setError(ErrKlondikeEmptySource)
			return m, nil
		}
		m.selected = ref
		if ref == klondikeWasteRef {
			m.runLen = 1
		} else {
			m.runLen = m.game.movableRun(int(ref))
		}
		m.message = ""
		return m, nil
	}

	if m.selected == ref {
		m.clearSelection()
		return m, nil
	}

	var err error
	if m.selected == klondikeWasteRef {
		err = m.game.MoveWasteToTableau(int(ref))
	} else {
		err = m.game.MoveTableauToTableauN(int(m.selected), int(ref), m.runLen)
	}
	if err != nil {
		m.setError(err)
		return m, nil
	}
	m.setInfo(T("klondike.msg.moved"))
	m.clearSelection()
	return m, nil
}

func (m KlondikeModel) handleFoundationKey() (tea.Model, tea.Cmd) {
	if m.selected == klondikeNoSelection {
		m.setError(ErrKlondikeEmptySource)
		return m, nil
	}
	var err error
	if m.selected == klondikeWasteRef {
		err = m.game.MoveWasteToFoundation()
	} else {
		if m.runLen != 1 {
			err = ErrKlondikeMultiToFoundation
		} else {
			err = m.game.MoveTableauToFoundation(int(m.selected))
		}
	}
	if err != nil {
		m.setError(err)
		return m, nil
	}
	m.setInfo(T("klondike.msg.to_foundation"))
	m.clearSelection()
	return m, nil
}

func (m KlondikeModel) pileHasMovableCard(ref klondikePileRef) bool {
	if ref == klondikeWasteRef {
		return len(m.game.Waste) > 0
	}
	return m.game.movableRun(int(ref)) > 0
}

// --- Отрисовка -----------------------------------------------------

func (m KlondikeModel) renderFoundations() string {
	suits := []Suit{Clubs, Diamonds, Hearts, Spades}
	boxes := make([][]string, 4)
	for i, s := range suits {
		rank := m.game.Foundations[s]
		if rank == 0 {
			top := "┌───┐"
			bottom := "└───┘"
			label := dimStyle.Render(" " + s.String() + " ")
			boxes[i] = []string{dimStyle.Render(top), "│" + label + "│", "│   │", dimStyle.Render(bottom)}
			continue
		}
		card := Card{Suit: s, Rank: Rank(rank)}
		boxes[i] = renderCardBox(card, CardNormal)
	}
	lines := make([]string, 4)
	for row := 0; row < 4; row++ {
		parts := make([]string, 4)
		for i := range suits {
			parts[i] = boxes[i][row]
		}
		lines[row] = strings.Join(parts, " ")
	}
	return strings.Join(lines, "\n")
}

func (m KlondikeModel) renderStockWaste() string {
	stockLine := Tf("klondike.stock_line", len(m.game.Stock))
	if len(m.game.Waste) == 0 {
		return stockLine + "   " + dimStyle.Render(T("klondike.empty"))
	}
	top := m.game.Waste[len(m.game.Waste)-1]
	highlight := CardNormal
	if m.selected == klondikeWasteRef {
		highlight = CardCursor
	}
	waste := strings.Join(renderCardBox(top, highlight), "\n")
	return stockLine + "   " + T("klondike.waste_label") + "\n" + waste
}

func (m KlondikeModel) renderTableau() string {
	var rows []string
	maxLen := 0
	for _, col := range m.game.Tableau {
		if len(col) > maxLen {
			maxLen = len(col)
		}
	}

	header := make([]string, 7)
	for i := range header {
		label := fmt.Sprintf(" %d ", i+1)
		if m.selected == klondikePileRef(i) {
			label = cursorStyle.Render(label)
		} else {
			label = dimStyle.Render(label)
		}
		header[i] = label
	}
	rows = append(rows, strings.Join(header, "  "))

	for row := 0; row < maxLen; row++ {
		cells := make([]string, 7)
		for col := 0; col < 7; col++ {
			if row >= len(m.game.Tableau[col]) {
				cells[col] = "   "
				continue
			}
			kc := m.game.Tableau[col][row]
			if !kc.FaceUp {
				cells[col] = dimStyle.Render("[?]")
				continue
			}
			// паддинг до фиксированной ширины 3 (как у "[?]" и самой
			// длинной карты "10♠") ДО применения цвета — иначе более
			// короткие карты (например "A♠", 2 символа против 3 у
			// "10♠") сдвигали бы соседние колонки в той же строке
			padded := fmt.Sprintf("%-3s", kc.Card.String())
			text := cardColorStyle(kc.Card).Render(padded)
			isTopOfSelectedRun := m.selected == klondikePileRef(col) &&
				row >= len(m.game.Tableau[col])-m.runLen
			if isTopOfSelectedRun {
				text = cursorStyle.Render(padded)
			}
			cells[col] = text
		}
		rows = append(rows, strings.Join(cells, "  "))
	}
	return strings.Join(rows, "\n")
}

func (m KlondikeModel) View() string {
	if m.quitting {
		return T("common.goodbye")
	}

	var b strings.Builder
	fmt.Fprintln(&b, titleStyle.Render(T("klondike.title")))
	fmt.Fprintln(&b)
	fmt.Fprintf(&b, T("klondike.foundations_line"), m.renderFoundations())
	fmt.Fprintln(&b, m.renderStockWaste())
	fmt.Fprintf(&b, T("klondike.moves_line"), m.game.Moves)
	fmt.Fprintln(&b, m.renderTableau())
	fmt.Fprintln(&b)

	if m.game.Won {
		fmt.Fprintln(&b, winStyle.Render(Tf("klondike.won", m.game.Moves)))
		fmt.Fprintln(&b)
		fmt.Fprintln(&b, dimStyle.Render(T("klondike.hint.gameover")))
		return b.String()
	}

	if m.selected == klondikeNoSelection {
		fmt.Fprintln(&b, T("klondike.hint.noselect"))
	} else {
		fmt.Fprintln(&b, T("klondike.hint.selected"))
		if m.selected != klondikeWasteRef && m.game.movableRun(int(m.selected)) > 1 {
			fmt.Fprintln(&b, dimStyle.Render(Tf("klondike.hint.runlen", m.runLen, m.game.movableRun(int(m.selected)))))
		}
	}
	fmt.Fprintln(&b, dimStyle.Render(T("klondike.hint.footer")))

	fmt.Fprintln(&b)
	if m.message != "" {
		if m.isError {
			fmt.Fprintln(&b, errorStyle.Render("! "+m.message))
		} else {
			fmt.Fprintln(&b, infoStyle.Render(m.message))
		}
	}

	return b.String()
}
