{
  pkgs,

  # Note, this should be "the standard library" + HM extensions.
  lib ? import ../modules/lib/stdlib-extended.nix pkgs.lib,

  release,
  isReleaseBranch,
}:

let

  # Recursively replace each derivation in the given attribute set
  # with the same derivation but with the `outPath` attribute set to
  # the string `"\${pkgs.attribute.path}"`. This allows the
  # documentation to refer to derivations through their values without
  # establishing an actual dependency on the derivation output.
  #
  # This is not perfect, but it seems to cover a vast majority of use
  # cases.
  #
  # Caveat: even if the package is reached by a different means, the
  # path above will be shown and not e.g.
  # `${config.services.foo.package}`.
  scrubDerivations =
    prefixPath: attrs:
    let
      scrubDerivation =
        name: value:
        let
          pkgAttrName = prefixPath + "." + name;
        in
        if lib.isAttrs value then
          scrubDerivations pkgAttrName value
          // lib.optionalAttrs (lib.isDerivation value) {
            outPath = "\${${pkgAttrName}}";
          }
        else
          value;
    in
    lib.mapAttrs scrubDerivation attrs;

  # Make sure the used package is scrubbed to avoid actually
  # instantiating derivations.
  scrubbedPkgsModule = {
    imports = [
      {
        _module.args = {
          pkgs = lib.mkForce (scrubDerivations "pkgs" pkgs);
          pkgs_i686 = lib.mkForce { };
        };
      }
    ];
  };

  dontCheckDefinitions = {
    _module.check = false;
  };

  gitHubDeclaration =
    user: repo: subpath:
    let
      urlRef = if isReleaseBranch then "release-${release}" else "master";
    in
    {
      url = "https://github.com/${user}/${repo}/blob/${urlRef}/${subpath}";
      name = "<${repo}/${subpath}>";
    };

  hmPath = toString ./..;

  buildOptionsDocs =
    args@{
      modules,
      includeModuleSystemOptions ? true,
      ...
    }:
    let
      # to discourage references from option descriptions and defaults.
      poisonModule =
        let
          poisonAttr = n: {
            name = n;
            value = abort ''
              error: the option documentation has a dependency on the configuration.

              You may, for example, have added an option attribute like

                default = ''${config.some.value};

              Since the default value is included in the Home Manager manual, this
              would make the manual depend on the user's configuration.

              To avoid this problem in this particular case, consider changing to

                default = ''${config.some.value};
                defaultText = lib.literalExpression "\\''${config.some.value}";'';
          };
        in
        { options, ... }:
        {
          config = lib.listToAttrs (map poisonAttr (lib.filter (n: n != "_module") (lib.attrNames options)));
        };

      options =
        (lib.evalModules {
          modules = modules ++ [ poisonModule ];
          class = "homeManager";
        }).options;
    in
    pkgs.buildPackages.nixosOptionsDoc (
      {
        options =
          if includeModuleSystemOptions then options else builtins.removeAttrs options [ "_module" ];
        transformOptions =
          opt:
          opt
          // {
            # Clean up declaration sites to not refer to the Home Manager
            # source tree.
            declarations = map (
              decl:
              if lib.hasPrefix hmPath (toString decl) then
                gitHubDeclaration "nix-community" "home-manager" (
                  lib.removePrefix "/" (lib.removePrefix hmPath (toString decl))
                )
              else if decl == "lib/modules.nix" then
                # TODO: handle this in a better way (may require upstream
                # changes to nixpkgs)
                gitHubDeclaration "NixOS" "nixpkgs" decl
              else
                decl
            ) opt.declarations;
          };
      }
      // builtins.removeAttrs args [
        "modules"
        "includeModuleSystemOptions"
      ]
    );

  hmOptionsDocs = buildOptionsDocs {
    modules =
      import ../modules/modules.nix {
        inherit lib pkgs;
        check = false;
      }
      ++ [ scrubbedPkgsModule ];
    variablelistId = "home-manager-options";
  };

  nixosOptionsDocs = buildOptionsDocs {
    modules = [
      ../nixos
      scrubbedPkgsModule
      dontCheckDefinitions
    ];
    includeModuleSystemOptions = false;
    variablelistId = "nixos-options";
    optionIdPrefix = "nixos-opt-";
  };

  nixDarwinOptionsDocs = buildOptionsDocs {
    modules = [
      ../nix-darwin
      scrubbedPkgsModule
      dontCheckDefinitions
    ];
    includeModuleSystemOptions = false;
    variablelistId = "nix-darwin-options";
    optionIdPrefix = "nix-darwin-opt-";
  };

  release-config = builtins.fromJSON (builtins.readFile ../release.json);
  revision = "release-${release-config.release}";
  # Generate the `man home-configuration.nix` package
  home-configuration-manual =
    pkgs.runCommand "home-configuration-reference-manpage"
      {
        nativeBuildInputs = [
          pkgs.buildPackages.installShellFiles
          pkgs.nixos-render-docs
        ];
        allowedReferences = [ "out" ];
      }
      ''
        # Generate manpages.
        mkdir -p $out/share/man/man5
        mkdir -p $out/share/man/man1
        nixos-render-docs -j $NIX_BUILD_CORES options manpage \
          --revision ${revision} \
          --header ${./home-configuration-nix-header.5} \
          --footer ${./home-configuration-nix-footer.5} \
          ${hmOptionsDocs.optionsJSON}/share/doc/nixos/options.json \
          $out/share/man/man5/home-configuration.nix.5
        cp ${./home-manager.1} $out/share/man/man1/home-manager.1
      '';
  # Generate the HTML manual pages
  home-manager-manual = pkgs.callPackage ./home-manager-manual.nix {
    home-manager-options = {
      home-manager = hmOptionsDocs.optionsJSON;
      nixos = nixosOptionsDocs.optionsJSON;
      nix-darwin = nixDarwinOptionsDocs.optionsJSON;
    };
    inherit revision;
  };
  html = home-manager-manual;
  htmlOpenTool = pkgs.callPackage ./html-open-tool.nix { } { inherit html; };
in
{
  options = {
    # TODO: Use `hmOptionsDocs.optionsJSON` directly once upstream
    # `nixosOptionsDoc` is more customizable.
    json =
      pkgs.runCommand "options.json"
        {
          meta.description = "List of Home Manager options in JSON format";
        }
        ''
          mkdir -p $out/{share/doc,nix-support}
          cp -a ${hmOptionsDocs.optionsJSON}/share/doc/nixos $out/share/doc/home-manager
          substitute \
            ${hmOptionsDocs.optionsJSON}/nix-support/hydra-build-products \
            $out/nix-support/hydra-build-products \
            --replace-fail \
              '${hmOptionsDocs.optionsJSON}/share/doc/nixos' \
              "$out/share/doc/home-manager"
        '';
  };

  manPages = home-configuration-manual;

  manual = { inherit html htmlOpenTool; };

  # Unstable, mainly for CI.
  jsonModuleMaintainers = pkgs.writeText "hm-module-maintainers.json" (
    let
      result = lib.evalModules {
        modules =
          import ../modules/modules.nix {
            inherit lib pkgs;
            check = false;
          }
          ++ [ scrubbedPkgsModule ];
        class = "homeManager";
      };
    in
    builtins.toJSON result.config.meta.maintainers
  );
}
