module Day8

import Prelude
import Node
import Aoc
import Data.SortedMap

XYZ : U
XYZ = (Int × Int × Int)

-- don't need sqrt for sorting
dist2 : XYZ → XYZ → Int
dist2 (a,(b,c)) (d,(e,f)) = (a - d)*(a - d) + (b - e)*(b - e) + (c - f) * (c - f)

parseLine : String → XYZ
parseLine line =
  let (x :: y :: z :: Nil) = split line "," | _ => fatalError "parseLine"
  in (stringToInt x,(stringToInt y, stringToInt z))

parse : String → List XYZ
parse text = map parseLine $ split (trim text) "\n"

-- List distances between pairs of points
dists : List XYZ → List (Int × XYZ × XYZ)
dists pts = go pts Nil
  where
    pairs : XYZ → List XYZ  → List (Int × XYZ × XYZ) → List (Int × XYZ × XYZ)
    pairs pt Nil acc = acc
    pairs a (b :: pts) acc = pairs a pts ((dist2 a b, a, b) :: acc)

    go : List XYZ → List (Int × XYZ × XYZ) → List (Int × XYZ × XYZ)
    go Nil acc = acc
    go (pt :: pts) acc = go pts (pairs pt pts acc)

lookup : ∀ a b. {{Eq a}} → a → List (a × b) → Maybe b
lookup key Nil = Nothing
lookup key ((a,b) :: rest) = if a == key then Just b else lookup key rest

part1 : List XYZ → List (Int × XYZ × XYZ) → Int → Int
part1 pts pairs count =
  -- node -> component
  let g = map (\ a => (a, a)) pts in
  let node2id = merge g (cast count) pairs in
  let id2nodes = foldl addNode emptyMap $ map swap node2id in
  let stuff = take (cast 3) $ qsort _>_ $ map fst $ map getSize $ toList id2nodes in
  foldl _*_ 1 stuff
  where
    getSize : XYZ × List XYZ → Int × XYZ
    getSize (id,pts) = (length' pts, id)

    addNode : SortedMap XYZ (List XYZ) → XYZ × XYZ → SortedMap XYZ (List XYZ)
    addNode g (a,b) = updateMap a (b :: fromMaybe Nil (lookupMap' a g)) g

    rename : XYZ → XYZ → XYZ × XYZ → XYZ × XYZ
    rename a b (c,d) = if d == a then (c,b) else (c,d)

    merge : List (XYZ × XYZ) → Nat → List (Int × XYZ × XYZ) → List (XYZ × XYZ)
    merge g Z _ = g
    merge g (S k) ((_, (a,b)) :: rest) =
      let (Just a') = lookup a g | _ => fatalError "No \{show a}" in
      let (Just b') = lookup b g | _ => fatalError "No \{show b}" in
      if a' /= b'
        then merge (map (rename a' b') g) k rest
        else merge g k rest
    merge g (S k) Nil = fatalError "too few points"

part2 : List XYZ → List (Int × XYZ × XYZ) → Int
part2 pts pairs = merge (map (\ a => (a, a)) pts) pairs
  where
    rename : XYZ → XYZ → XYZ × XYZ → XYZ × XYZ
    rename a b (c,d) = if d == a then (c,b) else (c,d)

    connected : List (XYZ × XYZ) → Bool
    connected (a :: b :: rest) = if snd a == snd b then connected (a :: rest) else False
    connected _ = True

    merge : List (XYZ × XYZ) → List (Int × XYZ × XYZ) → Int
    merge g ((_, (a,b)) :: rest) =
      let (Just a') = lookup a g | _ => fatalError "No \{show a}" in
      let (Just b') = lookup b g | _ => fatalError "No \{show b}" in
      if a' == b'
        then merge g rest
        else let g = map (rename a' b') g in
          if connected g
            then fst a * fst b
            else merge g rest
    merge g Nil = fatalError "too few points"


run : String -> Int → IO Unit
run fn cnt = do
  putStrLn fn
  text <- readFile fn
  let points = parse text
  let pairs = qsort _<_ $ dists points
  putStrLn $ "part1 " ++ show (part1 points pairs cnt)
  putStrLn $ "part2 " ++ show (part2 points pairs)

main : IO Unit
main = do
  run "aoc2025/day8/eg.txt" 10
  run "aoc2025/day8/input.txt" 1000
