package day18

import (
	"container/heap"
	"strconv"
	"strings"
)

type Point struct {
	x, y int
}

// Item represents a node in the priority queue.
type Item struct {
	point    Point
	distance int
	index    int
}

func Part1(input string) int {
	grid := parseInput(input, 1024)
	_, distance := dijkstra(grid)
	return distance
}

func Part2(input string) int {
	lines := strings.Split(strings.TrimSpace(input), "\n")
	numLines := len(lines)
	for i := 1; i <= numLines; i++ {
		grid := parseInput(input, i)
		_, distance := dijkstra(grid)
		if distance == -1 {
			println(lines[i-1])
			return 2
		}
	}
	return -1
}

func parseInput(input string, upToLine int) [][]rune {
	grid := make([][]rune, 71)
	for i := range grid {
		grid[i] = make([]rune, 71)
		for j := range grid[i] {
			grid[i][j] = '.'
		}
	}

	lines := strings.Split(strings.TrimSpace(input), "\n")

	for index, line := range lines {
		if index == upToLine {
			break
		}
		cords := strings.Split(strings.TrimSpace(line), ",")
		x, _ := strconv.Atoi(cords[0])
		y, _ := strconv.Atoi(cords[1])
		grid[x][y] = '#'
	}
	return grid
}

// PriorityQueue implements a priority queue for Dijkstra's algorithm.
type PriorityQueue []*Item

func (pq PriorityQueue) Len() int { return len(pq) }
func (pq PriorityQueue) Less(i, j int) bool {
	return pq[i].distance < pq[j].distance
}
func (pq PriorityQueue) Swap(i, j int) {
	pq[i], pq[j] = pq[j], pq[i]
	pq[i].index = i
	pq[j].index = j
}

func (pq *PriorityQueue) Push(x interface{}) {
	n := len(*pq)
	item := x.(*Item)
	item.index = n
	*pq = append(*pq, item)
}

func (pq *PriorityQueue) Pop() interface{} {
	old := *pq
	n := len(old)
	item := old[n-1]
	item.index = -1
	*pq = old[0 : n-1]
	return item
}

func isValid(grid [][]rune, x, y int) bool {
	return x >= 0 && x < len(grid) && y >= 0 && y < len(grid[0]) && grid[x][y] == '.'
}

func dijkstra(grid [][]rune) ([][]rune, int) {
	directions := []Point{{0, 1}, {1, 0}, {0, -1}, {-1, 0}}
	rows, cols := len(grid), len(grid[0])

	// Validate start and end points
	if grid[0][0] != '.' || grid[rows-1][cols-1] != '.' {
		return grid, -1 // No path if start or end is not valid
	}

	dist := make([][]int, rows)
	for i := range dist {
		dist[i] = make([]int, cols)
		for j := range dist[i] {
			dist[i][j] = 1 << 30
		}
	}

	pq := &PriorityQueue{}
	heap.Init(pq)
	heap.Push(pq, &Item{point: Point{0, 0}, distance: 0})
	dist[0][0] = 0

	path := make([][]Point, rows)
	for i := range path {
		path[i] = make([]Point, cols)
	}

	for pq.Len() > 0 {
		item := heap.Pop(pq).(*Item)
		x, y := item.point.x, item.point.y
		if x == rows-1 && y == cols-1 {
			break
		}
		for _, dir := range directions {
			nx, ny := x+dir.x, y+dir.y
			if isValid(grid, nx, ny) {
				newDist := dist[x][y] + 1
				if newDist < dist[nx][ny] {
					dist[nx][ny] = newDist
					heap.Push(pq, &Item{point: Point{nx, ny}, distance: newDist})
					path[nx][ny] = Point{x, y}
				}
			}
		}
	}

	// Check if no path exists
	if dist[rows-1][cols-1] == 1<<30 {
		return grid, -1 // No path found
	}

	// Reconstruct the path
	x, y := rows-1, cols-1
	for x != 0 || y != 0 {
		grid[x][y] = 'O'
		x, y = path[x][y].x, path[x][y].y
	}
	grid[0][0] = 'O'

	return grid, dist[rows-1][cols-1]
}
