{ 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
      options = (lib.evalModules {
        inherit modules;
        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 \
          '${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);
}
