#![allow(clippy::needless_range_loop)]
#![forbid(elided_lifetimes_in_paths, unsafe_code)]

use aoc23::read;
use chumsky::{prelude::*, text::int};

struct History(Vec<i64>);

impl History {
	fn parser() -> impl Parser<char, Self, Error = Simple<char>> {
		one_of(['-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'])
			.repeated()
			.at_least(1)
			.separated_by(just(" "))
			.map(|nums: Vec<Vec<char>>| {
				Self(
					nums.into_iter()
						.map(|num| num.into_iter().collect::<String>().parse().unwrap())
						.collect()
				)
			})
	}
}

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

impl History {
	fn diff(&self) -> Self {
		// intentionally reserve one space extra for .extend()
		let mut diffs = Vec::with_capacity(self.0.len());
		if self.0.len() > 1 {
			for i in 1 .. self.0.len() {
				diffs.push(self.0[i] - self.0[i - 1]);
			}
		}
		Self(diffs)
	}

	fn extend(&mut self) {
		if self.0.iter().any(|num| *num != 0) {
			let mut diff = self.diff();
			diff.extend();
			self.0.push(self.0.last().unwrap() + diff.0.last().unwrap());
		} else {
			self.0.push(0);
		}
	}
}

fn main() -> anyhow::Result<()> {
	let histories = read("inputs/day9.txt", parser())?;

	let mut sum = 0;
	for mut h in histories {
		h.extend();
		sum += h.0.last().unwrap();
	}
	println!("{sum}");

	Ok(())
}