package day10

import (
	"strings"
)

var directions = [][2]int{
	{-1, 0},
	{1, 0},
	{0, -1},
	{0, 1},
}

func Part1(input string) int {
	grid := ParseInput(input)
	return CalculateTotalTrailheadScore(grid)
}

func Part2(input string) int {
	grid := ParseInput(input)
	return CalculateTotalTrailheadRating(grid)
}

func ParseInput(input string) [][]int {
	lines := strings.Split(strings.TrimSpace(input), "\n")
	grid := make([][]int, len(lines))
	for i, line := range lines {
		grid[i] = make([]int, len(line))
		for j, char := range line {
			grid[i][j] = int(char - '0')
		}
	}
	return grid
}

func IsValidPosition(x, y, rows, cols int) bool {
	return x >= 0 && x < rows && y >= 0 && y < cols
}

func DFS(grid [][]int, x, y, prevHeight int, visited [][]bool, reached9 map[[2]int]bool) {
	rows := len(grid)
	cols := len(grid[0])
	if !IsValidPosition(x, y, rows, cols) || visited[x][y] || grid[x][y] != prevHeight+1 {
		return
	}

	if grid[x][y] == 9 {
		reached9[[2]int{x, y}] = true
		return
	}

	visited[x][y] = true
	for _, dir := range directions {
		nx, ny := x+dir[0], y+dir[1]
		DFS(grid, nx, ny, grid[x][y], visited, reached9)
	}
	visited[x][y] = false
}

func CalculateTrailheadScore(grid [][]int, x, y int) int {
	rows := len(grid)
	cols := len(grid[0])
	visited := make([][]bool, rows)
	for i := range visited {
		visited[i] = make([]bool, cols)
	}

	reached9 := make(map[[2]int]bool)
	DFS(grid, x, y, -1, visited, reached9)
	return len(reached9)
}

func DFSForRatings(grid [][]int, x, y, prevHeight int, visited [][]bool, trailCache map[[3]int]int) int {
	rows := len(grid)
	cols := len(grid[0])
	if !IsValidPosition(x, y, rows, cols) || visited[x][y] || grid[x][y] != prevHeight+1 {
		return 0
	}

	if grid[x][y] == 9 {
		return 1
	}

	cacheKey := [3]int{x, y, grid[x][y]}
	if count, exists := trailCache[cacheKey]; exists {
		return count
	}

	visited[x][y] = true
	totalTrails := 0
	for _, dir := range directions {
		nx, ny := x+dir[0], y+dir[1]
		totalTrails += DFSForRatings(grid, nx, ny, grid[x][y], visited, trailCache)
	}
	visited[x][y] = false

	trailCache[cacheKey] = totalTrails
	return totalTrails
}

func CalculateTrailheadRating(grid [][]int, x, y int) int {
	rows := len(grid)
	cols := len(grid[0])
	visited := make([][]bool, rows)
	for i := range visited {
		visited[i] = make([]bool, cols)
	}

	trailCache := make(map[[3]int]int)
	return DFSForRatings(grid, x, y, -1, visited, trailCache)
}

func CalculateTotalTrailheadScore(grid [][]int) int {
	rows := len(grid)
	cols := len(grid[0])
	totalScore := 0
	for i := 0; i < rows; i++ {
		for j := 0; j < cols; j++ {
			if grid[i][j] == 0 {
				score := CalculateTrailheadScore(grid, i, j)
				totalScore += score
			}
		}
	}
	return totalScore
}

func CalculateTotalTrailheadRating(grid [][]int) int {
	rows := len(grid)
	cols := len(grid[0])
	totalRating := 0
	for i := 0; i < rows; i++ {
		for j := 0; j < cols; j++ {
			if grid[i][j] == 0 {
				rating := CalculateTrailheadRating(grid, i, j)
				totalRating += rating
			}
		}
	}
	return totalRating
}
