solve_day15 :: (test: bool) {
    contents := read_entire_file(ifx test then "inputs/day15_test.txt" else "inputs/day15.txt");
    lines    := split(contents, cast(u8) #char "\n");

    sensors: [..]Sensor;

    row := ifx test then 10 else 2_000_000;

    intervals: [..]Interval;

    for line: lines {
        equals_split := split(line, cast(u8) #char "=");

        beacon_x := string_to_int(slice(equals_split[3], 0, find_index_from_left(equals_split[3], cast(u8) #char ",")), 10, s64);
        beacon_y := string_to_int(slice(equals_split[4], 0, equals_split[4].count), 10, s64);

        sensor: = Sensor.{
            x = string_to_int(slice(equals_split[1], 0, find_index_from_left(equals_split[1], cast(u8) #char ",")), 10, s64),
            y = string_to_int(slice(equals_split[2], 0, find_index_from_left(equals_split[2], cast(u8) #char ":")), 10, s64),
        };
    
        sensor.closest = abs(beacon_x - sensor.x) + abs(beacon_y - sensor.y);

        array_add(*sensors, sensor);

        interval := Interval.{
            start = sensor.x - (sensor.closest - abs(row - sensor.y)),
            end   = sensor.x + (sensor.closest - abs(row - sensor.y)),
        };

        if interval.start > interval.end {
            interval.start, interval.end = interval.end, interval.start;
        }

        insert_interval(*intervals, interval);
    }

    part1 := 0;
    part2 := 0;

    for intervals {
        part1 += it.end - it.start;
    }
    max_dim := ifx test then 20 else 4_000_000;

    for s1: 0..sensors.count - 1 {
        for s2: s1 + 1..sensors.count - 1 {
            d := dist(sensors[s1].pos, sensors[s2].pos);
            if sensors[s1].closest + sensors[s2].closest + 2 == dist(sensors[s1].pos, sensors[s2].pos) {
                _s1 := sensors[s1];
                _s2 := sensors[s2];

                if _s1.x > _s2.x  _s1, _s2 = _s2, _s1;

                for _x: -_s1.closest - 1.._s1.closest + 1 {
                    pos := Vec2.{
                        x = _s1.x + _x,
                        y = _s1.y + ((_s1.closest + 1) - _x),
                    };

                    if dist(pos, .{_s2.x,_s2.y}) == _s2.closest + 1 {
                        to_remove := false;
                        for sensor: sensors {
                            if dist(pos, .{sensor.x, sensor.y}) <= sensor.closest {
                                to_remove = true;
                                break;
                            }
                        }

                        if !to_remove {
                            part2 = pos.x * 4_000_000 + pos.y;
                            break s1;
                        }
                    }
                }
            }
        }
    }
    

    print("Part 1: %\n", part1);
    print("Part 2: %\n", part2);
}

inside :: (point: Vec2, max_dim: s64) -> bool {
    return point.x >= 0 && point.x <= max_dim &&
           point.y >= 0 && point.y <= max_dim;
}

dist :: (v1: Vec2, v2: Vec2) -> s64 {
    return abs(v1.x - v2.x) + abs(v1.y - v2.y);
}

insert_interval :: (intervals: *[..]Interval, interval: Interval) {
    array_add(intervals, interval);
    collapse_intervals(intervals);
}

collapse_intervals :: (intervals: *[..]Interval) {
    while m := true {
        for *f, f_index: (<< intervals) {
            for f_index + 1..intervals.count - 1 {
                if ((<< intervals)[it].start <= f.start && (<< intervals)[it].end   >= f.start) ||
                   ((<< intervals)[it].start <= f.end   && (<< intervals)[it].end   >= f.end) ||
                   ((<< intervals)[it].start >= f.start && (<< intervals)[it].start <= f.end) {
                       f.start = min(f.start, (<< intervals)[it].start);
                       f.end   = max(f.end,   (<< intervals)[it].end);

                       array_unordered_remove_by_index(intervals, it);
                       continue m;
                }
            }
        }
        break m;
    }
}

#scope_file

Vec2 :: struct {
    x: s64;
    y: s64;
}

Interval :: struct {
    start: s64;
    end:   s64;
}

Sensor :: struct {
    using pos: Vec2;
    closest: s64;
}