{ lib, ... }:

lib.makeExtensible (_: rec {
  mkDate = longDate: lib.concatStringsSep "-" [
    (builtins.substring 0 4 longDate)
    (builtins.substring 4 2 longDate)
    (builtins.substring 6 2 longDate)
  ];
  mkVersionSrc = src: "unstable-" + builtins.substring 0 7 src.rev;
  mkVersionInput = input:
    let
      date = mkDate (input.lastModifiedDate or "19700101");
      rev = input.shortRev or "dirty";
    in
    "unstable-${date}_${rev}";

  mapModules = f: dir:
    let
      filterModules = name: type:
        let
          isPublic = !(lib.hasPrefix "_" name);
          isSomething = type != null;
          isModule =
            let
              path = "${toString dir}/${name}";
              isDefault = type == "directory" && builtins.pathExists "${path}/default.nix";
              isFile = type == "regular" && lib.hasSuffix ".nix" name && name != "default.nix";
            in
            isDefault || isFile;
        in
        isPublic && isSomething && isModule;

      modulesInDir = lib.filterAttrs filterModules (builtins.readDir dir);

      mkModule = name: _:
        let
          path = "${toString dir}/${name}";
          normalizedName =
            if name == "default.nix"
            then name
            else lib.removeSuffix ".nix" name;
        in
        lib.nameValuePair normalizedName (f path);
    in
    lib.mapAttrs' mkModule modulesInDir;

  mapModules' = f: dir: lib.attrValues (mapModules f dir);

  listModules = mapModules' (path: path);

  mapModulesRec = f: dir:
    let
      filterDirs = name: type:
        let
          isPublic = !(lib.hasPrefix "_" name);
          isSomething = type != null;
          isDir = type == "directory";
          isDefault = isDir && builtins.pathExists "${toString dir}/${name}/default.nix";
        in
        isPublic && isSomething && isDir && !isDefault;

      subDirs = lib.filterAttrs filterDirs (builtins.readDir dir);

      mkSubModule = name: _:
        let
          path = "${toString dir}/${name}";
        in
        lib.nameValuePair name (mapModulesRec f path);
    in
    mapModules f dir //
    (lib.mapAttrs' mkSubModule subDirs);

  mapModulesRec' = f: dir:
    let
      attrValuesRec = attrs: map (values: if lib.isAttrs values then attrValuesRec values else values) (lib.attrValues attrs);
    in
    lib.flatten (attrValuesRec (mapModulesRec f dir));

  listModulesRec = mapModulesRec' (path: path);
})
