function get_intersections_links($grid, $start, $end, $slopes = []) {
$intersections = [$start, $end];
foreach ($grid as $y=>$row) foreach ($row as $x=>$c) {
if ($c == "#") continue;
$paths = 0;
foreach (neighbors([$x, $y]) as [$nx, $ny]) if (($grid[$ny][$nx]??"#") != "#") $paths++;
if ($paths >= 3) $intersections[] = "$x;$y";
}
$links = [$start => [], $end => []];
foreach ($intersections as $int) {
$queue = [$int];
while ($queue) {
$current = array_pop($queue);
$path = explode("|", $current);
[$x, $y] = explode(";", $path[count($path) - 1]);
if ("$x;$y" != $int && in_array("$x;$y", $intersections)) {
$links[$int]["$x;$y"] = max($links[$int]["$x;$y"]??0, count($path) - 1);
continue;
}
foreach (neighbors([$x, $y]) as $dir=>[$nx, $ny]) {
if (($grid[$ny][$nx]??"#") == "#") continue;
if (in_array("$nx;$ny", $path)) continue;
if (isset($slopes[$grid[$y][$x]]) && $slopes[$grid[$y][$x]] != $dir) continue;
$queue[] = $current . "|$nx;$ny";
}
}
}
return $links;
}
function solve($links, $current, $end, $distance = 0, $visited = []) {
if ($current == $end) return $distance;
$visited[$current] = true;
foreach ($links[$current] as $n=>$d) {
if (isset($visited[$n])) continue;
$max = max($max??0, solve($links, $n, $end, $distance + $d, $visited));
}
return $max??0;
}
// ==================================================
// > SOLVE
// ==================================================
$grid = (array) $input->lines->map("str_split");
[$w, $h] = [count($grid[0]), count($grid)];
[$start, $end] = ["1;0", ($w - 2) . ";" . ($h - 1)];
// Part 1
$solution_1 = solve(
get_intersections_links($grid, $start, $end, [">" => "right", "<" => "left", "^" => "up", "v" => "down"]),
$start, $end
);
// Part 2
$links = get_intersections_links($grid, $start, $end);
$offset = $links[$end] ? array_values($links[$end])[0] : 0;
$end = $links[$end] ? array_keys($links[$end])[0] : $end;
$solution_2 = solve($links, $start, $end) + $offset;