{lib, ...}: input: rec {
  inherit lib;
  inherit (lib) splitString mod;
  inherit (lib.lists) findFirstIndex imap0;
  inherit (lib.strings) stringToCharacters;
  inherit (builtins) elemAt concatStringsSep length elem filter foldl' concatLists floor;

  index = i: list: elemAt list i;
  index2d = {x, y}: m: m |> index y |> index x;

  init = let
    parts = splitString "\n\n" input;
    chart = elemAt parts 0 |> splitString "\n" |> map stringToCharacters;
    ins = elemAt parts 1 |> splitString "\n" |> map stringToCharacters;
    width = (elemAt chart 0 |> length) + 1;
    startIndex = elemAt parts 0 |> stringToCharacters |> findFirstIndex (char: char == "@") null;
  in {
    inherit chart ins;
    pos = {
      x = mod startIndex width;
      y = startIndex / width;
    };
  }
  ;

  addVec = a: b: {x = a.x or 0 + b.x or 0; y = a.y or 0 + b.y or 0;};

  objects = {
    wall = "#";
    moveable = "O";
    leftedge = "[";
    rightedge = "]";
    empty = ".";
  };

  replace2d = {x, y}: updated: chart: let
    newRow = chart |> index y |> replace x updated;
  in
    replace y newRow chart;

  replace = i: updated: list: 
  let before = lib.sublist 0 i list;
    after = lib.sublist (i + 1) (builtins.length list - i) list;
  in
    if i < 0 || i > builtins.length list then list else
    before ++ [updated] ++ after;

  strToDir = strDir: {
    x = if strDir == "<" then -1 else if strDir == ">" then 1 else 0;
    y = if strDir == "^" then -1 else if strDir == "v" then 1 else 0;
  };
  
  move = {chart, pos, ...}: dir: let
    nextPos = addVec pos dir;
    atPos = chart |> index2d pos;
    moved = move {inherit chart; pos = nextPos;} dir;
  in if atPos == objects.wall
  then {
    inherit chart pos;
    moved = false;
  } else if atPos == objects.empty
  then {
    inherit chart pos;
    moved = true;
  } else if !moved.moved
  then {inherit chart pos; moved = false;}
  else {
    moved = true;
    pos = nextPos;
    chart = moved.chart |> replace2d pos objects.empty |> replace2d nextPos atPos;
  };

  chartToStr = chart: chart |> (map (concatStringsSep "")) |> concatStringsSep "\n";

  applyIns = foldl' (c: ins: let
    dir = strToDir ins;
  in move c dir);

  applyInsList = foldl' (chart: ins:
    # evaluate each line of instructions one at a time, to avoid stack overflow.
    let res = applyIns chart ins; in builtins.deepSeq res
    (res)
  );

  result = applyInsList init init.ins;

  getScore = chart: chart
    |> imap0 (y: row: row
      |> imap0 (x: obj:
        if obj == objects.moveable then x + y * 100 else 0
      )
    )
    |> concatLists
    |> foldl' builtins.add 0
  ;

  part1result = getScore result.chart;

  init2 = {
    chart = init.chart
      |> map (map (c: if c == objects.moveable then ["[" "]"]
      else if c == "@" then ["@" "."] else [c c]))
      |> map concatLists
    ;
    pos = {
      inherit (init.pos) y;
      x = init.pos.x * 2;
    };
  };

  move2 = dir: {chart, pos, ...}:
  let
    nextPos = addVec pos dir;
    char = chart |> index2d pos;
    switch = state: state // {chart = state.chart |> replace2d pos objects.empty |> replace2d nextPos char;};
    moved = move2 dir {inherit chart; pos = nextPos;} |> switch;
    moved2 = if dir.y == 0 || !moved.moved then moved else if char == objects.leftedge then
        move2 dir {inherit (moved) chart; pos = addVec pos {x = 1;};}
      else if char == objects.rightedge then
        move2 dir {inherit (moved) chart; pos = addVec pos {x = -1;};}
      else
        moved;
  in if char == objects.wall
  then {
    inherit chart pos;
    moved = false;
  } else if char == objects.empty
  then {
    inherit chart pos;
    moved = true;
  } else if !moved2.moved
  then {inherit chart pos; moved = false;}
  else {
    moved = true;
    pos = nextPos;
    inherit (moved2) chart;
  }
  ;

  applyIns2 = foldl' (c: ins: let
    dir = strToDir ins;
  in move2 dir c);

  applyInsList2 = foldl' (chart: ins:
    # evaluate each line of instructions one at a time, to avoid stack overflow.
    let res = applyIns2 chart ins; in builtins.deepSeq res
    res
  );

  result2 = applyInsList2 init2 init.ins;

  getScore2 = chart: chart
    |> imap0 (y: row: row
      |> imap0 (x: obj:
        if obj == objects.leftedge then x + y * 100 else 0
      )
    )
    |> concatLists
    |> foldl' builtins.add 0
  ;

  part2result = getScore2 result2.chart;

}