package generator

import (
	"slices"
	"termdoku/internal/solver"
	"time"
)

// Puzzle returns a copy of the grid suitable for rendering: 0 means blank.
func (g Grid) Puzzle() [9][9]uint8 {
	return g
}

// Clone creates a deep copy of the grid.
func (g Grid) Clone() Grid {
	var clone Grid
	for r := range 9 {
		for c := range 9 {
			clone[r][c] = g[r][c]
		}
	}
	return clone
}

// IsValid checks if the current grid state is valid (no conflicts).
func (g Grid) IsValid() bool {
	for r := range 9 {
		seen := make(map[uint8]bool)
		for c := range 9 {
			v := g[r][c]
			if v == 0 {
				continue
			}
			if seen[v] {
				return false
			}
			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] {
				return false
			}
			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] {
						return false
					}
					seen[v] = true
				}
			}
		}
	}

	return true
}

// IsSolved checks if the grid is completely filled and valid.
func (g Grid) IsSolved() bool {
	if !g.IsValid() {
		return false
	}

	for r := range 9 {
		for c := range 9 {
			if g[r][c] == 0 {
				return false
			}
		}
	}

	return true
}

// CountFilledCells returns the number of non-zero cells.
func (g Grid) CountFilledCells() int {
	count := 0
	for r := range 9 {
		for c := range 9 {
			if g[r][c] != 0 {
				count++
			}
		}
	}
	return count
}

// GetCandidates returns possible values for a given cell.
func (g Grid) GetCandidates(row, col int) []uint8 {
	if g[row][col] != 0 {
		return nil
	}

	used := make(map[uint8]bool)

	for c := range 9 {
		if g[row][c] != 0 {
			used[g[row][c]] = true
		}
	}

	for r := range 9 {
		if g[r][col] != 0 {
			used[g[r][col]] = true
		}
	}

	// Check 3x3 block
	r0 := (row / 3) * 3
	c0 := (col / 3) * 3
	for r := r0; r < r0+3; r++ {
		for c := c0; c < c0+3; c++ {
			if g[r][c] != 0 {
				used[g[r][c]] = true
			}
		}
	}

	var candidates []uint8
	for v := uint8(1); v <= 9; v++ {
		if !used[v] {
			candidates = append(candidates, v)
		}
	}

	return candidates
}

// Hint represents a hint for the player.
type Hint struct {
	Row   int
	Col   int
	Value uint8
	Type  HintType
}

// HintType categorizes different hint strategies.
type HintType int

const (
	HintNakedSingle  HintType = iota // Only one candidate for a cell
	HintHiddenSingle                 // Only cell in row/col/box for a value
	HintRandom                       // Random valid cell (fallback)
)

// GenerateHint provides a hint for the puzzle based on solving techniques.
func (g Grid) GenerateHint(solution Grid) *Hint {
	// First, try to find a naked single (cell with only one candidate)
	for r := range 9 {
		for c := range 9 {
			if g[r][c] == 0 {
				candidates := g.GetCandidates(r, c)
				if len(candidates) == 1 {
					return &Hint{
						Row:   r,
						Col:   c,
						Value: candidates[0],
						Type:  HintNakedSingle,
					}
				}
			}
		}
	}

	// Try to find a hidden single
	if hint := g.findHiddenSingle(); hint != nil {
		return hint
	}

	// Fallback: return a random empty cell with its solution
	for r := range 9 {
		for c := range 9 {
			if g[r][c] == 0 {
				return &Hint{
					Row:   r,
					Col:   c,
					Value: solution[r][c],
					Type:  HintRandom,
				}
			}
		}
	}

	return nil
}

// findHiddenSingle finds a cell where a value can only go in one place in a row/col/box.
func (g Grid) findHiddenSingle() *Hint {
	// Check rows
	for r := range 9 {
		for v := uint8(1); v <= 9; v++ {
			var possibleCols []int
			for c := range 9 {
				if g[r][c] == 0 {
					candidates := g.GetCandidates(r, c)
					if slices.Contains(candidates, v) {
						possibleCols = append(possibleCols, c)
					}
				}
			}
			if len(possibleCols) == 1 {
				return &Hint{
					Row:   r,
					Col:   possibleCols[0],
					Value: v,
					Type:  HintHiddenSingle,
				}
			}
		}
	}

	// Check columns
	for c := range 9 {
		for v := uint8(1); v <= 9; v++ {
			var possibleRows []int
			for r := range 9 {
				if g[r][c] == 0 {
					candidates := g.GetCandidates(r, c)
					for _, cand := range candidates {
						if cand == v {
							possibleRows = append(possibleRows, r)
							break
						}
					}
				}
			}
			if len(possibleRows) == 1 {
				return &Hint{
					Row:   possibleRows[0],
					Col:   c,
					Value: v,
					Type:  HintHiddenSingle,
				}
			}
		}
	}

	// Check 3x3 blocks
	for br := range 3 {
		for bc := range 3 {
			for v := uint8(1); v <= 9; v++ {
				type pos struct{ r, c int }
				var possiblePos []pos
				for r := br * 3; r < br*3+3; r++ {
					for c := bc * 3; c < bc*3+3; c++ {
						if g[r][c] == 0 {
							candidates := g.GetCandidates(r, c)
							for _, cand := range candidates {
								if cand == v {
									possiblePos = append(possiblePos, pos{r, c})
									break
								}
							}
						}
					}
				}
				if len(possiblePos) == 1 {
					return &Hint{
						Row:   possiblePos[0].r,
						Col:   possiblePos[0].c,
						Value: v,
						Type:  HintHiddenSingle,
					}
				}
			}
		}
	}

	return nil
}

