{ pkgs, lib, ... }:
let
  manImplementations = [
    "mandoc"
    "man-db"
  ];

  machineNames = map machineSafe manImplementations;

  makeConfig = useImpl: {
    # Note: mandoc currently can't index symlinked section directories.
    # So if a man section comes from one package exclusively (e. g.
    # 1p from man-pages-posix and 2 from man-pages), it isn't searchable.
    environment.systemPackages = [
      pkgs.man-pages
      pkgs.openssl
      pkgs.libunwind
    ];

    documentation = {
      enable = true;
      nixos.enable = lib.mkForce true;
      dev.enable = true;
      man = {
        enable = true;
        generateCaches = true;
      }
      // lib.listToAttrs (
        map (impl: {
          name = impl;
          value = {
            enable = useImpl == impl;
          };
        }) manImplementations
      );
    };
  };

  machineSafe = builtins.replaceStrings [ "-" ] [ "_" ];
in
{
  name = "man";
  meta.maintainers = [ lib.maintainers.sternenseemann ];

  nodes = lib.listToAttrs (
    map (i: {
      name = machineSafe i;
      value = makeConfig i;
    }) manImplementations
  );

  testScript = ''
    import re
    start_all()

    def match_man_k(page, section, haystack):
      """
      Check if the man page {page}({section}) occurs in
      the output of `man -k` given as haystack. Note:
      This is not super reliable, e. g. it can't deal
      with man pages that are in multiple sections.
      """

      for line in haystack.split("\n"):
        # man -k can look like this:
        # page(3) - bla
        # page (3) - bla
        # pagea, pageb (3, 3P) - foo
        # pagea, pageb, pagec(3) - bar
        pages = line.split("(")[0]
        sections = re.search("\\([a-zA-Z1-9, ]+\\)", line)
        if sections is None:
          continue
        else:
          sections = sections.group(0)[1:-1]

        if page in pages and f'{section}' in sections:
          return True

      return False

  ''
  + lib.concatMapStrings (machine: ''
    with subtest("Test direct man page lookups in ${machine}"):
      # man works
      ${machine}.succeed("man man > /dev/null")
      # devman works
      ${machine}.succeed("man 3 libunwind > /dev/null")
      # NixOS configuration man page is installed
      ${machine}.succeed("man configuration.nix > /dev/null")
      # Linux `man-pages` work
      ${machine}.succeed("man 5 proc_vmstat > /dev/null")

    with subtest("Test generateCaches via man -k in ${machine}"):
      expected = [
        ("openssl", "ssl", 3),
        ("unwind", "libunwind", 3),
        ("user", "useradd", 8),
        ("user", "userdel", 8),
        ("mem", "free", 3),
        ("mem", "free", 1),
        ("statistics", "proc_vmstat", 5),
      ]

      for (keyword, page, section) in expected:
        matches = ${machine}.succeed(f"man -k {keyword}")
        if not match_man_k(page, section, matches):
          raise Exception(f"{page}({section}) missing in matches: {matches}")
  '') machineNames;
}
