{ flake, flakeInputs, home-manager, path, hostName, config, options, lib, pkgs, customUtils, ... }:

with lib;

let
  profileSet = customUtils.types.attrNameSet flake.nixosModules.systemProfiles;
  userProfileSet = customUtils.types.attrNameSet (zipAttrs (attrValues flake.nixosModules.userProfiles));
  hasSops = config.sops.secrets != {};
in {
  imports =
    [ flakeInputs.sops-nix.nixosModules.sops
      home-manager.nixosModules.home-manager
    ];

  options = {
    # See mkSystemProfile in ../flake.nix
    system.profiles = mkOption {
      type = profileSet;
      default = [];
      description = ''
        Set (list without duplicates) of ‘systemProfiles’ enabled for this host
      '';
    };

    users.users = mkOption {
      type = types.attrsOf (types.submodule {
        options.profiles = mkOption {
          type = userProfileSet;
          default = [];
          description = ''
            Set (list without duplicates) of ‘userProfiles’ enabled for this user
          '';
        };
      });
    };

    nixpkgs.externalConfig = mkOption {
      default = {};
      example = literalExpression
        ''
          { allowBroken = true; allowUnfree = true; }
        '';
      type = mkOptionType {
        name = "nixpkgs-config";
        description = "nixpkgs config";
        check = x:
          let traceXIfNot = c:
                if c x then true
                else traceSeqN 1 x false;
              isConfig = x:
                builtins.isAttrs x || isFunction x;
          in traceXIfNot isConfig;
        merge = args:
          let
            optCall = f: x:
              if isFunction f
              then f x
              else f;
            mergeConfig = lhs_: rhs_:
              let
                lhs = optCall lhs_ { inherit pkgs; };
                rhs = optCall rhs_ { inherit pkgs; };
              in
              recursiveUpdate lhs rhs //
              optionalAttrs (lhs ? packageOverrides) {
                packageOverrides = pkgs:
                  optCall lhs.packageOverrides pkgs //
                  optCall (attrByPath [ "packageOverrides" ] { } rhs) pkgs;
              } //
              optionalAttrs (lhs ? perlPackageOverrides) {
                perlPackageOverrides = pkgs:
                  optCall lhs.perlPackageOverrides pkgs //
                  optCall (attrByPath [ "perlPackageOverrides" ] { } rhs) pkgs;
              };
          in foldr (def: mergeConfig def.value) {};
      };
      description = ''
        The configuration of the Nix Packages collection.  (For
        details, see the Nixpkgs documentation.)  It allows you to set
        package configuration options.

        Used to construct `nixpkgs.pkgs`.
      '';
    };

    nixpkgs = {
      flakeInput = mkOption {
        type = types.enum (attrNames flakeInputs);
        default = if flakeInputs ? "nixpkgs-${hostName}" then "nixpkgs-${hostName}" else "nixpkgs";
        defaultText = literalExpression ''if flakeInputs ? "nixpkgs-''${hostName}" then "nixpkgs-''${hostName}" else "nixpkgs"'';
        internal = true;
      };
      extraOverlays = mkOption {
        default = [];
        type = types.listOf (mkOptionType {
          name = "nixpkgs-overlay";
          description = "nixpkgs overlay";
          check = lib.isFunction;
          merge = lib.mergeOneOption;
        });
      };
    };
  };

  config = foldr recursiveUpdate {} ([
    {
      networking.hostName = hostName;
      system.configurationRevision = mkIf (flake ? rev) flake.rev;

      nixpkgs.pkgs = import (flakeInputs.${config.nixpkgs.flakeInput}.outPath + "/pkgs/top-level") {
        overlays = attrValues flake.overlays ++ config.nixpkgs.extraOverlays;
        config = config.nixpkgs.externalConfig;
        localSystem = config.nixpkgs.system;
      };

      nix = {
        package = if builtins.hasAttr "latest" pkgs.nixVersions then pkgs.nixVersions.latest else pkgs.nixUnstable;
        settings = {
          sandbox = true;
          allowed-users = [ "*" ];
          trusted-users = [ "root" "@wheel" ];

          experimental-features = ["nix-command" "flakes" "auto-allocate-uids" "cgroups"];
          auto-allocate-uids = true;
          use-cgroups = true;
          use-xdg-base-directories = true;

          flake-registry = "${flakeInputs.flake-registry}/flake-registry.json";
        };
        nixPath = [
          "nixpkgs=${pkgs.runCommand "nixpkgs" {} ''
            mkdir $out
            ln -s ${./nixpkgs.nix} $out/default.nix
            ln -s /run/nixpkgs/lib $out/lib
          ''}"
        ];
        registry =
          let override = { self = "nixos"; };
          in mapAttrs' (inpName: inpFlake: nameValuePair
            (override.${inpName} or inpName)
            { flake = inpFlake; } ) flakeInputs;
      };

      systemd.tmpfiles.rules = [
        "L+ /run/nixpkgs - - - - ${flakeInputs.${config.nixpkgs.flakeInput}.outPath}"
        "L+ /run/nixpkgs-overlays.nix - - - - ${pkgs.writeText "overlays.nix" ''
          with builtins;

          attrValues (import
            (
              let lock = fromJSON (readFile ${flake + "/flake.lock"}); in
              fetchTarball {
                url = "https://github.com/edolstra/flake-compat/archive/''${lock.nodes.flake-compat.locked.rev}.tar.gz";
                sha256 = lock.nodes.flake-compat.locked.narHash;
              }
            )
            { src = ${flake}; }
          ).defaultNix.overlays
        ''}"
        "L+ /etc/nixos - - - - ${flake}"
      ] ++ map (input: "L+ /run/flake-inputs/${input} - - - - ${flakeInputs.${input}.outPath}") (attrNames flakeInputs);

      users.mutableUsers = false;

      documentation.nixos = {
        includeAllModules = true;
        options.warningsAreErrors = false;
        extraModuleSources = map toString ([flake] ++ attrValues flakeInputs);
      };

      home-manager = {
        useGlobalPkgs = true; # Otherwise home-manager would only work impurely
        useUserPackages = false;
        useUserService = true;
        backupFileExtension = "bak";
        sharedModules = lib.attrValues flake.homeModules ++ [
          {
            manual.manpages.enable = true;
            systemd.user.startServices = "sd-switch";

            programs.ssh.internallyManaged = mkForce true;
          }
        ];
        extraSpecialArgs = { inherit flake flakeInputs path; hostConfig = config; };
      };

      sops = mkIf hasSops {
        age = {
          keyFile = "/var/lib/sops-nix/key.txt";
          generateKey = false;
          sshKeyPaths = [];
        };
        gnupg = {
          home = null;
          sshKeyPaths = [];
        };
      };

      programs.git = {
        enable = true;
        lfs.enable = true;
      };
      environment.systemPackages = with pkgs; [ git-annex scutiger ];
    }
  ] ++ (optional (options ? system.switch.enableNg) {
    system.switch = lib.mkDefault {
      enable = false;
      enableNg = true;
    };
  })
  ++ (optional (options ? system.rebuild.enableNg) {
    system.rebuild.enableNg = lib.mkDefault true;
  })
  ++ (optional (options ? services.userborn) {
    services.userborn = {
      enable = lib.mkDefault true;
      passwordFilesLocation = lib.mkDefault "/var/lib/nixos";
    };
  })
  ++ (optional (!(options ? services.userborn) && (options ? system.etc)) {
    systemd.sysusers.enable = lib.mkDefault true;
  })
  ++ (optional (options ? system.etc) {
    boot.initrd.systemd.enable = lib.mkDefault true;
    system.etc.overlay.enable = lib.mkDefault true;
    system.etc.overlay.mutable = lib.mkDefault (!config.systemd.sysusers.enable);

    # Random perl remnants
    system.disableInstallerTools = lib.mkDefault true;
    programs.less.lessopen = lib.mkDefault null;
    programs.command-not-found.enable = lib.mkDefault false;
    boot.enableContainers = lib.mkDefault false;
    boot.loader.grub.enable = lib.mkDefault false;
    environment.defaultPackages = lib.mkDefault [ ];
    documentation.info.enable = lib.mkDefault false;
  })
  ++ (optional (options ? nixpkgs.flake) {
    nixpkgs.flake = {
      setNixPath = false;
      setFlakeRegistry = false;
    };
  }));
}