module Day10

import Prelude
import Node
import Aoc
import SortedMap

-- move to lib

gridPoints : String → List (Char × Int × Int)
gridPoints text = go 0 0 (unpack text) Nil
  where
    -- might as well be tail recursive
    go : Int → Int → List Char → List (Char × Int × Int) → List (Char × Int × Int)
    go row col Nil points = points
    go row col ('\n' :: cs) points = go (row + 1) 0 cs points
    go row col (c :: cs) points = go row (col + 1) cs ((c,row,col) :: points)

-- TODO add parameter a and pass Char -> a into getGrid
Grid : U
Grid = SortedMap Point Int

digitToInt : Char → Int
digitToInt c = ord c - 48

getGrid : String → Grid

getGrid text = foldl update EmptyMap $ gridPoints text
  where
    update : Grid → Char × Point → Grid
    update grid (c,pt) = updateMap pt (digitToInt c) grid

peers : Point → List Point
peers pt = map (_+_ pt) ((0, 0 - 1) :: (0,1) :: (0 - 1,0) :: (1,0) :: Nil)


paths : Grid → List Point → Int → Int
paths grid pts ht =
  if ht == 9 then cast $ length pts else
  -- Maybe I should nub with a sortedMap
  let cands = ordNub $ map fst $ filter (_==_ (ht + 1) ∘ snd) $ join $ map getCands pts
  in paths grid cands (ht + 1)
  where
    getCands : Point → List (Point × Int)
    getCands pt = mapMaybe (\ p => lookupMap p grid) (peers pt)

paths2 : Grid → List (Point × Int) → Int → Int
paths2 grid pts ht =
  if ht == 9 then foldl _+_ 0 $ map snd $ pts else
  let cands = join $ map getCands pts
      pts' = toList $ foldMap _+_ EmptyMap cands
  in paths2 grid pts' (ht + 1)
  where
    getCands : Point × Int → List (Point × Int)
    getCands (pt,cnt) =
      map (\ x => fst x , cnt)
        $ filter (_==_ (ht + 1) ∘ snd)
        $ mapMaybe (\ p => lookupMap p grid) (peers pt)

run : String -> IO Unit
run fn = do
  putStrLn fn
  text <- readFile fn
  let grid = getGrid text
  let starts = filter (_==_ 0 ∘ snd) $ toList grid
  let all = map (\ pt => paths grid (fst pt :: Nil) 0) starts
  let part1 = foldl _+_ 0 all
  putStrLn $ "part1 " ++ show part1

  let all = map (\ pt => paths2 grid ((fst pt, 1) :: Nil) 0) starts
  let part2 = foldl _+_ 0 all
  putStrLn $ "part2 " ++ show part2

main : IO Unit
main = do
  run "aoc2024/day10/eg.txt"
  run "aoc2024/day10/eg2.txt"
  run "aoc2024/day10/eg3.txt"
  run "aoc2024/day10/eg4.txt"
  run "aoc2024/day10/eg5.txt"
  run "aoc2024/day10/input.txt"
