{ lib, korora, ... }:
let
  k = korora;

  # {{{ Helpers
  helpers = rec {
    removeEmptyLines =
      str:
      lib.pipe str [
        (lib.splitString "\n")
        (lib.lists.filter (line: lib.any (c: c != " ") (lib.stringToCharacters line)))
        (lib.concatStringsSep "\n")
      ];

    hasType =
      type: value:
      let
        err = type.verify value;
      in
      lib.assertMsg (err == null) err;

    lua =
      value:
      assert hasType k.string value;
      lib.fix (self: {
        inherit value;
        __luaEncoderTag = "lua";
        __functor =
          _: arg:
          if builtins.typeOf arg == "path" then
            if arg == /. then
              self
            else
              let
                object = self (builtins.dirOf arg);
                attr = builtins.toString (builtins.baseNameOf arg);
              in
              lua "${encode object}.${attr}"
          else
            lua "${value}(${encode arg})";
      });

    # {{{ Verification helpers
    toPretty = lib.generators.toPretty { indent = "    "; };
    withError = cond: err: if cond then null else err;
    hasProp = obj: p: obj ? ${p};
    propExists = p: obj: helpers.withError (helpers.hasProp obj p) "Expected property ${p}";
    propXor =
      a: b: obj:
      helpers.withError (
        (hasProp obj a) != (hasProp obj b)
      ) "Expected only one of the properties ${a} and ${b} in object ${toPretty obj}";
    propOnlyOne =
      props: obj:
      helpers.withError (
        1 == lib.count (hasProp obj) props
      ) "Expected exactly one property from the list ${toPretty props} in object ${toPretty obj}";
    propImplies =
      a: b: obj:
      helpers.withError (
        (hasProp obj a) -> (hasProp obj b)
      ) "Property ${a} should imply property ${b} in object ${toPretty obj}";
    mkVerify =
      checks: obj:
      assert lib.all lib.isFunction checks;
      let
        results = lib.lists.map (c: c obj) checks;
        errors = lib.lists.filter (v: v != null) results;
      in
      assert lib.all lib.isString errors;
      if errors == [ ] then
        null
      else
        let
          prettyErrors = lib.lists.map (s: "\n- ${s}") errors;
        in
        "Multiple errors occured: ${lib.concatStrings prettyErrors}";
    # }}}
    # {{{ Custom types
    intersection =
      l: r:
      k.typedef' "${l.name} ∧ ${r.name}" (
        helpers.mkVerify [
          l
          r
        ]
      );

    # dependentAttrsOf =
    #   name: mkType:
    #   let
    #     typeError = name: v: "Expected type '${name}' but value '${toPretty v}' is of type '${typeOf v}'";
    #     addErrorContext = context: error: if error == null then null else "${context}: ${error}";
    #
    #     withErrorContext = addErrorContext "in ${name} value";
    #   in
    #   k.typedef' name
    #     (v:
    #     if ! lib.isAttrs v then
    #       typeError name v
    #     else
    #       withErrorContext
    #         (mkVerify
    #           (lib.mapAttrsToList (k: _: mkType k) v)
    #           v));
    # }}}
    # {{{ Encoding helpers
    mkRawLuaObject = chunks: ''
      {
        ${lib.concatStringsSep "," (lib.filter (s: s != "") chunks)}
      }
    '';

    encodeString =
      given:
      if lib.hasInfix "\n" given then
        ''[[${(toString given)}]]'' # This is not perfect but oh well
      else
        ''"${
          lib.escape [
            "\""
            "\\"
          ] (toString given)
        }"'';

    mkAttrName =
      s:
      let
        # A "good enough" approach to figuring out the correct encoding for attribute names
        allowedStartingChars = lib.stringToCharacters "abcdefghijklmnopqrstuvwxyz_";
        allowedChars = allowedStartingChars ++ lib.stringToCharacters "0123456789";
        keywords = [
          "if"
          "then"
          "else"
          "do"
          "for"
          "local"
          ""
        ];
      in
      if builtins.match "([a-zA-Z_][a-zA-Z_0-9]*)" s != [ s ] || lib.elem s keywords then
        "[${helpers.encodeString s}]"
      else
        s;
    # }}}
  };
  # }}}
  # {{{ Encoding
  isLuaLiteral = given: lib.isAttrs given && given.__luaEncoderTag or null == "lua";
  encodeWithDepth =
    depth: given:
    let
      recurse = encodeWithDepth depth;
    in
    if lib.isString given || lib.isDerivation given || lib.isPath given then
      helpers.encodeString given
    else if lib.isInt given || lib.isFloat given then
      toString given
    else if lib.isBool given then
      if given then "true" else "false"
    else if given == null then
      "nil"
    else if lib.isList given then
      helpers.mkRawLuaObject (lib.lists.map recurse given)
    else if lib.isAttrs given && given.__luaEncoderTag or null == "foldedList" then
      let
        makeFold = name: value: ''
          -- {{{ ${name}
          ${recurse value},
          -- }}}'';
        folds = lib.mapAttrsToList makeFold given.value;
      in
      ''
        {
          ${lib.concatStringsSep "\n" folds}
        }''
    else if isLuaLiteral given then
      given.value
    else if lib.isAttrs given then
      helpers.mkRawLuaObject (
        lib.mapAttrsToList (
          name: value:
          let
            result = recurse value;
          in
          lib.optionalString (result != "nil") "${helpers.mkAttrName name} = ${result}"
        ) given
      )
    else if lib.isFunction given then
      let
        argNames = [
          "context"
          "inner_context"
          "local_context"
        ];
        secretArg =
          if depth >= builtins.length argNames then "arg_${depth}" else builtins.elemAt argNames depth;
        body = given secretArg;
        encoded = encodeWithDepth (depth + 1) body;
        encodedBody = if isLuaLiteral body then encoded else "return ${encoded}";
      in
      if lib.hasInfix secretArg encoded then
        ''
          function(${secretArg})
            ${encodedBody}
          end''
      else
        ''
          function()
            ${encodedBody}
          end''
    else
      builtins.throw "Cannot encode value ${helpers.toPretty given}";
  # }}}
  encode = encodeWithDepth 0;
in
{
  inherit encode helpers;
}