package generator

import (
	"fmt"
	"strings"
	"termdoku/internal/solver"
	"time"
)

// PrintGrid prints the grid in a human-readable format.
func PrintGrid(g Grid) string {
	var sb strings.Builder

	sb.WriteString("┌───────┬───────┬───────┐\n")
	for r := range 9 {
		sb.WriteString("│ ")
		for c := range 9 {
			if g[r][c] == 0 {
				sb.WriteString(".")
			} else {
				sb.WriteString(fmt.Sprintf("%d", g[r][c]))
			}

			if c%3 == 2 {
				sb.WriteString(" │ ")
			} else {
				sb.WriteString(" ")
			}
		}
		sb.WriteString("\n")

		if r%3 == 2 && r != 8 {
			sb.WriteString("├───────┼───────┼───────┤\n")
		}
	}
	sb.WriteString("└───────┴───────┴───────┘\n")

	return sb.String()
}

// GridToString converts a grid to a compact string representation.
func GridToString(g Grid) string {
	var sb strings.Builder
	for r := range 9 {
		for c := range 9 {
			if g[r][c] == 0 {
				sb.WriteString(".")
			} else {
				sb.WriteString(fmt.Sprintf("%d", g[r][c]))
			}
		}
	}
	return sb.String()
}

// StringToGrid converts a string representation back to a grid.
// The string should be 81 characters long, with '0' or '.' for empty cells.
func StringToGrid(s string) (Grid, error) {
	if len(s) != 81 {
		return Grid{}, fmt.Errorf("invalid string length: expected 81, got %d", len(s))
	}

	var g Grid
	for i, ch := range s {
		r := i / 9
		c := i % 9

		if ch == '.' || ch == '0' {
			g[r][c] = 0
		} else if ch >= '1' && ch <= '9' {
			g[r][c] = uint8(ch - '0')
		} else {
			return Grid{}, fmt.Errorf("invalid character at position %d: %c", i, ch)
		}
	}

	return g, nil
}

// CompareGrids returns the differences between two grids.
func CompareGrids(g1, g2 Grid) []struct {
	Row, Col   int
	Val1, Val2 uint8
} {
	var diffs []struct {
		Row, Col   int
		Val1, Val2 uint8
	}

	for r := 0; r < 9; r++ {
		for c := 0; c < 9; c++ {
			if g1[r][c] != g2[r][c] {
				diffs = append(diffs, struct {
					Row, Col   int
					Val1, Val2 uint8
				}{
					Row:  r,
					Col:  c,
					Val1: g1[r][c],
					Val2: g2[r][c],
				})
			}
		}
	}

	return diffs
}

// GetEmptyCells returns a list of all empty cell positions.
func GetEmptyCells(g Grid) []struct{ Row, Col int } {
	var empty []struct{ Row, Col int }

	for r := 0; r < 9; r++ {
		for c := 0; c < 9; c++ {
			if g[r][c] == 0 {
				empty = append(empty, struct{ Row, Col int }{r, c})
			}
		}
	}

	return empty
}

// GetFilledCells returns a list of all filled cell positions with their values.
func GetFilledCells(g Grid) []struct {
	Row, Col int
	Value    uint8
} {
	var filled []struct {
		Row, Col int
		Value    uint8
	}

	for r := 0; r < 9; r++ {
		for c := 0; c < 9; c++ {
			if g[r][c] != 0 {
				filled = append(filled, struct {
					Row, Col int
					Value    uint8
				}{r, c, g[r][c]})
			}
		}
	}

	return filled
}

// GetRegionCells returns all cell positions in a 3x3 region.
func GetRegionCells(blockRow, blockCol int) []struct{ Row, Col int } {
	if blockRow < 0 || blockRow > 2 || blockCol < 0 || blockCol > 2 {
		return nil
	}

	var cells []struct{ Row, Col int }
	startRow := blockRow * 3
	startCol := blockCol * 3

	for r := startRow; r < startRow+3; r++ {
		for c := startCol; c < startCol+3; c++ {
			cells = append(cells, struct{ Row, Col int }{r, c})
		}
	}

	return cells
}

// ValidateGridStructure checks if a grid has valid structure (no duplicates).
func ValidateGridStructure(g Grid) []string {
	var errors []string

	// Check rows
	for r := range 9 {
		seen := make(map[uint8]bool)
		for c := 0; c < 9; c++ {
			v := g[r][c]
			if v == 0 {
				continue
			}
			if seen[v] {
				errors = append(errors, fmt.Sprintf("Duplicate %d in row %d", v, r+1))
			}
			seen[v] = true
		}
	}

	// Check columns
	for c := range 9 {
		seen := make(map[uint8]bool)
		for r := range 9 {
			v := g[r][c]
			if v == 0 {
				continue
			}
			if seen[v] {
				errors = append(errors, fmt.Sprintf("Duplicate %d in column %d", v, c+1))
			}
			seen[v] = true
		}
	}

	// Check 3x3 blocks
	for br := range 3 {
		for bc := range 3 {
			seen := make(map[uint8]bool)
			for r := br * 3; r < br*3+3; r++ {
				for c := bc * 3; c < bc*3+3; c++ {
					v := g[r][c]
					if v == 0 {
						continue
					}
					if seen[v] {
						errors = append(errors, fmt.Sprintf("Duplicate %d in block (%d,%d)", v, br+1, bc+1))
					}
					seen[v] = true
				}
			}
		}
	}

	return errors
}

// GetCandidateMap returns a map of all candidates for all empty cells.
func GetCandidateMap(g Grid) map[struct{ Row, Col int }][]uint8 {
	candidateMap := make(map[struct{ Row, Col int }][]uint8)

	for r := range 9 {
		for c := range 9 {
			if g[r][c] == 0 {
				candidates := g.GetCandidates(r, c)
				if len(candidates) > 0 {
					candidateMap[struct{ Row, Col int }{r, c}] = candidates
				}
			}
		}
	}

	return candidateMap
}

// CalculateCompletionPercentage returns the percentage of filled cells.
func CalculateCompletionPercentage(g Grid) float64 {
	filled := g.CountFilledCells()
	return (float64(filled) / 81.0) * 100.0
}

// GetMostConstrainedCell returns the empty cell with the fewest candidates.
// This is useful for implementing solving strategies.
func GetMostConstrainedCell(g Grid) (row, col int, candidates []uint8, found bool) {
	minCandidates := 10
	found = false

	for r := range 9 {
		for c := range 9 {
			if g[r][c] == 0 {
				cands := g.GetCandidates(r, c)
				if len(cands) < minCandidates {
					minCandidates = len(cands)
					row = r
					col = c
					candidates = cands
					found = true
				}
			}
		}
	}

	return
}

// IsMinimalPuzzle checks if removing any filled cell would result in multiple solutions.
// This is computationally expensive and should be used sparingly.
func IsMinimalPuzzle(g Grid) bool {
	// For each filled cell, try removing it and check if puzzle still has unique solution
	for r := range 9 {
		for c := range 9 {
			if g[r][c] != 0 {
				// Try removing this cell
				backup := g[r][c]
				g[r][c] = 0

				// Check if still unique
				solverGrid := convertToSolverGrid(g)
				solutionCount := solver.CountSolutions(solverGrid, 100*time.Millisecond, 2)

				// Restore the cell
				g[r][c] = backup

				// If removing this cell doesn't maintain uniqueness, it's not minimal
				if solutionCount != 1 {
					return false
				}
			}
		}
	}

	return true
}