// PuzzleAnalysis contains metrics about puzzle difficulty and characteristics.
type PuzzleAnalysis struct {
	FilledCells         int
	EmptyCells          int
	MinCandidates       int
	MaxCandidates       int
	AvgCandidates       float64
	HasUniqueSolution   bool
	EstimatedDifficulty Difficulty
	SolvingTechniques   []string
	SymmetryType        SymmetryType
}

// SymmetryType represents the symmetry pattern of the puzzle.
type SymmetryType int

const (
	SymmetryNone SymmetryType = iota
	SymmetryRotational180
	SymmetryRotational90
	SymmetryVertical
	SymmetryHorizontal
	SymmetryDiagonal
)

func (s SymmetryType) String() string {
	switch s {
	case SymmetryRotational180:
		return "180° Rotational"
	case SymmetryRotational90:
		return "90° Rotational"
	case SymmetryVertical:
		return "Vertical"
	case SymmetryHorizontal:
		return "Horizontal"
	case SymmetryDiagonal:
		return "Diagonal"
	default:
		return "None"
	}
}

// Analyze performs a comprehensive analysis of the puzzle.
func (g Grid) Analyze() PuzzleAnalysis {
	analysis := PuzzleAnalysis{
		FilledCells: g.CountFilledCells(),
		EmptyCells:  81 - g.CountFilledCells(),
	}

	// Analyze candidates
	var totalCandidates int
	var emptyCellCount int
	analysis.MinCandidates = 9
	analysis.MaxCandidates = 0

	for r := range 9 {
		for c := range 9 {
			if g[r][c] == 0 {
				emptyCellCount++
				candidates := g.GetCandidates(r, c)
				count := len(candidates)
				totalCandidates += count
				if count < analysis.MinCandidates {
					analysis.MinCandidates = count
				}
				if count > analysis.MaxCandidates {
					analysis.MaxCandidates = count
				}
			}
		}
	}

	if emptyCellCount > 0 {
		analysis.AvgCandidates = float64(totalCandidates) / float64(emptyCellCount)
	}

	solverGrid := convertToSolverGrid(g)
	analysis.HasUniqueSolution = solver.CountSolutions(solverGrid, 100*time.Millisecond, 2) == 1

	analysis.SolvingTechniques = g.detectSolvingTechniques()

	analysis.EstimatedDifficulty = g.estimateDifficulty(analysis)

	analysis.SymmetryType = g.detectSymmetry()

	return analysis
}

// detectSolvingTechniques identifies which solving techniques are needed.
func (g Grid) detectSolvingTechniques() []string {
	var techniques []string

	hasNakedSingle := false
	for r := range 9 {
		for c := range 9 {
			if g[r][c] == 0 {
				if len(g.GetCandidates(r, c)) == 1 {
					hasNakedSingle = true
					break
				}
			}
		}
	}
	if hasNakedSingle {
		techniques = append(techniques, "Naked Singles")
	}

	if g.findHiddenSingle() != nil {
		techniques = append(techniques, "Hidden Singles")
	}
	if len(techniques) == 0 {
		techniques = append(techniques, "Advanced Techniques Required")
	}

	return techniques
}

// estimateDifficulty estimates puzzle difficulty based on analysis.
func (g Grid) estimateDifficulty(analysis PuzzleAnalysis) Difficulty {
	// Use multiple factors to estimate difficulty
	emptyCells := analysis.EmptyCells
	avgCandidates := analysis.AvgCandidates
	minCandidates := analysis.MinCandidates

	// More empty cells generally means harder
	if emptyCells >= 58 {
		return Lunatic
	} else if emptyCells >= 52 {
		// Check if it requires advanced techniques
		if minCandidates <= 2 || avgCandidates < 3.5 {
			return Lunatic
		}
		return Hard
	} else if emptyCells >= 46 {
		if minCandidates <= 2 {
			return Hard
		}
		return Normal
	} else if emptyCells >= 38 {
		return Normal
	}
	return Easy
}

// detectSymmetry detects the symmetry pattern of empty cells.
func (g Grid) detectSymmetry() SymmetryType {
	if g.hasRotational180Symmetry() {
		return SymmetryRotational180
	}

	if g.hasVerticalSymmetry() {
		return SymmetryVertical
	}
	if g.hasHorizontalSymmetry() {
		return SymmetryHorizontal
	}

	return SymmetryNone
}

func (g Grid) hasRotational180Symmetry() bool {
	for r := range 9 {
		for c := range 9 {
			// Check if empty cells are symmetric
			if (g[r][c] == 0) != (g[8-r][8-c] == 0) {
				return false
			}
		}
	}
	return true
}

func (g Grid) hasVerticalSymmetry() bool {
	for r := range 9 {
		for c := range 9 {
			if (g[r][c] == 0) != (g[r][8-c] == 0) {
				return false
			}
		}
	}
	return true
}

func (g Grid) hasHorizontalSymmetry() bool {
	for r := range 9 {
		for c := range 9 {
			if (g[r][c] == 0) != (g[8-r][c] == 0) {
				return false
			}
		}
	}
	return true
}
