package day18

import (
	"adventofcode2024/utils"
	"adventofcode2024/utils/dijkstra"
	"adventofcode2024/utils/grid2d"
	"fmt"
	"strings"
)

type (
	Vec  struct{ x, y int }
	Dir  struct{ x, y int }
	Cell rune
	Move struct {
		p Vec
		d Dir
	}
	Maze struct {
		height, width int
		start         Vec
		end           Vec
		grid          *grid2d.Grid[Cell]
	}
)

const (
	WALL     Cell = '#'
	CORRIDOR Cell = '.'
	START    Cell = 'S'
	END      Cell = 'E'
	PATH     Cell = '*'
)

var (
	NORTH      = Dir{0, -1}
	EAST       = Dir{1, 0}
	SOUTH      = Dir{0, 1}
	WEST       = Dir{-1, 0}
	DIRECTIONS = []Dir{NORTH, EAST, SOUTH, WEST}
)

func Part1(input string) int {
	maze := Maze{}.Parse(input, 1024)
	fmt.Println(maze)
	data := inputGraph(*maze)
	graph := dijkstra.CreateGraph(data)
	path, dist := dijkstra.GetShortestPath(data.From, data.To, graph)
	for _, p := range path {
		maze.grid.Set(p.X, p.Y, PATH)
	}
	fmt.Println(maze)
	return dist
}
func Part2(input string) int {
	maze := Maze{}.Parse(input, 2877)
	fmt.Println(maze)
	data := inputGraph(*maze)
	graph := dijkstra.CreateGraph(data)
	_, count := dijkstra.GetShortestPath(data.From, data.To, graph)
	return count
}

func inputGraph(maze Maze) dijkstra.InputGraph {
	data := dijkstra.InputGraph{}
	data.From = dijkstra.Point{X: maze.start.x, Y: maze.start.y}
	data.To = dijkstra.Point{X: maze.end.x, Y: maze.end.y}
	for y := 0; y < maze.height; y++ {
		for x := 0; x < maze.width; x++ {
			if maze.grid.Get(x, y) == WALL {
				continue
			}
			for _, dir := range DIRECTIONS {
				nx, ny := x+dir.x, y+dir.y
				if nx < 0 || ny < 0 || nx >= maze.width || ny >= maze.height {
					continue
				}
				if maze.grid.Get(nx, ny) == WALL {
					continue
				}
				data.Graph = append(data.Graph, dijkstra.InputData{
					Source:      dijkstra.Point{X: x, Y: y},
					Destination: dijkstra.Point{X: nx, Y: ny},
					Weight:      1,
				})
			}
		}
	}

	return data
}

func (m Maze) Parse(input string, bytes int) *Maze {
	m.grid = grid2d.NewGrid(71, 71, CORRIDOR)
	for _, line := range strings.Split(input, "\n") {
		if bytes == 0 {
			break
		}
		in := strings.Split(line, ",")
		x := utils.MustAtoi(in[0])
		y := utils.MustAtoi(in[1])
		m.grid.Set(x, y, WALL)
		bytes--
	}
	m.start = Vec{0, 0}
	m.end = Vec{70, 70}
	m.height, m.width = m.grid.SizeY(), m.grid.SizeX()
	return &m
}

func (m *Maze) String() string {
	s := ""
	for y := 0; y < m.height; y++ {
		for x := 0; x < m.width; x++ {
			if y == m.start.y && x == m.start.x {
				s += string(START)
			} else if y == m.end.y && x == m.end.x {
				s += string(END)
			} else {
				s += string(m.grid.Get(x, y))
			}
		}
		s += "\n"
	}
	return s
}
