// Package board holds the compact game board: a row-major grid of cell bytes encoded
// per internal/encoding (0 = empty, letter+1, with 0x80 marking a blank). It is
// otherwise alphabet-agnostic.
package board

import (
	"fmt"
	"unicode"

	"github.com/iliadenisov/alphabet"

	"gitea.iliadenisov.ru/developer/scrabble-solver/internal/encoding"
)

// Board is a row-major grid of encoded cells.
type Board struct {
	rows, cols int
	cells      []byte
}

// New returns an empty rows×cols board.
func New(rows, cols int) *Board {
	return &Board{rows: rows, cols: cols, cells: make([]byte, rows*cols)}
}

// Rows returns the number of rows.
func (b *Board) Rows() int { return b.rows }

// Cols returns the number of columns.
func (b *Board) Cols() int { return b.cols }

// At returns the encoded cell at (r, c).
func (b *Board) At(r, c int) byte { return b.cells[r*b.cols+c] }

// Set stores the encoded cell v at (r, c).
func (b *Board) Set(r, c int, v byte) { b.cells[r*b.cols+c] = v }

// InBounds reports whether (r, c) is on the board.
func (b *Board) InBounds(r, c int) bool {
	return r >= 0 && r < b.rows && c >= 0 && c < b.cols
}

// Empty reports whether (r, c) is an empty square.
func (b *Board) Empty(r, c int) bool { return encoding.IsEmpty(b.cells[r*b.cols+c]) }

// Filled reports whether (r, c) is on the board and occupied.
func (b *Board) Filled(r, c int) bool {
	return b.InBounds(r, c) && !encoding.IsEmpty(b.cells[r*b.cols+c])
}

// IsEmpty reports whether the whole board is empty (used for the first move).
func (b *Board) IsEmpty() bool {
	for _, c := range b.cells {
		if !encoding.IsEmpty(c) {
			return false
		}
	}
	return true
}

// Clone returns a deep copy of the board.
func (b *Board) Clone() *Board {
	cp := &Board{rows: b.rows, cols: b.cols, cells: make([]byte, len(b.cells))}
	copy(cp.cells, b.cells)
	return cp
}

// Transpose returns a new board with rows and columns swapped, turning vertical lines
// into horizontal ones. Down-play generation runs on the transpose.
func (b *Board) Transpose() *Board {
	t := &Board{rows: b.cols, cols: b.rows, cells: make([]byte, len(b.cells))}
	for r := range b.rows {
		for c := range b.cols {
			t.cells[c*t.cols+r] = b.cells[r*b.cols+c]
		}
	}
	return t
}

// Parse builds a board from text rows: '.' (or space) is an empty square, a lowercase
// letter is a normal tile, and an uppercase letter is a blank standing for that letter.
// Letters are resolved through idx.
func Parse(rows []string, idx alphabet.Indexer) (*Board, error) {
	if len(rows) == 0 {
		return nil, fmt.Errorf("board: no rows")
	}
	cols := len([]rune(rows[0]))
	b := New(len(rows), cols)
	for r, line := range rows {
		runes := []rune(line)
		for c := 0; c < cols && c < len(runes); c++ {
			ch := runes[c]
			if ch == '.' || ch == ' ' {
				continue
			}
			blank := unicode.IsUpper(ch)
			li, err := idx.Index(string(unicode.ToLower(ch)))
			if err != nil {
				return nil, fmt.Errorf("board: row %d col %d %q: %w", r, c, string(ch), err)
			}
			b.Set(r, c, encoding.Cell(li, blank))
		}
	}
	return b, nil
}
