#![forbid(elided_lifetimes_in_paths, unsafe_code)]

use aoc23::read;
use chumsky::prelude::*;
use std::collections::HashMap;

enum Instruction {
	Left,
	Right
}

impl Instruction {
	fn parser() -> impl Parser<char, Self, Error = Simple<char>> {
		choice((
			just('L').map(|_| Self::Left),
			just('R').map(|_| Self::Right)
		))
	}
}

#[derive(Debug)]
struct Node {
	left: String,
	right: String
}

fn parse_node_name() -> impl Parser<char, String, Error = Simple<char>> {
	none_of([',', ')', ' '])
		.repeated()
		.at_least(1)
		.map(|chars| chars.into_iter().collect())
}

impl Node {
	fn parser() -> impl Parser<char, Self, Error = Simple<char>> {
		just("(")
			.ignore_then(parse_node_name())
			.then_ignore(just(", "))
			.then(parse_node_name())
			.then_ignore(just(")"))
			.map(|(left, right)| Self { left, right })
	}
}

#[derive(Debug)]
struct Network {
	nodes: HashMap<String, Node>
}

impl Network {
	fn parser() -> impl Parser<char, Self, Error = Simple<char>> {
		parse_node_name()
			.then_ignore(just(" = "))
			.then(Node::parser())
			.then_ignore(just("\n"))
			.repeated()
			.map(|nodes| Self {
				nodes: nodes.into_iter().collect()
			})
	}

	fn node(&self, node: &str) -> &Node {
		self.nodes
			.get(node)
			.unwrap_or_else(|| panic!("Failed to get node {node:?}"))
	}

	fn left(&self, node: &str) -> &str {
		&self.node(node).left
	}

	fn right(&self, node: &str) -> &str {
		&self.node(node).right
	}
}

fn parser() -> impl Parser<char, (Vec<Instruction>, Network), Error = Simple<char>> {
	Instruction::parser()
		.repeated()
		.at_least(1)
		.then_ignore(just("\n\n"))
		.then(Network::parser())
		.then_ignore(end())
}

fn main() -> anyhow::Result<()> {
	let (instructions, network) = read("inputs/day8.txt", parser())?;
	// eprintln!("{network:?}");

	let mut curr_node = "AAA";
	let mut i = 0;
	let mut steps = 0;
	while curr_node != "ZZZ" {
		curr_node = match instructions[i] {
			Instruction::Left => network.left(curr_node),
			Instruction::Right => network.right(curr_node)
		};
		i = (i + 1) % instructions.len();
		steps += 1;
	}
	println!("{steps}");

	Ok(())
}