#![warn(clippy::pedantic)]

use std::io::stdin;

use aoc::{vec2::Vec2, SectionRange, UpToTwo};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Sensor {
    position: Vec2,
    closest_beacon: Vec2,
}

impl Sensor {
    fn beacon_distance(self) -> u32 {
        self.closest_beacon.manhattan_dist(self.position)
    }
}

fn main() {
    let sensors: Vec<_> = stdin()
        .lines()
        .map(|line| {
            let line = line.unwrap();
            let (sensor, beacon) = line.split_once(": ").unwrap();
            let point = |s: &str| {
                let (x, y) = s.split_once(", ").unwrap();
                let x = x.strip_prefix("x=").unwrap().parse().unwrap();
                let y = y.strip_prefix("y=").unwrap().parse().unwrap();
                (x, y).into()
            };
            let sensor = sensor.strip_prefix("Sensor at ").unwrap();
            let beacon = beacon.strip_prefix("closest beacon is at ").unwrap();

            Sensor {
                position: point(sensor),
                closest_beacon: point(beacon),
            }
        })
        .collect();

    for sensor in &sensors {
        let dist = sensor.beacon_distance();
        let candidates_x = sensor
            .position
            .on_x_with_manhattan_dist(sensor.closest_beacon.x, dist)
            .unwrap();
        let candidates_y = sensor
            .position
            .on_y_with_manhattan_dist(sensor.closest_beacon.y, dist)
            .unwrap();
        let candidates = [
            candidates_x.0,
            candidates_x.1,
            candidates_y.0,
            candidates_y.1,
        ];
        assert!(candidates.contains(&sensor.closest_beacon));
    }

    let mut ranges: Vec<_> = sensors
        .iter()
        .flat_map(|sensor| {
            let y = 2_000_000;
            let Some((left, right)) = sensor
                .position
                .on_y_with_manhattan_dist(y, sensor.beacon_distance())
            else {
                return UpToTwo::Zero;
            };

            let range = SectionRange::try_new(left.x, right.x).unwrap();
            if sensor.closest_beacon.y == y {
                let beacon_pos = sensor.closest_beacon.y;
                let beacon_pos = SectionRange::try_new(beacon_pos, beacon_pos).unwrap();

                range - beacon_pos
            } else {
                UpToTwo::One(range)
            }
        })
        .collect();
    ranges.sort_unstable();
    let mut covered = ranges.into_iter();

    let mut count = 0;
    let mut current_range = covered.next().unwrap();
    for range in covered {
        match current_range | range {
            UpToTwo::Zero => unreachable!(),
            UpToTwo::One(new) => current_range = new,
            UpToTwo::Two(_old, new) => {
                count += current_range.len().get();
                current_range = new;
            }
        }
    }
    count += current_range.len().get();

    println!("{:?}", count);
}
